Skip to content

Commit 0e9ede6

Browse files
committed
Add RemovePrivateFieldUnderscores recipe
This recipe removes prefix or suffix underscores from private class field names. This helps enforce modern Java coding standards. For clarity and consistency, the recipe also adds a qualifier to all updated field accesses. This prevents ambiguity with local variables and ensures a uniform style throughout the class. Fixes #630
1 parent 9093b7c commit 0e9ede6

File tree

2 files changed

+313
-0
lines changed

2 files changed

+313
-0
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.staticanalysis;
17+
18+
import org.openrewrite.*;
19+
import org.openrewrite.java.*;
20+
import org.openrewrite.java.tree.*;
21+
import org.openrewrite.marker.Markers;
22+
23+
import static java.util.Collections.emptyList;
24+
25+
public class RemovePrivateFieldUnderscores extends Recipe {
26+
27+
@Override
28+
public String getDisplayName() {
29+
return "Remove underscores from private class field names";
30+
}
31+
32+
@Override
33+
public String getDescription() {
34+
return "Removes prefix or suffix underscores from private class field names and adds `this.` to all field accesses for clarity and consistency.";
35+
}
36+
37+
@Override
38+
public TreeVisitor<?, ExecutionContext> getVisitor() {
39+
return new JavaVisitor<ExecutionContext>() {
40+
@Override
41+
public J visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) {
42+
J.VariableDeclarations mv = (J.VariableDeclarations) super.visitVariableDeclarations(multiVariable, ctx);
43+
if (!mv.hasModifier(J.Modifier.Type.Private)) {
44+
return mv;
45+
}
46+
47+
Cursor parent = getCursor().getParentTreeCursor();
48+
if (!(parent.getValue() instanceof J.Block && parent.getParentTreeCursor().getValue() instanceof J.ClassDeclaration)) {
49+
return mv;
50+
}
51+
52+
for (J.VariableDeclarations.NamedVariable variable : mv.getVariables()) {
53+
String oldName = variable.getSimpleName();
54+
if (oldName.startsWith("_") || oldName.endsWith("_")) {
55+
String newName = oldName.startsWith("_") ? oldName.substring(1) : oldName.substring(0, oldName.length() - 1);
56+
doAfterVisit(new AddThisToFieldUses(variable));
57+
doAfterVisit(new RenameVariable(variable, newName));
58+
}
59+
}
60+
return mv;
61+
}
62+
};
63+
}
64+
65+
private static class AddThisToFieldUses extends JavaVisitor<ExecutionContext> {
66+
private final JavaType.Variable fieldType;
67+
68+
public AddThisToFieldUses(J.VariableDeclarations.NamedVariable field) {
69+
this.fieldType = field.getVariableType();
70+
}
71+
72+
@Override
73+
public J visitIdentifier(J.Identifier identifier, ExecutionContext ctx) {
74+
J.Identifier id = (J.Identifier) super.visitIdentifier(identifier, ctx);
75+
if (id.getFieldType() != null && id.getFieldType().equals(this.fieldType)) {
76+
if (getCursor().getParentTreeCursor().getValue() instanceof J.VariableDeclarations.NamedVariable) {
77+
return id;
78+
}
79+
80+
if (getCursor().getParentTreeCursor().getValue() instanceof J.FieldAccess) {
81+
return id;
82+
}
83+
84+
return new J.FieldAccess(
85+
Tree.randomId(),
86+
identifier.getPrefix(),
87+
Markers.EMPTY,
88+
new J.Identifier(
89+
Tree.randomId(),
90+
Space.EMPTY,
91+
Markers.EMPTY,
92+
emptyList(),
93+
"this",
94+
identifier.getType(),
95+
null
96+
),
97+
JLeftPadded.build(identifier.withPrefix(Space.EMPTY).withSimpleName(id.getSimpleName())),
98+
identifier.getType()
99+
);
100+
}
101+
return id;
102+
}
103+
}
104+
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.staticanalysis;
17+
18+
import org.junit.jupiter.api.Test;
19+
import org.openrewrite.DocumentExample;
20+
import org.openrewrite.test.RecipeSpec;
21+
import org.openrewrite.test.RewriteTest;
22+
23+
import static org.openrewrite.java.Assertions.java;
24+
25+
class RemovePrivateFieldUnderscoresTest implements RewriteTest {
26+
27+
@Override
28+
public void defaults(RecipeSpec spec) {
29+
spec.recipe(new RemovePrivateFieldUnderscores());
30+
}
31+
32+
@DocumentExample
33+
@Test
34+
void removesPrefixUnderscore() {
35+
rewriteRun(
36+
//language=java
37+
java(
38+
"""
39+
public class ParseLocation {
40+
private String _ruleName;
41+
42+
public String getRuleName() {
43+
return _ruleName;
44+
}
45+
46+
public void setRuleName(String ruleName) {
47+
_ruleName = ruleName;
48+
}
49+
}
50+
""",
51+
"""
52+
public class ParseLocation {
53+
private String ruleName;
54+
55+
public String getRuleName() {
56+
return this.ruleName;
57+
}
58+
59+
public void setRuleName(String ruleName) {
60+
this.ruleName = ruleName;
61+
}
62+
}
63+
"""
64+
)
65+
);
66+
}
67+
68+
@Test
69+
void removesSuffixUnderscore() {
70+
rewriteRun(
71+
//language=java
72+
java(
73+
"""
74+
public class ParseLocation {
75+
private String ruleName_;
76+
77+
public String getRuleName() {
78+
return ruleName_;
79+
}
80+
81+
public void setRuleName(String ruleName) {
82+
ruleName_ = ruleName;
83+
}
84+
}
85+
""",
86+
"""
87+
public class ParseLocation {
88+
private String ruleName;
89+
90+
public String getRuleName() {
91+
return this.ruleName;
92+
}
93+
94+
public void setRuleName(String ruleName) {
95+
this.ruleName = ruleName;
96+
}
97+
}
98+
"""
99+
)
100+
);
101+
}
102+
103+
@Test
104+
void handlesFieldAccessInOtherMethods() {
105+
rewriteRun(
106+
//language=java
107+
java(
108+
"""
109+
public class Calculator {
110+
private int _operand1;
111+
private int operand2_;
112+
113+
public Calculator(int a, int b) {
114+
_operand1 = a;
115+
operand2_ = b;
116+
}
117+
118+
public int sum() {
119+
return _operand1 + operand2_;
120+
}
121+
}
122+
""",
123+
"""
124+
public class Calculator {
125+
private int operand1;
126+
private int operand2;
127+
128+
public Calculator(int a, int b) {
129+
this.operand1 = a;
130+
this.operand2 = b;
131+
}
132+
133+
public int sum() {
134+
return this.operand1 + this.operand2;
135+
}
136+
}
137+
"""
138+
)
139+
);
140+
}
141+
142+
@Test
143+
void handlesFieldsAlreadyQualifiedWithThis() {
144+
rewriteRun(
145+
//language=java
146+
java(
147+
"""
148+
public class ParseLocation {
149+
private String _ruleName;
150+
151+
public String getRuleName() {
152+
return this._ruleName;
153+
}
154+
155+
public void setRuleName(String ruleName) {
156+
this._ruleName = ruleName;
157+
}
158+
}
159+
""",
160+
"""
161+
public class ParseLocation {
162+
private String ruleName;
163+
164+
public String getRuleName() {
165+
return this.ruleName;
166+
}
167+
168+
public void setRuleName(String ruleName) {
169+
this.ruleName = ruleName;
170+
}
171+
}
172+
"""
173+
)
174+
);
175+
}
176+
177+
@Test
178+
void doesNotChangeNonPrivateFields() {
179+
rewriteRun(
180+
//language=java
181+
java(
182+
"""
183+
public class MyClass {
184+
public String _publicField;
185+
protected String _protectedField;
186+
String packagePrivateField_;
187+
}
188+
"""
189+
)
190+
);
191+
}
192+
193+
@Test
194+
void doesNotChangeLocalVariables() {
195+
rewriteRun(
196+
//language=java
197+
java(
198+
"""
199+
public class MyClass {
200+
public void myMethod() {
201+
String _localVar = "prefixUnderscore";
202+
String localVar_ = "suffixUnderscore";
203+
}
204+
}
205+
"""
206+
)
207+
);
208+
}
209+
}

0 commit comments

Comments
 (0)