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