/
RenamePrivateFieldsToCamelCase.java
163 lines (145 loc) · 7.86 KB
/
RenamePrivateFieldsToCamelCase.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/*
* Copyright 2020 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.staticanalysis;
import org.openrewrite.*;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.java.AnnotationMatcher;
import org.openrewrite.java.service.AnnotationService;
import org.openrewrite.java.tree.Flag;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.Space;
import org.openrewrite.marker.Markers;
import java.time.Duration;
import java.util.*;
import static java.util.Collections.emptyList;
import static org.openrewrite.internal.NameCaseConvention.LOWER_CAMEL;
/**
* This recipe converts private fields to camel case convention.
* <p>
* The first character is set to lower case and existing capital letters are preserved.
* Special characters that are allowed in java field names `$` and `_` are removed.
* If a special character is removed the next valid alpha-numeric will be capitalized.
* <p>
* Currently, unsupported:
* - The recipe will not rename fields if the result already exists in a class or the result will be a java reserved keyword.
*/
public class RenamePrivateFieldsToCamelCase extends Recipe {
private static final AnnotationMatcher LOMBOK_ANNOTATION = new AnnotationMatcher("@lombok.*");
@Override
public String getDisplayName() {
return "Reformat private field names to camelCase";
}
@Override
public String getDescription() {
return "Reformat private field names to camelCase to comply with Java naming convention. " +
"The recipe will not rename fields with default, protected or public access modifiers." +
"The recipe will not rename private constants." +
"The first character is set to lower case and existing capital letters are preserved. " +
"Special characters that are allowed in java field names `$` and `_` are removed. " +
"If a special character is removed the next valid alphanumeric will be capitalized. " +
"The recipe will not rename a field if the result already exists in the class, conflicts with a java reserved keyword, or the result is blank.";
}
@Override
public Set<String> getTags() {
return new LinkedHashSet<>(Arrays.asList("RSPEC-S116", "RSPEC-S3008"));
}
@Override
public Duration getEstimatedEffortPerOccurrence() {
return Duration.ofMinutes(2);
}
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new RenameToCamelCase() {
@Override
protected boolean shouldRename(Set<String> hasNameSet, J.VariableDeclarations.NamedVariable variable, String toName) {
if (toName.isEmpty() || !Character.isAlphabetic(toName.charAt(0))) {
return false;
}
return hasNameSet.stream().noneMatch(key ->
key.equals(toName) ||
key.equals(variable.getSimpleName()) ||
key.endsWith(" " + toName) ||
key.endsWith(" " + variable.getSimpleName())
);
}
@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
// Skip classes annotated with Lombok annotations, as their fields might be set or exposed by Lombok.
if (service(AnnotationService.class).matches(getCursor(), LOMBOK_ANNOTATION)) {
return classDecl;
}
return super.visitClassDeclaration(classDecl, ctx);
}
@SuppressWarnings("all")
@Override
public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations.NamedVariable variable, ExecutionContext ctx) {
Cursor parentScope = getCursorToParentScope(getCursor());
// Does support renaming fields in a J.ClassDeclaration.
// We must have a variable type to make safe changes.
// Only make changes to private fields that are not constants.
// Does not apply for instance variables of inner classes
// Only make a change if the variable does not conform to lower camelcase format.
JavaType.Variable type = variable.getVariableType();
if (parentScope.getParent() != null &&
parentScope.getParent().getValue() instanceof J.ClassDeclaration &&
!(parentScope.getValue() instanceof J.ClassDeclaration) &&
type != null &&
type.hasFlags(Flag.Private) &&
!(type.hasFlags(Flag.Static, Flag.Final)) &&
!((J.ClassDeclaration) parentScope.getParent().getValue()).getType().getFullyQualifiedName().contains("$") &&
!LOWER_CAMEL.matches(variable.getSimpleName())) {
if (variable.getSimpleName().toUpperCase(Locale.getDefault()).equals(variable.getSimpleName()) &&
type.hasFlags(Flag.Private, Flag.Final) && !type.hasFlags(Flag.Static) && variable.getInitializer() instanceof J.Literal) {
// instead, add a static modifier
Set<Flag> flags = new HashSet<>(type.getFlags());
flags.add(Flag.Static);
getCursor().getParentTreeCursor().putMessage("ADD_STATIC", true);
return variable.withVariableType(type.withFlags(flags));
}
String toName = LOWER_CAMEL.format(variable.getSimpleName());
renameVariable(variable, toName);
} else {
hasNameKey(computeKey(variable.getSimpleName(), variable));
}
return super.visitVariable(variable, ctx);
}
@Override
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) {
if (service(AnnotationService.class).matches(getCursor(), LOMBOK_ANNOTATION)) {
return multiVariable;
}
J.VariableDeclarations vds = super.visitVariableDeclarations(multiVariable, ctx);
if (getCursor().getMessage("ADD_STATIC", false)) {
return vds.withModifiers(ListUtils.insert(vds.getModifiers(),
new J.Modifier(Tree.randomId(), Space.format(" "), Markers.EMPTY,
"static", J.Modifier.Type.Static, emptyList()), 1));
}
return vds;
}
/**
* Returns either the current block or a J.Type that may create a reference to a variable.
* I.E. for(int target = 0; target < N; target++) creates a new name scope for `target`.
* The name scope in the next J.Block `{}` cannot create new variables with the name `target`.
* <p>
* J.* types that may only reference an existing name and do not create a new name scope are excluded.
*/
private Cursor getCursorToParentScope(Cursor cursor) {
return cursor.dropParentUntil(is -> is instanceof J.ClassDeclaration || is instanceof J.Block || is instanceof SourceFile);
}
};
}
}