001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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.AbstractMap.SimpleEntry; 023import java.util.ArrayList; 024import java.util.List; 025import java.util.Map.Entry; 026import java.util.Objects; 027import java.util.Optional; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 030 031import com.puppycrawl.tools.checkstyle.StatelessCheck; 032import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 033import com.puppycrawl.tools.checkstyle.api.DetailAST; 034import com.puppycrawl.tools.checkstyle.api.FullIdent; 035import com.puppycrawl.tools.checkstyle.api.TokenTypes; 036import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 037 038/** 039 * <p> 040 * Checks the distance between declaration of variable and its first usage. 041 * Note : Variable declaration/initialization statements are not counted while calculating length. 042 * </p> 043 * <ul> 044 * <li> 045 * Property {@code allowedDistance} - Specify distance between declaration 046 * of variable and its first usage. Values should be greater than 0. 047 * Type is {@code int}. 048 * Default value is {@code 3}. 049 * </li> 050 * <li> 051 * Property {@code ignoreVariablePattern} - Define RegExp to ignore distance calculation 052 * for variables listed in this pattern. 053 * Type is {@code java.util.regex.Pattern}. 054 * Default value is {@code ""}. 055 * </li> 056 * <li> 057 * Property {@code validateBetweenScopes} - Allow to calculate the distance between 058 * declaration of variable and its first usage in the different scopes. 059 * Type is {@code boolean}. 060 * Default value is {@code false}. 061 * </li> 062 * <li> 063 * Property {@code ignoreFinal} - Allow to ignore variables with a 'final' modifier. 064 * Type is {@code boolean}. 065 * Default value is {@code true}. 066 * </li> 067 * </ul> 068 * <p> 069 * To configure the check with default config: 070 * </p> 071 * <pre> 072 * <module name="VariableDeclarationUsageDistance"/> 073 * </pre> 074 * <p>Example:</p> 075 * <pre> 076 * public class Test { 077 * 078 * public void foo1() { 079 * int num; // violation, distance = 4 080 * final double PI; // OK, final variables not checked 081 * System.out.println("Statement 1"); 082 * System.out.println("Statement 2"); 083 * System.out.println("Statement 3"); 084 * num = 1; 085 * PI = 3.14; 086 * } 087 * 088 * public void foo2() { 089 * int a; // OK, used in different scope 090 * int b; // OK, used in different scope 091 * int count = 0; // OK, used in different scope 092 * 093 * { 094 * System.out.println("Inside inner scope"); 095 * a = 1; 096 * b = 2; 097 * count++; 098 * } 099 * } 100 * } 101 * </pre> 102 * <p> 103 * Check can detect a block of initialization methods. If a variable is used in 104 * such a block and there are no other statements after variable declaration, then distance = 1. 105 * </p> 106 * <p>Case #1:</p> 107 * <pre> 108 * int minutes = 5; 109 * Calendar cal = Calendar.getInstance(); 110 * cal.setTimeInMillis(timeNow); 111 * cal.set(Calendar.SECOND, 0); 112 * cal.set(Calendar.MILLISECOND, 0); 113 * cal.set(Calendar.HOUR_OF_DAY, hh); 114 * cal.set(Calendar.MINUTE, minutes); 115 * </pre> 116 * <p> 117 * The distance for the variable "minutes" is 1 even 118 * though this variable is used in the fifth method's call. 119 * </p> 120 * <p>Case #2:</p> 121 * <pre> 122 * int minutes = 5; 123 * Calendar cal = Calendar.getInstance(); 124 * cal.setTimeInMillis(timeNow); 125 * cal.set(Calendar.SECOND, 0); 126 * cal.set(Calendar.MILLISECOND, 0); 127 * <i>System.out.println(cal);</i> 128 * cal.set(Calendar.HOUR_OF_DAY, hh); 129 * cal.set(Calendar.MINUTE, minutes); 130 * </pre> 131 * <p> 132 * The distance for the variable "minutes" is 6 because there is one more expression 133 * (except the initialization block) between the declaration of this variable and its usage. 134 * </p> 135 * <p> 136 * To configure the check to set allowed distance: 137 * </p> 138 * <pre> 139 * <module name="VariableDeclarationUsageDistance"> 140 * <property name="allowedDistance" value="4"/> 141 * </module> 142 * </pre> 143 * <p>Example:</p> 144 * <pre> 145 * public class Test { 146 * 147 * public void foo1() { 148 * int num; // OK, distance = 4 149 * final double PI; // OK, final variables not checked 150 * System.out.println("Statement 1"); 151 * System.out.println("Statement 2"); 152 * System.out.println("Statement 3"); 153 * num = 1; 154 * PI = 3.14; 155 * } 156 * 157 * public void foo2() { 158 * int a; // OK, used in different scope 159 * int b; // OK, used in different scope 160 * int count = 0; // OK, used in different scope 161 * 162 * { 163 * System.out.println("Inside inner scope"); 164 * a = 1; 165 * b = 2; 166 * count++; 167 * } 168 * } 169 * } 170 * </pre> 171 * <p> 172 * To configure the check to ignore certain variables: 173 * </p> 174 * <pre> 175 * <module name="VariableDeclarationUsageDistance"> 176 * <property name="ignoreVariablePattern" value="^num$"/> 177 * </module> 178 * </pre> 179 * <p> 180 * This configuration ignores variables named "num". 181 * </p> 182 * <p>Example:</p> 183 * <pre> 184 * public class Test { 185 * 186 * public void foo1() { 187 * int num; // OK, variable ignored 188 * final double PI; // OK, final variables not checked 189 * System.out.println("Statement 1"); 190 * System.out.println("Statement 2"); 191 * System.out.println("Statement 3"); 192 * num = 1; 193 * PI = 3.14; 194 * } 195 * 196 * public void foo2() { 197 * int a; // OK, used in different scope 198 * int b; // OK, used in different scope 199 * int count = 0; // OK, used in different scope 200 * 201 * { 202 * System.out.println("Inside inner scope"); 203 * a = 1; 204 * b = 2; 205 * count++; 206 * } 207 * } 208 * } 209 * </pre> 210 * <p> 211 * To configure the check to force validation between scopes: 212 * </p> 213 * <pre> 214 * <module name="VariableDeclarationUsageDistance"> 215 * <property name="validateBetweenScopes" value="true"/> 216 * </module> 217 * </pre> 218 * <p>Example:</p> 219 * <pre> 220 * public class Test { 221 * 222 * public void foo1() { 223 * int num; // violation, distance = 4 224 * final double PI; // OK, final variables not checked 225 * System.out.println("Statement 1"); 226 * System.out.println("Statement 2"); 227 * System.out.println("Statement 3"); 228 * num = 1; 229 * PI = 3.14; 230 * } 231 * 232 * public void foo2() { 233 * int a; // OK, distance = 2 234 * int b; // OK, distance = 3 235 * int count = 0; // violation, distance = 4 236 * 237 * { 238 * System.out.println("Inside inner scope"); 239 * a = 1; 240 * b = 2; 241 * count++; 242 * } 243 * } 244 * } 245 * </pre> 246 * <p> 247 * To configure the check to check final variables: 248 * </p> 249 * <pre> 250 * <module name="VariableDeclarationUsageDistance"> 251 * <property name="ignoreFinal" value="false"/> 252 * </module> 253 * </pre> 254 * <p>Example:</p> 255 * <pre> 256 * public class Test { 257 * 258 * public void foo1() { 259 * int num; // violation, distance = 4 260 * final double PI; // violation, distance = 5 261 * System.out.println("Statement 1"); 262 * System.out.println("Statement 2"); 263 * System.out.println("Statement 3"); 264 * num = 1; 265 * PI = 3.14; 266 * } 267 * 268 * public void foo2() { 269 * int a; // OK, used in different scope 270 * int b; // OK, used in different scope 271 * int count = 0; // OK, used in different scope 272 * 273 * { 274 * System.out.println("Inside inner scope"); 275 * a = 1; 276 * b = 2; 277 * count++; 278 * } 279 * } 280 * } 281 * </pre> 282 * <p> 283 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 284 * </p> 285 * <p> 286 * Violation Message Keys: 287 * </p> 288 * <ul> 289 * <li> 290 * {@code variable.declaration.usage.distance} 291 * </li> 292 * <li> 293 * {@code variable.declaration.usage.distance.extend} 294 * </li> 295 * </ul> 296 * 297 * @since 5.8 298 */ 299@StatelessCheck 300public class VariableDeclarationUsageDistanceCheck extends AbstractCheck { 301 302 /** 303 * Warning message key. 304 */ 305 public static final String MSG_KEY = "variable.declaration.usage.distance"; 306 307 /** 308 * Warning message key. 309 */ 310 public static final String MSG_KEY_EXT = "variable.declaration.usage.distance.extend"; 311 312 /** 313 * Default value of distance between declaration of variable and its first 314 * usage. 315 */ 316 private static final int DEFAULT_DISTANCE = 3; 317 318 /** 319 * Specify distance between declaration of variable and its first usage. 320 * Values should be greater than 0. 321 */ 322 private int allowedDistance = DEFAULT_DISTANCE; 323 324 /** 325 * Define RegExp to ignore distance calculation for variables listed in 326 * this pattern. 327 */ 328 private Pattern ignoreVariablePattern = Pattern.compile(""); 329 330 /** 331 * Allow to calculate the distance between declaration of variable and its 332 * first usage in the different scopes. 333 */ 334 private boolean validateBetweenScopes; 335 336 /** Allow to ignore variables with a 'final' modifier. */ 337 private boolean ignoreFinal = true; 338 339 /** 340 * Setter to specify distance between declaration of variable and its first usage. 341 * Values should be greater than 0. 342 * 343 * @param allowedDistance 344 * Allowed distance between declaration of variable and its first 345 * usage. 346 */ 347 public void setAllowedDistance(int allowedDistance) { 348 this.allowedDistance = allowedDistance; 349 } 350 351 /** 352 * Setter to define RegExp to ignore distance calculation for variables listed in this pattern. 353 * 354 * @param pattern a pattern. 355 */ 356 public void setIgnoreVariablePattern(Pattern pattern) { 357 ignoreVariablePattern = pattern; 358 } 359 360 /** 361 * Setter to allow to calculate the distance between declaration of 362 * variable and its first usage in the different scopes. 363 * 364 * @param validateBetweenScopes 365 * Defines if allow to calculate distance between declaration of 366 * variable and its first usage in different scopes or not. 367 */ 368 public void setValidateBetweenScopes(boolean validateBetweenScopes) { 369 this.validateBetweenScopes = validateBetweenScopes; 370 } 371 372 /** 373 * Setter to allow to ignore variables with a 'final' modifier. 374 * 375 * @param ignoreFinal 376 * Defines if ignore variables with 'final' modifier or not. 377 */ 378 public void setIgnoreFinal(boolean ignoreFinal) { 379 this.ignoreFinal = ignoreFinal; 380 } 381 382 @Override 383 public int[] getDefaultTokens() { 384 return getRequiredTokens(); 385 } 386 387 @Override 388 public int[] getAcceptableTokens() { 389 return getRequiredTokens(); 390 } 391 392 @Override 393 public int[] getRequiredTokens() { 394 return new int[] {TokenTypes.VARIABLE_DEF}; 395 } 396 397 @Override 398 public void visitToken(DetailAST ast) { 399 final int parentType = ast.getParent().getType(); 400 final DetailAST modifiers = ast.getFirstChild(); 401 402 if (parentType != TokenTypes.OBJBLOCK 403 && (!ignoreFinal || modifiers.findFirstToken(TokenTypes.FINAL) == null)) { 404 final DetailAST variable = ast.findFirstToken(TokenTypes.IDENT); 405 406 if (!isVariableMatchesIgnorePattern(variable.getText())) { 407 final DetailAST semicolonAst = ast.getNextSibling(); 408 final Entry<DetailAST, Integer> entry; 409 if (validateBetweenScopes) { 410 entry = calculateDistanceBetweenScopes(semicolonAst, variable); 411 } 412 else { 413 entry = calculateDistanceInSingleScope(semicolonAst, variable); 414 } 415 final DetailAST variableUsageAst = entry.getKey(); 416 final int dist = entry.getValue(); 417 if (dist > allowedDistance 418 && !isInitializationSequence(variableUsageAst, variable.getText())) { 419 if (ignoreFinal) { 420 log(ast, MSG_KEY_EXT, variable.getText(), dist, allowedDistance); 421 } 422 else { 423 log(ast, MSG_KEY, variable.getText(), dist, allowedDistance); 424 } 425 } 426 } 427 } 428 } 429 430 /** 431 * Get name of instance whose method is called. 432 * 433 * @param methodCallAst 434 * DetailAST of METHOD_CALL. 435 * @return name of instance. 436 */ 437 private static String getInstanceName(DetailAST methodCallAst) { 438 final String methodCallName = 439 FullIdent.createFullIdentBelow(methodCallAst).getText(); 440 final int lastDotIndex = methodCallName.lastIndexOf('.'); 441 String instanceName = ""; 442 if (lastDotIndex != -1) { 443 instanceName = methodCallName.substring(0, lastDotIndex); 444 } 445 return instanceName; 446 } 447 448 /** 449 * Processes statements until usage of variable to detect sequence of 450 * initialization methods. 451 * 452 * @param variableUsageAst 453 * DetailAST of expression that uses variable named variableName. 454 * @param variableName 455 * name of considered variable. 456 * @return true if statements between declaration and usage of variable are 457 * initialization methods. 458 */ 459 private static boolean isInitializationSequence( 460 DetailAST variableUsageAst, String variableName) { 461 boolean result = true; 462 boolean isUsedVariableDeclarationFound = false; 463 DetailAST currentSiblingAst = variableUsageAst; 464 String initInstanceName = ""; 465 466 while (result 467 && !isUsedVariableDeclarationFound 468 && currentSiblingAst != null) { 469 switch (currentSiblingAst.getType()) { 470 case TokenTypes.EXPR: 471 final DetailAST methodCallAst = currentSiblingAst.getFirstChild(); 472 473 if (methodCallAst.getType() == TokenTypes.METHOD_CALL) { 474 final String instanceName = 475 getInstanceName(methodCallAst); 476 // method is called without instance 477 if (instanceName.isEmpty()) { 478 result = false; 479 } 480 // differs from previous instance 481 else if (!instanceName.equals(initInstanceName)) { 482 if (initInstanceName.isEmpty()) { 483 initInstanceName = instanceName; 484 } 485 else { 486 result = false; 487 } 488 } 489 } 490 else { 491 // is not method call 492 result = false; 493 } 494 break; 495 496 case TokenTypes.VARIABLE_DEF: 497 final String currentVariableName = currentSiblingAst 498 .findFirstToken(TokenTypes.IDENT).getText(); 499 isUsedVariableDeclarationFound = variableName.equals(currentVariableName); 500 break; 501 502 case TokenTypes.SEMI: 503 break; 504 505 default: 506 result = false; 507 } 508 509 currentSiblingAst = currentSiblingAst.getPreviousSibling(); 510 } 511 512 return result; 513 } 514 515 /** 516 * Calculates distance between declaration of variable and its first usage 517 * in single scope. 518 * 519 * @param semicolonAst 520 * Regular node of Ast which is checked for content of checking 521 * variable. 522 * @param variableIdentAst 523 * Variable which distance is calculated for. 524 * @return entry which contains expression with variable usage and distance. 525 * If variable usage is not found, then the expression node is null, 526 * although the distance can be greater than zero. 527 */ 528 private static Entry<DetailAST, Integer> calculateDistanceInSingleScope( 529 DetailAST semicolonAst, DetailAST variableIdentAst) { 530 int dist = 0; 531 boolean firstUsageFound = false; 532 DetailAST currentAst = semicolonAst; 533 DetailAST variableUsageAst = null; 534 535 while (!firstUsageFound && currentAst != null) { 536 if (currentAst.getFirstChild() != null) { 537 if (isChild(currentAst, variableIdentAst)) { 538 dist = getDistToVariableUsageInChildNode(currentAst, variableIdentAst, dist); 539 variableUsageAst = currentAst; 540 firstUsageFound = true; 541 } 542 else if (currentAst.getType() != TokenTypes.VARIABLE_DEF) { 543 dist++; 544 } 545 } 546 currentAst = currentAst.getNextSibling(); 547 } 548 549 return new SimpleEntry<>(variableUsageAst, dist); 550 } 551 552 /** 553 * Returns the distance to variable usage for in the child node. 554 * 555 * @param childNode child node. 556 * @param varIdent variable variable identifier. 557 * @param currentDistToVarUsage current distance to the variable usage. 558 * @return the distance to variable usage for in the child node. 559 */ 560 private static int getDistToVariableUsageInChildNode(DetailAST childNode, DetailAST varIdent, 561 int currentDistToVarUsage) { 562 DetailAST examineNode = childNode; 563 if (examineNode.getType() == TokenTypes.LABELED_STAT) { 564 examineNode = examineNode.getFirstChild().getNextSibling(); 565 } 566 567 int resultDist = currentDistToVarUsage; 568 switch (examineNode.getType()) { 569 case TokenTypes.VARIABLE_DEF: 570 resultDist++; 571 break; 572 case TokenTypes.SLIST: 573 resultDist = 0; 574 break; 575 case TokenTypes.LITERAL_FOR: 576 case TokenTypes.LITERAL_WHILE: 577 case TokenTypes.LITERAL_DO: 578 case TokenTypes.LITERAL_IF: 579 case TokenTypes.LITERAL_SWITCH: 580 if (isVariableInOperatorExpr(examineNode, varIdent)) { 581 resultDist++; 582 } 583 else { 584 // variable usage is in inner scope 585 // reset counters, because we can't determine distance 586 resultDist = 0; 587 } 588 break; 589 default: 590 if (examineNode.findFirstToken(TokenTypes.SLIST) == null) { 591 resultDist++; 592 } 593 else { 594 resultDist = 0; 595 } 596 } 597 return resultDist; 598 } 599 600 /** 601 * Calculates distance between declaration of variable and its first usage 602 * in multiple scopes. 603 * 604 * @param ast 605 * Regular node of Ast which is checked for content of checking 606 * variable. 607 * @param variable 608 * Variable which distance is calculated for. 609 * @return entry which contains expression with variable usage and distance. 610 */ 611 private static Entry<DetailAST, Integer> calculateDistanceBetweenScopes( 612 DetailAST ast, DetailAST variable) { 613 int dist = 0; 614 DetailAST currentScopeAst = ast; 615 DetailAST variableUsageAst = null; 616 while (currentScopeAst != null) { 617 final Entry<List<DetailAST>, Integer> searchResult = 618 searchVariableUsageExpressions(variable, currentScopeAst); 619 620 currentScopeAst = null; 621 622 final List<DetailAST> variableUsageExpressions = searchResult.getKey(); 623 dist += searchResult.getValue(); 624 625 // If variable usage exists in a single scope, then look into 626 // this scope and count distance until variable usage. 627 if (variableUsageExpressions.size() == 1) { 628 final DetailAST blockWithVariableUsage = variableUsageExpressions 629 .get(0); 630 DetailAST exprWithVariableUsage = null; 631 switch (blockWithVariableUsage.getType()) { 632 case TokenTypes.VARIABLE_DEF: 633 case TokenTypes.EXPR: 634 dist++; 635 break; 636 case TokenTypes.LITERAL_FOR: 637 case TokenTypes.LITERAL_WHILE: 638 case TokenTypes.LITERAL_DO: 639 exprWithVariableUsage = getFirstNodeInsideForWhileDoWhileBlocks( 640 blockWithVariableUsage, variable); 641 break; 642 case TokenTypes.LITERAL_IF: 643 exprWithVariableUsage = getFirstNodeInsideIfBlock( 644 blockWithVariableUsage, variable); 645 break; 646 case TokenTypes.LITERAL_SWITCH: 647 exprWithVariableUsage = getFirstNodeInsideSwitchBlock( 648 blockWithVariableUsage, variable); 649 break; 650 case TokenTypes.LITERAL_TRY: 651 exprWithVariableUsage = 652 getFirstNodeInsideTryCatchFinallyBlocks(blockWithVariableUsage, 653 variable); 654 break; 655 default: 656 exprWithVariableUsage = blockWithVariableUsage.getFirstChild(); 657 } 658 currentScopeAst = exprWithVariableUsage; 659 variableUsageAst = 660 Objects.requireNonNullElse(exprWithVariableUsage, blockWithVariableUsage); 661 } 662 663 // If there's no any variable usage, then distance = 0. 664 else if (variableUsageExpressions.isEmpty()) { 665 variableUsageAst = null; 666 } 667 // If variable usage exists in different scopes, then distance = 668 // distance until variable first usage. 669 else { 670 dist++; 671 variableUsageAst = variableUsageExpressions.get(0); 672 } 673 } 674 return new SimpleEntry<>(variableUsageAst, dist); 675 } 676 677 /** 678 * Searches variable usages starting from specified statement. 679 * 680 * @param variableAst Variable that is used. 681 * @param statementAst DetailAST to start searching from. 682 * @return entry which contains list with found expressions that use the variable 683 * and distance from specified statement to first found expression. 684 */ 685 private static Entry<List<DetailAST>, Integer> 686 searchVariableUsageExpressions(final DetailAST variableAst, final DetailAST statementAst) { 687 final List<DetailAST> variableUsageExpressions = new ArrayList<>(); 688 int distance = 0; 689 DetailAST currentStatementAst = statementAst; 690 while (currentStatementAst != null) { 691 if (currentStatementAst.getFirstChild() != null) { 692 if (isChild(currentStatementAst, variableAst)) { 693 variableUsageExpressions.add(currentStatementAst); 694 } 695 // If expression doesn't contain variable and this variable 696 // hasn't been met yet, then distance + 1. 697 else if (variableUsageExpressions.isEmpty() 698 && currentStatementAst.getType() != TokenTypes.VARIABLE_DEF) { 699 distance++; 700 } 701 } 702 currentStatementAst = currentStatementAst.getNextSibling(); 703 } 704 return new SimpleEntry<>(variableUsageExpressions, distance); 705 } 706 707 /** 708 * Gets first Ast node inside FOR, WHILE or DO-WHILE blocks if variable 709 * usage is met only inside the block (not in its declaration!). 710 * 711 * @param block 712 * Ast node represents FOR, WHILE or DO-WHILE block. 713 * @param variable 714 * Variable which is checked for content in block. 715 * @return If variable usage is met only inside the block 716 * (not in its declaration!) then return the first Ast node 717 * of this block, otherwise - null. 718 */ 719 private static DetailAST getFirstNodeInsideForWhileDoWhileBlocks( 720 DetailAST block, DetailAST variable) { 721 DetailAST firstNodeInsideBlock = null; 722 723 if (!isVariableInOperatorExpr(block, variable)) { 724 final DetailAST currentNode; 725 726 // Find currentNode for DO-WHILE block. 727 if (block.getType() == TokenTypes.LITERAL_DO) { 728 currentNode = block.getFirstChild(); 729 } 730 // Find currentNode for FOR or WHILE block. 731 else { 732 // Looking for RPAREN ( ')' ) token to mark the end of operator 733 // expression. 734 currentNode = block.findFirstToken(TokenTypes.RPAREN).getNextSibling(); 735 } 736 737 final int currentNodeType = currentNode.getType(); 738 739 if (currentNodeType != TokenTypes.EXPR) { 740 firstNodeInsideBlock = currentNode; 741 } 742 } 743 744 return firstNodeInsideBlock; 745 } 746 747 /** 748 * Gets first Ast node inside IF block if variable usage is met 749 * only inside the block (not in its declaration!). 750 * 751 * @param block 752 * Ast node represents IF block. 753 * @param variable 754 * Variable which is checked for content in block. 755 * @return If variable usage is met only inside the block 756 * (not in its declaration!) then return the first Ast node 757 * of this block, otherwise - null. 758 */ 759 private static DetailAST getFirstNodeInsideIfBlock( 760 DetailAST block, DetailAST variable) { 761 DetailAST firstNodeInsideBlock = null; 762 763 if (!isVariableInOperatorExpr(block, variable)) { 764 final Optional<DetailAST> slistToken = TokenUtil 765 .findFirstTokenByPredicate(block, token -> token.getType() == TokenTypes.SLIST); 766 final DetailAST lastNode = block.getLastChild(); 767 DetailAST previousNode = lastNode.getPreviousSibling(); 768 769 if (slistToken.isEmpty() 770 && lastNode.getType() == TokenTypes.LITERAL_ELSE) { 771 772 // Is if statement without '{}' and has a following else branch, 773 // then change previousNode to the if statement body. 774 previousNode = previousNode.getPreviousSibling(); 775 } 776 777 final List<DetailAST> variableUsageExpressions = new ArrayList<>(); 778 if (isChild(previousNode, variable)) { 779 variableUsageExpressions.add(previousNode); 780 } 781 782 if (isChild(lastNode, variable)) { 783 variableUsageExpressions.add(lastNode); 784 } 785 786 // If variable usage exists in several related blocks, then 787 // firstNodeInsideBlock = null, otherwise if variable usage exists 788 // only inside one block, then get node from 789 // variableUsageExpressions. 790 if (variableUsageExpressions.size() == 1) { 791 firstNodeInsideBlock = variableUsageExpressions.get(0); 792 } 793 } 794 795 return firstNodeInsideBlock; 796 } 797 798 /** 799 * Gets first Ast node inside SWITCH block if variable usage is met 800 * only inside the block (not in its declaration!). 801 * 802 * @param block 803 * Ast node represents SWITCH block. 804 * @param variable 805 * Variable which is checked for content in block. 806 * @return If variable usage is met only inside the block 807 * (not in its declaration!) then return the first Ast node 808 * of this block, otherwise - null. 809 */ 810 private static DetailAST getFirstNodeInsideSwitchBlock( 811 DetailAST block, DetailAST variable) { 812 final List<DetailAST> variableUsageExpressions = 813 getVariableUsageExpressionsInsideSwitchBlock(block, variable); 814 815 // If variable usage exists in several related blocks, then 816 // firstNodeInsideBlock = null, otherwise if variable usage exists 817 // only inside one block, then get node from 818 // variableUsageExpressions. 819 DetailAST firstNodeInsideBlock = null; 820 if (variableUsageExpressions.size() == 1) { 821 firstNodeInsideBlock = variableUsageExpressions.get(0); 822 } 823 824 return firstNodeInsideBlock; 825 } 826 827 /** 828 * Helper method for getFirstNodeInsideSwitchBlock to return all variable 829 * usage expressions inside a given switch block. 830 * 831 * @param block the switch block to check. 832 * @param variable variable which is checked for in switch block. 833 * @return List of usages or empty list if none are found. 834 */ 835 private static List<DetailAST> getVariableUsageExpressionsInsideSwitchBlock(DetailAST block, 836 DetailAST variable) { 837 final Optional<DetailAST> firstToken = TokenUtil.findFirstTokenByPredicate(block, child -> { 838 return child.getType() == TokenTypes.SWITCH_RULE 839 || child.getType() == TokenTypes.CASE_GROUP; 840 }); 841 842 final List<DetailAST> variableUsageExpressions = new ArrayList<>(); 843 844 firstToken.ifPresent(token -> { 845 TokenUtil.forEachChild(block, token.getType(), child -> { 846 final DetailAST lastNodeInCaseGroup = child.getLastChild(); 847 if (isChild(lastNodeInCaseGroup, variable)) { 848 variableUsageExpressions.add(lastNodeInCaseGroup); 849 } 850 }); 851 }); 852 853 return variableUsageExpressions; 854 } 855 856 /** 857 * Gets first Ast node inside TRY-CATCH-FINALLY blocks if variable usage is 858 * met only inside the block (not in its declaration!). 859 * 860 * @param block 861 * Ast node represents TRY-CATCH-FINALLY block. 862 * @param variable 863 * Variable which is checked for content in block. 864 * @return If variable usage is met only inside the block 865 * (not in its declaration!) then return the first Ast node 866 * of this block, otherwise - null. 867 */ 868 private static DetailAST getFirstNodeInsideTryCatchFinallyBlocks( 869 DetailAST block, DetailAST variable) { 870 DetailAST currentNode = block.getFirstChild(); 871 final List<DetailAST> variableUsageExpressions = 872 new ArrayList<>(); 873 874 // Checking variable usage inside TRY block. 875 if (isChild(currentNode, variable)) { 876 variableUsageExpressions.add(currentNode); 877 } 878 879 // Switch on CATCH block. 880 currentNode = currentNode.getNextSibling(); 881 882 // Checking variable usage inside all CATCH blocks. 883 while (currentNode != null 884 && currentNode.getType() == TokenTypes.LITERAL_CATCH) { 885 final DetailAST catchBlock = currentNode.getLastChild(); 886 887 if (isChild(catchBlock, variable)) { 888 variableUsageExpressions.add(catchBlock); 889 } 890 currentNode = currentNode.getNextSibling(); 891 } 892 893 // Checking variable usage inside FINALLY block. 894 if (currentNode != null) { 895 final DetailAST finalBlock = currentNode.getLastChild(); 896 897 if (isChild(finalBlock, variable)) { 898 variableUsageExpressions.add(finalBlock); 899 } 900 } 901 902 DetailAST variableUsageNode = null; 903 904 // If variable usage exists in several related blocks, then 905 // firstNodeInsideBlock = null, otherwise if variable usage exists 906 // only inside one block, then get node from 907 // variableUsageExpressions. 908 if (variableUsageExpressions.size() == 1) { 909 variableUsageNode = variableUsageExpressions.get(0).getFirstChild(); 910 } 911 912 return variableUsageNode; 913 } 914 915 /** 916 * Checks if variable is in operator declaration. For instance: 917 * <pre> 918 * boolean b = true; 919 * if (b) {...} 920 * </pre> 921 * Variable 'b' is in declaration of operator IF. 922 * 923 * @param operator 924 * Ast node which represents operator. 925 * @param variable 926 * Variable which is checked for content in operator. 927 * @return true if operator contains variable in its declaration, otherwise 928 * - false. 929 */ 930 private static boolean isVariableInOperatorExpr( 931 DetailAST operator, DetailAST variable) { 932 boolean isVarInOperatorDeclaration = false; 933 final DetailAST openingBracket = 934 operator.findFirstToken(TokenTypes.LPAREN); 935 936 // Get EXPR between brackets 937 DetailAST exprBetweenBrackets = openingBracket.getNextSibling(); 938 939 // Look if variable is in operator expression 940 while (exprBetweenBrackets.getType() != TokenTypes.RPAREN) { 941 if (isChild(exprBetweenBrackets, variable)) { 942 isVarInOperatorDeclaration = true; 943 break; 944 } 945 exprBetweenBrackets = exprBetweenBrackets.getNextSibling(); 946 } 947 948 // Variable may be met in ELSE declaration 949 // So, check variable usage in these declarations. 950 if (!isVarInOperatorDeclaration) { 951 final DetailAST elseBlock = operator.getLastChild(); 952 953 if (elseBlock.getType() == TokenTypes.LITERAL_ELSE) { 954 // Get IF followed by ELSE 955 final DetailAST firstNodeInsideElseBlock = elseBlock.getFirstChild(); 956 957 if (firstNodeInsideElseBlock.getType() == TokenTypes.LITERAL_IF) { 958 isVarInOperatorDeclaration = 959 isVariableInOperatorExpr(firstNodeInsideElseBlock, variable); 960 } 961 } 962 } 963 964 return isVarInOperatorDeclaration; 965 } 966 967 /** 968 * Checks if Ast node contains given element. 969 * 970 * @param parent 971 * Node of AST. 972 * @param ast 973 * Ast element which is checked for content in Ast node. 974 * @return true if Ast element was found in Ast node, otherwise - false. 975 */ 976 private static boolean isChild(DetailAST parent, DetailAST ast) { 977 boolean isChild = false; 978 DetailAST curNode = parent.getFirstChild(); 979 980 while (curNode != null) { 981 if (curNode.getType() == ast.getType() && curNode.getText().equals(ast.getText())) { 982 isChild = true; 983 break; 984 } 985 986 DetailAST toVisit = curNode.getFirstChild(); 987 while (toVisit == null) { 988 toVisit = curNode.getNextSibling(); 989 curNode = curNode.getParent(); 990 991 if (curNode == parent) { 992 break; 993 } 994 } 995 996 curNode = toVisit; 997 } 998 999 return isChild; 1000 } 1001 1002 /** 1003 * Checks if entrance variable is contained in ignored pattern. 1004 * 1005 * @param variable 1006 * Variable which is checked for content in ignored pattern. 1007 * @return true if variable was found, otherwise - false. 1008 */ 1009 private boolean isVariableMatchesIgnorePattern(String variable) { 1010 final Matcher matcher = ignoreVariablePattern.matcher(variable); 1011 return matcher.matches(); 1012 } 1013 1014}