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 final DetailAST identifier = dotAst.findFirstToken(TokenTypes.IDENT); 267 if (identifier != null) { 268 checkIdentifierAst(identifier, variablesStack); 269 } 270 } 271 } 272 273 /** 274 * Visit ast of type {@link TokenTypes#VARIABLE_DEF}. 275 * 276 * @param varDefAst varDefAst 277 */ 278 private void visitVariableDefToken(DetailAST varDefAst) { 279 addLocalVariables(varDefAst, variables); 280 addInstanceOrClassVar(varDefAst); 281 } 282 283 /** 284 * Visit ast of type {@link TokenTypes#IDENT}. 285 * 286 * @param identAst identAst 287 * @param variablesStack stack of all the relevant variables in the scope 288 */ 289 private static void visitIdentToken(DetailAST identAst, Deque<VariableDesc> variablesStack) { 290 final DetailAST parent = identAst.getParent(); 291 final boolean isMethodReferenceMethodName = parent.getType() == TokenTypes.METHOD_REF 292 && parent.getFirstChild() != identAst; 293 final boolean isConstructorReference = parent.getType() == TokenTypes.METHOD_REF 294 && parent.getLastChild().getType() == TokenTypes.LITERAL_NEW; 295 final boolean isNestedClassInitialization = 296 TokenUtil.isOfType(identAst.getNextSibling(), TokenTypes.LITERAL_NEW) 297 && parent.getType() == TokenTypes.DOT; 298 299 if (isNestedClassInitialization || !isMethodReferenceMethodName 300 && !isConstructorReference 301 && !TokenUtil.isOfType(parent, UNACCEPTABLE_PARENT_OF_IDENT)) { 302 checkIdentifierAst(identAst, variablesStack); 303 } 304 } 305 306 /** 307 * Visit the type declaration token. 308 * 309 * @param typeDeclAst type declaration ast 310 */ 311 private void visitTypeDeclarationToken(DetailAST typeDeclAst) { 312 if (isNonLocalTypeDeclaration(typeDeclAst)) { 313 final String qualifiedName = getQualifiedTypeDeclarationName(typeDeclAst); 314 final TypeDeclDesc currTypeDecl = new TypeDeclDesc(qualifiedName, depth, typeDeclAst); 315 depth++; 316 typeDeclarations.push(currTypeDecl); 317 typeDeclAstToTypeDeclDesc.put(typeDeclAst, currTypeDecl); 318 } 319 } 320 321 /** 322 * Visit the local anon inner class. 323 * 324 * @param literalNewAst literalNewAst 325 */ 326 private void visitLocalAnonInnerClass(DetailAST literalNewAst) { 327 anonInnerAstToTypeDeclDesc.put(literalNewAst, typeDeclarations.peek()); 328 anonInnerClassHolders.add(getBlockContainingLocalAnonInnerClass(literalNewAst)); 329 } 330 331 /** 332 * Whether ast node of type {@link TokenTypes#LITERAL_NEW} is a part of a local 333 * anonymous inner class. 334 * 335 * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW} 336 * @return true if variableDefAst is an instance variable in local anonymous inner class 337 */ 338 private static boolean isInsideLocalAnonInnerClass(DetailAST literalNewAst) { 339 boolean result = false; 340 final DetailAST lastChild = literalNewAst.getLastChild(); 341 if (lastChild != null && lastChild.getType() == TokenTypes.OBJBLOCK) { 342 DetailAST currentAst = literalNewAst; 343 while (!TokenUtil.isTypeDeclaration(currentAst.getType())) { 344 if (currentAst.getType() == TokenTypes.SLIST) { 345 result = true; 346 break; 347 } 348 currentAst = currentAst.getParent(); 349 } 350 } 351 return result; 352 } 353 354 /** 355 * Traverse {@code variablesStack} stack and log the violations. 356 * 357 * @param scopeAst ast node of type {@link UnusedLocalVariableCheck#SCOPES} 358 * @param variablesStack stack of all the relevant variables in the scope 359 */ 360 private void logViolations(DetailAST scopeAst, Deque<VariableDesc> variablesStack) { 361 while (!variablesStack.isEmpty() && variablesStack.peek().getScope() == scopeAst) { 362 final VariableDesc variableDesc = variablesStack.pop(); 363 if (!variableDesc.isUsed() 364 && !variableDesc.isInstVarOrClassVar()) { 365 final DetailAST typeAst = variableDesc.getTypeAst(); 366 log(typeAst, MSG_UNUSED_LOCAL_VARIABLE, variableDesc.getName()); 367 } 368 } 369 } 370 371 /** 372 * We process all the blocks containing local anonymous inner classes 373 * separately after processing all the other nodes. This is being done 374 * due to the fact the instance variables of local anon inner classes can 375 * cast a shadow on local variables. 376 */ 377 private void leaveCompilationUnit() { 378 anonInnerClassHolders.forEach(holder -> { 379 iterateOverBlockContainingLocalAnonInnerClass(holder, new ArrayDeque<>()); 380 }); 381 } 382 383 /** 384 * Whether a type declaration is non-local. Annotated interfaces are always non-local. 385 * 386 * @param typeDeclAst type declaration ast 387 * @return true if type declaration is non-local 388 */ 389 private static boolean isNonLocalTypeDeclaration(DetailAST typeDeclAst) { 390 return TokenUtil.isTypeDeclaration(typeDeclAst.getType()) 391 && typeDeclAst.getParent().getType() != TokenTypes.SLIST; 392 } 393 394 /** 395 * Get the block containing local anon inner class. 396 * 397 * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW} 398 * @return the block containing local anon inner class 399 */ 400 private static DetailAST getBlockContainingLocalAnonInnerClass(DetailAST literalNewAst) { 401 DetailAST currentAst = literalNewAst; 402 DetailAST result = null; 403 while (!TokenUtil.isOfType(currentAst, CONTAINERS_FOR_ANON_INNERS)) { 404 if (currentAst.getType() == TokenTypes.LAMBDA 405 && currentAst.getParent() 406 .getParent().getParent().getType() == TokenTypes.OBJBLOCK) { 407 result = currentAst; 408 break; 409 } 410 currentAst = currentAst.getParent(); 411 result = currentAst; 412 } 413 return result; 414 } 415 416 /** 417 * Add local variables to the {@code variablesStack} stack. 418 * Also adds the instance variables defined in a local anonymous inner class. 419 * 420 * @param varDefAst ast node of type {@link TokenTypes#VARIABLE_DEF} 421 * @param variablesStack stack of all the relevant variables in the scope 422 */ 423 private static void addLocalVariables(DetailAST varDefAst, Deque<VariableDesc> variablesStack) { 424 final DetailAST parentAst = varDefAst.getParent(); 425 final DetailAST grandParent = parentAst.getParent(); 426 final boolean isInstanceVarInInnerClass = 427 grandParent.getType() == TokenTypes.LITERAL_NEW 428 || grandParent.getType() == TokenTypes.CLASS_DEF; 429 if (isInstanceVarInInnerClass 430 || parentAst.getType() != TokenTypes.OBJBLOCK) { 431 final DetailAST ident = varDefAst.findFirstToken(TokenTypes.IDENT); 432 final VariableDesc desc = new VariableDesc(ident.getText(), 433 varDefAst.findFirstToken(TokenTypes.TYPE), findScopeOfVariable(varDefAst)); 434 if (isInstanceVarInInnerClass) { 435 desc.registerAsInstOrClassVar(); 436 } 437 variablesStack.push(desc); 438 } 439 } 440 441 /** 442 * Add instance variables and class variables to the 443 * {@link TypeDeclDesc#instanceAndClassVarStack}. 444 * 445 * @param varDefAst ast node of type {@link TokenTypes#VARIABLE_DEF} 446 */ 447 private void addInstanceOrClassVar(DetailAST varDefAst) { 448 final DetailAST parentAst = varDefAst.getParent(); 449 if (isNonLocalTypeDeclaration(parentAst.getParent()) 450 && !isPrivateInstanceVariable(varDefAst)) { 451 final DetailAST ident = varDefAst.findFirstToken(TokenTypes.IDENT); 452 final VariableDesc desc = new VariableDesc(ident.getText()); 453 typeDeclAstToTypeDeclDesc.get(parentAst.getParent()).addInstOrClassVar(desc); 454 } 455 } 456 457 /** 458 * Whether instance variable or class variable have private access modifier. 459 * 460 * @param varDefAst ast node of type {@link TokenTypes#VARIABLE_DEF} 461 * @return true if instance variable or class variable have private access modifier 462 */ 463 private static boolean isPrivateInstanceVariable(DetailAST varDefAst) { 464 final AccessModifierOption varAccessModifier = 465 CheckUtil.getAccessModifierFromModifiersToken(varDefAst); 466 return varAccessModifier == AccessModifierOption.PRIVATE; 467 } 468 469 /** 470 * Get the {@link TypeDeclDesc} of the super class of anonymous inner class. 471 * 472 * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW} 473 * @return {@link TypeDeclDesc} of the super class of anonymous inner class 474 */ 475 private TypeDeclDesc getSuperClassOfAnonInnerClass(DetailAST literalNewAst) { 476 TypeDeclDesc obtainedClass = null; 477 final String shortNameOfClass = CheckUtil.getShortNameOfAnonInnerClass(literalNewAst); 478 if (packageName != null && shortNameOfClass.startsWith(packageName)) { 479 final Optional<TypeDeclDesc> classWithCompletePackageName = 480 typeDeclAstToTypeDeclDesc.values() 481 .stream() 482 .filter(typeDeclDesc -> { 483 return typeDeclDesc.getQualifiedName().equals(shortNameOfClass); 484 }) 485 .findFirst(); 486 if (classWithCompletePackageName.isPresent()) { 487 obtainedClass = classWithCompletePackageName.orElseThrow(); 488 } 489 } 490 else { 491 final List<TypeDeclDesc> typeDeclWithSameName = typeDeclWithSameName(shortNameOfClass); 492 if (!typeDeclWithSameName.isEmpty()) { 493 obtainedClass = getTheNearestClass( 494 anonInnerAstToTypeDeclDesc.get(literalNewAst).getQualifiedName(), 495 typeDeclWithSameName); 496 } 497 } 498 return obtainedClass; 499 } 500 501 /** 502 * Add non-private instance and class variables of the super class of the anonymous class 503 * to the variables stack. 504 * 505 * @param obtainedClass super class of the anon inner class 506 * @param variablesStack stack of all the relevant variables in the scope 507 * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW} 508 */ 509 private void modifyVariablesStack(TypeDeclDesc obtainedClass, 510 Deque<VariableDesc> variablesStack, 511 DetailAST literalNewAst) { 512 if (obtainedClass != null) { 513 final Deque<VariableDesc> instAndClassVarDeque = typeDeclAstToTypeDeclDesc 514 .get(obtainedClass.getTypeDeclAst()) 515 .getUpdatedCopyOfVarStack(literalNewAst); 516 instAndClassVarDeque.forEach(variablesStack::push); 517 } 518 } 519 520 /** 521 * Checks if there is a type declaration with same name as the super class. 522 * 523 * @param superClassName name of the super class 524 * @return list if there is another type declaration with same name. 525 */ 526 private List<TypeDeclDesc> typeDeclWithSameName(String superClassName) { 527 return typeDeclAstToTypeDeclDesc.values().stream() 528 .filter(typeDeclDesc -> { 529 return hasSameNameAsSuperClass(superClassName, typeDeclDesc); 530 }) 531 .collect(Collectors.toUnmodifiableList()); 532 } 533 534 /** 535 * Whether the qualified name of {@code typeDeclDesc} matches the super class name. 536 * 537 * @param superClassName name of the super class 538 * @param typeDeclDesc type declaration description 539 * @return {@code true} if the qualified name of {@code typeDeclDesc} 540 * matches the super class name 541 */ 542 private boolean hasSameNameAsSuperClass(String superClassName, TypeDeclDesc typeDeclDesc) { 543 final boolean result; 544 if (packageName == null && typeDeclDesc.getDepth() == 0) { 545 result = typeDeclDesc.getQualifiedName().equals(superClassName); 546 } 547 else { 548 result = typeDeclDesc.getQualifiedName() 549 .endsWith(PACKAGE_SEPARATOR + superClassName); 550 } 551 return result; 552 } 553 554 /** 555 * For all type declarations with the same name as the superclass, gets the nearest type 556 * declaration. 557 * 558 * @param outerTypeDeclName outer type declaration of anonymous inner class 559 * @param typeDeclWithSameName typeDeclarations which have the same name as the super class 560 * @return the nearest class 561 */ 562 private static TypeDeclDesc getTheNearestClass(String outerTypeDeclName, 563 List<TypeDeclDesc> typeDeclWithSameName) { 564 return Collections.min(typeDeclWithSameName, (first, second) -> { 565 return getTypeDeclarationNameMatchingCountDiff(outerTypeDeclName, first, second); 566 }); 567 } 568 569 /** 570 * Get the difference between type declaration name matching count. If the 571 * difference between them is zero, then their depth is compared to obtain the result. 572 * 573 * @param outerTypeDeclName outer type declaration of anonymous inner class 574 * @param firstTypeDecl first input type declaration 575 * @param secondTypeDecl second input type declaration 576 * @return difference between type declaration name matching count 577 */ 578 private static int getTypeDeclarationNameMatchingCountDiff(String outerTypeDeclName, 579 TypeDeclDesc firstTypeDecl, 580 TypeDeclDesc secondTypeDecl) { 581 int diff = Integer.compare( 582 CheckUtil.typeDeclarationNameMatchingCount( 583 outerTypeDeclName, secondTypeDecl.getQualifiedName()), 584 CheckUtil.typeDeclarationNameMatchingCount( 585 outerTypeDeclName, firstTypeDecl.getQualifiedName())); 586 if (diff == 0) { 587 diff = Integer.compare(firstTypeDecl.getDepth(), secondTypeDecl.getDepth()); 588 } 589 return diff; 590 } 591 592 /** 593 * Get qualified type declaration name from type ast. 594 * 595 * @param typeDeclAst type declaration ast 596 * @return qualified name of type declaration 597 */ 598 private String getQualifiedTypeDeclarationName(DetailAST typeDeclAst) { 599 final String className = typeDeclAst.findFirstToken(TokenTypes.IDENT).getText(); 600 String outerClassQualifiedName = null; 601 if (!typeDeclarations.isEmpty()) { 602 outerClassQualifiedName = typeDeclarations.peek().getQualifiedName(); 603 } 604 return CheckUtil 605 .getQualifiedTypeDeclarationName(packageName, outerClassQualifiedName, className); 606 } 607 608 /** 609 * Iterate over all the ast nodes present under {@code ast}. 610 * 611 * @param ast ast 612 * @param variablesStack stack of all the relevant variables in the scope 613 */ 614 private void iterateOverBlockContainingLocalAnonInnerClass( 615 DetailAST ast, Deque<VariableDesc> variablesStack) { 616 DetailAST currNode = ast; 617 while (currNode != null) { 618 customVisitToken(currNode, variablesStack); 619 DetailAST toVisit = currNode.getFirstChild(); 620 while (currNode != ast && toVisit == null) { 621 customLeaveToken(currNode, variablesStack); 622 toVisit = currNode.getNextSibling(); 623 currNode = currNode.getParent(); 624 } 625 currNode = toVisit; 626 } 627 } 628 629 /** 630 * Visit all ast nodes under {@link UnusedLocalVariableCheck#anonInnerClassHolders} once 631 * again. 632 * 633 * @param ast ast 634 * @param variablesStack stack of all the relevant variables in the scope 635 */ 636 private void customVisitToken(DetailAST ast, Deque<VariableDesc> variablesStack) { 637 final int type = ast.getType(); 638 if (type == TokenTypes.DOT) { 639 visitDotToken(ast, variablesStack); 640 } 641 else if (type == TokenTypes.VARIABLE_DEF) { 642 addLocalVariables(ast, variablesStack); 643 } 644 else if (type == TokenTypes.IDENT) { 645 visitIdentToken(ast, variablesStack); 646 } 647 else if (isInsideLocalAnonInnerClass(ast)) { 648 final TypeDeclDesc obtainedClass = getSuperClassOfAnonInnerClass(ast); 649 modifyVariablesStack(obtainedClass, variablesStack, ast); 650 } 651 } 652 653 /** 654 * Leave all ast nodes under {@link UnusedLocalVariableCheck#anonInnerClassHolders} once 655 * again. 656 * 657 * @param ast ast 658 * @param variablesStack stack of all the relevant variables in the scope 659 */ 660 private void customLeaveToken(DetailAST ast, Deque<VariableDesc> variablesStack) { 661 logViolations(ast, variablesStack); 662 } 663 664 /** 665 * Whether to check identifier token nested under dotAst. 666 * 667 * @param dotAst dotAst 668 * @return true if ident nested under dotAst should be checked 669 */ 670 private static boolean shouldCheckIdentTokenNestedUnderDot(DetailAST dotAst) { 671 672 return TokenUtil.findFirstTokenByPredicate(dotAst, 673 childAst -> { 674 return TokenUtil.isOfType(childAst, 675 UNACCEPTABLE_CHILD_OF_DOT); 676 }) 677 .isEmpty(); 678 } 679 680 /** 681 * Checks the identifier ast. 682 * 683 * @param identAst ast of type {@link TokenTypes#IDENT} 684 * @param variablesStack stack of all the relevant variables in the scope 685 */ 686 private static void checkIdentifierAst(DetailAST identAst, Deque<VariableDesc> variablesStack) { 687 for (VariableDesc variableDesc : variablesStack) { 688 if (identAst.getText().equals(variableDesc.getName()) 689 && !isLeftHandSideValue(identAst)) { 690 variableDesc.registerAsUsed(); 691 break; 692 } 693 } 694 } 695 696 /** 697 * Find the scope of variable. 698 * 699 * @param variableDef ast of type {@link TokenTypes#VARIABLE_DEF} 700 * @return scope of variableDef 701 */ 702 private static DetailAST findScopeOfVariable(DetailAST variableDef) { 703 final DetailAST result; 704 final DetailAST parentAst = variableDef.getParent(); 705 if (TokenUtil.isOfType(parentAst, TokenTypes.SLIST, TokenTypes.OBJBLOCK)) { 706 result = parentAst; 707 } 708 else { 709 result = parentAst.getParent(); 710 } 711 return result; 712 } 713 714 /** 715 * Checks whether the ast of type {@link TokenTypes#IDENT} is 716 * used as left-hand side value. An identifier is being used as a left-hand side 717 * value if it is used as the left operand of an assignment or as an 718 * operand of a stand-alone increment or decrement. 719 * 720 * @param identAst ast of type {@link TokenTypes#IDENT} 721 * @return true if identAst is used as a left-hand side value 722 */ 723 private static boolean isLeftHandSideValue(DetailAST identAst) { 724 final DetailAST parent = identAst.getParent(); 725 return isStandAloneIncrementOrDecrement(identAst) 726 || parent.getType() == TokenTypes.ASSIGN 727 && identAst != parent.getLastChild(); 728 } 729 730 /** 731 * Checks whether the ast of type {@link TokenTypes#IDENT} is used as 732 * an operand of a stand-alone increment or decrement. 733 * 734 * @param identAst ast of type {@link TokenTypes#IDENT} 735 * @return true if identAst is used as an operand of stand-alone 736 * increment or decrement 737 */ 738 private static boolean isStandAloneIncrementOrDecrement(DetailAST identAst) { 739 final DetailAST parent = identAst.getParent(); 740 final DetailAST grandParent = parent.getParent(); 741 return TokenUtil.isOfType(parent, INCREMENT_AND_DECREMENT_TOKENS) 742 && TokenUtil.isOfType(grandParent, TokenTypes.EXPR) 743 && !isIncrementOrDecrementVariableUsed(grandParent); 744 } 745 746 /** 747 * A variable with increment or decrement operator is considered used if it 748 * is used as an argument or as an array index or for assigning value 749 * to a variable. 750 * 751 * @param exprAst ast of type {@link TokenTypes#EXPR} 752 * @return true if variable nested in exprAst is used 753 */ 754 private static boolean isIncrementOrDecrementVariableUsed(DetailAST exprAst) { 755 return TokenUtil.isOfType(exprAst.getParent(), 756 TokenTypes.ELIST, TokenTypes.INDEX_OP, TokenTypes.ASSIGN) 757 && exprAst.getParent().getParent().getType() != TokenTypes.FOR_ITERATOR; 758 } 759 760 /** 761 * Maintains information about the variable. 762 */ 763 private static final class VariableDesc { 764 765 /** 766 * The name of the variable. 767 */ 768 private final String name; 769 770 /** 771 * Ast of type {@link TokenTypes#TYPE}. 772 */ 773 private final DetailAST typeAst; 774 775 /** 776 * The scope of variable is determined by the ast of type 777 * {@link TokenTypes#SLIST} or {@link TokenTypes#LITERAL_FOR} 778 * or {@link TokenTypes#OBJBLOCK} which is enclosing the variable. 779 */ 780 private final DetailAST scope; 781 782 /** 783 * Is an instance variable or a class variable. 784 */ 785 private boolean instVarOrClassVar; 786 787 /** 788 * Is the variable used. 789 */ 790 private boolean used; 791 792 /** 793 * Create a new VariableDesc instance. 794 * 795 * @param name name of the variable 796 * @param typeAst ast of type {@link TokenTypes#TYPE} 797 * @param scope ast of type {@link TokenTypes#SLIST} or 798 * {@link TokenTypes#LITERAL_FOR} or {@link TokenTypes#OBJBLOCK} 799 * which is enclosing the variable 800 */ 801 private VariableDesc(String name, DetailAST typeAst, DetailAST scope) { 802 this.name = name; 803 this.typeAst = typeAst; 804 this.scope = scope; 805 } 806 807 /** 808 * Create a new VariableDesc instance. 809 * 810 * @param name name of the variable 811 */ 812 private VariableDesc(String name) { 813 this(name, null, null); 814 } 815 816 /** 817 * Create a new VariableDesc instance. 818 * 819 * @param name name of the variable 820 * @param scope ast of type {@link TokenTypes#SLIST} or 821 * {@link TokenTypes#LITERAL_FOR} or {@link TokenTypes#OBJBLOCK} 822 * which is enclosing the variable 823 */ 824 private VariableDesc(String name, DetailAST scope) { 825 this(name, null, scope); 826 } 827 828 /** 829 * Get the name of variable. 830 * 831 * @return name of variable 832 */ 833 public String getName() { 834 return name; 835 } 836 837 /** 838 * Get the associated ast node of type {@link TokenTypes#TYPE}. 839 * 840 * @return the associated ast node of type {@link TokenTypes#TYPE} 841 */ 842 public DetailAST getTypeAst() { 843 return typeAst; 844 } 845 846 /** 847 * Get ast of type {@link TokenTypes#SLIST} 848 * or {@link TokenTypes#LITERAL_FOR} or {@link TokenTypes#OBJBLOCK} 849 * which is enclosing the variable i.e. its scope. 850 * 851 * @return the scope associated with the variable 852 */ 853 public DetailAST getScope() { 854 return scope; 855 } 856 857 /** 858 * Register the variable as used. 859 */ 860 public void registerAsUsed() { 861 used = true; 862 } 863 864 /** 865 * Register the variable as an instance variable or 866 * class variable. 867 */ 868 public void registerAsInstOrClassVar() { 869 instVarOrClassVar = true; 870 } 871 872 /** 873 * Is the variable used or not. 874 * 875 * @return true if variable is used 876 */ 877 public boolean isUsed() { 878 return used; 879 } 880 881 /** 882 * Is an instance variable or a class variable. 883 * 884 * @return true if is an instance variable or a class variable 885 */ 886 public boolean isInstVarOrClassVar() { 887 return instVarOrClassVar; 888 } 889 } 890 891 /** 892 * Maintains information about the type declaration. 893 * Any ast node of type {@link TokenTypes#CLASS_DEF} or {@link TokenTypes#INTERFACE_DEF} 894 * or {@link TokenTypes#ENUM_DEF} or {@link TokenTypes#ANNOTATION_DEF} 895 * or {@link TokenTypes#RECORD_DEF} is considered as a type declaration. 896 */ 897 private static final class TypeDeclDesc { 898 899 /** 900 * Complete type declaration name with package name and outer type declaration name. 901 */ 902 private final String qualifiedName; 903 904 /** 905 * Depth of nesting of type declaration. 906 */ 907 private final int depth; 908 909 /** 910 * Type declaration ast node. 911 */ 912 private final DetailAST typeDeclAst; 913 914 /** 915 * A stack of type declaration's instance and static variables. 916 */ 917 private final Deque<VariableDesc> instanceAndClassVarStack; 918 919 /** 920 * Create a new TypeDeclDesc instance. 921 * 922 * @param qualifiedName qualified name 923 * @param depth depth of nesting 924 * @param typeDeclAst type declaration ast node 925 */ 926 private TypeDeclDesc(String qualifiedName, int depth, 927 DetailAST typeDeclAst) { 928 this.qualifiedName = qualifiedName; 929 this.depth = depth; 930 this.typeDeclAst = typeDeclAst; 931 instanceAndClassVarStack = new ArrayDeque<>(); 932 } 933 934 /** 935 * Get the complete type declaration name i.e. type declaration name with package name 936 * and outer type declaration name. 937 * 938 * @return qualified class name 939 */ 940 public String getQualifiedName() { 941 return qualifiedName; 942 } 943 944 /** 945 * Get the depth of type declaration. 946 * 947 * @return the depth of nesting of type declaration 948 */ 949 public int getDepth() { 950 return depth; 951 } 952 953 /** 954 * Get the type declaration ast node. 955 * 956 * @return ast node of the type declaration 957 */ 958 public DetailAST getTypeDeclAst() { 959 return typeDeclAst; 960 } 961 962 /** 963 * Get the copy of variables in instanceAndClassVar stack with updated scope. 964 * 965 * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW} 966 * @return copy of variables in instanceAndClassVar stack with updated scope. 967 */ 968 public Deque<VariableDesc> getUpdatedCopyOfVarStack(DetailAST literalNewAst) { 969 final DetailAST updatedScope = literalNewAst; 970 final Deque<VariableDesc> instAndClassVarDeque = new ArrayDeque<>(); 971 instanceAndClassVarStack.forEach(instVar -> { 972 final VariableDesc variableDesc = new VariableDesc(instVar.getName(), 973 updatedScope); 974 variableDesc.registerAsInstOrClassVar(); 975 instAndClassVarDeque.push(variableDesc); 976 }); 977 return instAndClassVarDeque; 978 } 979 980 /** 981 * Add an instance variable or class variable to the stack. 982 * 983 * @param variableDesc variable to be added 984 */ 985 public void addInstOrClassVar(VariableDesc variableDesc) { 986 instanceAndClassVarStack.push(variableDesc); 987 } 988 } 989}