Skip to content

Commit 8029c9b

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 3ff7394 commit 8029c9b

File tree

6 files changed

+225
-25
lines changed

6 files changed

+225
-25
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

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

Lines changed: 137 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import java.text.MessageFormat;
2020
import java.util.ArrayList;
2121
import java.util.Collections;
22+
import java.util.HashSet;
2223
import java.util.List;
24+
import java.util.Set;
2325
import java.util.concurrent.CompletableFuture;
2426
import java.util.logging.Level;
2527
import java.util.logging.Logger;
@@ -29,6 +31,7 @@
2931
import org.eclipse.lsp4j.Diagnostic;
3032
import org.eclipse.lsp4j.Position;
3133
import org.eclipse.lsp4j.Range;
34+
import org.eclipse.lsp4j.TextEdit;
3235

3336
import com.google.gson.JsonObject;
3437
import com.redhat.qute.ls.commons.BadLocationException;
@@ -41,7 +44,9 @@
4144
import com.redhat.qute.parser.template.Node;
4245
import com.redhat.qute.parser.template.NodeKind;
4346
import com.redhat.qute.parser.template.Section;
47+
import com.redhat.qute.parser.template.SectionKind;
4448
import com.redhat.qute.parser.template.Template;
49+
import com.redhat.qute.parser.template.sections.WithSection;
4550
import com.redhat.qute.project.QuteProject;
4651
import com.redhat.qute.services.commands.QuteClientCommandConstants;
4752
import com.redhat.qute.services.diagnostics.QuteErrorCode;
@@ -50,7 +55,7 @@
5055

5156
/**
5257
* Qute code actions support.
53-
*
58+
*
5459
* @author Angelo ZERR
5560
*
5661
*/
@@ -72,6 +77,8 @@ class QuteCodeActions {
7277

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

80+
private static final String QUTE_DEPRICATED_WITH_SECTION = "Replace `#with` with `#let`.";
81+
7582
public CompletableFuture<List<CodeAction>> doCodeActions(Template template, CodeActionContext context, Range range,
7683
SharedSettings sharedSettings) {
7784
List<CodeAction> codeActions = new ArrayList<>();
@@ -109,6 +116,15 @@ public CompletableFuture<List<CodeAction>> doCodeActions(Template template, Code
109116
// Create `undefinedTag`"
110117
doCodeActionsForUndefinedSectionTag(template, diagnostic, codeActions);
111118
break;
119+
case NotRecommendedWithSection:
120+
// The following Qute template:
121+
// {#with }
122+
//
123+
// will provide a quickfix like:
124+
//
125+
// Replace `with` with `let`.
126+
doCodeActionsForNotRecommendedWithSection(template, diagnostic, codeActions);
127+
break;
112128
default:
113129
break;
114130
}
@@ -186,15 +202,133 @@ private static void doCodeActionToDisableValidation(Template template, List<Diag
186202
codeActions.add(disableValidationForTemplateQuickFix);
187203
}
188204

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

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

Lines changed: 26 additions & 7 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;
@@ -246,6 +247,9 @@ private void validateDataModel(Node parent, Template template, ResolvingJavaType
246247
case INCLUDE:
247248
validateIncludeSection((IncludeSection) section, diagnostics);
248249
break;
250+
case WITH:
251+
validateWithSection((WithSection) section, diagnostics);
252+
break;
249253
default:
250254
validateSectionTag(section, template, resolvingJavaTypeContext, diagnostics);
251255
}
@@ -345,6 +349,21 @@ private static void validateIncludeSection(IncludeSection includeSection, List<D
345349
}
346350
}
347351

352+
/**
353+
* Report that `#with` section is deprecated.
354+
*
355+
* @param withSection the with section
356+
* @param diagnostics the diagnostics to fill
357+
*/
358+
private static void validateWithSection(WithSection withSection, List<Diagnostic> diagnostics) {
359+
if (withSection.getObjectParameter() != null) {
360+
Range range = QutePositionUtility.createRange(withSection);
361+
Diagnostic diagnostic = createDiagnostic(range, DiagnosticSeverity.Warning,
362+
QuteErrorCode.NotRecommendedWithSection);
363+
diagnostics.add(diagnostic);
364+
}
365+
}
366+
348367
private ResolvedJavaTypeInfo validateExpression(Expression expression, Section ownerSection, Template template,
349368
ResolutionContext resolutionContext, ResolvingJavaTypeContext resolvingJavaTypeContext,
350369
List<Diagnostic> diagnostics) {
@@ -391,7 +410,7 @@ private ResolvedJavaTypeInfo validateExpressionParts(Parts parts, Section ownerS
391410
ResolvedJavaTypeInfo resolvedJavaType = null;
392411
String namespace = null;
393412
for (int i = 0; i < parts.getChildCount(); i++) {
394-
Part current = ((Part) parts.getChild(i));
413+
Part current = parts.getChild(i);
395414

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

587606
/**
588607
* Validate the given property, method part.
589-
*
608+
*
590609
* @param part the property, method part to validate.
591610
* @param ownerSection the owner section and null otherwise.
592611
* @param template the template.
@@ -596,7 +615,7 @@ private ResolvedJavaTypeInfo validateObjectPart(ObjectPart objectPart, Section o
596615
* @param iterableOfType the iterable of type.
597616
* @param diagnostics the diagnostic list to fill.
598617
* @param resolvingJavaTypeContext the resolving Java type context.
599-
*
618+
*
600619
* @return the Java type returned by the member part and null otherwise.
601620
*/
602621
private ResolvedJavaTypeInfo validateMemberPart(Part part, Section ownerSection, Template template,
@@ -617,7 +636,7 @@ private ResolvedJavaTypeInfo validateMemberPart(Part part, Section ownerSection,
617636

618637
/**
619638
* Validate the given property part.
620-
*
639+
*
621640
* @param part the property part to validate.
622641
* @param ownerSection the owner section and null otherwise.
623642
* @param template the template.
@@ -627,7 +646,7 @@ private ResolvedJavaTypeInfo validateMemberPart(Part part, Section ownerSection,
627646
* @param iterableOfType the iterable of type.
628647
* @param diagnostics the diagnostic list to fill.
629648
* @param resolvingJavaTypeContext the resolving Java type context.
630-
*
649+
*
631650
* @return the Java type returned by the member part and null otherwise.
632651
*/
633652
private ResolvedJavaTypeInfo validatePropertyPart(PropertyPart part, Section ownerSection, Template template,
@@ -650,7 +669,7 @@ private ResolvedJavaTypeInfo validatePropertyPart(PropertyPart part, Section own
650669

651670
/**
652671
* Validate the given method part.
653-
*
672+
*
654673
* @param part the method part to validate.
655674
* @param ownerSection the owner section and null otherwise.
656675
* @param template the template.
@@ -660,7 +679,7 @@ private ResolvedJavaTypeInfo validatePropertyPart(PropertyPart part, Section own
660679
* @param iterableOfType the iterable of type.
661680
* @param diagnostics the diagnostic list to fill.
662681
* @param resolvingJavaTypeContext the resolving Java type context.
663-
*
682+
*
664683
* @return the Java type returned by the member part and null otherwise.
665684
*/
666685
private ResolvedJavaTypeInfo validateMethodPart(MethodPart methodPart, Section ownerSection, Template template,

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
@@ -45,7 +45,10 @@ public enum QuteErrorCode implements IQuteErrorCode {
4545

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

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

5053
private final String rawMessage;
5154

0 commit comments

Comments
 (0)