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