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.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 * <div> 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 * </div> 043 * 044 * <p> 045 * Notes: 046 * When configured to check parameters, the check ignores parameters of interface 047 * methods and abstract methods. 048 * </p> 049 * 050 * @since 3.2 051 */ 052@FileStatefulCheck 053public class FinalLocalVariableCheck extends AbstractCheck { 054 055 /** 056 * A key is pointing to the warning message text in "messages.properties" 057 * file. 058 */ 059 public static final String MSG_KEY = "final.variable"; 060 061 /** 062 * Assign operator types. 063 */ 064 private static final BitSet ASSIGN_OPERATOR_TYPES = TokenUtil.asBitSet( 065 TokenTypes.POST_INC, 066 TokenTypes.POST_DEC, 067 TokenTypes.ASSIGN, 068 TokenTypes.PLUS_ASSIGN, 069 TokenTypes.MINUS_ASSIGN, 070 TokenTypes.STAR_ASSIGN, 071 TokenTypes.DIV_ASSIGN, 072 TokenTypes.MOD_ASSIGN, 073 TokenTypes.SR_ASSIGN, 074 TokenTypes.BSR_ASSIGN, 075 TokenTypes.SL_ASSIGN, 076 TokenTypes.BAND_ASSIGN, 077 TokenTypes.BXOR_ASSIGN, 078 TokenTypes.BOR_ASSIGN, 079 TokenTypes.INC, 080 TokenTypes.DEC 081 ); 082 083 /** 084 * Loop types. 085 */ 086 private static final BitSet LOOP_TYPES = TokenUtil.asBitSet( 087 TokenTypes.LITERAL_FOR, 088 TokenTypes.LITERAL_WHILE, 089 TokenTypes.LITERAL_DO 090 ); 091 092 /** Scope Deque. */ 093 private final Deque<ScopeData> scopeStack = new ArrayDeque<>(); 094 095 /** Assigned variables of current scope. */ 096 private final Deque<Deque<DetailAST>> currentScopeAssignedVariables = 097 new ArrayDeque<>(); 098 099 /** 100 * Control whether to check 101 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2"> 102 * enhanced for-loop</a> variable. 103 */ 104 private boolean validateEnhancedForLoopVariable; 105 106 /** 107 * Control whether to check 108 * <a href="https://docs.oracle.com/javase/specs/jls/se21/preview/specs/unnamed-jls.html"> 109 * unnamed variables</a>. 110 */ 111 private boolean validateUnnamedVariables; 112 113 /** 114 * Control whether to check 115 * <a href="https://openjdk.org/jeps/394"> 116 * pattern variables</a>. 117 */ 118 private boolean validatePatternVariables; 119 120 /** 121 * Setter to control whether to check 122 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2"> 123 * enhanced for-loop</a> variable. 124 * 125 * @param validateEnhancedForLoopVariable whether to check for-loop variable 126 * @since 6.5 127 */ 128 public final void setValidateEnhancedForLoopVariable(boolean validateEnhancedForLoopVariable) { 129 this.validateEnhancedForLoopVariable = validateEnhancedForLoopVariable; 130 } 131 132 /** 133 * Setter to control whether to check 134 * <a href="https://docs.oracle.com/javase/specs/jls/se21/preview/specs/unnamed-jls.html"> 135 * unnamed variables</a>. 136 * 137 * @param validateUnnamedVariables whether to check unnamed variables 138 * @since 10.18.0 139 */ 140 public final void setValidateUnnamedVariables(boolean validateUnnamedVariables) { 141 this.validateUnnamedVariables = validateUnnamedVariables; 142 } 143 144 /** 145 * Setter to control whether to check 146 * <a href="https://openjdk.org/jeps/394"> 147 * pattern variables></a>. 148 * 149 * @param validatePatternVariables whether to check pattern variables 150 * @since 13.4.0 151 */ 152 public final void setValidatePatternVariables(boolean validatePatternVariables) { 153 this.validatePatternVariables = validatePatternVariables; 154 } 155 156 @Override 157 public int[] getRequiredTokens() { 158 return new int[] { 159 TokenTypes.IDENT, 160 TokenTypes.CTOR_DEF, 161 TokenTypes.METHOD_DEF, 162 TokenTypes.SLIST, 163 TokenTypes.OBJBLOCK, 164 TokenTypes.LITERAL_BREAK, 165 TokenTypes.LITERAL_FOR, 166 TokenTypes.EXPR, 167 TokenTypes.PATTERN_VARIABLE_DEF, 168 }; 169 } 170 171 @Override 172 public int[] getDefaultTokens() { 173 return new int[] { 174 TokenTypes.IDENT, 175 TokenTypes.CTOR_DEF, 176 TokenTypes.METHOD_DEF, 177 TokenTypes.SLIST, 178 TokenTypes.OBJBLOCK, 179 TokenTypes.LITERAL_BREAK, 180 TokenTypes.LITERAL_FOR, 181 TokenTypes.VARIABLE_DEF, 182 TokenTypes.EXPR, 183 TokenTypes.PATTERN_VARIABLE_DEF, 184 }; 185 } 186 187 @Override 188 public int[] getAcceptableTokens() { 189 return new int[] { 190 TokenTypes.IDENT, 191 TokenTypes.CTOR_DEF, 192 TokenTypes.METHOD_DEF, 193 TokenTypes.SLIST, 194 TokenTypes.OBJBLOCK, 195 TokenTypes.LITERAL_BREAK, 196 TokenTypes.LITERAL_FOR, 197 TokenTypes.VARIABLE_DEF, 198 TokenTypes.PARAMETER_DEF, 199 TokenTypes.EXPR, 200 TokenTypes.PATTERN_VARIABLE_DEF, 201 }; 202 } 203 204 // -@cs[CyclomaticComplexity] The only optimization which can be done here is moving CASE-block 205 // expressions to separate methods, but that will not increase readability. 206 @Override 207 public void visitToken(DetailAST ast) { 208 switch (ast.getType()) { 209 case TokenTypes.OBJBLOCK, TokenTypes.METHOD_DEF, 210 TokenTypes.CTOR_DEF, TokenTypes.LITERAL_FOR -> 211 scopeStack.push(new ScopeData()); 212 213 case TokenTypes.SLIST -> { 214 currentScopeAssignedVariables.push(new ArrayDeque<>()); 215 if (ast.getParent().getType() != TokenTypes.CASE_GROUP 216 || ast.getParent().getParent() 217 .findFirstToken(TokenTypes.CASE_GROUP) == ast.getParent()) { 218 storePrevScopeUninitializedVariableData(); 219 scopeStack.push(new ScopeData()); 220 } 221 } 222 223 case TokenTypes.PARAMETER_DEF -> { 224 if (!isInLambda(ast) 225 && ast.findFirstToken(TokenTypes.MODIFIERS) 226 .findFirstToken(TokenTypes.FINAL) == null 227 && !isInMethodWithoutBody(ast) 228 && !isMultipleTypeCatch(ast) 229 && !CheckUtil.isReceiverParameter(ast)) { 230 insertParameter(ast); 231 } 232 } 233 234 case TokenTypes.VARIABLE_DEF -> { 235 if (ast.getParent().getType() != TokenTypes.OBJBLOCK 236 && ast.findFirstToken(TokenTypes.MODIFIERS) 237 .findFirstToken(TokenTypes.FINAL) == null 238 && !isVariableInForInit(ast) 239 && shouldCheckEnhancedForLoopVariable(ast) 240 && shouldCheckUnnamedVariable(ast)) { 241 insertVariable(ast); 242 } 243 } 244 245 case TokenTypes.IDENT -> { 246 final int parentType = ast.getParent().getType(); 247 if (isAssignOperator(parentType) && isFirstChild(ast)) { 248 final Optional<FinalVariableCandidate> candidate = getFinalCandidate(ast); 249 if (candidate.isPresent()) { 250 determineAssignmentConditions(ast, candidate.orElseThrow()); 251 currentScopeAssignedVariables.peek().add(ast); 252 } 253 removeFinalVariableCandidateFromStack(ast); 254 } 255 } 256 257 case TokenTypes.LITERAL_BREAK -> scopeStack.peek().containsBreak = true; 258 259 case TokenTypes.EXPR -> { 260 // Switch labeled expression has no slist 261 if (ast.getParent().getType() == TokenTypes.SWITCH_RULE) { 262 storePrevScopeUninitializedVariableData(); 263 } 264 } 265 266 case TokenTypes.PATTERN_VARIABLE_DEF -> { 267 if (validatePatternVariables && isNotChildOfAssign(ast) 268 && ast.findFirstToken(TokenTypes.MODIFIERS) 269 .findFirstToken(TokenTypes.FINAL) == null) { 270 insertPatternVariable(ast); 271 } 272 } 273 274 default -> throw new IllegalStateException("Incorrect token type"); 275 } 276 } 277 278 @Override 279 public void leaveToken(DetailAST ast) { 280 Map<String, FinalVariableCandidate> scope = null; 281 final DetailAST parentAst = ast.getParent(); 282 switch (ast.getType()) { 283 case TokenTypes.OBJBLOCK, TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF, 284 TokenTypes.LITERAL_FOR -> 285 scope = scopeStack.pop().scope; 286 287 case TokenTypes.EXPR -> { 288 // Switch labeled expression has no slist 289 if (parentAst.getType() == TokenTypes.SWITCH_RULE 290 && shouldUpdateUninitializedVariables(parentAst)) { 291 updateAllUninitializedVariables(); 292 } 293 } 294 295 case TokenTypes.SLIST -> { 296 boolean containsBreak = false; 297 if (parentAst.getType() != TokenTypes.CASE_GROUP 298 || findLastCaseGroupWhichContainsSlist(parentAst.getParent()) 299 == parentAst) { 300 containsBreak = scopeStack.peek().containsBreak; 301 scope = scopeStack.pop().scope; 302 } 303 if (containsBreak || shouldUpdateUninitializedVariables(parentAst)) { 304 updateAllUninitializedVariables(); 305 } 306 updateCurrentScopeAssignedVariables(); 307 } 308 309 default -> { 310 // do nothing 311 } 312 } 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(ast)) { 545 scopeStack.peek().uninitializedVariables.add(astNode); 546 } 547 } 548 549 /** 550 * Insert a pattern variable at the topmost scope stack. 551 * 552 * @param ast the pattern variable to insert. 553 */ 554 private void insertPatternVariable(DetailAST ast) { 555 final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope; 556 final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT); 557 final FinalVariableCandidate previousCandidate = scope.get(astNode.getText()); 558 if (previousCandidate != null) { 559 final DetailAST ident = previousCandidate.variableIdent; 560 log(ident, MSG_KEY, ident.getText()); 561 } 562 final FinalVariableCandidate candidate = new FinalVariableCandidate(astNode); 563 scope.put(astNode.getText(), candidate); 564 } 565 566 /** 567 * Check if VARIABLE_DEF is initialized or not. 568 * 569 * @param ast VARIABLE_DEF to be checked 570 * @return true if initialized 571 */ 572 private static boolean isInitialized(DetailAST ast) { 573 return ast.getLastChild().getType() == TokenTypes.ASSIGN; 574 } 575 576 /** 577 * Whether the ast is the first child of its parent. 578 * 579 * @param ast the ast to check. 580 * @return true if the ast is the first child of its parent. 581 */ 582 private static boolean isFirstChild(DetailAST ast) { 583 return ast.getPreviousSibling() == null; 584 } 585 586 /** 587 * Checks if pattern variable is not used for assign. 588 * 589 * @param ast PATTERN_VARIABLE_DEF. 590 * @return true if it's in conditional statement. 591 */ 592 private static boolean isNotChildOfAssign(DetailAST ast) { 593 return ast.getParent().getParent() 594 .getParent().getType() != TokenTypes.ASSIGN; 595 } 596 597 /** 598 * Removes the final variable candidate from the Stack. 599 * 600 * @param ast variable to remove. 601 */ 602 private void removeFinalVariableCandidateFromStack(DetailAST ast) { 603 final Iterator<ScopeData> iterator = scopeStack.descendingIterator(); 604 while (iterator.hasNext()) { 605 final ScopeData scopeData = iterator.next(); 606 final Map<String, FinalVariableCandidate> scope = scopeData.scope; 607 final FinalVariableCandidate candidate = scope.get(ast.getText()); 608 DetailAST storedVariable = null; 609 if (candidate != null) { 610 storedVariable = candidate.variableIdent; 611 } 612 if (storedVariable != null && isSameVariables(storedVariable, ast)) { 613 if (shouldRemoveFinalVariableCandidate(scopeData, ast)) { 614 scope.remove(ast.getText()); 615 } 616 break; 617 } 618 } 619 } 620 621 /** 622 * Check if given parameter definition is a multiple type catch. 623 * 624 * @param parameterDefAst parameter definition 625 * @return true if it is a multiple type catch, false otherwise 626 */ 627 private static boolean isMultipleTypeCatch(DetailAST parameterDefAst) { 628 final DetailAST typeAst = parameterDefAst.findFirstToken(TokenTypes.TYPE); 629 return typeAst.findFirstToken(TokenTypes.BOR) != null; 630 } 631 632 /** 633 * Whether the final variable candidate should be removed from the list of final local variable 634 * candidates. 635 * 636 * @param scopeData the scope data of the variable. 637 * @param ast the variable ast. 638 * @return true, if the variable should be removed. 639 */ 640 private static boolean shouldRemoveFinalVariableCandidate(ScopeData scopeData, DetailAST ast) { 641 boolean shouldRemove = true; 642 for (DetailAST variable : scopeData.uninitializedVariables) { 643 if (variable.getText().equals(ast.getText())) { 644 // if the variable is declared outside the loop and initialized inside 645 // the loop, then it cannot be declared final, as it can be initialized 646 // more than once in this case 647 final DetailAST currAstLoopAstParent = getParentLoop(ast); 648 final DetailAST currVarLoopAstParent = getParentLoop(variable); 649 if (currAstLoopAstParent == currVarLoopAstParent) { 650 final FinalVariableCandidate candidate = scopeData.scope.get(ast.getText()); 651 shouldRemove = candidate.alreadyAssigned; 652 } 653 scopeData.uninitializedVariables.remove(variable); 654 break; 655 } 656 } 657 return shouldRemove; 658 } 659 660 /** 661 * Get the ast node of type {@link FinalVariableCandidate#LOOP_TYPES} that is the ancestor 662 * of the current ast node, if there is no such node, null is returned. 663 * 664 * @param ast ast node 665 * @return ast node of type {@link FinalVariableCandidate#LOOP_TYPES} that is the ancestor 666 * of the current ast node, null if no such node exists 667 */ 668 private static DetailAST getParentLoop(DetailAST ast) { 669 DetailAST parentLoop = ast; 670 while (parentLoop != null 671 && !isLoopAst(parentLoop.getType())) { 672 parentLoop = parentLoop.getParent(); 673 } 674 return parentLoop; 675 } 676 677 /** 678 * Is Arithmetic operator. 679 * 680 * @param parentType token AST 681 * @return true is token type is in arithmetic operator 682 */ 683 private static boolean isAssignOperator(int parentType) { 684 return ASSIGN_OPERATOR_TYPES.get(parentType); 685 } 686 687 /** 688 * Checks if current variable is defined in 689 * {@link TokenTypes#FOR_INIT for-loop init}, e.g.: 690 * 691 * <p> 692 * {@code 693 * for (int i = 0, j = 0; i < j; i++) { . . . } 694 * } 695 * </p> 696 * {@code i, j} are defined in {@link TokenTypes#FOR_INIT for-loop init} 697 * 698 * @param variableDef variable definition node. 699 * @return true if variable is defined in {@link TokenTypes#FOR_INIT for-loop init} 700 */ 701 private static boolean isVariableInForInit(DetailAST variableDef) { 702 return variableDef.getParent().getType() == TokenTypes.FOR_INIT; 703 } 704 705 /** 706 * Checks if a parameter is within a method that has no implementation body. 707 * 708 * @param parameterDefAst the AST node representing the parameter definition 709 * @return true if the parameter is in a method without a body 710 */ 711 private static boolean isInMethodWithoutBody(DetailAST parameterDefAst) { 712 final DetailAST methodDefAst = parameterDefAst.getParent().getParent(); 713 return methodDefAst.findFirstToken(TokenTypes.SLIST) == null; 714 } 715 716 /** 717 * Check if current param is lambda's param. 718 * 719 * @param paramDef {@link TokenTypes#PARAMETER_DEF parameter def}. 720 * @return true if current param is lambda's param. 721 */ 722 private static boolean isInLambda(DetailAST paramDef) { 723 return paramDef.getParent().getParent().getType() == TokenTypes.LAMBDA; 724 } 725 726 /** 727 * Find the Class, Constructor, Enum, Method, or Field in which it is defined. 728 * 729 * @param ast Variable for which we want to find the scope in which it is defined 730 * @return ast The Class or Constructor or Method in which it is defined. 731 */ 732 private static DetailAST findFirstUpperNamedBlock(DetailAST ast) { 733 DetailAST astTraverse = ast; 734 while (!TokenUtil.isOfType(astTraverse, TokenTypes.METHOD_DEF, TokenTypes.CLASS_DEF, 735 TokenTypes.ENUM_DEF, TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF) 736 && !ScopeUtil.isClassFieldDef(astTraverse)) { 737 astTraverse = astTraverse.getParent(); 738 } 739 return astTraverse; 740 } 741 742 /** 743 * Check if both the Variables are same. 744 * 745 * @param ast1 Variable to compare 746 * @param ast2 Variable to compare 747 * @return true if both the variables are same, otherwise false 748 */ 749 private static boolean isSameVariables(DetailAST ast1, DetailAST ast2) { 750 final DetailAST classOrMethodOfAst1 = 751 findFirstUpperNamedBlock(ast1); 752 final DetailAST classOrMethodOfAst2 = 753 findFirstUpperNamedBlock(ast2); 754 return classOrMethodOfAst1 == classOrMethodOfAst2 && ast1.getText().equals(ast2.getText()); 755 } 756 757 /** 758 * Checks whether the ast is a loop. 759 * 760 * @param ast the ast to check. 761 * @return true if the ast is a loop. 762 */ 763 private static boolean isLoopAst(int ast) { 764 return LOOP_TYPES.get(ast); 765 } 766 767 /** 768 * Holder for the scope data. 769 */ 770 private static final class ScopeData { 771 772 /** Contains variable definitions. */ 773 private final Map<String, FinalVariableCandidate> scope = new HashMap<>(); 774 775 /** Contains definitions of uninitialized variables. */ 776 private final Deque<DetailAST> uninitializedVariables = new ArrayDeque<>(); 777 778 /** Contains definitions of previous scope uninitialized variables. */ 779 private Deque<DetailAST> prevScopeUninitializedVariables = new ArrayDeque<>(); 780 781 /** Whether there is a {@code break} in the scope. */ 782 private boolean containsBreak; 783 784 /** 785 * Searches for final local variable candidate for ast in the scope. 786 * 787 * @param ast ast. 788 * @return Optional of {@link FinalVariableCandidate}. 789 */ 790 /* package */ Optional<FinalVariableCandidate> 791 findFinalVariableCandidateForAst(DetailAST ast) { 792 Optional<FinalVariableCandidate> result = Optional.empty(); 793 DetailAST storedVariable = null; 794 final Optional<FinalVariableCandidate> candidate = 795 Optional.ofNullable(scope.get(ast.getText())); 796 if (candidate.isPresent()) { 797 storedVariable = candidate.orElseThrow().variableIdent; 798 } 799 if (storedVariable != null && isSameVariables(storedVariable, ast)) { 800 result = candidate; 801 } 802 return result; 803 } 804 805 } 806 807 /** Represents information about final local variable candidate. */ 808 private static final class FinalVariableCandidate { 809 810 /** Identifier token. */ 811 private final DetailAST variableIdent; 812 /** Whether the variable is assigned. */ 813 private boolean assigned; 814 /** Whether the variable is already assigned. */ 815 private boolean alreadyAssigned; 816 817 /** 818 * Creates new instance. 819 * 820 * @param variableIdent variable identifier. 821 */ 822 private FinalVariableCandidate(DetailAST variableIdent) { 823 this.variableIdent = variableIdent; 824 } 825 826 } 827 828}