Skip to content

Commit 60eda3c

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

File tree

8 files changed

+259
-30
lines changed

8 files changed

+259
-30
lines changed

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

Lines changed: 156 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,84 @@ 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+
private final class MarkViolationVisitor extends JavaIsoVisitor<ExecutionContext> {
6375

6476
private Path sourcePath;
77+
private J.CompilationUnit currentCompilationUnit;
78+
79+
@Override
80+
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) {
81+
this.sourcePath = cu.getSourcePath().toAbsolutePath();
82+
this.currentCompilationUnit = cu;
83+
return super.visitCompilationUnit(cu, ctx);
84+
}
85+
86+
@Override
87+
public J.VariableDeclarations visitVariableDeclarations(
88+
J.VariableDeclarations multiVariable, ExecutionContext executionContext) {
89+
90+
final J.VariableDeclarations variableDeclarations;
91+
92+
final J.VariableDeclarations declarations = super
93+
.visitVariableDeclarations(multiVariable, executionContext);
94+
95+
if (!(getCursor().getParentTreeCursor().getValue() instanceof J.ClassDeclaration)
96+
&& !declarations.hasModifier(J.Modifier.Type.Final)) {
97+
98+
final List<J.VariableDeclarations.NamedVariable> variables = declarations
99+
.getVariables();
100+
final List<J.VariableDeclarations.NamedVariable> marked = new ArrayList<>();
101+
for (J.VariableDeclarations.NamedVariable variable : variables) {
102+
if (isAtViolationLocation(variable)) {
103+
marked.add(variable.withMarkers(
104+
variable.getMarkers()
105+
.add(new FinalLocalVariableMarker(UUID.randomUUID()))));
106+
}
107+
else {
108+
marked.add(variable);
109+
}
110+
}
111+
variableDeclarations = declarations.withVariables(marked);
112+
}
113+
else {
114+
variableDeclarations = declarations;
115+
}
116+
return variableDeclarations;
117+
}
118+
119+
private boolean isAtViolationLocation(J.VariableDeclarations.NamedVariable variable) {
120+
121+
final int line = PositionHelper
122+
.computeLinePosition(currentCompilationUnit, variable, getCursor());
123+
final int column = PositionHelper
124+
.computeColumnPosition(currentCompilationUnit, variable, getCursor());
125+
126+
return violations.removeIf(violation -> {
127+
final Path absolutePath = Path.of(violation.getFileName()).toAbsolutePath();
128+
return violation.getLine() == line
129+
&& violation.getColumn() == column
130+
&& absolutePath.endsWith(sourcePath)
131+
&& violation.getMessage().contains(variable.getSimpleName());
132+
});
133+
}
134+
}
135+
136+
private final class LocalVariableVisitor extends JavaIsoVisitor<ExecutionContext> {
65137

66138
@Override
67139
public J.CompilationUnit visitCompilationUnit(
68140
J.CompilationUnit cu, ExecutionContext executionContext) {
69-
this.sourcePath = cu.getSourcePath();
70141
return super.visitCompilationUnit(cu, executionContext);
71142
}
72143

@@ -83,33 +154,96 @@ public J.VariableDeclarations visitVariableDeclarations(
83154
&& !declarations.hasModifier(J.Modifier.Type.Final)) {
84155
final J.VariableDeclarations.NamedVariable variable = declarations
85156
.getVariables().get(0);
86-
if (isAtViolationLocation(variable)) {
87-
final List<J.Modifier> modifiers = new ArrayList<>();
157+
if (variable.getMarkers().findFirst(FinalLocalVariableMarker.class).isPresent()) {
158+
declarations = addFinalModifier(declarations);
159+
}
160+
}
161+
return declarations;
162+
}
163+
164+
@Override
165+
public J.Block visitBlock(J.Block block, ExecutionContext executionContext) {
166+
final J.Block visited = super.visitBlock(block, executionContext);
88167

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

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));
170+
for (Statement stmt : visited.getStatements()) {
171+
if (isVariableDeclaration(stmt)) {
172+
handleMultiVariableDeclaration((J.VariableDeclarations) stmt, newStatements);
173+
}
174+
else {
175+
newStatements.add(stmt);
97176
}
98177
}
99-
return declarations;
178+
179+
return visited.withStatements(newStatements);
100180
}
101181

102-
private boolean isAtViolationLocation(J.VariableDeclarations.NamedVariable literal) {
103-
final J.CompilationUnit cursor = getCursor().firstEnclosing(J.CompilationUnit.class);
182+
private void handleMultiVariableDeclaration(J.VariableDeclarations varDecl,
183+
List<Statement> newStatements) {
184+
final List<J.VariableDeclarations.NamedVariable> violationsList = new ArrayList<>();
185+
final List<J.VariableDeclarations.NamedVariable> nonViolations = new ArrayList<>();
104186

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

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

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

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

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

3333
public void methodWithCalculations() {
@@ -44,6 +44,11 @@ public String processResult(String input) {
4444
final String trimmed = input.trim();
4545
final String upperCase = trimmed.toUpperCase();
4646
final String formatted = "[" + upperCase + "]";
47+
int a;
48+
final int b;
49+
a = upperCase.indexOf(formatted);
50+
b = upperCase.indexOf(formatted);
51+
a = a + formatted.length();
4752
return formatted;
4853
}
4954

0 commit comments

Comments
 (0)