Skip to content

Commit 60ce3bd

Browse files
committed
Issue #80: Resolve MultiVariableDeclaration Bug
1 parent 85c5edb commit 60ce3bd

File tree

8 files changed

+264
-30
lines changed

8 files changed

+264
-30
lines changed

src/main/java/org/checkstyle/autofix/recipe/FinalLocalVariable.java

Lines changed: 161 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919

2020
import java.nio.file.Path;
2121
import java.util.ArrayList;
22+
import java.util.Collections;
2223
import java.util.List;
24+
import java.util.UUID;
2325

2426
import org.checkstyle.autofix.PositionHelper;
2527
import org.checkstyle.autofix.parser.CheckstyleViolation;
@@ -30,6 +32,8 @@
3032
import org.openrewrite.java.JavaIsoVisitor;
3133
import org.openrewrite.java.tree.J;
3234
import org.openrewrite.java.tree.Space;
35+
import org.openrewrite.java.tree.Statement;
36+
import org.openrewrite.marker.Marker;
3337
import org.openrewrite.marker.Markers;
3438

3539
/**
@@ -56,17 +60,89 @@ 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+
J.CompilationUnit compilationUnit = cu;
68+
compilationUnit = new MarkViolationVisitor()
69+
.visitCompilationUnit(compilationUnit, ctx);
70+
71+
compilationUnit = new LocalVariableVisitor()
72+
.visitCompilationUnit(compilationUnit, ctx);
73+
74+
return compilationUnit;
75+
}
76+
};
6077
}
6178

62-
private final class LocalVariableVisitor extends JavaIsoVisitor<ExecutionContext> {
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()
110+
.add(new FinalLocalVariableMarker(UUID.randomUUID()))));
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+
private final class LocalVariableVisitor extends JavaIsoVisitor<ExecutionContext> {
65142

66143
@Override
67144
public J.CompilationUnit visitCompilationUnit(
68145
J.CompilationUnit cu, ExecutionContext executionContext) {
69-
this.sourcePath = cu.getSourcePath();
70146
return super.visitCompilationUnit(cu, executionContext);
71147
}
72148

@@ -83,33 +159,96 @@ public J.VariableDeclarations visitVariableDeclarations(
83159
&& !declarations.hasModifier(J.Modifier.Type.Final)) {
84160
final J.VariableDeclarations.NamedVariable variable = declarations
85161
.getVariables().get(0);
86-
if (isAtViolationLocation(variable)) {
87-
final List<J.Modifier> modifiers = new ArrayList<>();
162+
if (variable.getMarkers().findFirst(FinalLocalVariableMarker.class).isPresent()) {
163+
declarations = addFinalModifier(declarations);
164+
}
165+
}
166+
return declarations;
167+
}
168+
169+
@Override
170+
public J.Block visitBlock(J.Block block, ExecutionContext executionContext) {
171+
final J.Block visited = super.visitBlock(block, executionContext);
88172

89-
final Space finalPrefix = declarations.getTypeExpression().getPrefix();
173+
final List<Statement> newStatements = new ArrayList<>();
90174

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));
175+
for (Statement stmt : visited.getStatements()) {
176+
if (isVariableDeclaration(stmt)) {
177+
handleMultiVariableDeclaration((J.VariableDeclarations) stmt, newStatements);
178+
}
179+
else {
180+
newStatements.add(stmt);
97181
}
98182
}
99-
return declarations;
183+
184+
return visited.withStatements(newStatements);
100185
}
101186

102-
private boolean isAtViolationLocation(J.VariableDeclarations.NamedVariable literal) {
103-
final J.CompilationUnit cursor = getCursor().firstEnclosing(J.CompilationUnit.class);
187+
private void handleMultiVariableDeclaration(J.VariableDeclarations varDecl,
188+
List<Statement> newStatements) {
189+
final List<J.VariableDeclarations.NamedVariable> violationsList = new ArrayList<>();
190+
final List<J.VariableDeclarations.NamedVariable> nonViolations = new ArrayList<>();
104191

105-
final int line = PositionHelper.computeLinePosition(cursor, literal, getCursor());
106-
final int column = PositionHelper.computeColumnPosition(cursor, literal, getCursor());
192+
for (J.VariableDeclarations.NamedVariable variable : varDecl.getVariables()) {
193+
if (variable.getMarkers().findFirst(FinalLocalVariableMarker.class).isPresent()) {
194+
violationsList.add(variable.withPrefix(Space.SINGLE_SPACE));
195+
}
196+
else {
197+
nonViolations.add(variable.withPrefix(Space.SINGLE_SPACE));
198+
}
199+
}
200+
if (violationsList.isEmpty()) {
201+
newStatements.add(varDecl);
202+
}
203+
else if (nonViolations.isEmpty()) {
204+
newStatements.add(addFinalModifier(varDecl));
205+
}
206+
else {
207+
newStatements.add(varDecl.withVariables(nonViolations));
208+
for (J.VariableDeclarations.NamedVariable variable : violationsList) {
209+
newStatements.add(addFinalModifier(varDecl
210+
.withVariables(Collections.singletonList(variable))));
211+
}
212+
}
213+
}
107214

108-
return violations.stream().anyMatch(violation -> {
109-
return violation.getLine() == line
110-
&& violation.getColumn() == column
111-
&& Path.of(violation.getFileName()).endsWith(sourcePath);
112-
});
215+
private J.VariableDeclarations addFinalModifier(J.VariableDeclarations varDecl) {
216+
final List<J.Modifier> modifiers = new ArrayList<>();
217+
final Space finalPrefix = varDecl.getTypeExpression().getPrefix();
218+
modifiers.add(new J.Modifier(Tree.randomId(), finalPrefix,
219+
Markers.EMPTY, null, J.Modifier.Type.Final, new ArrayList<>()));
220+
221+
modifiers.addAll(varDecl.getModifiers());
222+
223+
return varDecl.withModifiers(modifiers)
224+
.withTypeExpression(varDecl.getTypeExpression().withPrefix(Space.SINGLE_SPACE));
225+
}
226+
227+
private boolean isVariableDeclaration(Statement stmt) {
228+
return stmt instanceof J.VariableDeclarations varDecl
229+
&& varDecl.getVariables().size() > 1
230+
&& !varDecl.hasModifier(J.Modifier.Type.Final)
231+
&& varDecl.getTypeExpression() != null
232+
&& !(getCursor().getParentTreeCursor()
233+
.getValue() instanceof J.ClassDeclaration);
234+
}
235+
}
236+
237+
public static class FinalLocalVariableMarker implements Marker {
238+
private final UUID id;
239+
240+
public FinalLocalVariableMarker(UUID uuid) {
241+
this.id = uuid;
242+
}
243+
244+
@Override
245+
public UUID getId() {
246+
return id;
247+
}
248+
249+
@Override
250+
public <M extends Marker> M withId(UUID uuid) {
251+
return (M) new FinalLocalVariableMarker(uuid);
113252
}
114253
}
115254
}

src/test/java/org/checkstyle/autofix/recipe/FinalLocalVariableTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,9 @@ void annotationDeclaration() throws Exception {
5151
verify("AnnotationDeclaration");
5252
}
5353

54+
@Test
55+
void multiple() throws Exception {
56+
verify("MultipleVariable");
57+
}
58+
5459
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--- src/test/resources/org/checkstyle/autofix/recipe/finallocalvariable/multiplevariable/InputMultipleVariable.java
2+
+++ src/test/resources/org/checkstyle/autofix/recipe/finallocalvariable/multiplevariable/OutputMultipleVariable.java
3+
@@ -8,15 +8,17 @@
4+
5+
package org.checkstyle.autofix.recipe.finallocalvariable.multiplevariable;
6+
7+
-public class InputMultipleVariable {
8+
+public class OutputMultipleVariable {
9+
10+
public void multipleDeclarations() {
11+
12+
- int x = 10, y = 20, z = 30; // 3 violations
13+
+ final int x = 10, y = 20, z = 30;
14+
15+
- String name = "John", city = "NYC"; // 2 violations
16+
+ final String name = "John", city = "NYC";
17+
18+
- double price = 19.99, tax = 0.08; // violation, "should be declared final"
19+
+ double tax = 0.08;
20+
+
21+
+ final double price = 19.99;
22+
23+
tax = 50;
24+
System.out.println("Sum: " + (x + y + z));
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*xml
2+
<module name="Checker">
3+
<module name="TreeWalker">
4+
<module name="com.puppycrawl.tools.checkstyle.checks.coding.FinalLocalVariableCheck"/>
5+
</module>
6+
</module>
7+
*/
8+
9+
package org.checkstyle.autofix.recipe.finallocalvariable.multiplevariable;
10+
11+
public class InputMultipleVariable {
12+
13+
public void multipleDeclarations() {
14+
15+
int x = 10, y = 20, z = 30; // 3 violations
16+
17+
String name = "John", city = "NYC"; // 2 violations
18+
19+
double price = 19.99, tax = 0.08; // violation, "should be declared final"
20+
21+
tax = 50;
22+
System.out.println("Sum: " + (x + y + z));
23+
System.out.println(name + " lives in " + city);
24+
System.out.println("Total: " + (price + (price * tax)));
25+
}
26+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*xml
2+
<module name="Checker">
3+
<module name="TreeWalker">
4+
<module name="com.puppycrawl.tools.checkstyle.checks.coding.FinalLocalVariableCheck"/>
5+
</module>
6+
</module>
7+
*/
8+
9+
package org.checkstyle.autofix.recipe.finallocalvariable.multiplevariable;
10+
11+
public class OutputMultipleVariable {
12+
13+
public void multipleDeclarations() {
14+
15+
final int x = 10, y = 20, z = 30;
16+
17+
final String name = "John", city = "NYC";
18+
19+
double tax = 0.08;
20+
21+
final double price = 19.99;
22+
23+
tax = 50;
24+
System.out.println("Sum: " + (x + y + z));
25+
System.out.println(name + " lives in " + city);
26+
System.out.println("Total: " + (price + (price * tax)));
27+
}
28+
}

src/test/resources/org/checkstyle/autofix/recipe/finallocalvariable/singlelocaltest/DiffSingleLocalTest.diff

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
--- src/test/resources/org/checkstyle/autofix/recipe/finallocalvariable/singlelocaltest/InputSingleLocalTest.java
22
+++ src/test/resources/org/checkstyle/autofix/recipe/finallocalvariable/singlelocaltest/OutputSingleLocalTest.java
3-
@@ -15,36 +15,35 @@
3+
@@ -15,37 +15,37 @@
44
import java.util.HashMap;
55
import java.util.List;
66

@@ -17,7 +17,7 @@
1717
- StringBuilder builder = new StringBuilder(); // violation, "should be declared final"
1818
- Date currentDate = new Date(); // violation, "should be declared final"
1919
- File // prefix comment preceding target element
20-
- tempFile = new File("temp.txt"); // violation, "should be declared final"
20+
- tempFile = new File("temp.txt"); // violation, "should be declared final"
2121
+ final String name = "John Doe";
2222
+ final int age = 25;
2323
+ final double salary = 50000.0;
@@ -28,7 +28,7 @@
2828
+ final StringBuilder builder = new StringBuilder();
2929
+ final Date currentDate = new Date();
3030
+ final File // prefix comment preceding target element
31-
+ tempFile = new File("temp.txt");
31+
+ tempFile = new File("temp.txt");
3232
}
3333

3434
public void methodWithCalculations() {
@@ -53,9 +53,12 @@
5353
- String trimmed = input.trim(); // violation, "should be declared final"
5454
- String upperCase = trimmed.toUpperCase(); // violation, "should be declared final"
5555
- String formatted = "[" + upperCase + "]"; // violation, "should be declared final"
56+
- int a,b; // violation, "should be declared final"
5657
+ final String trimmed = input.trim();
5758
+ final String upperCase = trimmed.toUpperCase();
5859
+ final String formatted = "[" + upperCase + "]";
59-
return formatted;
60-
}
61-
60+
+ int a;
61+
+ final int b;
62+
a = upperCase.indexOf(formatted);
63+
b = upperCase.indexOf(formatted);
64+
a = a + formatted.length();

src/test/resources/org/checkstyle/autofix/recipe/finallocalvariable/singlelocaltest/InputSingleLocalTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public void basicLocalVariables() {
2727
StringBuilder builder = new StringBuilder(); // violation, "should be declared final"
2828
Date currentDate = new Date(); // violation, "should be declared final"
2929
File // prefix comment preceding target element
30-
tempFile = new File("temp.txt"); // violation, "should be declared final"
30+
tempFile = new File("temp.txt"); // violation, "should be declared final"
3131
}
3232

3333
public void methodWithCalculations() {
@@ -45,6 +45,10 @@ public String processResult(String input) {
4545
String trimmed = input.trim(); // violation, "should be declared final"
4646
String upperCase = trimmed.toUpperCase(); // violation, "should be declared final"
4747
String formatted = "[" + upperCase + "]"; // violation, "should be declared final"
48+
int a,b; // violation, "should be declared final"
49+
a = upperCase.indexOf(formatted);
50+
b = upperCase.indexOf(formatted);
51+
a = a + formatted.length();
4852
return formatted;
4953
}
5054

0 commit comments

Comments
 (0)