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