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