1919import java .text .MessageFormat ;
2020import java .util .ArrayList ;
2121import java .util .Collections ;
22+ import java .util .HashSet ;
2223import java .util .List ;
24+ import java .util .Set ;
2325import java .util .concurrent .CompletableFuture ;
2426import java .util .logging .Level ;
2527import java .util .logging .Logger ;
2931import org .eclipse .lsp4j .Diagnostic ;
3032import org .eclipse .lsp4j .Position ;
3133import org .eclipse .lsp4j .Range ;
34+ import org .eclipse .lsp4j .TextEdit ;
3235
3336import com .google .gson .JsonObject ;
3437import com .redhat .qute .ls .commons .BadLocationException ;
4043import com .redhat .qute .parser .template .Expression ;
4144import com .redhat .qute .parser .template .Node ;
4245import com .redhat .qute .parser .template .NodeKind ;
46+ import com .redhat .qute .parser .template .Parameter ;
4347import com .redhat .qute .parser .template .Section ;
48+ import com .redhat .qute .parser .template .SectionKind ;
4449import com .redhat .qute .parser .template .Template ;
50+ import com .redhat .qute .parser .template .sections .WithSection ;
4551import com .redhat .qute .project .QuteProject ;
4652import com .redhat .qute .services .commands .QuteClientCommandConstants ;
4753import com .redhat .qute .services .diagnostics .QuteErrorCode ;
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 ,
0 commit comments