Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a javadoc

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