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