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 ;
4144import com .redhat .qute .parser .template .Node ;
4245import com .redhat .qute .parser .template .NodeKind ;
4346import com .redhat .qute .parser .template .Section ;
47+ import com .redhat .qute .parser .template .SectionKind ;
4448import com .redhat .qute .parser .template .Template ;
49+ import com .redhat .qute .parser .template .sections .WithSection ;
4550import com .redhat .qute .project .QuteProject ;
4651import com .redhat .qute .services .commands .QuteClientCommandConstants ;
4752import com .redhat .qute .services .diagnostics .QuteErrorCode ;
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 ,
0 commit comments