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