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