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,92 @@ 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 ctx ) {
68+ return new LocalVariableVisitor ()
69+ .visitCompilationUnit (new MarkViolationVisitor ()
70+ .visitCompilationUnit (cu , ctx ), ctx );
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 , ExecutionContext ctx ) {
87+ this .sourcePath = cu .getSourcePath ().toAbsolutePath ();
88+ this .currentCompilationUnit = cu ;
89+ return super .visitCompilationUnit (cu , ctx );
90+ }
91+
92+ @ Override
93+ public J .VariableDeclarations visitVariableDeclarations (
94+ J .VariableDeclarations multiVariable , ExecutionContext executionContext ) {
95+
96+ final J .VariableDeclarations variableDeclarations ;
97+
98+ final J .VariableDeclarations declarations = super
99+ .visitVariableDeclarations (multiVariable , executionContext );
100+
101+ if (!(getCursor ().getParentTreeCursor ().getValue () instanceof J .ClassDeclaration )
102+ && !declarations .hasModifier (J .Modifier .Type .Final )) {
103+
104+ final List <J .VariableDeclarations .NamedVariable > variables = declarations
105+ .getVariables ();
106+ final List <J .VariableDeclarations .NamedVariable > marked = new ArrayList <>();
107+ for (J .VariableDeclarations .NamedVariable variable : variables ) {
108+ if (isAtViolationLocation (variable )) {
109+ marked .add (variable .withMarkers (
110+ variable .getMarkers ().add (new FinalLocalVariableMarker ())));
111+ }
112+ else {
113+ marked .add (variable );
114+ }
115+ }
116+ variableDeclarations = declarations .withVariables (marked );
117+ }
118+ else {
119+ variableDeclarations = declarations ;
120+ }
121+ return variableDeclarations ;
122+ }
123+
124+ private boolean isAtViolationLocation (J .VariableDeclarations .NamedVariable variable ) {
125+
126+ final int line = PositionHelper
127+ .computeLinePosition (currentCompilationUnit , variable , getCursor ());
128+ final int column = PositionHelper
129+ .computeColumnPosition (currentCompilationUnit , variable , getCursor ());
130+
131+ return violations .removeIf (violation -> {
132+ final Path absolutePath = Path .of (violation .getFileName ()).toAbsolutePath ();
133+ return violation .getLine () == line
134+ && violation .getColumn () == column
135+ && absolutePath .endsWith (sourcePath )
136+ && violation .getMessage ().contains (variable .getSimpleName ());
137+ });
138+ }
139+ }
140+
141+ /**
142+ * Visitor that processes marked variable declarations and applies the final modifier.
143+ * This visitor handles both single and multi-variable declarations.
144+ */
145+ private final class LocalVariableVisitor extends JavaIsoVisitor <ExecutionContext > {
65146
66147 @ Override
67148 public J .CompilationUnit visitCompilationUnit (
68149 J .CompilationUnit cu , ExecutionContext executionContext ) {
69- this .sourcePath = cu .getSourcePath ();
70150 return super .visitCompilationUnit (cu , executionContext );
71151 }
72152
@@ -83,33 +163,91 @@ public J.VariableDeclarations visitVariableDeclarations(
83163 && !declarations .hasModifier (J .Modifier .Type .Final )) {
84164 final J .VariableDeclarations .NamedVariable variable = declarations
85165 .getVariables ().get (0 );
86- if (isAtViolationLocation (variable )) {
87- final List <J .Modifier > modifiers = new ArrayList <>();
166+ if (variable .getMarkers ().findFirst (FinalLocalVariableMarker .class ).isPresent ()) {
167+ declarations = addFinalModifier (declarations );
168+ }
169+ }
170+ return declarations ;
171+ }
172+
173+ @ Override
174+ public J .Block visitBlock (J .Block block , ExecutionContext executionContext ) {
175+ final J .Block visited = super .visitBlock (block , executionContext );
88176
89- final Space finalPrefix = declarations . getTypeExpression (). getPrefix ();
177+ final List < Statement > newStatements = new ArrayList <> ();
90178
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 ));
179+ for (Statement stmt : visited .getStatements ()) {
180+ if (isVariableDeclaration (stmt )) {
181+ handleMultiVariableDeclaration ((J .VariableDeclarations ) stmt , newStatements );
182+ }
183+ else {
184+ newStatements .add (stmt );
185+ }
186+ }
187+
188+ return visited .withStatements (newStatements );
189+ }
190+
191+ private void handleMultiVariableDeclaration (J .VariableDeclarations varDecl ,
192+ List <Statement > newStatements ) {
193+ final List <J .VariableDeclarations .NamedVariable > violationsList = new ArrayList <>();
194+ final List <J .VariableDeclarations .NamedVariable > nonViolations = new ArrayList <>();
195+
196+ for (J .VariableDeclarations .NamedVariable variable : varDecl .getVariables ()) {
197+ if (variable .getMarkers ().findFirst (FinalLocalVariableMarker .class ).isPresent ()) {
198+ violationsList .add (variable .withPrefix (Space .SINGLE_SPACE ));
199+ }
200+ else {
201+ nonViolations .add (variable .withPrefix (Space .SINGLE_SPACE ));
202+ }
203+ }
204+ if (violationsList .isEmpty ()) {
205+ newStatements .add (varDecl );
206+ }
207+ else if (nonViolations .isEmpty ()) {
208+ newStatements .add (addFinalModifier (varDecl ));
209+ }
210+ else {
211+ newStatements .add (varDecl .withVariables (nonViolations ));
212+ for (J .VariableDeclarations .NamedVariable variable : violationsList ) {
213+ newStatements .add (addFinalModifier (varDecl
214+ .withVariables (Collections .singletonList (variable ))));
97215 }
98216 }
99- return declarations ;
100217 }
101218
102- private boolean isAtViolationLocation (J .VariableDeclarations .NamedVariable literal ) {
103- final J .CompilationUnit cursor = getCursor ().firstEnclosing (J .CompilationUnit .class );
219+ private J .VariableDeclarations addFinalModifier (J .VariableDeclarations varDecl ) {
220+ final List <J .Modifier > modifiers = new ArrayList <>(varDecl .getModifiers ());
221+ final Space finalPrefix = varDecl .getTypeExpression ().getPrefix ();
222+ modifiers .add (new J .Modifier (Tree .randomId (), finalPrefix ,
223+ Markers .EMPTY , null , J .Modifier .Type .Final , new ArrayList <>()));
104224
105- final int line = PositionHelper .computeLinePosition (cursor , literal , getCursor ());
106- final int column = PositionHelper .computeColumnPosition (cursor , literal , getCursor ());
225+ modifiers .addAll (varDecl .getModifiers ());
107226
108- return violations .stream ().anyMatch (violation -> {
109- return violation .getLine () == line
110- && violation .getColumn () == column
111- && Path .of (violation .getFileName ()).endsWith (sourcePath );
112- });
227+ return varDecl .withModifiers (modifiers )
228+ .withTypeExpression (varDecl .getTypeExpression ().withPrefix (Space .SINGLE_SPACE ));
229+ }
230+
231+ private boolean isVariableDeclaration (Statement stmt ) {
232+ return stmt instanceof J .VariableDeclarations varDecl
233+ && varDecl .getVariables ().size () > 1
234+ && !varDecl .hasModifier (J .Modifier .Type .Final )
235+ && varDecl .getTypeExpression () != null
236+ && !(getCursor ().getParentTreeCursor ()
237+ .getValue () instanceof J .ClassDeclaration );
238+ }
239+ }
240+
241+ public static class FinalLocalVariableMarker implements Marker {
242+
243+ @ Override
244+ public @ Nullable UUID getId () {
245+ return null ;
246+ }
247+
248+ @ Override
249+ public <M extends Marker > M withId (UUID id ) {
250+ return null ;
113251 }
114252 }
115253}
0 commit comments