Skip to content

Commit 416a9f6

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 b247849 commit 416a9f6

File tree

6 files changed

+243
-25
lines changed

6 files changed

+243
-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: 141 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;
@@ -40,8 +43,11 @@
4043
import com.redhat.qute.parser.template.Expression;
4144
import com.redhat.qute.parser.template.Node;
4245
import com.redhat.qute.parser.template.NodeKind;
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;
@@ -50,7 +56,7 @@
5056

5157
/**
5258
* Qute code actions support.
53-
*
59+
*
5460
* @author Angelo ZERR
5561
*
5662
*/
@@ -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,15 +203,136 @@ 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+
// Retrieve all expressions in #with section
268+
findObjectParts(withSection, withObjectParts);
269+
List<String> letObjectPartParameterList = new ArrayList<String>();
270+
for (String objectPart : withObjectParts) {
271+
letObjectPartParameterList.add(MessageFormat.format("{1}={0}.{1}", objectPartName, objectPart));
272+
}
273+
274+
// Build text edit string
275+
String letObjectPartParameters = String.join(" ", letObjectPartParameterList);
276+
String withSectionOpenReplacementText = MessageFormat.format("#let {0}", letObjectPartParameters);
277+
Range withSectionOpen = new Range(template.positionAt(withSection.getStartTagNameOpenOffset()),
278+
template.positionAt(withSection.getStartTagCloseOffset()));
279+
280+
return new TextEdit(withSectionOpen, withSectionOpenReplacementText);
281+
}
282+
283+
/**
284+
* Find all object parts by traversing AST Nodes, while retrieveing Expressions
285+
* nested in Sections with recursion
286+
*
287+
* @param node current node in AST traversal
288+
* @param objectParts set of found object parts
289+
*/
290+
private static void findObjectParts(Node node, Set<String> objectParts) {
291+
List<Node> children = node.getChildren();
292+
// Base case: Node is an expression
293+
if (children.isEmpty()) {
294+
if (node.getKind() == NodeKind.Expression) {
295+
objectParts.add(((Expression) node).getContent());
296+
}
297+
}
298+
for (Node child : children) {
299+
if (child.getKind() == NodeKind.Expression) {
300+
objectParts.add(((Expression) child).getContent());
301+
} else if (child.getKind() == NodeKind.Section && ((Section) child).getSectionKind() != SectionKind.WITH) {
302+
for (Parameter param : ((Section) child).getParameters()) {
303+
objectParts.add(param.getValue());
304+
}
305+
// Recursive call to traverse nested non-WithSection Sections
306+
findObjectParts(child, objectParts);
307+
}
308+
}
309+
}
310+
311+
/**
312+
* Create text edit to replace unrecommended with section closing tag
313+
*
314+
* @param template the Qute template.
315+
* @param withSection the Qute with section
316+
* @return
317+
* @throws BadLocationException
318+
*/
319+
private static TextEdit createWithSectionCloseEdit(Template template, WithSection withSection)
320+
throws BadLocationException {
321+
String withSectionCloseReplacementText = "/let";
322+
Range withSectionClose = new Range(template.positionAt(withSection.getEndTagNameOpenOffset()),
323+
template.positionAt(withSection.getEndTagCloseOffset()));
324+
return new TextEdit(withSectionClose, withSectionCloseReplacementText);
325+
}
326+
189327
/**
190328
* Create the configuration update (done on client side) quick fix.
191-
*
329+
*
192330
* @param title the displayed name of the QuickFix.
193331
* @param sectionName the section name of the settings to update.
194332
* @param item the section value of the settings to update.
195333
* @param editType the configuration edit type.
196334
* @param diagnostic the diagnostic list that this CodeAction will fix.
197-
*
335+
*
198336
* @return the configuration update (done on client side) quick fix.
199337
*/
200338
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 '.'
@@ -585,7 +604,7 @@ private ResolvedJavaTypeInfo validateObjectPart(String namespace, ObjectPart obj
585604

586605
/**
587606
* Validate the given property, method part.
588-
*
607+
*
589608
* @param part the property, method part to validate.
590609
* @param ownerSection the owner section and null otherwise.
591610
* @param template the template.
@@ -595,7 +614,7 @@ private ResolvedJavaTypeInfo validateObjectPart(String namespace, ObjectPart obj
595614
* @param iterableOfType the iterable of type.
596615
* @param diagnostics the diagnostic list to fill.
597616
* @param resolvingJavaTypeContext the resolving Java type context.
598-
*
617+
*
599618
* @return the Java type returned by the member part and null otherwise.
600619
*/
601620
private ResolvedJavaTypeInfo validateMemberPart(Part part, Section ownerSection, Template template,
@@ -616,7 +635,7 @@ private ResolvedJavaTypeInfo validateMemberPart(Part part, Section ownerSection,
616635

617636
/**
618637
* Validate the given property part.
619-
*
638+
*
620639
* @param part the property part to validate.
621640
* @param ownerSection the owner section and null otherwise.
622641
* @param template the template.
@@ -626,7 +645,7 @@ private ResolvedJavaTypeInfo validateMemberPart(Part part, Section ownerSection,
626645
* @param iterableOfType the iterable of type.
627646
* @param diagnostics the diagnostic list to fill.
628647
* @param resolvingJavaTypeContext the resolving Java type context.
629-
*
648+
*
630649
* @return the Java type returned by the member part and null otherwise.
631650
*/
632651
private ResolvedJavaTypeInfo validatePropertyPart(PropertyPart part, Section ownerSection, Template template,
@@ -649,7 +668,7 @@ private ResolvedJavaTypeInfo validatePropertyPart(PropertyPart part, Section own
649668

650669
/**
651670
* Validate the given method part.
652-
*
671+
*
653672
* @param part the method part to validate.
654673
* @param ownerSection the owner section and null otherwise.
655674
* @param template the template.
@@ -659,7 +678,7 @@ private ResolvedJavaTypeInfo validatePropertyPart(PropertyPart part, Section own
659678
* @param iterableOfType the iterable of type.
660679
* @param diagnostics the diagnostic list to fill.
661680
* @param resolvingJavaTypeContext the resolving Java type context.
662-
*
681+
*
663682
* @return the Java type returned by the member part and null otherwise.
664683
*/
665684
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
@@ -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)