Skip to content

Commit 7806f85

Browse files
author
Alexander Chen
committed
Added diagnostic and CodeAction to use let to replace with
Signed-off-by: Alexander Chen <alchen@redhat.com>
1 parent d9b8f83 commit 7806f85

File tree

7 files changed

+278
-13
lines changed

7 files changed

+278
-13
lines changed

qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/ls/commons/CodeActionFactory.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,16 @@ public static CodeAction replace(String title, Range range, String replaceText,
106106
return replace(title, Collections.singletonList(replace), document, diagnostic);
107107
}
108108

109+
@SuppressWarnings("null")
110+
public static CodeAction replace(String title, List<Range> ranges, String replaceText, TextDocumentItem document,
111+
Diagnostic diagnostic) {
112+
List<TextEdit> edits = null;
113+
for (Range range : ranges) {
114+
edits.add(new TextEdit(range, replaceText));
115+
}
116+
return replace(title, edits, document, diagnostic);
117+
}
118+
109119
public static CodeAction replace(String title, List<TextEdit> replace, TextDocumentItem document,
110120
Diagnostic diagnostic) {
111121

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2022 Red Hat Inc. and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* which accompanies this distribution, and is available at
5+
* http://www.eclipse.org/legal/epl-v20.html
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Red Hat Inc. - initial API and implementation
11+
*******************************************************************************/
12+
package com.redhat.qute.parser.template;
13+
14+
import java.util.HashSet;
15+
import java.util.Set;
16+
17+
import com.redhat.qute.parser.expression.ObjectPart;
18+
19+
/**
20+
* Collect object part names.
21+
*
22+
* @author Angelo ZERR
23+
*
24+
*/
25+
public class ObjectPartASTVisitor extends ASTVisitor {
26+
27+
private final Set<String> objectPartNames;
28+
29+
public ObjectPartASTVisitor() {
30+
this.objectPartNames = new HashSet<>();
31+
}
32+
33+
@Override
34+
public boolean visit(ObjectPart node) {
35+
objectPartNames.add(node.getPartName());
36+
return true;
37+
}
38+
39+
public Set<String> getObjectPartNames() {
40+
return objectPartNames;
41+
}
42+
}

qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/QuteCodeActions.java

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.ArrayList;
2121
import java.util.Collections;
2222
import java.util.List;
23+
import java.util.Set;
2324
import java.util.concurrent.CompletableFuture;
2425
import java.util.logging.Level;
2526
import java.util.logging.Logger;
@@ -29,6 +30,7 @@
2930
import org.eclipse.lsp4j.Diagnostic;
3031
import org.eclipse.lsp4j.Position;
3132
import org.eclipse.lsp4j.Range;
33+
import org.eclipse.lsp4j.TextEdit;
3234

3335
import com.google.gson.JsonObject;
3436
import com.redhat.qute.ls.commons.BadLocationException;
@@ -40,8 +42,12 @@
4042
import com.redhat.qute.parser.template.Expression;
4143
import com.redhat.qute.parser.template.Node;
4244
import com.redhat.qute.parser.template.NodeKind;
45+
import com.redhat.qute.parser.template.ObjectPartASTVisitor;
46+
import com.redhat.qute.parser.template.Parameter;
4347
import com.redhat.qute.parser.template.Section;
48+
import com.redhat.qute.parser.template.SectionKind;
4449
import com.redhat.qute.parser.template.Template;
50+
import com.redhat.qute.parser.template.sections.WithSection;
4551
import com.redhat.qute.project.QuteProject;
4652
import com.redhat.qute.services.commands.QuteClientCommandConstants;
4753
import com.redhat.qute.services.diagnostics.QuteErrorCode;
@@ -72,6 +78,8 @@ class QuteCodeActions {
7278

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

81+
private static final String QUTE_DEPRICATED_WITH_SECTION = "Replace `#with` with `#let`.";
82+
7583
public CompletableFuture<List<CodeAction>> doCodeActions(Template template, CodeActionContext context, Range range,
7684
SharedSettings sharedSettings) {
7785
List<CodeAction> codeActions = new ArrayList<>();
@@ -109,6 +117,15 @@ public CompletableFuture<List<CodeAction>> doCodeActions(Template template, Code
109117
// Create `undefinedTag`"
110118
doCodeActionsForUndefinedSectionTag(template, diagnostic, codeActions);
111119
break;
120+
case NotRecommendedWithSection:
121+
// The following Qute template:
122+
// {#with }
123+
//
124+
// will provide a quickfix like:
125+
//
126+
// Replace `with` with `let`.
127+
doCodeActionsForNotRecommendedWithSection(template, diagnostic, codeActions);
128+
break;
112129
default:
113130
break;
114131
}
@@ -186,6 +203,132 @@ private static void doCodeActionToDisableValidation(Template template, List<Diag
186203
codeActions.add(disableValidationForTemplateQuickFix);
187204
}
188205

206+
/**
207+
* Create CodeAction for unrecommended `with` Qute syntax.
208+
*
209+
* e.g. <code>
210+
* {#with item}
211+
* {name}
212+
* {/with}
213+
* </code> becomes <code>
214+
* {#let name=item.name}
215+
* {name}
216+
* {/let}
217+
* </code>
218+
*
219+
* @param template the Qute template.
220+
* @param diagnostic the diagnostic list that this CodeAction will fix.
221+
* @param codeActions the list of CodeActions to perform.
222+
*
223+
*/
224+
private static void doCodeActionsForNotRecommendedWithSection(Template template, Diagnostic diagnostic,
225+
List<CodeAction> codeActions) {
226+
Range withSectionRange = diagnostic.getRange();
227+
try {
228+
// 1. Retrieve the #with section node using diagnostic range
229+
int withSectionStart = template.offsetAt(withSectionRange.getStart());
230+
WithSection withSection = (WithSection) template.findNodeAt(withSectionStart + 1);
231+
232+
// 2. Initialize TextEdit array
233+
List<TextEdit> edits = new ArrayList<TextEdit>();
234+
235+
// 2.1 Create text edit to update section start from #with to #let
236+
// and collect all object parts from expression for text edit
237+
// (e.g. {name} -> {#let name=item.name})
238+
edits.add(createWithSectionOpenEdit(template, withSection));
239+
240+
// 2.2 Create text edit to update section end from /with to /let
241+
edits.add(createWithSectionCloseEdit(template, withSection));
242+
243+
// 3. Create CodeAction
244+
CodeAction replaceWithSection = CodeActionFactory.replace(QUTE_DEPRICATED_WITH_SECTION, edits,
245+
template.getTextDocument(), diagnostic);
246+
codeActions.add(replaceWithSection);
247+
} catch (BadLocationException e) {
248+
LOGGER.log(Level.SEVERE, "Creation of not recommended with section code action failed", e);
249+
}
250+
}
251+
252+
/**
253+
* Create text edit to replace unrecommended with section opening tag
254+
*
255+
* @param template the Qute template.
256+
* @param withSection the Qute with section
257+
* @return
258+
* @throws BadLocationException
259+
*/
260+
private static TextEdit createWithSectionOpenEdit(Template template, WithSection withSection)
261+
throws BadLocationException {
262+
String objectPartName = withSection.getObjectParameter() != null ? withSection.getObjectParameter().getName()
263+
: "";
264+
// Use set to avoid duplicate parameters
265+
// Set<String> withObjectParts = new HashSet<String>();
266+
267+
ObjectPartASTVisitor visitor = new ObjectPartASTVisitor();
268+
withSection.accept(visitor);
269+
270+
// Retrieve all expressions in #with section
271+
// findObjectParts(withSection, withObjectParts);
272+
Set<String> withObjectParts = visitor.getObjectPartNames();
273+
274+
List<String> letObjectPartParameterList = new ArrayList<String>();
275+
for (String objectPart : withObjectParts) {
276+
letObjectPartParameterList.add(MessageFormat.format("{1}={0}.{1}", objectPartName, objectPart));
277+
}
278+
279+
// Build text edit string
280+
String letObjectPartParameters = String.join(" ", letObjectPartParameterList);
281+
String withSectionOpenReplacementText = MessageFormat.format("#let {0}", letObjectPartParameters);
282+
Range withSectionOpen = new Range(template.positionAt(withSection.getStartTagNameOpenOffset()),
283+
template.positionAt(withSection.getStartTagCloseOffset()));
284+
285+
return new TextEdit(withSectionOpen, withSectionOpenReplacementText);
286+
}
287+
288+
/**
289+
* Find all object parts by traversing AST Nodes, while retrieveing Expressions
290+
* nested in Sections with recursion
291+
*
292+
* @param node current node in AST traversal
293+
* @param objectParts set of found object parts
294+
*/
295+
private static void findObjectParts(Node node, Set<String> objectParts) {
296+
List<Node> children = node.getChildren();
297+
// Base case: Node is an expression
298+
if (children.isEmpty()) {
299+
if (node.getKind() == NodeKind.Expression) {
300+
objectParts.add(((Expression) node).getContent());
301+
}
302+
}
303+
for (Node child : children) {
304+
if (child.getKind() == NodeKind.Expression) {
305+
objectParts.add(((Expression) child).getContent());
306+
} else if (child.getKind() == NodeKind.Section && ((Section) child).getSectionKind() != SectionKind.WITH) {
307+
for (Parameter param : ((Section) child).getParameters()) {
308+
objectParts.add(param.getValue());
309+
}
310+
// Recursive call to traverse nested non-WithSection Sections
311+
findObjectParts(child, objectParts);
312+
}
313+
}
314+
}
315+
316+
/**
317+
* Create text edit to replace unrecommended with section closing tag
318+
*
319+
* @param template the Qute template.
320+
* @param withSection the Qute with section
321+
* @return
322+
* @throws BadLocationException
323+
*/
324+
private static TextEdit createWithSectionCloseEdit(Template template, WithSection withSection)
325+
throws BadLocationException {
326+
String withSectionCloseReplacementText = "/let";
327+
Range withSectionClose = new Range(template.positionAt(withSection.getEndTagNameOpenOffset()),
328+
template.positionAt(withSection.getEndTagCloseOffset()));
329+
return new TextEdit(withSectionClose, withSectionCloseReplacementText);
330+
}
331+
189332
/**
190333
* Create the configuration update (done on client side) quick fix.
191334
*

qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/QuteDiagnostics.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import com.redhat.qute.parser.template.Template;
5555
import com.redhat.qute.parser.template.sections.IncludeSection;
5656
import com.redhat.qute.parser.template.sections.LoopSection;
57+
import com.redhat.qute.parser.template.sections.WithSection;
5758
import com.redhat.qute.project.JavaMemberResult;
5859
import com.redhat.qute.project.QuteProject;
5960
import com.redhat.qute.project.datamodel.JavaDataModelCache;
@@ -248,6 +249,9 @@ private void validateDataModel(Node parent, Template template, QuteValidationSet
248249
case INCLUDE:
249250
validateIncludeSection((IncludeSection) section, diagnostics);
250251
break;
252+
case WITH:
253+
validateWithSection((WithSection) section, diagnostics);
254+
break;
251255
default:
252256
validateSectionTag(section, template, resolvingJavaTypeContext, diagnostics);
253257
}
@@ -348,6 +352,21 @@ private static void validateIncludeSection(IncludeSection includeSection, List<D
348352
}
349353
}
350354

355+
/**
356+
* Report that `#with` section is deprecated.
357+
*
358+
* @param withSection the with section
359+
* @param diagnostics the diagnostics to fill
360+
*/
361+
private static void validateWithSection(WithSection withSection, List<Diagnostic> diagnostics) {
362+
if (withSection.getObjectParameter() != null) {
363+
Range range = QutePositionUtility.createRange(withSection);
364+
Diagnostic diagnostic = createDiagnostic(range, DiagnosticSeverity.Warning,
365+
QuteErrorCode.NotRecommendedWithSection);
366+
diagnostics.add(diagnostic);
367+
}
368+
}
369+
351370
private ResolvedJavaTypeInfo validateExpression(Expression expression, Section ownerSection, Template template,
352371
QuteValidationSettings validationSettings, ResolutionContext resolutionContext,
353372
ResolvingJavaTypeContext resolvingJavaTypeContext, List<Diagnostic> diagnostics) {

qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/diagnostics/DiagnosticDataFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
/**
2626
* Diagnostic factory.
27-
*
27+
*
2828
* @author Angelo ZERR
2929
*
3030
*/
@@ -55,4 +55,5 @@ public static Diagnostic createDiagnostic(Range range, String message, Diagnosti
5555
errorCode != null ? errorCode.getCode() : null);
5656
return diagnostic;
5757
}
58+
5859
}

qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/diagnostics/QuteErrorCode.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ public enum QuteErrorCode implements IQuteErrorCode {
4646

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

49-
SyntaxError("Syntax error: `{0}`.");
49+
SyntaxError("Syntax error: `{0}`."),
50+
51+
// Error code for deprecated #with section
52+
NotRecommendedWithSection("`with` is not recommended. Use `let` instead.");
5053

5154
private final String rawMessage;
5255

0 commit comments

Comments
 (0)