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 ;
3032import org .openrewrite .java .JavaIsoVisitor ;
3133import org .openrewrite .java .tree .J ;
3234import org .openrewrite .java .tree .Space ;
35+ import org .openrewrite .java .tree .Statement ;
36+ import org .openrewrite .marker .Marker ;
3337import org .openrewrite .marker .Markers ;
3438
3539/**
@@ -56,17 +60,94 @@ public String getDescription() {
5660
5761 @ Override
5862 public TreeVisitor <?, ExecutionContext > getVisitor () {
59- return new LocalVariableVisitor ();
63+ return new JavaIsoVisitor <>() {
64+ @ Override
65+ public J .CompilationUnit visitCompilationUnit (J .CompilationUnit cu ,
66+ ExecutionContext executionContext ) {
67+ return new LocalVariableVisitor ()
68+ .visitCompilationUnit (new MarkViolationVisitor ()
69+ .visitCompilationUnit (cu , executionContext ), executionContext );
70+ }
71+ };
6072 }
6173
62- private final class LocalVariableVisitor extends JavaIsoVisitor <ExecutionContext > {
74+ /**
75+ * Visitor that identifies and marks variable declarations at violation locations.
76+ * This visitor traverses the AST and adds markers to variables that match
77+ * the checkstyle violation locations, preparing them for the final modifier addition.
78+ */
79+ private final class MarkViolationVisitor extends JavaIsoVisitor <ExecutionContext > {
6380
6481 private Path sourcePath ;
82+ private J .CompilationUnit currentCompilationUnit ;
83+
84+ @ Override
85+ public J .CompilationUnit visitCompilationUnit (J .CompilationUnit cu ,
86+ ExecutionContext executionContext ) {
87+ this .sourcePath = cu .getSourcePath ().toAbsolutePath ();
88+ this .currentCompilationUnit = cu ;
89+ return super .visitCompilationUnit (cu , executionContext );
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 (
111+ new FinalLocalVariableMarker (UUID .randomUUID ()))));
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,97 @@ 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 <>();
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+ private static final class FinalLocalVariableMarker implements Marker {
243+
244+ private final UUID id ;
245+
246+ private FinalLocalVariableMarker (UUID uuid ) {
247+ this .id = uuid ;
248+ }
249+
250+ @ Override
251+ public UUID getId () {
252+ return id ;
253+ }
254+
255+ @ Override
256+ public <M extends Marker > M withId (UUID uuid ) {
257+ return (M ) new FinalLocalVariableMarker (uuid );
113258 }
114259 }
115260}
0 commit comments