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 7, 2022
1 parent 3ff7394 commit cc21c50
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 25 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
Expand Up @@ -19,7 +19,9 @@
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
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 +31,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 +43,11 @@
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.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 All @@ -50,7 +56,7 @@

/**
* Qute code actions support.
*
*
* @author Angelo ZERR
*
*/
Expand All @@ -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,15 +203,136 @@ 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>();

// Retrieve all expressions in #with section
findObjectParts(withSection, withObjectParts);
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.
*
*
* @param title the displayed name of the QuickFix.
* @param sectionName the section name of the settings to update.
* @param item the section value of the settings to update.
* @param editType the configuration edit type.
* @param diagnostic the diagnostic list that this CodeAction will fix.
*
*
* @return the configuration update (done on client side) quick fix.
*/
private static CodeAction createConfigurationUpdateCodeAction(String title, String scopeUri, String sectionName,
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 @@ -246,6 +247,9 @@ private void validateDataModel(Node parent, Template template, ResolvingJavaType
case INCLUDE:
validateIncludeSection((IncludeSection) section, diagnostics);
break;
case WITH:
validateWithSection((WithSection) section, diagnostics);
break;
default:
validateSectionTag(section, template, resolvingJavaTypeContext, diagnostics);
}
Expand Down Expand Up @@ -345,6 +349,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,
ResolutionContext resolutionContext, ResolvingJavaTypeContext resolvingJavaTypeContext,
List<Diagnostic> diagnostics) {
Expand Down Expand Up @@ -391,7 +410,7 @@ private ResolvedJavaTypeInfo validateExpressionParts(Parts parts, Section ownerS
ResolvedJavaTypeInfo resolvedJavaType = null;
String namespace = null;
for (int i = 0; i < parts.getChildCount(); i++) {
Part current = ((Part) parts.getChild(i));
Part current = parts.getChild(i);

if (current.isLast()) {
// It's the last part, check if it is not ended with '.'
Expand Down Expand Up @@ -586,7 +605,7 @@ private ResolvedJavaTypeInfo validateObjectPart(ObjectPart objectPart, Section o

/**
* Validate the given property, method part.
*
*
* @param part the property, method part to validate.
* @param ownerSection the owner section and null otherwise.
* @param template the template.
Expand All @@ -596,7 +615,7 @@ private ResolvedJavaTypeInfo validateObjectPart(ObjectPart objectPart, Section o
* @param iterableOfType the iterable of type.
* @param diagnostics the diagnostic list to fill.
* @param resolvingJavaTypeContext the resolving Java type context.
*
*
* @return the Java type returned by the member part and null otherwise.
*/
private ResolvedJavaTypeInfo validateMemberPart(Part part, Section ownerSection, Template template,
Expand All @@ -617,7 +636,7 @@ private ResolvedJavaTypeInfo validateMemberPart(Part part, Section ownerSection,

/**
* Validate the given property part.
*
*
* @param part the property part to validate.
* @param ownerSection the owner section and null otherwise.
* @param template the template.
Expand All @@ -627,7 +646,7 @@ private ResolvedJavaTypeInfo validateMemberPart(Part part, Section ownerSection,
* @param iterableOfType the iterable of type.
* @param diagnostics the diagnostic list to fill.
* @param resolvingJavaTypeContext the resolving Java type context.
*
*
* @return the Java type returned by the member part and null otherwise.
*/
private ResolvedJavaTypeInfo validatePropertyPart(PropertyPart part, Section ownerSection, Template template,
Expand All @@ -650,7 +669,7 @@ private ResolvedJavaTypeInfo validatePropertyPart(PropertyPart part, Section own

/**
* Validate the given method part.
*
*
* @param part the method part to validate.
* @param ownerSection the owner section and null otherwise.
* @param template the template.
Expand All @@ -660,7 +679,7 @@ private ResolvedJavaTypeInfo validatePropertyPart(PropertyPart part, Section own
* @param iterableOfType the iterable of type.
* @param diagnostics the diagnostic list to fill.
* @param resolvingJavaTypeContext the resolving Java type context.
*
*
* @return the Java type returned by the member part and null otherwise.
*/
private ResolvedJavaTypeInfo validateMethodPart(MethodPart methodPart, Section ownerSection, Template template,
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 @@ -45,7 +45,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 cc21c50

Please sign in to comment.