1818
1919import static com .google .errorprone .BugPattern .SeverityLevel .WARNING ;
2020import static com .google .errorprone .matchers .Description .NO_MATCH ;
21+ import static com .google .errorprone .util .ASTHelpers .getStartPosition ;
2122import static com .google .errorprone .util .ASTHelpers .getSymbol ;
2223
2324import com .google .errorprone .BugPattern ;
2425import com .google .errorprone .ErrorProneFlags ;
2526import com .google .errorprone .VisitorState ;
2627import com .google .errorprone .bugpatterns .BugChecker .CompilationUnitTreeMatcher ;
28+ import com .google .errorprone .fixes .SuggestedFix ;
2729import com .google .errorprone .fixes .SuggestedFixes ;
2830import com .google .errorprone .matchers .Description ;
2931import com .google .errorprone .util .SourceVersion ;
32+ import com .sun .source .tree .BlockTree ;
3033import com .sun .source .tree .CompilationUnitTree ;
34+ import com .sun .source .tree .EnhancedForLoopTree ;
35+ import com .sun .source .tree .ForLoopTree ;
3136import com .sun .source .tree .IdentifierTree ;
3237import com .sun .source .tree .LambdaExpressionTree ;
38+ import com .sun .source .tree .Tree ;
39+ import com .sun .source .tree .TryTree ;
3340import com .sun .source .tree .VariableTree ;
41+ import com .sun .source .util .TreePath ;
3442import com .sun .source .util .TreePathScanner ;
3543import com .sun .tools .javac .code .Symbol ;
3644import java .util .HashMap ;
45+ import java .util .List ;
3746import java .util .Map ;
3847import 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