1919
2020import java .nio .file .Path ;
2121import java .util .ArrayList ;
22+ import java .util .Collections ;
2223import java .util .List ;
24+ import java .util .UUID ;
2325
2426import org .checkstyle .autofix .PositionHelper ;
2527import org .checkstyle .autofix .parser .CheckstyleViolation ;
28+ import org .jspecify .annotations .Nullable ;
2629import org .openrewrite .ExecutionContext ;
2730import org .openrewrite .Recipe ;
2831import org .openrewrite .Tree ;
2932import org .openrewrite .TreeVisitor ;
3033import org .openrewrite .java .JavaIsoVisitor ;
3134import org .openrewrite .java .tree .J ;
3235import org .openrewrite .java .tree .Space ;
36+ import org .openrewrite .java .tree .Statement ;
37+ import org .openrewrite .marker .Marker ;
3338import org .openrewrite .marker .Markers ;
3439
3540/**
@@ -56,17 +61,93 @@ public String getDescription() {
5661
5762 @ Override
5863 public TreeVisitor <?, ExecutionContext > getVisitor () {
59- return new LocalVariableVisitor ();
64+ return new JavaIsoVisitor <>() {
65+ @ Override
66+ public J .CompilationUnit visitCompilationUnit (J .CompilationUnit cu ,
67+ ExecutionContext executionContext ) {
68+ return new LocalVariableVisitor ()
69+ .visitCompilationUnit (new MarkViolationVisitor ()
70+ .visitCompilationUnit (cu , executionContext ), executionContext );
71+ }
72+ };
6073 }
6174
62- private final class LocalVariableVisitor extends JavaIsoVisitor <ExecutionContext > {
75+ /**
76+ * Visitor that identifies and marks variable declarations at violation locations.
77+ * This visitor traverses the AST and adds markers to variables that match
78+ * the checkstyle violation locations, preparing them for the final modifier addition.
79+ */
80+ private final class MarkViolationVisitor extends JavaIsoVisitor <ExecutionContext > {
6381
6482 private Path sourcePath ;
83+ private J .CompilationUnit currentCompilationUnit ;
84+
85+ @ Override
86+ public J .CompilationUnit visitCompilationUnit (J .CompilationUnit cu ,
87+ ExecutionContext executionContext ) {
88+ this .sourcePath = cu .getSourcePath ().toAbsolutePath ();
89+ this .currentCompilationUnit = cu ;
90+ return super .visitCompilationUnit (cu , executionContext );
91+ }
92+
93+ @ Override
94+ public J .VariableDeclarations visitVariableDeclarations (
95+ J .VariableDeclarations multiVariable , ExecutionContext executionContext ) {
96+
97+ final J .VariableDeclarations variableDeclarations ;
98+
99+ final J .VariableDeclarations declarations = super
100+ .visitVariableDeclarations (multiVariable , executionContext );
101+
102+ if (!(getCursor ().getParentTreeCursor ().getValue () instanceof J .ClassDeclaration )
103+ && !declarations .hasModifier (J .Modifier .Type .Final )) {
104+
105+ final List <J .VariableDeclarations .NamedVariable > variables = declarations
106+ .getVariables ();
107+ final List <J .VariableDeclarations .NamedVariable > marked = new ArrayList <>();
108+ for (J .VariableDeclarations .NamedVariable variable : variables ) {
109+ if (isAtViolationLocation (variable )) {
110+ marked .add (variable .withMarkers (
111+ variable .getMarkers ().add (new FinalLocalVariableMarker ())));
112+ }
113+ else {
114+ marked .add (variable );
115+ }
116+ }
117+ variableDeclarations = declarations .withVariables (marked );
118+ }
119+ else {
120+ variableDeclarations = declarations ;
121+ }
122+ return variableDeclarations ;
123+ }
124+
125+ private boolean isAtViolationLocation (J .VariableDeclarations .NamedVariable variable ) {
126+
127+ final int line = PositionHelper
128+ .computeLinePosition (currentCompilationUnit , variable , getCursor ());
129+ final int column = PositionHelper
130+ .computeColumnPosition (currentCompilationUnit , variable , getCursor ());
131+
132+ return violations .removeIf (violation -> {
133+ final Path absolutePath = Path .of (violation .getFileName ()).toAbsolutePath ();
134+ return violation .getLine () == line
135+ && violation .getColumn () == column
136+ && absolutePath .endsWith (sourcePath )
137+ && violation .getMessage ().contains (variable .getSimpleName ());
138+ });
139+ }
140+ }
141+
142+ /**
143+ * Visitor that processes marked variable declarations and applies the final modifier.
144+ * This visitor handles both single and multi-variable declarations.
145+ */
146+ private final class LocalVariableVisitor extends JavaIsoVisitor <ExecutionContext > {
65147
66148 @ Override
67149 public J .CompilationUnit visitCompilationUnit (
68150 J .CompilationUnit cu , ExecutionContext executionContext ) {
69- this .sourcePath = cu .getSourcePath ();
70151 return super .visitCompilationUnit (cu , executionContext );
71152 }
72153
@@ -83,33 +164,91 @@ public J.VariableDeclarations visitVariableDeclarations(
83164 && !declarations .hasModifier (J .Modifier .Type .Final )) {
84165 final J .VariableDeclarations .NamedVariable variable = declarations
85166 .getVariables ().get (0 );
86- if (isAtViolationLocation (variable )) {
87- final List <J .Modifier > modifiers = new ArrayList <>();
167+ if (variable .getMarkers ().findFirst (FinalLocalVariableMarker .class ).isPresent ()) {
168+ declarations = addFinalModifier (declarations );
169+ }
170+ }
171+ return declarations ;
172+ }
173+
174+ @ Override
175+ public J .Block visitBlock (J .Block block , ExecutionContext executionContext ) {
176+ final J .Block visited = super .visitBlock (block , executionContext );
88177
89- final Space finalPrefix = declarations . getTypeExpression (). getPrefix ();
178+ final List < Statement > newStatements = new ArrayList <> ();
90179
91- modifiers . add ( new J . Modifier ( Tree . randomId (), finalPrefix ,
92- Markers . EMPTY , null , J . Modifier . Type . Final , new ArrayList <>()));
93- modifiers . addAll ( declarations . getModifiers () );
94- declarations = declarations . withModifiers ( modifiers )
95- . withTypeExpression ( declarations . getTypeExpression ()
96- . withPrefix ( Space . SINGLE_SPACE ) );
180+ for ( Statement stmt : visited . getStatements ()) {
181+ if ( isVariableDeclaration ( stmt )) {
182+ handleMultiVariableDeclaration (( J . VariableDeclarations ) stmt , newStatements );
183+ }
184+ else {
185+ newStatements . add ( stmt );
97186 }
98187 }
99- return declarations ;
188+
189+ return visited .withStatements (newStatements );
100190 }
101191
102- private boolean isAtViolationLocation (J .VariableDeclarations .NamedVariable literal ) {
103- final J .CompilationUnit cursor = getCursor ().firstEnclosing (J .CompilationUnit .class );
192+ private void handleMultiVariableDeclaration (J .VariableDeclarations varDecl ,
193+ List <Statement > newStatements ) {
194+ final List <J .VariableDeclarations .NamedVariable > violationsList = new ArrayList <>();
195+ final List <J .VariableDeclarations .NamedVariable > nonViolations = new ArrayList <>();
104196
105- final int line = PositionHelper .computeLinePosition (cursor , literal , getCursor ());
106- final int column = PositionHelper .computeColumnPosition (cursor , literal , getCursor ());
197+ for (J .VariableDeclarations .NamedVariable variable : varDecl .getVariables ()) {
198+ if (variable .getMarkers ().findFirst (FinalLocalVariableMarker .class ).isPresent ()) {
199+ violationsList .add (variable .withPrefix (Space .SINGLE_SPACE ));
200+ }
201+ else {
202+ nonViolations .add (variable .withPrefix (Space .SINGLE_SPACE ));
203+ }
204+ }
205+ if (violationsList .isEmpty ()) {
206+ newStatements .add (varDecl );
207+ }
208+ else if (nonViolations .isEmpty ()) {
209+ newStatements .add (addFinalModifier (varDecl ));
210+ }
211+ else {
212+ newStatements .add (varDecl .withVariables (nonViolations ));
213+ for (J .VariableDeclarations .NamedVariable variable : violationsList ) {
214+ newStatements .add (addFinalModifier (varDecl
215+ .withVariables (Collections .singletonList (variable ))));
216+ }
217+ }
218+ }
107219
108- return violations .stream ().anyMatch (violation -> {
109- return violation .getLine () == line
110- && violation .getColumn () == column
111- && Path .of (violation .getFileName ()).endsWith (sourcePath );
112- });
220+ private J .VariableDeclarations addFinalModifier (J .VariableDeclarations varDecl ) {
221+ final List <J .Modifier > modifiers = new ArrayList <>(varDecl .getModifiers ());
222+ final Space finalPrefix = varDecl .getTypeExpression ().getPrefix ();
223+ modifiers .add (new J .Modifier (Tree .randomId (), finalPrefix ,
224+ Markers .EMPTY , null , J .Modifier .Type .Final , new ArrayList <>()));
225+
226+ modifiers .addAll (varDecl .getModifiers ());
227+
228+ return varDecl .withModifiers (modifiers )
229+ .withTypeExpression (varDecl .getTypeExpression ().withPrefix (Space .SINGLE_SPACE ));
230+ }
231+
232+ private boolean isVariableDeclaration (Statement stmt ) {
233+ return stmt instanceof J .VariableDeclarations varDecl
234+ && varDecl .getVariables ().size () > 1
235+ && !varDecl .hasModifier (J .Modifier .Type .Final )
236+ && varDecl .getTypeExpression () != null
237+ && !(getCursor ().getParentTreeCursor ()
238+ .getValue () instanceof J .ClassDeclaration );
239+ }
240+ }
241+
242+ public static class FinalLocalVariableMarker implements Marker {
243+
244+ @ Override
245+ public @ Nullable UUID getId () {
246+ return null ;
247+ }
248+
249+ @ Override
250+ public <M extends Marker > @ Nullable M withId (UUID id ) {
251+ return null ;
113252 }
114253 }
115254}
0 commit comments