001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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.BitSet; 024import java.util.Deque; 025import java.util.HashMap; 026import java.util.Iterator; 027import java.util.Map; 028import java.util.Optional; 029 030import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 031import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 032import com.puppycrawl.tools.checkstyle.api.DetailAST; 033import com.puppycrawl.tools.checkstyle.api.TokenTypes; 034import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 035import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 036import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 037 038/** 039 * <p> 040 * Checks that local variables that never have their values changed are declared final. 041 * The check can be configured to also check that unchanged parameters are declared final. 042 * </p> 043 * <p> 044 * When configured to check parameters, the check ignores parameters of interface 045 * methods and abstract methods. 046 * </p> 047 * <ul> 048 * <li> 049 * Property {@code validateEnhancedForLoopVariable} - Control whether to check 050 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2"> 051 * enhanced for-loop</a> variable. 052 * Type is {@code boolean}. 053 * Default value is {@code false}. 054 * </li> 055 * <li> 056 * Property {@code validateUnnamedVariables} - Control whether to check 057 * <a href="https://docs.oracle.com/javase/specs/jls/se21/preview/specs/unnamed-jls.html"> 058 * unnamed variables</a>. 059 * Type is {@code boolean}. 060 * Default value is {@code false}. 061 * </li> 062 * <li> 063 * Property {@code tokens} - tokens to check 064 * Type is {@code java.lang.String[]}. 065 * Validation type is {@code tokenSet}. 066 * Default value is: 067 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 068 * VARIABLE_DEF</a>. 069 * </li> 070 * </ul> 071 * <p> 072 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 073 * </p> 074 * <p> 075 * Violation Message Keys: 076 * </p> 077 * <ul> 078 * <li> 079 * {@code final.variable} 080 * </li> 081 * </ul> 082 * 083 * @since 3.2 084 */ 085@FileStatefulCheck 086public class FinalLocalVariableCheck extends AbstractCheck { 087 088 /** 089 * A key is pointing to the warning message text in "messages.properties" 090 * file. 091 */ 092 public static final String MSG_KEY = "final.variable"; 093 094 /** 095 * Assign operator types. 096 */ 097 private static final BitSet ASSIGN_OPERATOR_TYPES = TokenUtil.asBitSet( 098 TokenTypes.POST_INC, 099 TokenTypes.POST_DEC, 100 TokenTypes.ASSIGN, 101 TokenTypes.PLUS_ASSIGN, 102 TokenTypes.MINUS_ASSIGN, 103 TokenTypes.STAR_ASSIGN, 104 TokenTypes.DIV_ASSIGN, 105 TokenTypes.MOD_ASSIGN, 106 TokenTypes.SR_ASSIGN, 107 TokenTypes.BSR_ASSIGN, 108 TokenTypes.SL_ASSIGN, 109 TokenTypes.BAND_ASSIGN, 110 TokenTypes.BXOR_ASSIGN, 111 TokenTypes.BOR_ASSIGN, 112 TokenTypes.INC, 113 TokenTypes.DEC 114 ); 115 116 /** 117 * Loop types. 118 */ 119 private static final BitSet LOOP_TYPES = TokenUtil.asBitSet( 120 TokenTypes.LITERAL_FOR, 121 TokenTypes.LITERAL_WHILE, 122 TokenTypes.LITERAL_DO 123 ); 124 125 /** Scope Deque. */ 126 private final Deque<ScopeData> scopeStack = new ArrayDeque<>(); 127 128 /** Assigned variables of current scope. */ 129 private final Deque<Deque<DetailAST>> currentScopeAssignedVariables = 130 new ArrayDeque<>(); 131 132 /** 133 * Control whether to check 134 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2"> 135 * enhanced for-loop</a> variable. 136 */ 137 private boolean validateEnhancedForLoopVariable; 138 139 /** 140 * Control whether to check 141 * <a href="https://docs.oracle.com/javase/specs/jls/se21/preview/specs/unnamed-jls.html"> 142 * unnamed variables</a>. 143 */ 144 private boolean validateUnnamedVariables; 145 146 /** 147 * Setter to control whether to check 148 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2"> 149 * enhanced for-loop</a> variable. 150 * 151 * @param validateEnhancedForLoopVariable whether to check for-loop variable 152 * @since 6.5 153 */ 154 public final void setValidateEnhancedForLoopVariable(boolean validateEnhancedForLoopVariable) { 155 this.validateEnhancedForLoopVariable = validateEnhancedForLoopVariable; 156 } 157 158 /** 159 * Setter to control whether to check 160 * <a href="https://docs.oracle.com/javase/specs/jls/se21/preview/specs/unnamed-jls.html"> 161 * unnamed variables</a>. 162 * 163 * @param validateUnnamedVariables whether to check unnamed variables 164 * @since 10.18.0 165 */ 166 public final void setValidateUnnamedVariables(boolean validateUnnamedVariables) { 167 this.validateUnnamedVariables = validateUnnamedVariables; 168 } 169 170 @Override 171 public int[] getRequiredTokens() { 172 return new int[] { 173 TokenTypes.IDENT, 174 TokenTypes.CTOR_DEF, 175 TokenTypes.METHOD_DEF, 176 TokenTypes.SLIST, 177 TokenTypes.OBJBLOCK, 178 TokenTypes.LITERAL_BREAK, 179 TokenTypes.LITERAL_FOR, 180 TokenTypes.EXPR, 181 }; 182 } 183 184 @Override 185 public int[] getDefaultTokens() { 186 return new int[] { 187 TokenTypes.IDENT, 188 TokenTypes.CTOR_DEF, 189 TokenTypes.METHOD_DEF, 190 TokenTypes.SLIST, 191 TokenTypes.OBJBLOCK, 192 TokenTypes.LITERAL_BREAK, 193 TokenTypes.LITERAL_FOR, 194 TokenTypes.VARIABLE_DEF, 195 TokenTypes.EXPR, 196 }; 197 } 198 199 @Override 200 public int[] getAcceptableTokens() { 201 return new int[] { 202 TokenTypes.IDENT, 203 TokenTypes.CTOR_DEF, 204 TokenTypes.METHOD_DEF, 205 TokenTypes.SLIST, 206 TokenTypes.OBJBLOCK, 207 TokenTypes.LITERAL_BREAK, 208 TokenTypes.LITERAL_FOR, 209 TokenTypes.VARIABLE_DEF, 210 TokenTypes.PARAMETER_DEF, 211 TokenTypes.EXPR, 212 }; 213 } 214 215 // -@cs[CyclomaticComplexity] The only optimization which can be done here is moving CASE-block 216 // expressions to separate methods, but that will not increase readability. 217 @Override 218 public void visitToken(DetailAST ast) { 219 switch (ast.getType()) { 220 case TokenTypes.OBJBLOCK: 221 case TokenTypes.METHOD_DEF: 222 case TokenTypes.CTOR_DEF: 223 case TokenTypes.LITERAL_FOR: 224 scopeStack.push(new ScopeData()); 225 break; 226 case TokenTypes.SLIST: 227 currentScopeAssignedVariables.push(new ArrayDeque<>()); 228 if (ast.getParent().getType() != TokenTypes.CASE_GROUP 229 || ast.getParent().getParent().findFirstToken(TokenTypes.CASE_GROUP) 230 == ast.getParent()) { 231 storePrevScopeUninitializedVariableData(); 232 scopeStack.push(new ScopeData()); 233 } 234 break; 235 case TokenTypes.PARAMETER_DEF: 236 if (!isInLambda(ast) 237 && ast.findFirstToken(TokenTypes.MODIFIERS) 238 .findFirstToken(TokenTypes.FINAL) == null 239 && !isInAbstractOrNativeMethod(ast) 240 && !ScopeUtil.isInInterfaceBlock(ast) 241 && !isMultipleTypeCatch(ast) 242 && !CheckUtil.isReceiverParameter(ast)) { 243 insertParameter(ast); 244 } 245 break; 246 case TokenTypes.VARIABLE_DEF: 247 if (ast.getParent().getType() != TokenTypes.OBJBLOCK 248 && ast.findFirstToken(TokenTypes.MODIFIERS) 249 .findFirstToken(TokenTypes.FINAL) == null 250 && !isVariableInForInit(ast) 251 && shouldCheckEnhancedForLoopVariable(ast) 252 && shouldCheckUnnamedVariable(ast)) { 253 insertVariable(ast); 254 } 255 break; 256 case TokenTypes.IDENT: 257 final int parentType = ast.getParent().getType(); 258 if (isAssignOperator(parentType) && isFirstChild(ast)) { 259 final Optional<FinalVariableCandidate> candidate = getFinalCandidate(ast); 260 if (candidate.isPresent()) { 261 determineAssignmentConditions(ast, candidate.orElseThrow()); 262 currentScopeAssignedVariables.peek().add(ast); 263 } 264 removeFinalVariableCandidateFromStack(ast); 265 } 266 break; 267 case TokenTypes.LITERAL_BREAK: 268 scopeStack.peek().containsBreak = true; 269 break; 270 case TokenTypes.EXPR: 271 // Switch labeled expression has no slist 272 if (ast.getParent().getType() == TokenTypes.SWITCH_RULE) { 273 storePrevScopeUninitializedVariableData(); 274 } 275 break; 276 default: 277 throw new IllegalStateException("Incorrect token type"); 278 } 279 } 280 281 @Override 282 public void leaveToken(DetailAST ast) { 283 Map<String, FinalVariableCandidate> scope = null; 284 final DetailAST parentAst = ast.getParent(); 285 switch (ast.getType()) { 286 case TokenTypes.OBJBLOCK: 287 case TokenTypes.CTOR_DEF: 288 case TokenTypes.METHOD_DEF: 289 case TokenTypes.LITERAL_FOR: 290 scope = scopeStack.pop().scope; 291 break; 292 case TokenTypes.EXPR: 293 // Switch labeled expression has no slist 294 if (parentAst.getType() == TokenTypes.SWITCH_RULE 295 && shouldUpdateUninitializedVariables(parentAst)) { 296 updateAllUninitializedVariables(); 297 } 298 break; 299 case TokenTypes.SLIST: 300 boolean containsBreak = false; 301 if (parentAst.getType() != TokenTypes.CASE_GROUP 302 || findLastCaseGroupWhichContainsSlist(parentAst.getParent()) == parentAst) { 303 containsBreak = scopeStack.peek().containsBreak; 304 scope = scopeStack.pop().scope; 305 } 306 if (containsBreak || shouldUpdateUninitializedVariables(parentAst)) { 307 updateAllUninitializedVariables(); 308 } 309 updateCurrentScopeAssignedVariables(); 310 break; 311 default: 312 // do nothing 313 } 314 if (scope != null) { 315 for (FinalVariableCandidate candidate : scope.values()) { 316 final DetailAST ident = candidate.variableIdent; 317 log(ident, MSG_KEY, ident.getText()); 318 } 319 } 320 } 321 322 /** 323 * Update assigned variables in a temporary stack. 324 */ 325 private void updateCurrentScopeAssignedVariables() { 326 // -@cs[MoveVariableInsideIf] assignment value is a modification call, so it can't be moved 327 final Deque<DetailAST> poppedScopeAssignedVariableData = 328 currentScopeAssignedVariables.pop(); 329 final Deque<DetailAST> currentScopeAssignedVariableData = 330 currentScopeAssignedVariables.peek(); 331 if (currentScopeAssignedVariableData != null) { 332 currentScopeAssignedVariableData.addAll(poppedScopeAssignedVariableData); 333 } 334 } 335 336 /** 337 * Determines identifier assignment conditions (assigned or already assigned). 338 * 339 * @param ident identifier. 340 * @param candidate final local variable candidate. 341 */ 342 private static void determineAssignmentConditions(DetailAST ident, 343 FinalVariableCandidate candidate) { 344 if (candidate.assigned) { 345 final int[] blockTypes = { 346 TokenTypes.LITERAL_ELSE, 347 TokenTypes.CASE_GROUP, 348 TokenTypes.SWITCH_RULE, 349 }; 350 if (!isInSpecificCodeBlocks(ident, blockTypes)) { 351 candidate.alreadyAssigned = true; 352 } 353 } 354 else { 355 candidate.assigned = true; 356 } 357 } 358 359 /** 360 * Checks whether the scope of a node is restricted to a specific code blocks. 361 * 362 * @param node node. 363 * @param blockTypes int array of all block types to check. 364 * @return true if the scope of a node is restricted to specific code block types. 365 */ 366 private static boolean isInSpecificCodeBlocks(DetailAST node, int... blockTypes) { 367 boolean returnValue = false; 368 for (int blockType : blockTypes) { 369 for (DetailAST token = node; token != null; token = token.getParent()) { 370 final int type = token.getType(); 371 if (type == blockType) { 372 returnValue = true; 373 break; 374 } 375 } 376 } 377 return returnValue; 378 } 379 380 /** 381 * Gets final variable candidate for ast. 382 * 383 * @param ast ast. 384 * @return Optional of {@link FinalVariableCandidate} for ast from scopeStack. 385 */ 386 private Optional<FinalVariableCandidate> getFinalCandidate(DetailAST ast) { 387 Optional<FinalVariableCandidate> result = Optional.empty(); 388 final Iterator<ScopeData> iterator = scopeStack.descendingIterator(); 389 while (iterator.hasNext() && result.isEmpty()) { 390 final ScopeData scopeData = iterator.next(); 391 result = scopeData.findFinalVariableCandidateForAst(ast); 392 } 393 return result; 394 } 395 396 /** 397 * Store un-initialized variables in a temporary stack for future use. 398 */ 399 private void storePrevScopeUninitializedVariableData() { 400 final ScopeData scopeData = scopeStack.peek(); 401 final Deque<DetailAST> prevScopeUninitializedVariableData = 402 new ArrayDeque<>(); 403 scopeData.uninitializedVariables.forEach(prevScopeUninitializedVariableData::push); 404 scopeData.prevScopeUninitializedVariables = prevScopeUninitializedVariableData; 405 } 406 407 /** 408 * Update current scope data uninitialized variable according to the whole scope data. 409 */ 410 private void updateAllUninitializedVariables() { 411 final boolean hasSomeScopes = !currentScopeAssignedVariables.isEmpty(); 412 if (hasSomeScopes) { 413 scopeStack.forEach(scopeData -> { 414 updateUninitializedVariables(scopeData.prevScopeUninitializedVariables); 415 }); 416 } 417 } 418 419 /** 420 * Update current scope data uninitialized variable according to the specific scope data. 421 * 422 * @param scopeUninitializedVariableData variable for specific stack of uninitialized variables 423 */ 424 private void updateUninitializedVariables(Deque<DetailAST> scopeUninitializedVariableData) { 425 final Iterator<DetailAST> iterator = currentScopeAssignedVariables.peek().iterator(); 426 while (iterator.hasNext()) { 427 final DetailAST assignedVariable = iterator.next(); 428 boolean shouldRemove = false; 429 for (DetailAST variable : scopeUninitializedVariableData) { 430 for (ScopeData scopeData : scopeStack) { 431 final FinalVariableCandidate candidate = 432 scopeData.scope.get(variable.getText()); 433 DetailAST storedVariable = null; 434 if (candidate != null) { 435 storedVariable = candidate.variableIdent; 436 } 437 if (storedVariable != null 438 && isSameVariables(assignedVariable, variable)) { 439 scopeData.uninitializedVariables.push(variable); 440 shouldRemove = true; 441 } 442 } 443 } 444 if (shouldRemove) { 445 iterator.remove(); 446 } 447 } 448 } 449 450 /** 451 * If there is an {@code else} following or token is CASE_GROUP or 452 * SWITCH_RULE and there is another {@code case} following, then update the 453 * uninitialized variables. 454 * 455 * @param ast token to be checked 456 * @return true if should be updated, else false 457 */ 458 private static boolean shouldUpdateUninitializedVariables(DetailAST ast) { 459 return ast.getLastChild().getType() == TokenTypes.LITERAL_ELSE 460 || isCaseTokenWithAnotherCaseFollowing(ast); 461 } 462 463 /** 464 * If token is CASE_GROUP or SWITCH_RULE and there is another {@code case} following. 465 * 466 * @param ast token to be checked 467 * @return true if token is CASE_GROUP or SWITCH_RULE and there is another {@code case} 468 * following, else false 469 */ 470 private static boolean isCaseTokenWithAnotherCaseFollowing(DetailAST ast) { 471 boolean result = false; 472 if (ast.getType() == TokenTypes.CASE_GROUP) { 473 result = findLastCaseGroupWhichContainsSlist(ast.getParent()) != ast; 474 } 475 else if (ast.getType() == TokenTypes.SWITCH_RULE) { 476 result = ast.getNextSibling().getType() == TokenTypes.SWITCH_RULE; 477 } 478 return result; 479 } 480 481 /** 482 * Returns the last token of type {@link TokenTypes#CASE_GROUP} which contains 483 * {@link TokenTypes#SLIST}. 484 * 485 * @param literalSwitchAst ast node of type {@link TokenTypes#LITERAL_SWITCH} 486 * @return the matching token, or null if no match 487 */ 488 private static DetailAST findLastCaseGroupWhichContainsSlist(DetailAST literalSwitchAst) { 489 DetailAST returnValue = null; 490 for (DetailAST astIterator = literalSwitchAst.getFirstChild(); astIterator != null; 491 astIterator = astIterator.getNextSibling()) { 492 if (astIterator.findFirstToken(TokenTypes.SLIST) != null) { 493 returnValue = astIterator; 494 } 495 } 496 return returnValue; 497 } 498 499 /** 500 * Determines whether enhanced for-loop variable should be checked or not. 501 * 502 * @param ast The ast to compare. 503 * @return true if enhanced for-loop variable should be checked. 504 */ 505 private boolean shouldCheckEnhancedForLoopVariable(DetailAST ast) { 506 return validateEnhancedForLoopVariable 507 || ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE; 508 } 509 510 /** 511 * Determines whether unnamed variable should be checked or not. 512 * 513 * @param ast The ast to compare. 514 * @return true if unnamed variable should be checked. 515 */ 516 private boolean shouldCheckUnnamedVariable(DetailAST ast) { 517 return validateUnnamedVariables 518 || !"_".equals(ast.findFirstToken(TokenTypes.IDENT).getText()); 519 } 520 521 /** 522 * Insert a parameter at the topmost scope stack. 523 * 524 * @param ast the variable to insert. 525 */ 526 private void insertParameter(DetailAST ast) { 527 final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope; 528 final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT); 529 scope.put(astNode.getText(), new FinalVariableCandidate(astNode)); 530 } 531 532 /** 533 * Insert a variable at the topmost scope stack. 534 * 535 * @param ast the variable to insert. 536 */ 537 private void insertVariable(DetailAST ast) { 538 final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope; 539 final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT); 540 final FinalVariableCandidate candidate = new FinalVariableCandidate(astNode); 541 // for-each variables are implicitly assigned 542 candidate.assigned = ast.getParent().getType() == TokenTypes.FOR_EACH_CLAUSE; 543 scope.put(astNode.getText(), candidate); 544 if (!isInitialized(astNode)) { 545 scopeStack.peek().uninitializedVariables.add(astNode); 546 } 547 } 548 549 /** 550 * Check if VARIABLE_DEF is initialized or not. 551 * 552 * @param ast VARIABLE_DEF to be checked 553 * @return true if initialized 554 */ 555 private static boolean isInitialized(DetailAST ast) { 556 return ast.getParent().getLastChild().getType() == TokenTypes.ASSIGN; 557 } 558 559 /** 560 * Whether the ast is the first child of its parent. 561 * 562 * @param ast the ast to check. 563 * @return true if the ast is the first child of its parent. 564 */ 565 private static boolean isFirstChild(DetailAST ast) { 566 return ast.getPreviousSibling() == null; 567 } 568 569 /** 570 * Removes the final variable candidate from the Stack. 571 * 572 * @param ast variable to remove. 573 */ 574 private void removeFinalVariableCandidateFromStack(DetailAST ast) { 575 final Iterator<ScopeData> iterator = scopeStack.descendingIterator(); 576 while (iterator.hasNext()) { 577 final ScopeData scopeData = iterator.next(); 578 final Map<String, FinalVariableCandidate> scope = scopeData.scope; 579 final FinalVariableCandidate candidate = scope.get(ast.getText()); 580 DetailAST storedVariable = null; 581 if (candidate != null) { 582 storedVariable = candidate.variableIdent; 583 } 584 if (storedVariable != null && isSameVariables(storedVariable, ast)) { 585 if (shouldRemoveFinalVariableCandidate(scopeData, ast)) { 586 scope.remove(ast.getText()); 587 } 588 break; 589 } 590 } 591 } 592 593 /** 594 * Check if given parameter definition is a multiple type catch. 595 * 596 * @param parameterDefAst parameter definition 597 * @return true if it is a multiple type catch, false otherwise 598 */ 599 private static boolean isMultipleTypeCatch(DetailAST parameterDefAst) { 600 final DetailAST typeAst = parameterDefAst.findFirstToken(TokenTypes.TYPE); 601 return typeAst.findFirstToken(TokenTypes.BOR) != null; 602 } 603 604 /** 605 * Whether the final variable candidate should be removed from the list of final local variable 606 * candidates. 607 * 608 * @param scopeData the scope data of the variable. 609 * @param ast the variable ast. 610 * @return true, if the variable should be removed. 611 */ 612 private static boolean shouldRemoveFinalVariableCandidate(ScopeData scopeData, DetailAST ast) { 613 boolean shouldRemove = true; 614 for (DetailAST variable : scopeData.uninitializedVariables) { 615 if (variable.getText().equals(ast.getText())) { 616 // if the variable is declared outside the loop and initialized inside 617 // the loop, then it cannot be declared final, as it can be initialized 618 // more than once in this case 619 final DetailAST currAstLoopAstParent = getParentLoop(ast); 620 final DetailAST currVarLoopAstParent = getParentLoop(variable); 621 if (currAstLoopAstParent == currVarLoopAstParent) { 622 final FinalVariableCandidate candidate = scopeData.scope.get(ast.getText()); 623 shouldRemove = candidate.alreadyAssigned; 624 } 625 scopeData.uninitializedVariables.remove(variable); 626 break; 627 } 628 } 629 return shouldRemove; 630 } 631 632 /** 633 * Get the ast node of type {@link FinalVariableCandidate#LOOP_TYPES} that is the ancestor 634 * of the current ast node, if there is no such node, null is returned. 635 * 636 * @param ast ast node 637 * @return ast node of type {@link FinalVariableCandidate#LOOP_TYPES} that is the ancestor 638 * of the current ast node, null if no such node exists 639 */ 640 private static DetailAST getParentLoop(DetailAST ast) { 641 DetailAST parentLoop = ast; 642 while (parentLoop != null 643 && !isLoopAst(parentLoop.getType())) { 644 parentLoop = parentLoop.getParent(); 645 } 646 return parentLoop; 647 } 648 649 /** 650 * Is Arithmetic operator. 651 * 652 * @param parentType token AST 653 * @return true is token type is in arithmetic operator 654 */ 655 private static boolean isAssignOperator(int parentType) { 656 return ASSIGN_OPERATOR_TYPES.get(parentType); 657 } 658 659 /** 660 * Checks if current variable is defined in 661 * {@link TokenTypes#FOR_INIT for-loop init}, e.g.: 662 * <p> 663 * {@code 664 * for (int i = 0, j = 0; i < j; i++) { . . . } 665 * } 666 * </p> 667 * {@code i, j} are defined in {@link TokenTypes#FOR_INIT for-loop init} 668 * 669 * @param variableDef variable definition node. 670 * @return true if variable is defined in {@link TokenTypes#FOR_INIT for-loop init} 671 */ 672 private static boolean isVariableInForInit(DetailAST variableDef) { 673 return variableDef.getParent().getType() == TokenTypes.FOR_INIT; 674 } 675 676 /** 677 * Determines whether an AST is a descendant of an abstract or native method. 678 * 679 * @param ast the AST to check. 680 * @return true if ast is a descendant of an abstract or native method. 681 */ 682 private static boolean isInAbstractOrNativeMethod(DetailAST ast) { 683 boolean abstractOrNative = false; 684 DetailAST currentAst = ast; 685 while (currentAst != null && !abstractOrNative) { 686 if (currentAst.getType() == TokenTypes.METHOD_DEF) { 687 final DetailAST modifiers = 688 currentAst.findFirstToken(TokenTypes.MODIFIERS); 689 abstractOrNative = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null 690 || modifiers.findFirstToken(TokenTypes.LITERAL_NATIVE) != null; 691 } 692 currentAst = currentAst.getParent(); 693 } 694 return abstractOrNative; 695 } 696 697 /** 698 * Check if current param is lambda's param. 699 * 700 * @param paramDef {@link TokenTypes#PARAMETER_DEF parameter def}. 701 * @return true if current param is lambda's param. 702 */ 703 private static boolean isInLambda(DetailAST paramDef) { 704 return paramDef.getParent().getParent().getType() == TokenTypes.LAMBDA; 705 } 706 707 /** 708 * Find the Class, Constructor, Enum, Method, or Field in which it is defined. 709 * 710 * @param ast Variable for which we want to find the scope in which it is defined 711 * @return ast The Class or Constructor or Method in which it is defined. 712 */ 713 private static DetailAST findFirstUpperNamedBlock(DetailAST ast) { 714 DetailAST astTraverse = ast; 715 while (!TokenUtil.isOfType(astTraverse, TokenTypes.METHOD_DEF, TokenTypes.CLASS_DEF, 716 TokenTypes.ENUM_DEF, TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF) 717 && !ScopeUtil.isClassFieldDef(astTraverse)) { 718 astTraverse = astTraverse.getParent(); 719 } 720 return astTraverse; 721 } 722 723 /** 724 * Check if both the Variables are same. 725 * 726 * @param ast1 Variable to compare 727 * @param ast2 Variable to compare 728 * @return true if both the variables are same, otherwise false 729 */ 730 private static boolean isSameVariables(DetailAST ast1, DetailAST ast2) { 731 final DetailAST classOrMethodOfAst1 = 732 findFirstUpperNamedBlock(ast1); 733 final DetailAST classOrMethodOfAst2 = 734 findFirstUpperNamedBlock(ast2); 735 return classOrMethodOfAst1 == classOrMethodOfAst2 && ast1.getText().equals(ast2.getText()); 736 } 737 738 /** 739 * Checks whether the ast is a loop. 740 * 741 * @param ast the ast to check. 742 * @return true if the ast is a loop. 743 */ 744 private static boolean isLoopAst(int ast) { 745 return LOOP_TYPES.get(ast); 746 } 747 748 /** 749 * Holder for the scope data. 750 */ 751 private static final class ScopeData { 752 753 /** Contains variable definitions. */ 754 private final Map<String, FinalVariableCandidate> scope = new HashMap<>(); 755 756 /** Contains definitions of uninitialized variables. */ 757 private final Deque<DetailAST> uninitializedVariables = new ArrayDeque<>(); 758 759 /** Contains definitions of previous scope uninitialized variables. */ 760 private Deque<DetailAST> prevScopeUninitializedVariables = new ArrayDeque<>(); 761 762 /** Whether there is a {@code break} in the scope. */ 763 private boolean containsBreak; 764 765 /** 766 * Searches for final local variable candidate for ast in the scope. 767 * 768 * @param ast ast. 769 * @return Optional of {@link FinalVariableCandidate}. 770 */ 771 public Optional<FinalVariableCandidate> findFinalVariableCandidateForAst(DetailAST ast) { 772 Optional<FinalVariableCandidate> result = Optional.empty(); 773 DetailAST storedVariable = null; 774 final Optional<FinalVariableCandidate> candidate = 775 Optional.ofNullable(scope.get(ast.getText())); 776 if (candidate.isPresent()) { 777 storedVariable = candidate.orElseThrow().variableIdent; 778 } 779 if (storedVariable != null && isSameVariables(storedVariable, ast)) { 780 result = candidate; 781 } 782 return result; 783 } 784 785 } 786 787 /** Represents information about final local variable candidate. */ 788 private static final class FinalVariableCandidate { 789 790 /** Identifier token. */ 791 private final DetailAST variableIdent; 792 /** Whether the variable is assigned. */ 793 private boolean assigned; 794 /** Whether the variable is already assigned. */ 795 private boolean alreadyAssigned; 796 797 /** 798 * Creates new instance. 799 * 800 * @param variableIdent variable identifier. 801 */ 802 private FinalVariableCandidate(DetailAST variableIdent) { 803 this.variableIdent = variableIdent; 804 } 805 806 } 807 808}