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