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 /** 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}