View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.coding;
21  
22  import java.util.ArrayDeque;
23  import java.util.Collections;
24  import java.util.Deque;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.LinkedHashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Optional;
31  import java.util.Set;
32  import java.util.stream.Collectors;
33  
34  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
35  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
36  import com.puppycrawl.tools.checkstyle.api.DetailAST;
37  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
38  import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
39  import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
40  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
41  
42  /**
43   * <div>
44   * Checks that a local variable is declared and/or assigned, but not used.
45   * Doesn't support
46   * <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-14.html#jls-14.30">
47   * pattern variables yet</a>.
48   * Doesn't check
49   * <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.12.3">
50   * array components</a> as array
51   * components are classified as different kind of variables by
52   * <a href="https://docs.oracle.com/javase/specs/jls/se17/html/index.html">JLS</a>.
53   * </div>
54   * <ul>
55   * <li>
56   * Property {@code allowUnnamedVariables} - Allow variables named with a single underscore
57   * (known as <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
58   * unnamed variables</a> in Java 21+).
59   * Type is {@code boolean}.
60   * Default value is {@code true}.
61   * </li>
62   * </ul>
63   *
64   * <p>
65   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
66   * </p>
67   *
68   * <p>
69   * Violation Message Keys:
70   * </p>
71   * <ul>
72   * <li>
73   * {@code unused.local.var}
74   * </li>
75   * <li>
76   * {@code unused.named.local.var}
77   * </li>
78   * </ul>
79   *
80   * @since 9.3
81   */
82  @FileStatefulCheck
83  public class UnusedLocalVariableCheck extends AbstractCheck {
84  
85      /**
86       * A key is pointing to the warning message text in "messages.properties"
87       * file.
88       */
89      public static final String MSG_UNUSED_LOCAL_VARIABLE = "unused.local.var";
90  
91      /**
92       * A key is pointing to the warning message text in "messages.properties"
93       * file.
94       */
95      public static final String MSG_UNUSED_NAMED_LOCAL_VARIABLE = "unused.named.local.var";
96  
97      /**
98       * An array of increment and decrement tokens.
99       */
100     private static final int[] INCREMENT_AND_DECREMENT_TOKENS = {
101         TokenTypes.POST_INC,
102         TokenTypes.POST_DEC,
103         TokenTypes.INC,
104         TokenTypes.DEC,
105     };
106 
107     /**
108      * An array of scope tokens.
109      */
110     private static final int[] SCOPES = {
111         TokenTypes.SLIST,
112         TokenTypes.LITERAL_FOR,
113         TokenTypes.OBJBLOCK,
114     };
115 
116     /**
117      * An array of unacceptable children of ast of type {@link TokenTypes#DOT}.
118      */
119     private static final int[] UNACCEPTABLE_CHILD_OF_DOT = {
120         TokenTypes.DOT,
121         TokenTypes.METHOD_CALL,
122         TokenTypes.LITERAL_NEW,
123         TokenTypes.LITERAL_SUPER,
124         TokenTypes.LITERAL_CLASS,
125         TokenTypes.LITERAL_THIS,
126     };
127 
128     /**
129      * An array of unacceptable parent of ast of type {@link TokenTypes#IDENT}.
130      */
131     private static final int[] UNACCEPTABLE_PARENT_OF_IDENT = {
132         TokenTypes.VARIABLE_DEF,
133         TokenTypes.DOT,
134         TokenTypes.LITERAL_NEW,
135         TokenTypes.PATTERN_VARIABLE_DEF,
136         TokenTypes.METHOD_CALL,
137         TokenTypes.TYPE,
138     };
139 
140     /**
141      * An array of blocks in which local anon inner classes can exist.
142      */
143     private static final int[] ANONYMOUS_CLASS_PARENT_TOKENS = {
144         TokenTypes.METHOD_DEF,
145         TokenTypes.CTOR_DEF,
146         TokenTypes.STATIC_INIT,
147         TokenTypes.INSTANCE_INIT,
148         TokenTypes.COMPACT_CTOR_DEF,
149     };
150 
151     /**
152      * An array of token types that indicate a variable is being used within
153      * an expression involving increment or decrement operators, or within a switch statement.
154      * When a token of one of these types is the parent of an expression, it indicates that the
155      * variable associated with the increment or decrement operation is being used.
156      * Ex:- TokenTypes.LITERAL_SWITCH: Indicates a switch statement. Variables used within the
157      * switch expression are considered to be used
158      */
159     private static final int[] INCREMENT_DECREMENT_VARIABLE_USAGE_TYPES = {
160         TokenTypes.ELIST,
161         TokenTypes.INDEX_OP,
162         TokenTypes.ASSIGN,
163         TokenTypes.LITERAL_SWITCH,
164     };
165 
166     /** Package separator. */
167     private static final String PACKAGE_SEPARATOR = ".";
168 
169     /**
170      * Keeps tracks of the variables declared in file.
171      */
172     private final Deque<VariableDesc> variables = new ArrayDeque<>();
173 
174     /**
175      * Keeps track of all the type declarations present in the file.
176      * Pops the type out of the stack while leaving the type
177      * in visitor pattern.
178      */
179     private final Deque<TypeDeclDesc> typeDeclarations = new ArrayDeque<>();
180 
181     /**
182      * Maps type declaration ast to their respective TypeDeclDesc objects.
183      */
184     private final Map<DetailAST, TypeDeclDesc> typeDeclAstToTypeDeclDesc = new LinkedHashMap<>();
185 
186     /**
187      * Maps local anonymous inner class to the TypeDeclDesc object
188      * containing it.
189      */
190     private final Map<DetailAST, TypeDeclDesc> anonInnerAstToTypeDeclDesc = new HashMap<>();
191 
192     /**
193      * Set of tokens of type {@link UnusedLocalVariableCheck#ANONYMOUS_CLASS_PARENT_TOKENS}
194      * and {@link TokenTypes#LAMBDA} in some cases.
195      */
196     private final Set<DetailAST> anonInnerClassHolders = new HashSet<>();
197 
198     /**
199      * Allow variables named with a single underscore
200      * (known as  <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
201      *  unnamed variables</a> in Java 21+).
202      */
203     private boolean allowUnnamedVariables = true;
204 
205     /**
206      * Name of the package.
207      */
208     private String packageName;
209 
210     /**
211      * Depth at which a type declaration is nested, 0 for top level type declarations.
212      */
213     private int depth;
214 
215     /**
216      * Setter to allow variables named with a single underscore
217      * (known as <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
218      * unnamed variables</a> in Java 21+).
219      *
220      * @param allowUnnamedVariables true or false.
221      * @since 10.18.0
222      */
223     public void setAllowUnnamedVariables(boolean allowUnnamedVariables) {
224         this.allowUnnamedVariables = allowUnnamedVariables;
225     }
226 
227     @Override
228     public int[] getDefaultTokens() {
229         return new int[] {
230             TokenTypes.DOT,
231             TokenTypes.VARIABLE_DEF,
232             TokenTypes.IDENT,
233             TokenTypes.SLIST,
234             TokenTypes.LITERAL_FOR,
235             TokenTypes.OBJBLOCK,
236             TokenTypes.CLASS_DEF,
237             TokenTypes.INTERFACE_DEF,
238             TokenTypes.ANNOTATION_DEF,
239             TokenTypes.PACKAGE_DEF,
240             TokenTypes.LITERAL_NEW,
241             TokenTypes.METHOD_DEF,
242             TokenTypes.CTOR_DEF,
243             TokenTypes.STATIC_INIT,
244             TokenTypes.INSTANCE_INIT,
245             TokenTypes.COMPILATION_UNIT,
246             TokenTypes.LAMBDA,
247             TokenTypes.ENUM_DEF,
248             TokenTypes.RECORD_DEF,
249             TokenTypes.COMPACT_CTOR_DEF,
250         };
251     }
252 
253     @Override
254     public int[] getAcceptableTokens() {
255         return getDefaultTokens();
256     }
257 
258     @Override
259     public int[] getRequiredTokens() {
260         return getDefaultTokens();
261     }
262 
263     @Override
264     public void beginTree(DetailAST root) {
265         variables.clear();
266         typeDeclarations.clear();
267         typeDeclAstToTypeDeclDesc.clear();
268         anonInnerAstToTypeDeclDesc.clear();
269         anonInnerClassHolders.clear();
270         packageName = null;
271         depth = 0;
272     }
273 
274     @Override
275     public void visitToken(DetailAST ast) {
276         final int type = ast.getType();
277         if (type == TokenTypes.DOT) {
278             visitDotToken(ast, variables);
279         }
280         else if (type == TokenTypes.VARIABLE_DEF && !skipUnnamedVariables(ast)) {
281             visitVariableDefToken(ast);
282         }
283         else if (type == TokenTypes.IDENT) {
284             visitIdentToken(ast, variables);
285         }
286         else if (isInsideLocalAnonInnerClass(ast)) {
287             visitLocalAnonInnerClass(ast);
288         }
289         else if (isNonLocalTypeDeclaration(ast)) {
290             visitNonLocalTypeDeclarationToken(ast);
291         }
292         else if (type == TokenTypes.PACKAGE_DEF) {
293             packageName = CheckUtil.extractQualifiedName(ast.getFirstChild().getNextSibling());
294         }
295     }
296 
297     @Override
298     public void leaveToken(DetailAST ast) {
299         if (TokenUtil.isOfType(ast, SCOPES)) {
300             logViolations(ast, variables);
301         }
302         else if (ast.getType() == TokenTypes.COMPILATION_UNIT) {
303             leaveCompilationUnit();
304         }
305         else if (isNonLocalTypeDeclaration(ast)) {
306             depth--;
307             typeDeclarations.pop();
308         }
309     }
310 
311     /**
312      * Visit ast of type {@link TokenTypes#DOT}.
313      *
314      * @param dotAst dotAst
315      * @param variablesStack stack of all the relevant variables in the scope
316      */
317     private static void visitDotToken(DetailAST dotAst, Deque<VariableDesc> variablesStack) {
318         if (dotAst.getParent().getType() != TokenTypes.LITERAL_NEW
319                 && shouldCheckIdentTokenNestedUnderDot(dotAst)) {
320             final DetailAST identifier = dotAst.findFirstToken(TokenTypes.IDENT);
321             if (identifier != null) {
322                 checkIdentifierAst(identifier, variablesStack);
323             }
324         }
325     }
326 
327     /**
328      * Visit ast of type {@link TokenTypes#VARIABLE_DEF}.
329      *
330      * @param varDefAst varDefAst
331      */
332     private void visitVariableDefToken(DetailAST varDefAst) {
333         addLocalVariables(varDefAst, variables);
334         addInstanceOrClassVar(varDefAst);
335     }
336 
337     /**
338      * Visit ast of type {@link TokenTypes#IDENT}.
339      *
340      * @param identAst identAst
341      * @param variablesStack stack of all the relevant variables in the scope
342      */
343     private static void visitIdentToken(DetailAST identAst, Deque<VariableDesc> variablesStack) {
344         final DetailAST parent = identAst.getParent();
345         final boolean isMethodReferenceMethodName = parent.getType() == TokenTypes.METHOD_REF
346                 && parent.getFirstChild() != identAst;
347         final boolean isConstructorReference = parent.getType() == TokenTypes.METHOD_REF
348                 && parent.getLastChild().getType() == TokenTypes.LITERAL_NEW;
349         final boolean isNestedClassInitialization =
350                 TokenUtil.isOfType(identAst.getNextSibling(), TokenTypes.LITERAL_NEW)
351                 && parent.getType() == TokenTypes.DOT;
352 
353         if (isNestedClassInitialization || !isMethodReferenceMethodName
354                 && !isConstructorReference
355                 && !TokenUtil.isOfType(parent, UNACCEPTABLE_PARENT_OF_IDENT)) {
356             checkIdentifierAst(identAst, variablesStack);
357         }
358     }
359 
360     /**
361      * Visit the non-local type declaration token.
362      *
363      * @param typeDeclAst type declaration ast
364      */
365     private void visitNonLocalTypeDeclarationToken(DetailAST typeDeclAst) {
366         final String qualifiedName = getQualifiedTypeDeclarationName(typeDeclAst);
367         final TypeDeclDesc currTypeDecl = new TypeDeclDesc(qualifiedName, depth, typeDeclAst);
368         depth++;
369         typeDeclarations.push(currTypeDecl);
370         typeDeclAstToTypeDeclDesc.put(typeDeclAst, currTypeDecl);
371     }
372 
373     /**
374      * Visit the local anon inner class.
375      *
376      * @param literalNewAst literalNewAst
377      */
378     private void visitLocalAnonInnerClass(DetailAST literalNewAst) {
379         anonInnerAstToTypeDeclDesc.put(literalNewAst, typeDeclarations.peek());
380         anonInnerClassHolders.add(getBlockContainingLocalAnonInnerClass(literalNewAst));
381     }
382 
383     /**
384      * Check for skip current {@link TokenTypes#VARIABLE_DEF}
385      * due to <b>allowUnnamedVariable</b> option.
386      *
387      * @param varDefAst varDefAst variable to check
388      * @return true if the current variable should be skipped.
389      */
390     private boolean skipUnnamedVariables(DetailAST varDefAst) {
391         final DetailAST ident = varDefAst.findFirstToken(TokenTypes.IDENT);
392         return allowUnnamedVariables && "_".equals(ident.getText());
393     }
394 
395     /**
396      * Whether ast node of type {@link TokenTypes#LITERAL_NEW} is a part of a local
397      * anonymous inner class.
398      *
399      * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
400      * @return true if variableDefAst is an instance variable in local anonymous inner class
401      */
402     private static boolean isInsideLocalAnonInnerClass(DetailAST literalNewAst) {
403         boolean result = false;
404         final DetailAST lastChild = literalNewAst.getLastChild();
405         if (lastChild != null && lastChild.getType() == TokenTypes.OBJBLOCK) {
406             DetailAST currentAst = literalNewAst;
407             while (!TokenUtil.isTypeDeclaration(currentAst.getType())) {
408                 if (currentAst.getType() == TokenTypes.SLIST) {
409                     result = true;
410                     break;
411                 }
412                 currentAst = currentAst.getParent();
413             }
414         }
415         return result;
416     }
417 
418     /**
419      * Traverse {@code variablesStack} stack and log the violations.
420      *
421      * @param scopeAst ast node of type {@link UnusedLocalVariableCheck#SCOPES}
422      * @param variablesStack stack of all the relevant variables in the scope
423      */
424     private void logViolations(DetailAST scopeAst, Deque<VariableDesc> variablesStack) {
425         while (!variablesStack.isEmpty() && variablesStack.peek().getScope() == scopeAst) {
426             final VariableDesc variableDesc = variablesStack.pop();
427             if (!variableDesc.isUsed()
428                     && !variableDesc.isInstVarOrClassVar()) {
429                 final DetailAST typeAst = variableDesc.getTypeAst();
430                 if (allowUnnamedVariables) {
431                     log(typeAst, MSG_UNUSED_NAMED_LOCAL_VARIABLE, variableDesc.getName());
432                 }
433                 else {
434                     log(typeAst, MSG_UNUSED_LOCAL_VARIABLE, variableDesc.getName());
435                 }
436             }
437         }
438     }
439 
440     /**
441      * We process all the blocks containing local anonymous inner classes
442      * separately after processing all the other nodes. This is being done
443      * due to the fact the instance variables of local anon inner classes can
444      * cast a shadow on local variables.
445      */
446     private void leaveCompilationUnit() {
447         anonInnerClassHolders.forEach(holder -> {
448             iterateOverBlockContainingLocalAnonInnerClass(holder, new ArrayDeque<>());
449         });
450     }
451 
452     /**
453      * Whether a type declaration is non-local. Annotated interfaces are always non-local.
454      *
455      * @param typeDeclAst type declaration ast
456      * @return true if type declaration is non-local
457      */
458     private static boolean isNonLocalTypeDeclaration(DetailAST typeDeclAst) {
459         return TokenUtil.isTypeDeclaration(typeDeclAst.getType())
460                 && typeDeclAst.getParent().getType() != TokenTypes.SLIST;
461     }
462 
463     /**
464      * Get the block containing local anon inner class.
465      *
466      * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
467      * @return the block containing local anon inner class
468      */
469     private static DetailAST getBlockContainingLocalAnonInnerClass(DetailAST literalNewAst) {
470         DetailAST currentAst = literalNewAst;
471         DetailAST result = null;
472         DetailAST topMostLambdaAst = null;
473         while (currentAst != null && !TokenUtil.isOfType(currentAst,
474                 ANONYMOUS_CLASS_PARENT_TOKENS)) {
475             if (currentAst.getType() == TokenTypes.LAMBDA) {
476                 topMostLambdaAst = currentAst;
477             }
478             currentAst = currentAst.getParent();
479             result = currentAst;
480         }
481 
482         if (currentAst == null) {
483             result = topMostLambdaAst;
484         }
485         return result;
486     }
487 
488     /**
489      * Add local variables to the {@code variablesStack} stack.
490      * Also adds the instance variables defined in a local anonymous inner class.
491      *
492      * @param varDefAst ast node of type {@link TokenTypes#VARIABLE_DEF}
493      * @param variablesStack stack of all the relevant variables in the scope
494      */
495     private static void addLocalVariables(DetailAST varDefAst, Deque<VariableDesc> variablesStack) {
496         final DetailAST parentAst = varDefAst.getParent();
497         final DetailAST grandParent = parentAst.getParent();
498         final boolean isInstanceVarInInnerClass =
499                 grandParent.getType() == TokenTypes.LITERAL_NEW
500                 || grandParent.getType() == TokenTypes.CLASS_DEF;
501         if (isInstanceVarInInnerClass
502                 || parentAst.getType() != TokenTypes.OBJBLOCK) {
503             final DetailAST ident = varDefAst.findFirstToken(TokenTypes.IDENT);
504             final VariableDesc desc = new VariableDesc(ident.getText(),
505                     varDefAst.findFirstToken(TokenTypes.TYPE), findScopeOfVariable(varDefAst));
506             if (isInstanceVarInInnerClass) {
507                 desc.registerAsInstOrClassVar();
508             }
509             variablesStack.push(desc);
510         }
511     }
512 
513     /**
514      * Add instance variables and class variables to the
515      * {@link TypeDeclDesc#instanceAndClassVarStack}.
516      *
517      * @param varDefAst ast node of type {@link TokenTypes#VARIABLE_DEF}
518      */
519     private void addInstanceOrClassVar(DetailAST varDefAst) {
520         final DetailAST parentAst = varDefAst.getParent();
521         if (isNonLocalTypeDeclaration(parentAst.getParent())
522                 && !isPrivateInstanceVariable(varDefAst)) {
523             final DetailAST ident = varDefAst.findFirstToken(TokenTypes.IDENT);
524             final VariableDesc desc = new VariableDesc(ident.getText());
525             typeDeclAstToTypeDeclDesc.get(parentAst.getParent()).addInstOrClassVar(desc);
526         }
527     }
528 
529     /**
530      * Whether instance variable or class variable have private access modifier.
531      *
532      * @param varDefAst ast node of type {@link TokenTypes#VARIABLE_DEF}
533      * @return true if instance variable or class variable have private access modifier
534      */
535     private static boolean isPrivateInstanceVariable(DetailAST varDefAst) {
536         final AccessModifierOption varAccessModifier =
537                 CheckUtil.getAccessModifierFromModifiersToken(varDefAst);
538         return varAccessModifier == AccessModifierOption.PRIVATE;
539     }
540 
541     /**
542      * Get the {@link TypeDeclDesc} of the super class of anonymous inner class.
543      *
544      * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
545      * @return {@link TypeDeclDesc} of the super class of anonymous inner class
546      */
547     private TypeDeclDesc getSuperClassOfAnonInnerClass(DetailAST literalNewAst) {
548         TypeDeclDesc obtainedClass = null;
549         final String shortNameOfClass = CheckUtil.getShortNameOfAnonInnerClass(literalNewAst);
550         if (packageName != null && shortNameOfClass.startsWith(packageName)) {
551             final Optional<TypeDeclDesc> classWithCompletePackageName =
552                     typeDeclAstToTypeDeclDesc.values()
553                     .stream()
554                     .filter(typeDeclDesc -> {
555                         return typeDeclDesc.getQualifiedName().equals(shortNameOfClass);
556                     })
557                     .findFirst();
558             if (classWithCompletePackageName.isPresent()) {
559                 obtainedClass = classWithCompletePackageName.orElseThrow();
560             }
561         }
562         else {
563             final List<TypeDeclDesc> typeDeclWithSameName = typeDeclWithSameName(shortNameOfClass);
564             if (!typeDeclWithSameName.isEmpty()) {
565                 obtainedClass = getClosestMatchingTypeDeclaration(
566                         anonInnerAstToTypeDeclDesc.get(literalNewAst).getQualifiedName(),
567                         typeDeclWithSameName);
568             }
569         }
570         return obtainedClass;
571     }
572 
573     /**
574      * Add non-private instance and class variables of the super class of the anonymous class
575      * to the variables stack.
576      *
577      * @param obtainedClass super class of the anon inner class
578      * @param variablesStack stack of all the relevant variables in the scope
579      * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
580      */
581     private void modifyVariablesStack(TypeDeclDesc obtainedClass,
582             Deque<VariableDesc> variablesStack,
583             DetailAST literalNewAst) {
584         if (obtainedClass != null) {
585             final Deque<VariableDesc> instAndClassVarDeque = typeDeclAstToTypeDeclDesc
586                     .get(obtainedClass.getTypeDeclAst())
587                     .getUpdatedCopyOfVarStack(literalNewAst);
588             instAndClassVarDeque.forEach(variablesStack::push);
589         }
590     }
591 
592     /**
593      * Checks if there is a type declaration with same name as the super class.
594      *
595      * @param superClassName name of the super class
596      * @return list if there is another type declaration with same name.
597      */
598     private List<TypeDeclDesc> typeDeclWithSameName(String superClassName) {
599         return typeDeclAstToTypeDeclDesc.values().stream()
600                 .filter(typeDeclDesc -> {
601                     return hasSameNameAsSuperClass(superClassName, typeDeclDesc);
602                 })
603                 .collect(Collectors.toUnmodifiableList());
604     }
605 
606     /**
607      * Whether the qualified name of {@code typeDeclDesc} matches the super class name.
608      *
609      * @param superClassName name of the super class
610      * @param typeDeclDesc type declaration description
611      * @return {@code true} if the qualified name of {@code typeDeclDesc}
612      *         matches the super class name
613      */
614     private boolean hasSameNameAsSuperClass(String superClassName, TypeDeclDesc typeDeclDesc) {
615         final boolean result;
616         if (packageName == null && typeDeclDesc.getDepth() == 0) {
617             result = typeDeclDesc.getQualifiedName().equals(superClassName);
618         }
619         else {
620             result = typeDeclDesc.getQualifiedName()
621                     .endsWith(PACKAGE_SEPARATOR + superClassName);
622         }
623         return result;
624     }
625 
626     /**
627      * For all type declarations with the same name as the superclass, gets the nearest type
628      * declaration.
629      *
630      * @param outerTypeDeclName outer type declaration of anonymous inner class
631      * @param typeDeclWithSameName typeDeclarations which have the same name as the super class
632      * @return the nearest class
633      */
634     private static TypeDeclDesc getClosestMatchingTypeDeclaration(String outerTypeDeclName,
635             List<TypeDeclDesc> typeDeclWithSameName) {
636         return Collections.min(typeDeclWithSameName, (first, second) -> {
637             return calculateTypeDeclarationDistance(outerTypeDeclName, first, second);
638         });
639     }
640 
641     /**
642      * Get the difference between type declaration name matching count. If the
643      * difference between them is zero, then their depth is compared to obtain the result.
644      *
645      * @param outerTypeName outer type declaration of anonymous inner class
646      * @param firstType first input type declaration
647      * @param secondType second input type declaration
648      * @return difference between type declaration name matching count
649      */
650     private static int calculateTypeDeclarationDistance(String outerTypeName,
651                                                         TypeDeclDesc firstType,
652                                                         TypeDeclDesc secondType) {
653         final int firstMatchCount =
654                 countMatchingQualifierChars(outerTypeName, firstType.getQualifiedName());
655         final int secondMatchCount =
656                 countMatchingQualifierChars(outerTypeName, secondType.getQualifiedName());
657         final int matchDistance = Integer.compare(secondMatchCount, firstMatchCount);
658 
659         final int distance;
660         if (matchDistance == 0) {
661             distance = Integer.compare(firstType.getDepth(), secondType.getDepth());
662         }
663         else {
664             distance = matchDistance;
665         }
666 
667         return distance;
668     }
669 
670     /**
671      * Calculates the type declaration matching count for the superclass of an anonymous inner
672      * class.
673      *
674      * <p>
675      * For example, if the pattern class is {@code Main.ClassOne} and the class to be matched is
676      * {@code Main.ClassOne.ClassTwo.ClassThree}, then the matching count would be calculated by
677      * comparing the characters at each position, and updating the count whenever a '.'
678      * is encountered.
679      * This is necessary because pattern class can include anonymous inner classes, unlike regular
680      * inheritance where nested classes cannot be extended.
681      * </p>
682      *
683      * @param pattern type declaration to match against
684      * @param candidate type declaration to be matched
685      * @return the type declaration matching count
686      */
687     private static int countMatchingQualifierChars(String pattern,
688                                                    String candidate) {
689         final int typeDeclarationToBeMatchedLength = candidate.length();
690         final int minLength = Math
691                 .min(typeDeclarationToBeMatchedLength, pattern.length());
692         final boolean shouldCountBeUpdatedAtLastCharacter =
693                 typeDeclarationToBeMatchedLength > minLength
694                 && candidate.charAt(minLength) == PACKAGE_SEPARATOR.charAt(0);
695 
696         int result = 0;
697         for (int idx = 0;
698              idx < minLength
699                 && pattern.charAt(idx) == candidate.charAt(idx);
700              idx++) {
701 
702             if (shouldCountBeUpdatedAtLastCharacter
703                     || pattern.charAt(idx) == PACKAGE_SEPARATOR.charAt(0)) {
704                 result = idx;
705             }
706         }
707         return result;
708     }
709 
710     /**
711      * Get qualified type declaration name from type ast.
712      *
713      * @param typeDeclAst type declaration ast
714      * @return qualified name of type declaration
715      */
716     private String getQualifiedTypeDeclarationName(DetailAST typeDeclAst) {
717         final String className = typeDeclAst.findFirstToken(TokenTypes.IDENT).getText();
718         String outerClassQualifiedName = null;
719         if (!typeDeclarations.isEmpty()) {
720             outerClassQualifiedName = typeDeclarations.peek().getQualifiedName();
721         }
722         return CheckUtil
723             .getQualifiedTypeDeclarationName(packageName, outerClassQualifiedName, className);
724     }
725 
726     /**
727      * Iterate over all the ast nodes present under {@code ast}.
728      *
729      * @param ast ast
730      * @param variablesStack stack of all the relevant variables in the scope
731      */
732     private void iterateOverBlockContainingLocalAnonInnerClass(
733             DetailAST ast, Deque<VariableDesc> variablesStack) {
734         DetailAST currNode = ast;
735         while (currNode != null) {
736             customVisitToken(currNode, variablesStack);
737             DetailAST toVisit = currNode.getFirstChild();
738             while (currNode != ast && toVisit == null) {
739                 customLeaveToken(currNode, variablesStack);
740                 toVisit = currNode.getNextSibling();
741                 currNode = currNode.getParent();
742             }
743             currNode = toVisit;
744         }
745     }
746 
747     /**
748      * Visit all ast nodes under {@link UnusedLocalVariableCheck#anonInnerClassHolders} once
749      * again.
750      *
751      * @param ast ast
752      * @param variablesStack stack of all the relevant variables in the scope
753      */
754     private void customVisitToken(DetailAST ast, Deque<VariableDesc> variablesStack) {
755         final int type = ast.getType();
756         if (type == TokenTypes.DOT) {
757             visitDotToken(ast, variablesStack);
758         }
759         else if (type == TokenTypes.VARIABLE_DEF) {
760             addLocalVariables(ast, variablesStack);
761         }
762         else if (type == TokenTypes.IDENT) {
763             visitIdentToken(ast, variablesStack);
764         }
765         else if (isInsideLocalAnonInnerClass(ast)) {
766             final TypeDeclDesc obtainedClass = getSuperClassOfAnonInnerClass(ast);
767             modifyVariablesStack(obtainedClass, variablesStack, ast);
768         }
769     }
770 
771     /**
772      * Leave all ast nodes under {@link UnusedLocalVariableCheck#anonInnerClassHolders} once
773      * again.
774      *
775      * @param ast ast
776      * @param variablesStack stack of all the relevant variables in the scope
777      */
778     private void customLeaveToken(DetailAST ast, Deque<VariableDesc> variablesStack) {
779         logViolations(ast, variablesStack);
780     }
781 
782     /**
783      * Whether to check identifier token nested under dotAst.
784      *
785      * @param dotAst dotAst
786      * @return true if ident nested under dotAst should be checked
787      */
788     private static boolean shouldCheckIdentTokenNestedUnderDot(DetailAST dotAst) {
789 
790         return TokenUtil.findFirstTokenByPredicate(dotAst,
791                         childAst -> {
792                             return TokenUtil.isOfType(childAst,
793                                     UNACCEPTABLE_CHILD_OF_DOT);
794                         })
795                 .isEmpty();
796     }
797 
798     /**
799      * Checks the identifier ast.
800      *
801      * @param identAst ast of type {@link TokenTypes#IDENT}
802      * @param variablesStack stack of all the relevant variables in the scope
803      */
804     private static void checkIdentifierAst(DetailAST identAst, Deque<VariableDesc> variablesStack) {
805         for (VariableDesc variableDesc : variablesStack) {
806             if (identAst.getText().equals(variableDesc.getName())
807                     && !isLeftHandSideValue(identAst)) {
808                 variableDesc.registerAsUsed();
809                 break;
810             }
811         }
812     }
813 
814     /**
815      * Find the scope of variable.
816      *
817      * @param variableDef ast of type {@link TokenTypes#VARIABLE_DEF}
818      * @return scope of variableDef
819      */
820     private static DetailAST findScopeOfVariable(DetailAST variableDef) {
821         final DetailAST result;
822         final DetailAST parentAst = variableDef.getParent();
823         if (TokenUtil.isOfType(parentAst, TokenTypes.SLIST, TokenTypes.OBJBLOCK)) {
824             result = parentAst;
825         }
826         else {
827             result = parentAst.getParent();
828         }
829         return result;
830     }
831 
832     /**
833      * Checks whether the ast of type {@link TokenTypes#IDENT} is
834      * used as left-hand side value. An identifier is being used as a left-hand side
835      * value if it is used as the left operand of an assignment or as an
836      * operand of a stand-alone increment or decrement.
837      *
838      * @param identAst ast of type {@link TokenTypes#IDENT}
839      * @return true if identAst is used as a left-hand side value
840      */
841     private static boolean isLeftHandSideValue(DetailAST identAst) {
842         final DetailAST parent = identAst.getParent();
843         return isStandAloneIncrementOrDecrement(identAst)
844                 || parent.getType() == TokenTypes.ASSIGN
845                 && identAst != parent.getLastChild();
846     }
847 
848     /**
849      * Checks whether the ast of type {@link TokenTypes#IDENT} is used as
850      * an operand of a stand-alone increment or decrement.
851      *
852      * @param identAst ast of type {@link TokenTypes#IDENT}
853      * @return true if identAst is used as an operand of stand-alone
854      *         increment or decrement
855      */
856     private static boolean isStandAloneIncrementOrDecrement(DetailAST identAst) {
857         final DetailAST parent = identAst.getParent();
858         final DetailAST grandParent = parent.getParent();
859         return TokenUtil.isOfType(parent, INCREMENT_AND_DECREMENT_TOKENS)
860                 && TokenUtil.isOfType(grandParent, TokenTypes.EXPR)
861                 && !isIncrementOrDecrementVariableUsed(grandParent);
862     }
863 
864     /**
865      * A variable with increment or decrement operator is considered used if it
866      * is used as an argument or as an array index or for assigning value
867      * to a variable.
868      *
869      * @param exprAst ast of type {@link TokenTypes#EXPR}
870      * @return true if variable nested in exprAst is used
871      */
872     private static boolean isIncrementOrDecrementVariableUsed(DetailAST exprAst) {
873         return TokenUtil.isOfType(exprAst.getParent(), INCREMENT_DECREMENT_VARIABLE_USAGE_TYPES)
874                 && exprAst.getParent().getParent().getType() != TokenTypes.FOR_ITERATOR;
875     }
876 
877     /**
878      * Maintains information about the variable.
879      */
880     private static final class VariableDesc {
881 
882         /**
883          * The name of the variable.
884          */
885         private final String name;
886 
887         /**
888          * Ast of type {@link TokenTypes#TYPE}.
889          */
890         private final DetailAST typeAst;
891 
892         /**
893          * The scope of variable is determined by the ast of type
894          * {@link TokenTypes#SLIST} or {@link TokenTypes#LITERAL_FOR}
895          * or {@link TokenTypes#OBJBLOCK} which is enclosing the variable.
896          */
897         private final DetailAST scope;
898 
899         /**
900          * Is an instance variable or a class variable.
901          */
902         private boolean instVarOrClassVar;
903 
904         /**
905          * Is the variable used.
906          */
907         private boolean used;
908 
909         /**
910          * Create a new VariableDesc instance.
911          *
912          * @param name name of the variable
913          * @param typeAst ast of type {@link TokenTypes#TYPE}
914          * @param scope ast of type {@link TokenTypes#SLIST} or
915          *              {@link TokenTypes#LITERAL_FOR} or {@link TokenTypes#OBJBLOCK}
916          *              which is enclosing the variable
917          */
918         private VariableDesc(String name, DetailAST typeAst, DetailAST scope) {
919             this.name = name;
920             this.typeAst = typeAst;
921             this.scope = scope;
922         }
923 
924         /**
925          * Create a new VariableDesc instance.
926          *
927          * @param name name of the variable
928          */
929         private VariableDesc(String name) {
930             this(name, null, null);
931         }
932 
933         /**
934          * Create a new VariableDesc instance.
935          *
936          * @param name name of the variable
937          * @param scope ast of type {@link TokenTypes#SLIST} or
938          *              {@link TokenTypes#LITERAL_FOR} or {@link TokenTypes#OBJBLOCK}
939          *              which is enclosing the variable
940          */
941         private VariableDesc(String name, DetailAST scope) {
942             this(name, null, scope);
943         }
944 
945         /**
946          * Get the name of variable.
947          *
948          * @return name of variable
949          */
950         public String getName() {
951             return name;
952         }
953 
954         /**
955          * Get the associated ast node of type {@link TokenTypes#TYPE}.
956          *
957          * @return the associated ast node of type {@link TokenTypes#TYPE}
958          */
959         public DetailAST getTypeAst() {
960             return typeAst;
961         }
962 
963         /**
964          * Get ast of type {@link TokenTypes#SLIST}
965          * or {@link TokenTypes#LITERAL_FOR} or {@link TokenTypes#OBJBLOCK}
966          * which is enclosing the variable i.e. its scope.
967          *
968          * @return the scope associated with the variable
969          */
970         public DetailAST getScope() {
971             return scope;
972         }
973 
974         /**
975          * Register the variable as used.
976          */
977         public void registerAsUsed() {
978             used = true;
979         }
980 
981         /**
982          * Register the variable as an instance variable or
983          * class variable.
984          */
985         public void registerAsInstOrClassVar() {
986             instVarOrClassVar = true;
987         }
988 
989         /**
990          * Is the variable used or not.
991          *
992          * @return true if variable is used
993          */
994         public boolean isUsed() {
995             return used;
996         }
997 
998         /**
999          * Is an instance variable or a class variable.
1000          *
1001          * @return true if is an instance variable or a class variable
1002          */
1003         public boolean isInstVarOrClassVar() {
1004             return instVarOrClassVar;
1005         }
1006     }
1007 
1008     /**
1009      * Maintains information about the type declaration.
1010      * Any ast node of type {@link TokenTypes#CLASS_DEF} or {@link TokenTypes#INTERFACE_DEF}
1011      * or {@link TokenTypes#ENUM_DEF} or {@link TokenTypes#ANNOTATION_DEF}
1012      * or {@link TokenTypes#RECORD_DEF} is considered as a type declaration.
1013      */
1014     private static final class TypeDeclDesc {
1015 
1016         /**
1017          * Complete type declaration name with package name and outer type declaration name.
1018          */
1019         private final String qualifiedName;
1020 
1021         /**
1022          * Depth of nesting of type declaration.
1023          */
1024         private final int depth;
1025 
1026         /**
1027          * Type declaration ast node.
1028          */
1029         private final DetailAST typeDeclAst;
1030 
1031         /**
1032          * A stack of type declaration's instance and static variables.
1033          */
1034         private final Deque<VariableDesc> instanceAndClassVarStack;
1035 
1036         /**
1037          * Create a new TypeDeclDesc instance.
1038          *
1039          * @param qualifiedName qualified name
1040          * @param depth depth of nesting
1041          * @param typeDeclAst type declaration ast node
1042          */
1043         private TypeDeclDesc(String qualifiedName, int depth,
1044                 DetailAST typeDeclAst) {
1045             this.qualifiedName = qualifiedName;
1046             this.depth = depth;
1047             this.typeDeclAst = typeDeclAst;
1048             instanceAndClassVarStack = new ArrayDeque<>();
1049         }
1050 
1051         /**
1052          * Get the complete type declaration name i.e. type declaration name with package name
1053          * and outer type declaration name.
1054          *
1055          * @return qualified class name
1056          */
1057         public String getQualifiedName() {
1058             return qualifiedName;
1059         }
1060 
1061         /**
1062          * Get the depth of type declaration.
1063          *
1064          * @return the depth of nesting of type declaration
1065          */
1066         public int getDepth() {
1067             return depth;
1068         }
1069 
1070         /**
1071          * Get the type declaration ast node.
1072          *
1073          * @return ast node of the type declaration
1074          */
1075         public DetailAST getTypeDeclAst() {
1076             return typeDeclAst;
1077         }
1078 
1079         /**
1080          * Get the copy of variables in instanceAndClassVar stack with updated scope.
1081          *
1082          * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
1083          * @return copy of variables in instanceAndClassVar stack with updated scope.
1084          */
1085         public Deque<VariableDesc> getUpdatedCopyOfVarStack(DetailAST literalNewAst) {
1086             final DetailAST updatedScope = literalNewAst;
1087             final Deque<VariableDesc> instAndClassVarDeque = new ArrayDeque<>();
1088             instanceAndClassVarStack.forEach(instVar -> {
1089                 final VariableDesc variableDesc = new VariableDesc(instVar.getName(),
1090                         updatedScope);
1091                 variableDesc.registerAsInstOrClassVar();
1092                 instAndClassVarDeque.push(variableDesc);
1093             });
1094             return instAndClassVarDeque;
1095         }
1096 
1097         /**
1098          * Add an instance variable or class variable to the stack.
1099          *
1100          * @param variableDesc variable to be added
1101          */
1102         public void addInstOrClassVar(VariableDesc variableDesc) {
1103             instanceAndClassVarStack.push(variableDesc);
1104         }
1105     }
1106 }