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