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 = getTheNearestClass( 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 getTheNearestClass(String outerTypeDeclName, 635 List<TypeDeclDesc> typeDeclWithSameName) { 636 return Collections.min(typeDeclWithSameName, (first, second) -> { 637 return getTypeDeclarationNameMatchingCountDiff(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 outerTypeDeclName outer type declaration of anonymous inner class 646 * @param firstTypeDecl first input type declaration 647 * @param secondTypeDecl second input type declaration 648 * @return difference between type declaration name matching count 649 */ 650 private static int getTypeDeclarationNameMatchingCountDiff(String outerTypeDeclName, 651 TypeDeclDesc firstTypeDecl, 652 TypeDeclDesc secondTypeDecl) { 653 int diff = Integer.compare( 654 CheckUtil.typeDeclarationNameMatchingCount( 655 outerTypeDeclName, secondTypeDecl.getQualifiedName()), 656 CheckUtil.typeDeclarationNameMatchingCount( 657 outerTypeDeclName, firstTypeDecl.getQualifiedName())); 658 if (diff == 0) { 659 diff = Integer.compare(firstTypeDecl.getDepth(), secondTypeDecl.getDepth()); 660 } 661 return diff; 662 } 663 664 /** 665 * Get qualified type declaration name from type ast. 666 * 667 * @param typeDeclAst type declaration ast 668 * @return qualified name of type declaration 669 */ 670 private String getQualifiedTypeDeclarationName(DetailAST typeDeclAst) { 671 final String className = typeDeclAst.findFirstToken(TokenTypes.IDENT).getText(); 672 String outerClassQualifiedName = null; 673 if (!typeDeclarations.isEmpty()) { 674 outerClassQualifiedName = typeDeclarations.peek().getQualifiedName(); 675 } 676 return CheckUtil 677 .getQualifiedTypeDeclarationName(packageName, outerClassQualifiedName, className); 678 } 679 680 /** 681 * Iterate over all the ast nodes present under {@code ast}. 682 * 683 * @param ast ast 684 * @param variablesStack stack of all the relevant variables in the scope 685 */ 686 private void iterateOverBlockContainingLocalAnonInnerClass( 687 DetailAST ast, Deque<VariableDesc> variablesStack) { 688 DetailAST currNode = ast; 689 while (currNode != null) { 690 customVisitToken(currNode, variablesStack); 691 DetailAST toVisit = currNode.getFirstChild(); 692 while (currNode != ast && toVisit == null) { 693 customLeaveToken(currNode, variablesStack); 694 toVisit = currNode.getNextSibling(); 695 currNode = currNode.getParent(); 696 } 697 currNode = toVisit; 698 } 699 } 700 701 /** 702 * Visit all ast nodes under {@link UnusedLocalVariableCheck#anonInnerClassHolders} once 703 * again. 704 * 705 * @param ast ast 706 * @param variablesStack stack of all the relevant variables in the scope 707 */ 708 private void customVisitToken(DetailAST ast, Deque<VariableDesc> variablesStack) { 709 final int type = ast.getType(); 710 if (type == TokenTypes.DOT) { 711 visitDotToken(ast, variablesStack); 712 } 713 else if (type == TokenTypes.VARIABLE_DEF) { 714 addLocalVariables(ast, variablesStack); 715 } 716 else if (type == TokenTypes.IDENT) { 717 visitIdentToken(ast, variablesStack); 718 } 719 else if (isInsideLocalAnonInnerClass(ast)) { 720 final TypeDeclDesc obtainedClass = getSuperClassOfAnonInnerClass(ast); 721 modifyVariablesStack(obtainedClass, variablesStack, ast); 722 } 723 } 724 725 /** 726 * Leave all ast nodes under {@link UnusedLocalVariableCheck#anonInnerClassHolders} once 727 * again. 728 * 729 * @param ast ast 730 * @param variablesStack stack of all the relevant variables in the scope 731 */ 732 private void customLeaveToken(DetailAST ast, Deque<VariableDesc> variablesStack) { 733 logViolations(ast, variablesStack); 734 } 735 736 /** 737 * Whether to check identifier token nested under dotAst. 738 * 739 * @param dotAst dotAst 740 * @return true if ident nested under dotAst should be checked 741 */ 742 private static boolean shouldCheckIdentTokenNestedUnderDot(DetailAST dotAst) { 743 744 return TokenUtil.findFirstTokenByPredicate(dotAst, 745 childAst -> { 746 return TokenUtil.isOfType(childAst, 747 UNACCEPTABLE_CHILD_OF_DOT); 748 }) 749 .isEmpty(); 750 } 751 752 /** 753 * Checks the identifier ast. 754 * 755 * @param identAst ast of type {@link TokenTypes#IDENT} 756 * @param variablesStack stack of all the relevant variables in the scope 757 */ 758 private static void checkIdentifierAst(DetailAST identAst, Deque<VariableDesc> variablesStack) { 759 for (VariableDesc variableDesc : variablesStack) { 760 if (identAst.getText().equals(variableDesc.getName()) 761 && !isLeftHandSideValue(identAst)) { 762 variableDesc.registerAsUsed(); 763 break; 764 } 765 } 766 } 767 768 /** 769 * Find the scope of variable. 770 * 771 * @param variableDef ast of type {@link TokenTypes#VARIABLE_DEF} 772 * @return scope of variableDef 773 */ 774 private static DetailAST findScopeOfVariable(DetailAST variableDef) { 775 final DetailAST result; 776 final DetailAST parentAst = variableDef.getParent(); 777 if (TokenUtil.isOfType(parentAst, TokenTypes.SLIST, TokenTypes.OBJBLOCK)) { 778 result = parentAst; 779 } 780 else { 781 result = parentAst.getParent(); 782 } 783 return result; 784 } 785 786 /** 787 * Checks whether the ast of type {@link TokenTypes#IDENT} is 788 * used as left-hand side value. An identifier is being used as a left-hand side 789 * value if it is used as the left operand of an assignment or as an 790 * operand of a stand-alone increment or decrement. 791 * 792 * @param identAst ast of type {@link TokenTypes#IDENT} 793 * @return true if identAst is used as a left-hand side value 794 */ 795 private static boolean isLeftHandSideValue(DetailAST identAst) { 796 final DetailAST parent = identAst.getParent(); 797 return isStandAloneIncrementOrDecrement(identAst) 798 || parent.getType() == TokenTypes.ASSIGN 799 && identAst != parent.getLastChild(); 800 } 801 802 /** 803 * Checks whether the ast of type {@link TokenTypes#IDENT} is used as 804 * an operand of a stand-alone increment or decrement. 805 * 806 * @param identAst ast of type {@link TokenTypes#IDENT} 807 * @return true if identAst is used as an operand of stand-alone 808 * increment or decrement 809 */ 810 private static boolean isStandAloneIncrementOrDecrement(DetailAST identAst) { 811 final DetailAST parent = identAst.getParent(); 812 final DetailAST grandParent = parent.getParent(); 813 return TokenUtil.isOfType(parent, INCREMENT_AND_DECREMENT_TOKENS) 814 && TokenUtil.isOfType(grandParent, TokenTypes.EXPR) 815 && !isIncrementOrDecrementVariableUsed(grandParent); 816 } 817 818 /** 819 * A variable with increment or decrement operator is considered used if it 820 * is used as an argument or as an array index or for assigning value 821 * to a variable. 822 * 823 * @param exprAst ast of type {@link TokenTypes#EXPR} 824 * @return true if variable nested in exprAst is used 825 */ 826 private static boolean isIncrementOrDecrementVariableUsed(DetailAST exprAst) { 827 return TokenUtil.isOfType(exprAst.getParent(), INCREMENT_DECREMENT_VARIABLE_USAGE_TYPES) 828 && exprAst.getParent().getParent().getType() != TokenTypes.FOR_ITERATOR; 829 } 830 831 /** 832 * Maintains information about the variable. 833 */ 834 private static final class VariableDesc { 835 836 /** 837 * The name of the variable. 838 */ 839 private final String name; 840 841 /** 842 * Ast of type {@link TokenTypes#TYPE}. 843 */ 844 private final DetailAST typeAst; 845 846 /** 847 * The scope of variable is determined by the ast of type 848 * {@link TokenTypes#SLIST} or {@link TokenTypes#LITERAL_FOR} 849 * or {@link TokenTypes#OBJBLOCK} which is enclosing the variable. 850 */ 851 private final DetailAST scope; 852 853 /** 854 * Is an instance variable or a class variable. 855 */ 856 private boolean instVarOrClassVar; 857 858 /** 859 * Is the variable used. 860 */ 861 private boolean used; 862 863 /** 864 * Create a new VariableDesc instance. 865 * 866 * @param name name of the variable 867 * @param typeAst ast of type {@link TokenTypes#TYPE} 868 * @param scope ast of type {@link TokenTypes#SLIST} or 869 * {@link TokenTypes#LITERAL_FOR} or {@link TokenTypes#OBJBLOCK} 870 * which is enclosing the variable 871 */ 872 private VariableDesc(String name, DetailAST typeAst, DetailAST scope) { 873 this.name = name; 874 this.typeAst = typeAst; 875 this.scope = scope; 876 } 877 878 /** 879 * Create a new VariableDesc instance. 880 * 881 * @param name name of the variable 882 */ 883 private VariableDesc(String name) { 884 this(name, null, null); 885 } 886 887 /** 888 * Create a new VariableDesc instance. 889 * 890 * @param name name of the variable 891 * @param scope ast of type {@link TokenTypes#SLIST} or 892 * {@link TokenTypes#LITERAL_FOR} or {@link TokenTypes#OBJBLOCK} 893 * which is enclosing the variable 894 */ 895 private VariableDesc(String name, DetailAST scope) { 896 this(name, null, scope); 897 } 898 899 /** 900 * Get the name of variable. 901 * 902 * @return name of variable 903 */ 904 public String getName() { 905 return name; 906 } 907 908 /** 909 * Get the associated ast node of type {@link TokenTypes#TYPE}. 910 * 911 * @return the associated ast node of type {@link TokenTypes#TYPE} 912 */ 913 public DetailAST getTypeAst() { 914 return typeAst; 915 } 916 917 /** 918 * Get ast of type {@link TokenTypes#SLIST} 919 * or {@link TokenTypes#LITERAL_FOR} or {@link TokenTypes#OBJBLOCK} 920 * which is enclosing the variable i.e. its scope. 921 * 922 * @return the scope associated with the variable 923 */ 924 public DetailAST getScope() { 925 return scope; 926 } 927 928 /** 929 * Register the variable as used. 930 */ 931 public void registerAsUsed() { 932 used = true; 933 } 934 935 /** 936 * Register the variable as an instance variable or 937 * class variable. 938 */ 939 public void registerAsInstOrClassVar() { 940 instVarOrClassVar = true; 941 } 942 943 /** 944 * Is the variable used or not. 945 * 946 * @return true if variable is used 947 */ 948 public boolean isUsed() { 949 return used; 950 } 951 952 /** 953 * Is an instance variable or a class variable. 954 * 955 * @return true if is an instance variable or a class variable 956 */ 957 public boolean isInstVarOrClassVar() { 958 return instVarOrClassVar; 959 } 960 } 961 962 /** 963 * Maintains information about the type declaration. 964 * Any ast node of type {@link TokenTypes#CLASS_DEF} or {@link TokenTypes#INTERFACE_DEF} 965 * or {@link TokenTypes#ENUM_DEF} or {@link TokenTypes#ANNOTATION_DEF} 966 * or {@link TokenTypes#RECORD_DEF} is considered as a type declaration. 967 */ 968 private static final class TypeDeclDesc { 969 970 /** 971 * Complete type declaration name with package name and outer type declaration name. 972 */ 973 private final String qualifiedName; 974 975 /** 976 * Depth of nesting of type declaration. 977 */ 978 private final int depth; 979 980 /** 981 * Type declaration ast node. 982 */ 983 private final DetailAST typeDeclAst; 984 985 /** 986 * A stack of type declaration's instance and static variables. 987 */ 988 private final Deque<VariableDesc> instanceAndClassVarStack; 989 990 /** 991 * Create a new TypeDeclDesc instance. 992 * 993 * @param qualifiedName qualified name 994 * @param depth depth of nesting 995 * @param typeDeclAst type declaration ast node 996 */ 997 private TypeDeclDesc(String qualifiedName, int depth, 998 DetailAST typeDeclAst) { 999 this.qualifiedName = qualifiedName; 1000 this.depth = depth; 1001 this.typeDeclAst = typeDeclAst; 1002 instanceAndClassVarStack = new ArrayDeque<>(); 1003 } 1004 1005 /** 1006 * Get the complete type declaration name i.e. type declaration name with package name 1007 * and outer type declaration name. 1008 * 1009 * @return qualified class name 1010 */ 1011 public String getQualifiedName() { 1012 return qualifiedName; 1013 } 1014 1015 /** 1016 * Get the depth of type declaration. 1017 * 1018 * @return the depth of nesting of type declaration 1019 */ 1020 public int getDepth() { 1021 return depth; 1022 } 1023 1024 /** 1025 * Get the type declaration ast node. 1026 * 1027 * @return ast node of the type declaration 1028 */ 1029 public DetailAST getTypeDeclAst() { 1030 return typeDeclAst; 1031 } 1032 1033 /** 1034 * Get the copy of variables in instanceAndClassVar stack with updated scope. 1035 * 1036 * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW} 1037 * @return copy of variables in instanceAndClassVar stack with updated scope. 1038 */ 1039 public Deque<VariableDesc> getUpdatedCopyOfVarStack(DetailAST literalNewAst) { 1040 final DetailAST updatedScope = literalNewAst; 1041 final Deque<VariableDesc> instAndClassVarDeque = new ArrayDeque<>(); 1042 instanceAndClassVarStack.forEach(instVar -> { 1043 final VariableDesc variableDesc = new VariableDesc(instVar.getName(), 1044 updatedScope); 1045 variableDesc.registerAsInstOrClassVar(); 1046 instAndClassVarDeque.push(variableDesc); 1047 }); 1048 return instAndClassVarDeque; 1049 } 1050 1051 /** 1052 * Add an instance variable or class variable to the stack. 1053 * 1054 * @param variableDesc variable to be added 1055 */ 1056 public void addInstOrClassVar(VariableDesc variableDesc) { 1057 instanceAndClassVarStack.push(variableDesc); 1058 } 1059 } 1060}