Skip to content

Commit a7df2d1

Browse files
klueverError Prone Team
authored andcommitted
When renaming a variable to _, also use var.
#java25 PiperOrigin-RevId: 896589557
1 parent a875dd0 commit a7df2d1

File tree

2 files changed

+277
-42
lines changed

2 files changed

+277
-42
lines changed

core/src/main/java/com/google/errorprone/bugpatterns/UnnamedVariable.java

Lines changed: 102 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,36 @@
1818

1919
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
2020
import static com.google.errorprone.matchers.Description.NO_MATCH;
21+
import static com.google.errorprone.util.ASTHelpers.getStartPosition;
2122
import static com.google.errorprone.util.ASTHelpers.getSymbol;
2223

2324
import com.google.errorprone.BugPattern;
2425
import com.google.errorprone.ErrorProneFlags;
2526
import com.google.errorprone.VisitorState;
2627
import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher;
28+
import com.google.errorprone.fixes.SuggestedFix;
2729
import com.google.errorprone.fixes.SuggestedFixes;
2830
import com.google.errorprone.matchers.Description;
2931
import com.google.errorprone.util.SourceVersion;
32+
import com.sun.source.tree.BlockTree;
3033
import com.sun.source.tree.CompilationUnitTree;
34+
import com.sun.source.tree.EnhancedForLoopTree;
35+
import com.sun.source.tree.ForLoopTree;
3136
import com.sun.source.tree.IdentifierTree;
3237
import com.sun.source.tree.LambdaExpressionTree;
38+
import com.sun.source.tree.Tree;
39+
import com.sun.source.tree.TryTree;
3340
import com.sun.source.tree.VariableTree;
41+
import com.sun.source.util.TreePath;
3442
import com.sun.source.util.TreePathScanner;
3543
import com.sun.tools.javac.code.Symbol;
3644
import java.util.HashMap;
45+
import java.util.List;
3746
import java.util.Map;
3847
import javax.inject.Inject;
48+
import javax.lang.model.element.ElementKind;
3949

40-
/** Bugpattern to rename unused variables to _. */
50+
/** A bug pattern that suggests renaming unused variables to {@code var _}. */
4151
@BugPattern(
4252
summary = "Consider renaming unused variables and lambda parameters to _",
4353
severity = WARNING)
@@ -58,18 +68,95 @@ public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState s
5868
}
5969
VariableFinder finder = new VariableFinder(state);
6070
finder.scan(tree, null);
61-
for (Map.Entry<Symbol, VariableTree> unused : finder.unused.entrySet()) {
62-
state.reportMatch(
63-
describeMatch(
64-
unused.getValue(), SuggestedFixes.renameVariable(unused.getValue(), "_", state)));
65-
}
71+
finder.unusedSymbols.forEach(
72+
(symbol, path) -> {
73+
VariableTree varTree = (VariableTree) path.getLeaf();
74+
state.reportMatch(describeMatch(varTree, buildFix(varTree, symbol, path, state)));
75+
});
6676
return NO_MATCH;
6777
}
6878

79+
private static SuggestedFix buildFix(
80+
VariableTree tree, Symbol symbol, TreePath path, VisitorState state) {
81+
if (symbol.getKind() == ElementKind.PARAMETER) {
82+
// TODO(kak): we should also drop unnecessary parens. E.g.,
83+
// before: `(String unused) -> 1`
84+
// after: `(_) -> 1`
85+
// ideally: `_ -> 1`
86+
return SuggestedFix.replace(tree, "_");
87+
}
88+
SuggestedFix fix = SuggestedFixes.renameVariable(tree, "_", state);
89+
if (canUseVar(tree, symbol, path)) {
90+
SuggestedFix.Builder builder = fix.toBuilder();
91+
SuggestedFixes.replaceVariableType(tree, "var", state).ifPresent(builder::merge);
92+
return builder.build();
93+
}
94+
return fix;
95+
}
96+
97+
/**
98+
* Returns true if the given variable can be declared using {@code var}. This is true if the
99+
* variable is:
100+
*
101+
* <ul>
102+
* <li>A local or resource variable.
103+
* <li>Part of an enhanced for loop or has a non-null initializer.
104+
* <li>Not part of a compound declaration (e.g., {@code int a, b;}).
105+
* </ul>
106+
*/
107+
private static boolean canUseVar(VariableTree tree, Symbol symbol, TreePath path) {
108+
boolean isLocalOrResource =
109+
switch (symbol.getKind()) {
110+
case LOCAL_VARIABLE, RESOURCE_VARIABLE -> true;
111+
default -> false;
112+
};
113+
boolean isForEachOrHasInitializer =
114+
path.getParentPath().getLeaf() instanceof EnhancedForLoopTree
115+
|| (tree.getInitializer() != null
116+
&& tree.getInitializer().getKind() != Tree.Kind.NULL_LITERAL);
117+
boolean isNotCompound = !isPartOfCompoundDeclaration(tree, path);
118+
119+
return isLocalOrResource && isForEachOrHasInitializer && isNotCompound;
120+
}
121+
122+
/**
123+
* Returns true if the given variable is part of a compound declaration (e.g., {@code int a, b;}).
124+
* In javac, compound declarations share the same type tree object instance and start position.
125+
*/
126+
private static boolean isPartOfCompoundDeclaration(VariableTree tree, TreePath path) {
127+
Tree parent = path.getParentPath().getLeaf();
128+
if (parent instanceof BlockTree blockTree) {
129+
return isCompound(tree, blockTree.getStatements());
130+
}
131+
if (parent instanceof ForLoopTree forLoopTree) {
132+
return isCompound(tree, forLoopTree.getInitializer());
133+
}
134+
if (parent instanceof TryTree tryTree) {
135+
return isCompound(tree, tryTree.getResources());
136+
}
137+
return false;
138+
}
139+
140+
/**
141+
* Returns true if any other variable in the list of trees shares the same start position as the
142+
* given variable.
143+
*/
144+
private static boolean isCompound(VariableTree tree, List<? extends Tree> trees) {
145+
for (Tree sibling : trees) {
146+
if (sibling instanceof VariableTree siblingVar && siblingVar != tree) {
147+
if (getStartPosition(sibling) == getStartPosition(tree)) {
148+
return true;
149+
}
150+
}
151+
}
152+
return false;
153+
}
154+
69155
private final class VariableFinder extends TreePathScanner<Void, Void> {
156+
70157
private final VisitorState state;
71158

72-
private final Map<Symbol, VariableTree> unused = new HashMap<>();
159+
private final Map<Symbol, TreePath> unusedSymbols = new HashMap<>();
73160

74161
private VariableFinder(VisitorState state) {
75162
this.state = state;
@@ -84,7 +171,7 @@ public Void visitVariable(VariableTree tree, Void unused) {
84171
@Override
85172
public Void visitIdentifier(IdentifierTree tree, Void unused) {
86173
Symbol symbol = getSymbol(tree);
87-
this.unused.remove(symbol);
174+
this.unusedSymbols.remove(symbol);
88175
return null;
89176
}
90177

@@ -109,17 +196,20 @@ private void handleVariable(VariableTree tree) {
109196
if (onlyRenameVariablesNamedUnused && !symbol.getSimpleName().contentEquals("unused")) {
110197
return;
111198
}
199+
TreePath path = getCurrentPath();
112200
boolean allowedUnnamed =
113201
switch (symbol.getKind()) {
114-
case LOCAL_VARIABLE, EXCEPTION_PARAMETER, RESOURCE_VARIABLE, BINDING_VARIABLE -> true;
202+
case LOCAL_VARIABLE ->
203+
tree.getInitializer() != null
204+
|| getCurrentPath().getParentPath().getLeaf() instanceof EnhancedForLoopTree;
205+
case EXCEPTION_PARAMETER, RESOURCE_VARIABLE, BINDING_VARIABLE -> true;
115206
case PARAMETER ->
116207
getCurrentPath().getParentPath().getLeaf() instanceof LambdaExpressionTree;
117208
default -> false;
118209
};
119-
if (!allowedUnnamed) {
120-
return;
210+
if (allowedUnnamed) {
211+
unusedSymbols.put(symbol, path);
121212
}
122-
unused.put(symbol, tree);
123213
}
124214
}
125215
}

0 commit comments

Comments
 (0)