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