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