001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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.Optional; 027import java.util.Set; 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 * <div> 040 * Checks the distance between declaration of variable and its first usage. 041 * Note: Any additional variables declared or initialized between the declaration and 042 * the first usage of the said variable are not counted when calculating the distance. 043 * </div> 044 * 045 * @since 5.8 046 */ 047@StatelessCheck 048public class VariableDeclarationUsageDistanceCheck extends AbstractCheck { 049 050 /** 051 * Warning message key. 052 */ 053 public static final String MSG_KEY = "variable.declaration.usage.distance"; 054 055 /** 056 * Warning message key. 057 */ 058 public static final String MSG_KEY_EXT = "variable.declaration.usage.distance.extend"; 059 060 /** 061 * Default value of distance between declaration of variable and its first usage. 062 */ 063 private static final int DEFAULT_DISTANCE = 3; 064 065 /** Tokens that should be ignored when calculating usage distance. */ 066 private static final Set<Integer> ZERO_DISTANCE_TOKENS = Set.of( 067 TokenTypes.VARIABLE_DEF, 068 TokenTypes.TYPE, 069 TokenTypes.MODIFIERS, 070 TokenTypes.RESOURCE, 071 TokenTypes.EXTENDS_CLAUSE, 072 TokenTypes.IMPLEMENTS_CLAUSE, 073 TokenTypes.TYPE_PARAMETERS, 074 TokenTypes.PARAMETERS, 075 TokenTypes.LITERAL_THROWS 076 ); 077 078 /** Tokens of type definitions and compact source file that can contain methods. */ 079 private static final Set<Integer> TYPE_DEFINITION_TOKENS = Set.of( 080 TokenTypes.CLASS_DEF, 081 TokenTypes.INTERFACE_DEF, 082 TokenTypes.ANNOTATION_DEF, 083 TokenTypes.ENUM_DEF, 084 TokenTypes.RECORD_DEF, 085 TokenTypes.COMPACT_COMPILATION_UNIT 086 ); 087 088 /** 089 * Specify the maximum distance between a variable's declaration and its first usage. 090 * Value should be greater than 0. 091 */ 092 private int allowedDistance = DEFAULT_DISTANCE; 093 094 /** 095 * Define RegExp to ignore distance calculation for variables listed in this pattern. 096 */ 097 private Pattern ignoreVariablePattern = Pattern.compile(""); 098 099 /** 100 * Allow to calculate the distance between a variable's declaration and its first usage 101 * across different scopes. 102 */ 103 private boolean validateBetweenScopes; 104 105 /** Allow to ignore variables with a 'final' modifier. */ 106 private boolean ignoreFinal = true; 107 108 /** 109 * Creates a new {@code VariableDeclarationUsageDistanceCheck} instance. 110 */ 111 public VariableDeclarationUsageDistanceCheck() { 112 // no code by default 113 } 114 115 /** 116 * Setter to specify the maximum distance between a variable's declaration and its first usage. 117 * Value should be greater than 0. 118 * 119 * @param allowedDistance 120 * Allowed distance between declaration of variable and its first usage. 121 * @since 5.8 122 */ 123 public void setAllowedDistance(int allowedDistance) { 124 this.allowedDistance = allowedDistance; 125 } 126 127 /** 128 * Setter to define RegExp to ignore distance calculation for variables listed in this pattern. 129 * 130 * @param pattern a pattern. 131 * @since 5.8 132 */ 133 public void setIgnoreVariablePattern(Pattern pattern) { 134 ignoreVariablePattern = pattern; 135 } 136 137 /** 138 * Setter to allow to calculate the distance between a variable's declaration 139 * and its first usage across different scopes. 140 * 141 * @param validateBetweenScopes 142 * Defines if allow to calculate distance between declaration of 143 * variable and its first usage in different scopes or not. 144 * @since 5.8 145 */ 146 public void setValidateBetweenScopes(boolean validateBetweenScopes) { 147 this.validateBetweenScopes = validateBetweenScopes; 148 } 149 150 /** 151 * Setter to allow to ignore variables with a 'final' modifier. 152 * 153 * @param ignoreFinal 154 * Defines if ignore variables with 'final' modifier or not. 155 * @since 5.8 156 */ 157 public void setIgnoreFinal(boolean ignoreFinal) { 158 this.ignoreFinal = ignoreFinal; 159 } 160 161 @Override 162 public int[] getDefaultTokens() { 163 return getRequiredTokens(); 164 } 165 166 @Override 167 public int[] getAcceptableTokens() { 168 return getRequiredTokens(); 169 } 170 171 @Override 172 public int[] getRequiredTokens() { 173 return new int[] {TokenTypes.VARIABLE_DEF}; 174 } 175 176 @Override 177 public void visitToken(DetailAST ast) { 178 final int parentType = ast.getParent().getType(); 179 final DetailAST modifiers = ast.getFirstChild(); 180 181 if (parentType != TokenTypes.OBJBLOCK 182 && (!ignoreFinal || modifiers.findFirstToken(TokenTypes.FINAL) == null)) { 183 final DetailAST variable = ast.findFirstToken(TokenTypes.IDENT); 184 185 if (!isVariableMatchesIgnorePattern(variable.getText())) { 186 final DetailAST semicolonAst = ast.getNextSibling(); 187 final Entry<DetailAST, Integer> entry; 188 if (validateBetweenScopes) { 189 entry = calculateDistanceBetweenScopes(semicolonAst, variable); 190 } 191 else { 192 entry = calculateDistanceInSingleScope(semicolonAst, variable); 193 } 194 final DetailAST variableUsageAst = entry.getKey(); 195 final int dist = entry.getValue(); 196 if (dist > allowedDistance 197 && !isInitializationSequence(variableUsageAst, variable.getText())) { 198 if (ignoreFinal) { 199 log(ast, MSG_KEY_EXT, variable.getText(), dist, allowedDistance); 200 } 201 else { 202 log(ast, MSG_KEY, variable.getText(), dist, allowedDistance); 203 } 204 } 205 } 206 } 207 } 208 209 /** 210 * Get name of instance whose method is called. 211 * 212 * @param methodCallAst 213 * DetailAST of METHOD_CALL. 214 * @return name of instance. 215 */ 216 private static String getInstanceName(DetailAST methodCallAst) { 217 final String methodCallName = 218 FullIdent.createFullIdentBelow(methodCallAst).getText(); 219 final int lastDotIndex = methodCallName.lastIndexOf('.'); 220 String instanceName = ""; 221 if (lastDotIndex != -1) { 222 instanceName = methodCallName.substring(0, lastDotIndex); 223 } 224 return instanceName; 225 } 226 227 /** 228 * Processes statements until usage of variable to detect sequence of initialization methods. 229 * 230 * @param variableUsageAst 231 * DetailAST of expression that uses variable named variableName. 232 * @param variableName 233 * name of considered variable. 234 * @return true if statements between declaration and usage of variable are 235 * initialization methods. 236 */ 237 private static boolean isInitializationSequence( 238 DetailAST variableUsageAst, String variableName) { 239 DetailAST currentAst = variableUsageAst; 240 241 boolean result = true; 242 boolean isUsedVariableDeclarationFound = false; 243 String initInstanceName = ""; 244 while (result && !isUsedVariableDeclarationFound && currentAst != null) { 245 if (currentAst.getType() == TokenTypes.EXPR 246 && currentAst.getFirstChild().getType() == TokenTypes.METHOD_CALL) { 247 final DetailAST methodCallAst = currentAst.getFirstChild(); 248 final String instanceName = getInstanceName(methodCallAst); 249 if (instanceName.isEmpty()) { 250 result = false; 251 } 252 else if (!instanceName.equals(initInstanceName)) { 253 if (initInstanceName.isEmpty()) { 254 initInstanceName = instanceName; 255 } 256 else { 257 result = false; 258 } 259 } 260 261 } 262 else if (currentAst.getType() == TokenTypes.VARIABLE_DEF) { 263 final String currentVariableName = 264 currentAst.findFirstToken(TokenTypes.IDENT).getText(); 265 isUsedVariableDeclarationFound = variableName.equals(currentVariableName); 266 } 267 else { 268 result = Set.of( 269 TokenTypes.SEMI, 270 TokenTypes.LCURLY 271 ).contains(currentAst.getType()); 272 } 273 274 currentAst = getNextNodeToCheck(currentAst); 275 } 276 return result; 277 } 278 279 /** 280 * Returns the next node to check for initialization sequence. 281 * 282 * @param currentAst The current node. 283 * @return The next node to check for initialization sequence. 284 */ 285 private static DetailAST getNextNodeToCheck(DetailAST currentAst) { 286 final DetailAST nextAst; 287 288 if (currentAst.getPreviousSibling() != null) { 289 nextAst = currentAst.getPreviousSibling(); 290 } 291 else { 292 // go up the tree 293 final DetailAST predecessor = getFirstPredecessorOfTypes( 294 Set.of( 295 TokenTypes.SLIST, 296 TokenTypes.OBJBLOCK 297 ), currentAst); 298 299 if (predecessor.getType() == TokenTypes.SLIST) { 300 nextAst = getNextNodeToCheckBetweenScopesSlist(predecessor); 301 } 302 else { 303 nextAst = predecessor.getParent().getPreviousSibling(); 304 } 305 } 306 return nextAst; 307 } 308 309 /** 310 * Returns the next node to check for initialization sequence when the 311 * current node is a direct child of SLIST. 312 * 313 * @param ast The SLIST node which is the parent of the current node. 314 * @return The next node to check for initialization sequence. 315 */ 316 private static DetailAST getNextNodeToCheckBetweenScopesSlist(DetailAST ast) { 317 return switch (ast.getParent().getType()) { 318 case TokenTypes.LITERAL_ELSE, TokenTypes.LITERAL_CATCH, 319 TokenTypes.LITERAL_FINALLY, TokenTypes.CASE_GROUP -> 320 ast.getParent().getParent().getPreviousSibling(); 321 case TokenTypes.LITERAL_TRY, TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, 322 TokenTypes.LITERAL_DO, TokenTypes.LITERAL_IF, 323 TokenTypes.LITERAL_SYNCHRONIZED -> 324 ast.getParent().getPreviousSibling(); 325 case TokenTypes.METHOD_DEF, TokenTypes.INSTANCE_INIT -> 326 getFirstPredecessorOfTypes(TYPE_DEFINITION_TOKENS, ast).getPreviousSibling(); 327 default -> ast.getPreviousSibling(); 328 }; 329 } 330 331 /** 332 * Returns the AST node of the specified types that is the closest predecessor 333 * of the specified node. 334 * 335 * @param types The set of types of the predecessor to search for. 336 * @param ast The AST node for which predecessor is searched. 337 * @return The closest predecessor of one of the specified types; null if no such node exists. 338 */ 339 private static DetailAST getFirstPredecessorOfTypes(Set<Integer> types, DetailAST ast) { 340 DetailAST current = ast; 341 while (!types.contains(current.getType())) { 342 current = current.getParent(); 343 } 344 return current; 345 } 346 347 /** 348 * Calculates distance between declaration of variable and its first usage in single scope. 349 * 350 * @param semicolonAst 351 * Regular node of Ast which is checked for content of checking variable. 352 * @param variableIdentAst 353 * Variable which distance is calculated for. 354 * @return entry which contains expression with variable usage and distance. 355 * If variable usage is not found, then the expression node is null, 356 * although the distance can be greater than zero. 357 */ 358 private static Entry<DetailAST, Integer> calculateDistanceInSingleScope( 359 DetailAST semicolonAst, DetailAST variableIdentAst) { 360 int dist = 0; 361 boolean firstUsageFound = false; 362 DetailAST currentAst = semicolonAst; 363 DetailAST variableUsageAst = null; 364 365 while (!firstUsageFound && currentAst != null) { 366 if (currentAst.getFirstChild() != null) { 367 if (isChild(currentAst, variableIdentAst)) { 368 dist = getDistToVariableUsageInChildNode(currentAst, dist); 369 variableUsageAst = currentAst; 370 firstUsageFound = true; 371 } 372 else if (currentAst.getType() != TokenTypes.VARIABLE_DEF) { 373 dist++; 374 } 375 } 376 currentAst = currentAst.getNextSibling(); 377 } 378 379 return new SimpleEntry<>(variableUsageAst, dist); 380 } 381 382 /** 383 * Returns the distance to variable usage for in the child node. 384 * 385 * @param childNode child node. 386 * @param currentDistToVarUsage current distance to the variable usage. 387 * @return the distance to variable usage for in the child node. 388 */ 389 private static int getDistToVariableUsageInChildNode(DetailAST childNode, 390 int currentDistToVarUsage) { 391 return switch (childNode.getType()) { 392 case TokenTypes.SLIST -> 0; 393 case TokenTypes.LITERAL_FOR, 394 TokenTypes.LITERAL_WHILE, 395 TokenTypes.LITERAL_DO, 396 TokenTypes.LITERAL_IF, 397 TokenTypes.LITERAL_TRY -> currentDistToVarUsage + 1; 398 default -> { 399 if (childNode.findFirstToken(TokenTypes.SLIST) == null) { 400 yield currentDistToVarUsage + 1; 401 } 402 yield 0; 403 } 404 }; 405 } 406 407 /** 408 * Calculates distance between declaration of variable and its first usage in multiple scopes. 409 * 410 * @param ast 411 * Regular node of Ast which is checked for content of checking variable. 412 * @param variable 413 * Variable which distance is calculated for. 414 * @return entry which contains expression with variable usage and distance. 415 */ 416 private static Entry<DetailAST, Integer> calculateDistanceBetweenScopes( 417 DetailAST ast, DetailAST variable) { 418 int dist = 0; 419 DetailAST currentScopeAst = ast; 420 DetailAST variableUsageAst = null; 421 while (currentScopeAst != null) { 422 final Entry<List<DetailAST>, Integer> searchResult = 423 searchVariableUsageExpressions(variable, currentScopeAst); 424 425 currentScopeAst = null; 426 427 final List<DetailAST> variableUsageExpressions = searchResult.getKey(); 428 dist += searchResult.getValue(); 429 430 // If variable usage exists in a single scope, then look into 431 // this scope and count distance until variable usage. 432 if (variableUsageExpressions.size() == 1) { 433 final DetailAST blockWithVariableUsage = variableUsageExpressions.getFirst(); 434 currentScopeAst = switch (blockWithVariableUsage.getType()) { 435 case TokenTypes.VARIABLE_DEF, TokenTypes.EXPR -> { 436 dist++; 437 yield null; 438 } 439 case TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO -> 440 getFirstNodeInsideForWhileDoWhileBlocks(blockWithVariableUsage, variable); 441 case TokenTypes.LITERAL_IF -> 442 getFirstNodeInsideIfBlock(blockWithVariableUsage, variable); 443 case TokenTypes.LITERAL_SWITCH -> 444 getFirstNodeInsideSwitchBlock(blockWithVariableUsage, variable); 445 case TokenTypes.LITERAL_TRY -> 446 getFirstNodeInsideTryCatchFinallyBlocks(blockWithVariableUsage, variable); 447 default -> blockWithVariableUsage.getFirstChild(); 448 }; 449 variableUsageAst = blockWithVariableUsage; 450 } 451 452 // If there's no any variable usage, then distance = 0. 453 else if (variableUsageExpressions.isEmpty()) { 454 variableUsageAst = null; 455 dist = 0; 456 } 457 // If variable usage exists in different scopes, then distance = 458 // distance until variable first usage. 459 else { 460 dist++; 461 variableUsageAst = variableUsageExpressions.getFirst(); 462 } 463 } 464 return new SimpleEntry<>(variableUsageAst, dist); 465 } 466 467 /** 468 * Searches variable usages starting from specified statement. 469 * 470 * @param variableAst Variable that is used. 471 * @param statementAst DetailAST to start searching from. 472 * @return entry which contains list with found expressions that use the variable 473 * and distance from specified statement to first found expression. 474 */ 475 private static Entry<List<DetailAST>, Integer> 476 searchVariableUsageExpressions(final DetailAST variableAst, final DetailAST statementAst) { 477 final List<DetailAST> variableUsageExpressions = new ArrayList<>(); 478 int distance = 0; 479 DetailAST currentStatementAst = statementAst; 480 while (currentStatementAst != null) { 481 if (currentStatementAst.getFirstChild() != null) { 482 if (isChild(currentStatementAst, variableAst)) { 483 variableUsageExpressions.add(currentStatementAst); 484 } 485 // If expression hasn't been met yet, then distance + 1. 486 else if (variableUsageExpressions.isEmpty() 487 && !isZeroDistanceToken(currentStatementAst.getType())) { 488 distance++; 489 } 490 } 491 currentStatementAst = currentStatementAst.getNextSibling(); 492 } 493 return new SimpleEntry<>(variableUsageExpressions, distance); 494 } 495 496 /** 497 * Gets first Ast node inside FOR, WHILE or DO-WHILE blocks if variable 498 * usage is met only inside the block (not in its declaration!). 499 * 500 * @param block 501 * Ast node represents FOR, WHILE or DO-WHILE block. 502 * @param variable 503 * Variable which is checked for content in block. 504 * @return If variable usage is met only inside the block 505 * (not in its declaration!) then return the first Ast node 506 * of this block, otherwise - null. 507 */ 508 private static DetailAST getFirstNodeInsideForWhileDoWhileBlocks( 509 DetailAST block, DetailAST variable) { 510 DetailAST firstNodeInsideBlock = null; 511 512 if (!isVariableInOperatorExpr(block, variable)) { 513 final DetailAST currentNode; 514 515 // Find currentNode for DO-WHILE block. 516 if (block.getType() == TokenTypes.LITERAL_DO) { 517 currentNode = block.getFirstChild(); 518 } 519 // Find currentNode for FOR or WHILE block. 520 else { 521 // Looking for RPAREN ( ')' ) token to mark the end of operator 522 // expression. 523 currentNode = block.findFirstToken(TokenTypes.RPAREN).getNextSibling(); 524 } 525 526 final int currentNodeType = currentNode.getType(); 527 528 if (currentNodeType != TokenTypes.EXPR) { 529 firstNodeInsideBlock = currentNode; 530 } 531 } 532 533 return firstNodeInsideBlock; 534 } 535 536 /** 537 * Gets first Ast node inside IF block if variable usage is met 538 * only inside the block (not in its declaration!). 539 * 540 * @param block 541 * Ast node represents IF block. 542 * @param variable 543 * Variable which is checked for content in block. 544 * @return If variable usage is met only inside the block 545 * (not in its declaration!) then return the first Ast node 546 * of this block, otherwise - null. 547 */ 548 private static DetailAST getFirstNodeInsideIfBlock( 549 DetailAST block, DetailAST variable) { 550 DetailAST firstNodeInsideBlock = null; 551 552 if (!isVariableInOperatorExpr(block, variable)) { 553 final Optional<DetailAST> slistToken = TokenUtil 554 .findFirstTokenByPredicate(block, token -> token.getType() == TokenTypes.SLIST); 555 final DetailAST lastNode = block.getLastChild(); 556 DetailAST previousNode = lastNode.getPreviousSibling(); 557 558 if (slistToken.isEmpty() 559 && lastNode.getType() == TokenTypes.LITERAL_ELSE) { 560 561 // Is if statement without '{}' and has a following else branch, 562 // then change previousNode to the if statement body. 563 previousNode = previousNode.getPreviousSibling(); 564 } 565 566 final List<DetailAST> variableUsageExpressions = new ArrayList<>(); 567 if (isChild(previousNode, variable)) { 568 variableUsageExpressions.add(previousNode); 569 } 570 571 if (isChild(lastNode, variable)) { 572 variableUsageExpressions.add(lastNode); 573 } 574 575 // If variable usage exists in several related blocks, then 576 // firstNodeInsideBlock = null, otherwise if variable usage exists 577 // only inside one block, then get node from 578 // variableUsageExpressions. 579 if (variableUsageExpressions.size() == 1) { 580 firstNodeInsideBlock = variableUsageExpressions.getFirst(); 581 } 582 } 583 584 return firstNodeInsideBlock; 585 } 586 587 /** 588 * Gets first Ast node inside SWITCH block if variable usage is met 589 * only inside the block (not in its declaration!). 590 * 591 * @param block 592 * Ast node represents SWITCH block. 593 * @param variable 594 * Variable which is checked for content in block. 595 * @return If variable usage is met only inside the block 596 * (not in its declaration!) then return the first Ast node 597 * of this block, otherwise - null. 598 */ 599 private static DetailAST getFirstNodeInsideSwitchBlock( 600 DetailAST block, DetailAST variable) { 601 final List<DetailAST> variableUsageExpressions = 602 getVariableUsageExpressionsInsideSwitchBlock(block, variable); 603 604 // If variable usage exists in several related blocks, then 605 // firstNodeInsideBlock = null, otherwise if variable usage exists 606 // only inside one block, then get node from 607 // variableUsageExpressions. 608 DetailAST firstNodeInsideBlock = null; 609 if (variableUsageExpressions.size() == 1) { 610 firstNodeInsideBlock = variableUsageExpressions.getFirst(); 611 } 612 613 return firstNodeInsideBlock; 614 } 615 616 /** 617 * Helper method for getFirstNodeInsideSwitchBlock to return all variable 618 * usage expressions inside a given switch block. 619 * 620 * @param block the switch block to check. 621 * @param variable variable which is checked for in switch block. 622 * @return List of usages or empty list if none are found. 623 */ 624 private static List<DetailAST> getVariableUsageExpressionsInsideSwitchBlock(DetailAST block, 625 DetailAST variable) { 626 final Optional<DetailAST> firstToken = TokenUtil.findFirstTokenByPredicate(block, child -> { 627 return child.getType() == TokenTypes.SWITCH_RULE 628 || child.getType() == TokenTypes.CASE_GROUP; 629 }); 630 631 final List<DetailAST> variableUsageExpressions = new ArrayList<>(); 632 633 firstToken.ifPresent(token -> { 634 TokenUtil.forEachChild(block, token.getType(), child -> { 635 final DetailAST lastNodeInCaseGroup = child.getLastChild(); 636 if (isChild(lastNodeInCaseGroup, variable)) { 637 variableUsageExpressions.add(lastNodeInCaseGroup); 638 } 639 }); 640 }); 641 642 return variableUsageExpressions; 643 } 644 645 /** 646 * Gets first Ast node inside TRY-CATCH-FINALLY blocks if variable usage is 647 * met only inside the block (not in its declaration!). 648 * 649 * @param block 650 * Ast node represents TRY-CATCH-FINALLY block. 651 * @param variable 652 * Variable which is checked for content in block. 653 * @return If variable usage is met only inside the block 654 * (not in its declaration!) then return the first Ast node 655 * of this block, otherwise - null. 656 */ 657 private static DetailAST getFirstNodeInsideTryCatchFinallyBlocks( 658 DetailAST block, DetailAST variable) { 659 DetailAST variableUsageNode = null; 660 661 final DetailAST resourceSpec = block.findFirstToken(TokenTypes.RESOURCE_SPECIFICATION); 662 if (resourceSpec == null || !isVariableInOperatorExpr(resourceSpec, variable)) { 663 DetailAST currentNode = block.getFirstChild(); 664 // Skip resource specification if exists. 665 if (currentNode.getType() == TokenTypes.RESOURCE_SPECIFICATION) { 666 currentNode = currentNode.getNextSibling(); 667 } 668 669 final List<DetailAST> variableUsageExpressions = new ArrayList<>(); 670 // Checking variable usage inside TRY block. 671 if (isChild(currentNode, variable)) { 672 variableUsageExpressions.add(currentNode); 673 } 674 675 // Switch on CATCH block. 676 currentNode = currentNode.getNextSibling(); 677 678 // Checking variable usage inside all CATCH and FINALLY blocks. 679 while (currentNode != null) { 680 final DetailAST catchOrFinallyBlock = currentNode.findFirstToken(TokenTypes.SLIST); 681 682 if (isChild(catchOrFinallyBlock, variable)) { 683 variableUsageExpressions.add(catchOrFinallyBlock); 684 } 685 currentNode = currentNode.getNextSibling(); 686 } 687 688 // If variable usage exists in several related blocks, then 689 // firstNodeInsideBlock = null, otherwise if variable usage exists 690 // only inside one block, then get node from 691 // variableUsageExpressions. 692 if (variableUsageExpressions.size() == 1) { 693 variableUsageNode = variableUsageExpressions.getFirst(); 694 } 695 } 696 697 return variableUsageNode; 698 } 699 700 /** 701 * Checks if variable is in operator declaration. For instance: 702 * {@snippet lang="text" : 703 * boolean b = true; 704 * if (b) {...} 705 * } 706 * Variable 'b' is in declaration of operator IF. 707 * 708 * @param operator 709 * Ast node which represents operator. 710 * @param variable 711 * Variable which is checked for content in operator. 712 * @return true if operator contains variable in its declaration, otherwise - false. 713 */ 714 private static boolean isVariableInOperatorExpr( 715 DetailAST operator, DetailAST variable) { 716 boolean isVarInOperatorDeclaration = false; 717 718 DetailAST ast = operator.findFirstToken(TokenTypes.LPAREN); 719 720 // Look if variable is in operator expression 721 while (ast.getType() != TokenTypes.RPAREN) { 722 if (isChild(ast, variable)) { 723 isVarInOperatorDeclaration = true; 724 break; 725 } 726 ast = ast.getNextSibling(); 727 } 728 729 return isVarInOperatorDeclaration; 730 } 731 732 /** 733 * Checks if Ast node contains given element. 734 * 735 * @param parent 736 * Node of AST. 737 * @param ast 738 * Ast element which is checked for content in Ast node. 739 * @return true if Ast element was found in Ast node, otherwise - false. 740 */ 741 private static boolean isChild(DetailAST parent, DetailAST ast) { 742 boolean isChild = false; 743 DetailAST curNode = parent.getFirstChild(); 744 745 while (curNode != null) { 746 if (curNode.getType() == ast.getType() && curNode.getText().equals(ast.getText())) { 747 isChild = true; 748 break; 749 } 750 751 DetailAST toVisit = curNode.getFirstChild(); 752 while (toVisit == null) { 753 toVisit = curNode.getNextSibling(); 754 curNode = curNode.getParent(); 755 756 if (curNode == parent) { 757 break; 758 } 759 } 760 761 curNode = toVisit; 762 } 763 764 return isChild; 765 } 766 767 /** 768 * Checks if entrance variable is contained in ignored pattern. 769 * 770 * @param variable 771 * Variable which is checked for content in ignored pattern. 772 * @return true if variable was found, otherwise - false. 773 */ 774 private boolean isVariableMatchesIgnorePattern(String variable) { 775 final Matcher matcher = ignoreVariablePattern.matcher(variable); 776 return matcher.matches(); 777 } 778 779 /** 780 * Check if the token should be ignored for distance counting. 781 * For example, 782 * {@snippet lang="text" : 783 * try (final AutoCloseable t = new java.io.StringReader(a);) { 784 * } 785 * } 786 * final is a zero-distance token and should be ignored for distance counting. 787 * {@snippet lang="text" : 788 * class Table implements Comparator<Integer>{ 789 * } 790 * } 791 * An inner class may be defined. Both tokens implements and extends are zero-distance tokens. 792 * {@snippet lang="text" : 793 * public int method(Object b){ 794 * } 795 * } 796 * public is a modifier and zero-distance token. int is a type and zero-distance token. 797 * 798 * @param type 799 * Token type of the ast node. 800 * @return true if it should be ignored for distance counting, otherwise false. 801 */ 802 private static boolean isZeroDistanceToken(int type) { 803 return ZERO_DISTANCE_TOKENS.contains(type); 804 } 805 806}