Skip to content

Commit

Permalink
Added diagnostic and CodeAction to use let to replace with
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Chen <[email protected]>
  • Loading branch information
Alexander Chen committed Mar 11, 2022
1 parent d9b8f83 commit 11955cf
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ public static CodeAction replace(String title, Range range, String replaceText,
return replace(title, Collections.singletonList(replace), document, diagnostic);
}

@SuppressWarnings("null")
public static CodeAction replace(String title, List<Range> ranges, String replaceText, TextDocumentItem document,
Diagnostic diagnostic) {
List<TextEdit> edits = null;
for (Range range : ranges) {
edits.add(new TextEdit(range, replaceText));
}
return replace(title, edits, document, diagnostic);
}

public static CodeAction replace(String title, List<TextEdit> replace, TextDocumentItem document,
Diagnostic diagnostic) {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package com.redhat.qute.parser.template;

import java.util.HashSet;
import java.util.Set;

import com.redhat.qute.parser.expression.ObjectPart;

/**
* Collect object part names.
*
* @author Angelo ZERR
*
*/
public class ObjectPartASTVisitor extends ASTVisitor {

private final Set<String> objectPartNames;

public ObjectPartASTVisitor() {
this.objectPartNames = new HashSet<>();
}

@Override
public boolean visit(ObjectPart node) {
objectPartNames.add(node.getPartName());
return true;
}

public Set<String> getObjectPartNames() {
return objectPartNames;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -29,6 +30,7 @@
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;

import com.google.gson.JsonObject;
import com.redhat.qute.ls.commons.BadLocationException;
Expand All @@ -40,8 +42,12 @@
import com.redhat.qute.parser.template.Expression;
import com.redhat.qute.parser.template.Node;
import com.redhat.qute.parser.template.NodeKind;
import com.redhat.qute.parser.template.ObjectPartASTVisitor;
import com.redhat.qute.parser.template.Parameter;
import com.redhat.qute.parser.template.Section;
import com.redhat.qute.parser.template.SectionKind;
import com.redhat.qute.parser.template.Template;
import com.redhat.qute.parser.template.sections.WithSection;
import com.redhat.qute.project.QuteProject;
import com.redhat.qute.services.commands.QuteClientCommandConstants;
import com.redhat.qute.services.diagnostics.QuteErrorCode;
Expand Down Expand Up @@ -72,6 +78,8 @@ class QuteCodeActions {

private static final String EXCLUDED_VALIDATION_TITLE = "Exclude this file from validation.";

private static final String QUTE_DEPRICATED_WITH_SECTION = "Replace `#with` with `#let`.";

public CompletableFuture<List<CodeAction>> doCodeActions(Template template, CodeActionContext context, Range range,
SharedSettings sharedSettings) {
List<CodeAction> codeActions = new ArrayList<>();
Expand Down Expand Up @@ -109,6 +117,15 @@ public CompletableFuture<List<CodeAction>> doCodeActions(Template template, Code
// Create `undefinedTag`"
doCodeActionsForUndefinedSectionTag(template, diagnostic, codeActions);
break;
case NotRecommendedWithSection:
// The following Qute template:
// {#with }
//
// will provide a quickfix like:
//
// Replace `with` with `let`.
doCodeActionsForNotRecommendedWithSection(template, diagnostic, codeActions);
break;
default:
break;
}
Expand Down Expand Up @@ -186,6 +203,132 @@ private static void doCodeActionToDisableValidation(Template template, List<Diag
codeActions.add(disableValidationForTemplateQuickFix);
}

/**
* Create CodeAction for unrecommended `with` Qute syntax.
*
* e.g. <code>
* {#with item}
* {name}
* {/with}
* </code> becomes <code>
* {#let name=item.name}
* {name}
* {/let}
* </code>
*
* @param template the Qute template.
* @param diagnostic the diagnostic list that this CodeAction will fix.
* @param codeActions the list of CodeActions to perform.
*
*/
private static void doCodeActionsForNotRecommendedWithSection(Template template, Diagnostic diagnostic,
List<CodeAction> codeActions) {
Range withSectionRange = diagnostic.getRange();
try {
// 1. Retrieve the #with section node using diagnostic range
int withSectionStart = template.offsetAt(withSectionRange.getStart());
WithSection withSection = (WithSection) template.findNodeAt(withSectionStart + 1);

// 2. Initialize TextEdit array
List<TextEdit> edits = new ArrayList<TextEdit>();

// 2.1 Create text edit to update section start from #with to #let
// and collect all object parts from expression for text edit
// (e.g. {name} -> {#let name=item.name})
edits.add(createWithSectionOpenEdit(template, withSection));

// 2.2 Create text edit to update section end from /with to /let
edits.add(createWithSectionCloseEdit(template, withSection));

// 3. Create CodeAction
CodeAction replaceWithSection = CodeActionFactory.replace(QUTE_DEPRICATED_WITH_SECTION, edits,
template.getTextDocument(), diagnostic);
codeActions.add(replaceWithSection);
} catch (BadLocationException e) {
LOGGER.log(Level.SEVERE, "Creation of not recommended with section code action failed", e);
}
}

/**
* Create text edit to replace unrecommended with section opening tag
*
* @param template the Qute template.
* @param withSection the Qute with section
* @return
* @throws BadLocationException
*/
private static TextEdit createWithSectionOpenEdit(Template template, WithSection withSection)
throws BadLocationException {
String objectPartName = withSection.getObjectParameter() != null ? withSection.getObjectParameter().getName()
: "";
// Use set to avoid duplicate parameters
// Set<String> withObjectParts = new HashSet<String>();

ObjectPartASTVisitor visitor = new ObjectPartASTVisitor();
withSection.accept(visitor);

// Retrieve all expressions in #with section
// findObjectParts(withSection, withObjectParts);
Set<String> withObjectParts = visitor.getObjectPartNames();

List<String> letObjectPartParameterList = new ArrayList<String>();
for (String objectPart : withObjectParts) {
letObjectPartParameterList.add(MessageFormat.format("{1}={0}.{1}", objectPartName, objectPart));
}

// Build text edit string
String letObjectPartParameters = String.join(" ", letObjectPartParameterList);
String withSectionOpenReplacementText = MessageFormat.format("#let {0}", letObjectPartParameters);
Range withSectionOpen = new Range(template.positionAt(withSection.getStartTagNameOpenOffset()),
template.positionAt(withSection.getStartTagCloseOffset()));

return new TextEdit(withSectionOpen, withSectionOpenReplacementText);
}

/**
* Find all object parts by traversing AST Nodes, while retrieveing Expressions
* nested in Sections with recursion
*
* @param node current node in AST traversal
* @param objectParts set of found object parts
*/
private static void findObjectParts(Node node, Set<String> objectParts) {
List<Node> children = node.getChildren();
// Base case: Node is an expression
if (children.isEmpty()) {
if (node.getKind() == NodeKind.Expression) {
objectParts.add(((Expression) node).getContent());
}
}
for (Node child : children) {
if (child.getKind() == NodeKind.Expression) {
objectParts.add(((Expression) child).getContent());
} else if (child.getKind() == NodeKind.Section && ((Section) child).getSectionKind() != SectionKind.WITH) {
for (Parameter param : ((Section) child).getParameters()) {
objectParts.add(param.getValue());
}
// Recursive call to traverse nested non-WithSection Sections
findObjectParts(child, objectParts);
}
}
}

/**
* Create text edit to replace unrecommended with section closing tag
*
* @param template the Qute template.
* @param withSection the Qute with section
* @return
* @throws BadLocationException
*/
private static TextEdit createWithSectionCloseEdit(Template template, WithSection withSection)
throws BadLocationException {
String withSectionCloseReplacementText = "/let";
Range withSectionClose = new Range(template.positionAt(withSection.getEndTagNameOpenOffset()),
template.positionAt(withSection.getEndTagCloseOffset()));
return new TextEdit(withSectionClose, withSectionCloseReplacementText);
}

/**
* Create the configuration update (done on client side) quick fix.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import com.redhat.qute.parser.template.Template;
import com.redhat.qute.parser.template.sections.IncludeSection;
import com.redhat.qute.parser.template.sections.LoopSection;
import com.redhat.qute.parser.template.sections.WithSection;
import com.redhat.qute.project.JavaMemberResult;
import com.redhat.qute.project.QuteProject;
import com.redhat.qute.project.datamodel.JavaDataModelCache;
Expand Down Expand Up @@ -248,6 +249,9 @@ private void validateDataModel(Node parent, Template template, QuteValidationSet
case INCLUDE:
validateIncludeSection((IncludeSection) section, diagnostics);
break;
case WITH:
validateWithSection((WithSection) section, diagnostics);
break;
default:
validateSectionTag(section, template, resolvingJavaTypeContext, diagnostics);
}
Expand Down Expand Up @@ -348,6 +352,21 @@ private static void validateIncludeSection(IncludeSection includeSection, List<D
}
}

/**
* Report that `#with` section is deprecated.
*
* @param withSection the with section
* @param diagnostics the diagnostics to fill
*/
private static void validateWithSection(WithSection withSection, List<Diagnostic> diagnostics) {
if (withSection.getObjectParameter() != null) {
Range range = QutePositionUtility.createRange(withSection);
Diagnostic diagnostic = createDiagnostic(range, DiagnosticSeverity.Warning,
QuteErrorCode.NotRecommendedWithSection);
diagnostics.add(diagnostic);
}
}

private ResolvedJavaTypeInfo validateExpression(Expression expression, Section ownerSection, Template template,
QuteValidationSettings validationSettings, ResolutionContext resolutionContext,
ResolvingJavaTypeContext resolvingJavaTypeContext, List<Diagnostic> diagnostics) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

/**
* Diagnostic factory.
*
*
* @author Angelo ZERR
*
*/
Expand Down Expand Up @@ -55,4 +55,5 @@ public static Diagnostic createDiagnostic(Range range, String message, Diagnosti
errorCode != null ? errorCode.getCode() : null);
return diagnostic;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ public enum QuteErrorCode implements IQuteErrorCode {

UndefinedSectionTag("No section helper found for `{0}`."), //

SyntaxError("Syntax error: `{0}`.");
SyntaxError("Syntax error: `{0}`."),

// Error code for deprecated #with section
NotRecommendedWithSection("`with` is not recommended. Use `let` instead.");

private final String rawMessage;

Expand Down
Loading

0 comments on commit 11955cf

Please sign in to comment.