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