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 224 switch (ast.getType()) { 225 case TokenTypes.OBJBLOCK: 226 case TokenTypes.METHOD_DEF: 227 case TokenTypes.CTOR_DEF: 228 case TokenTypes.LITERAL_FOR: 229 scopeStack.push(new ScopeData()); 230 break; 231 case TokenTypes.SLIST: 232 currentScopeAssignedVariables.push(new ArrayDeque<>()); 233 if (ast.getParent().getType() != TokenTypes.CASE_GROUP 234 || ast.getParent().getParent().findFirstToken(TokenTypes.CASE_GROUP) 235 == ast.getParent()) { 236 storePrevScopeUninitializedVariableData(); 237 scopeStack.push(new ScopeData()); 238 } 239 break; 240 case TokenTypes.PARAMETER_DEF: 241 if (!isInLambda(ast) 242 && ast.findFirstToken(TokenTypes.MODIFIERS) 243 .findFirstToken(TokenTypes.FINAL) == null 244 && !isInMethodWithoutBody(ast) 245 && !isMultipleTypeCatch(ast) 246 && !CheckUtil.isReceiverParameter(ast)) { 247 insertParameter(ast); 248 } 249 break; 250 case TokenTypes.VARIABLE_DEF: 251 if (ast.getParent().getType() != TokenTypes.OBJBLOCK 252 && ast.findFirstToken(TokenTypes.MODIFIERS) 253 .findFirstToken(TokenTypes.FINAL) == null 254 && !isVariableInForInit(ast) 255 && shouldCheckEnhancedForLoopVariable(ast) 256 && shouldCheckUnnamedVariable(ast)) { 257 insertVariable(ast); 258 } 259 break; 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 break; 271 case TokenTypes.LITERAL_BREAK: 272 scopeStack.peek().containsBreak = true; 273 break; 274 case TokenTypes.EXPR: 275 // Switch labeled expression has no slist 276 if (ast.getParent().getType() == TokenTypes.SWITCH_RULE) { 277 storePrevScopeUninitializedVariableData(); 278 } 279 break; 280 default: 281 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: 291 case TokenTypes.CTOR_DEF: 292 case TokenTypes.METHOD_DEF: 293 case TokenTypes.LITERAL_FOR: 294 scope = scopeStack.pop().scope; 295 break; 296 case TokenTypes.EXPR: 297 // Switch labeled expression has no slist 298 if (parentAst.getType() == TokenTypes.SWITCH_RULE 299 && shouldUpdateUninitializedVariables(parentAst)) { 300 updateAllUninitializedVariables(); 301 } 302 break; 303 case TokenTypes.SLIST: 304 boolean containsBreak = false; 305 if (parentAst.getType() != TokenTypes.CASE_GROUP 306 || findLastCaseGroupWhichContainsSlist(parentAst.getParent()) == parentAst) { 307 containsBreak = scopeStack.peek().containsBreak; 308 scope = scopeStack.pop().scope; 309 } 310 if (containsBreak || shouldUpdateUninitializedVariables(parentAst)) { 311 updateAllUninitializedVariables(); 312 } 313 updateCurrentScopeAssignedVariables(); 314 break; 315 default: 316 // do nothing 317 } 318 if (scope != null) { 319 for (FinalVariableCandidate candidate : scope.values()) { 320 final DetailAST ident = candidate.variableIdent; 321 log(ident, MSG_KEY, ident.getText()); 322 } 323 } 324 } 325 326 /** 327 * Update assigned variables in a temporary stack. 328 */ 329 private void updateCurrentScopeAssignedVariables() { 330 // -@cs[MoveVariableInsideIf] assignment value is a modification call, so it can't be moved 331 final Deque<DetailAST> poppedScopeAssignedVariableData = 332 currentScopeAssignedVariables.pop(); 333 final Deque<DetailAST> currentScopeAssignedVariableData = 334 currentScopeAssignedVariables.peek(); 335 if (currentScopeAssignedVariableData != null) { 336 currentScopeAssignedVariableData.addAll(poppedScopeAssignedVariableData); 337 } 338 } 339 340 /** 341 * Determines identifier assignment conditions (assigned or already assigned). 342 * 343 * @param ident identifier. 344 * @param candidate final local variable candidate. 345 */ 346 private static void determineAssignmentConditions(DetailAST ident, 347 FinalVariableCandidate candidate) { 348 if (candidate.assigned) { 349 final int[] blockTypes = { 350 TokenTypes.LITERAL_ELSE, 351 TokenTypes.CASE_GROUP, 352 TokenTypes.SWITCH_RULE, 353 }; 354 if (!isInSpecificCodeBlocks(ident, blockTypes)) { 355 candidate.alreadyAssigned = true; 356 } 357 } 358 else { 359 candidate.assigned = true; 360 } 361 } 362 363 /** 364 * Checks whether the scope of a node is restricted to a specific code blocks. 365 * 366 * @param node node. 367 * @param blockTypes int array of all block types to check. 368 * @return true if the scope of a node is restricted to specific code block types. 369 */ 370 private static boolean isInSpecificCodeBlocks(DetailAST node, int... blockTypes) { 371 boolean returnValue = false; 372 for (int blockType : blockTypes) { 373 for (DetailAST token = node; token != null; token = token.getParent()) { 374 final int type = token.getType(); 375 if (type == blockType) { 376 returnValue = true; 377 break; 378 } 379 } 380 } 381 return returnValue; 382 } 383 384 /** 385 * Gets final variable candidate for ast. 386 * 387 * @param ast ast. 388 * @return Optional of {@link FinalVariableCandidate} for ast from scopeStack. 389 */ 390 private Optional<FinalVariableCandidate> getFinalCandidate(DetailAST ast) { 391 Optional<FinalVariableCandidate> result = Optional.empty(); 392 final Iterator<ScopeData> iterator = scopeStack.descendingIterator(); 393 while (iterator.hasNext() && result.isEmpty()) { 394 final ScopeData scopeData = iterator.next(); 395 result = scopeData.findFinalVariableCandidateForAst(ast); 396 } 397 return result; 398 } 399 400 /** 401 * Store un-initialized variables in a temporary stack for future use. 402 */ 403 private void storePrevScopeUninitializedVariableData() { 404 final ScopeData scopeData = scopeStack.peek(); 405 final Deque<DetailAST> prevScopeUninitializedVariableData = 406 new ArrayDeque<>(); 407 scopeData.uninitializedVariables.forEach(prevScopeUninitializedVariableData::push); 408 scopeData.prevScopeUninitializedVariables = prevScopeUninitializedVariableData; 409 } 410 411 /** 412 * Update current scope data uninitialized variable according to the whole scope data. 413 */ 414 private void updateAllUninitializedVariables() { 415 final boolean hasSomeScopes = !currentScopeAssignedVariables.isEmpty(); 416 if (hasSomeScopes) { 417 scopeStack.forEach(scopeData -> { 418 updateUninitializedVariables(scopeData.prevScopeUninitializedVariables); 419 }); 420 } 421 } 422 423 /** 424 * Update current scope data uninitialized variable according to the specific scope data. 425 * 426 * @param scopeUninitializedVariableData variable for specific stack of uninitialized variables 427 */ 428 private void updateUninitializedVariables(Deque<DetailAST> scopeUninitializedVariableData) { 429 final Iterator<DetailAST> iterator = currentScopeAssignedVariables.peek().iterator(); 430 while (iterator.hasNext()) { 431 final DetailAST assignedVariable = iterator.next(); 432 boolean shouldRemove = false; 433 for (DetailAST variable : scopeUninitializedVariableData) { 434 for (ScopeData scopeData : scopeStack) { 435 final FinalVariableCandidate candidate = 436 scopeData.scope.get(variable.getText()); 437 DetailAST storedVariable = null; 438 if (candidate != null) { 439 storedVariable = candidate.variableIdent; 440 } 441 if (storedVariable != null 442 && isSameVariables(assignedVariable, variable)) { 443 scopeData.uninitializedVariables.push(variable); 444 shouldRemove = true; 445 } 446 } 447 } 448 if (shouldRemove) { 449 iterator.remove(); 450 } 451 } 452 } 453 454 /** 455 * If there is an {@code else} following or token is CASE_GROUP or 456 * SWITCH_RULE and there is another {@code case} following, then update the 457 * uninitialized variables. 458 * 459 * @param ast token to be checked 460 * @return true if should be updated, else false 461 */ 462 private static boolean shouldUpdateUninitializedVariables(DetailAST ast) { 463 return ast.getLastChild().getType() == TokenTypes.LITERAL_ELSE 464 || isCaseTokenWithAnotherCaseFollowing(ast); 465 } 466 467 /** 468 * If token is CASE_GROUP or SWITCH_RULE and there is another {@code case} following. 469 * 470 * @param ast token to be checked 471 * @return true if token is CASE_GROUP or SWITCH_RULE and there is another {@code case} 472 * following, else false 473 */ 474 private static boolean isCaseTokenWithAnotherCaseFollowing(DetailAST ast) { 475 boolean result = false; 476 if (ast.getType() == TokenTypes.CASE_GROUP) { 477 result = findLastCaseGroupWhichContainsSlist(ast.getParent()) != ast; 478 } 479 else if (ast.getType() == TokenTypes.SWITCH_RULE) { 480 result = ast.getNextSibling().getType() == TokenTypes.SWITCH_RULE; 481 } 482 return result; 483 } 484 485 /** 486 * Returns the last token of type {@link TokenTypes#CASE_GROUP} which contains 487 * {@link TokenTypes#SLIST}. 488 * 489 * @param literalSwitchAst ast node of type {@link TokenTypes#LITERAL_SWITCH} 490 * @return the matching token, or null if no match 491 */ 492 private static DetailAST findLastCaseGroupWhichContainsSlist(DetailAST literalSwitchAst) { 493 DetailAST returnValue = null; 494 for (DetailAST astIterator = literalSwitchAst.getFirstChild(); astIterator != null; 495 astIterator = astIterator.getNextSibling()) { 496 if (astIterator.findFirstToken(TokenTypes.SLIST) != null) { 497 returnValue = astIterator; 498 } 499 } 500 return returnValue; 501 } 502 503 /** 504 * Determines whether enhanced for-loop variable should be checked or not. 505 * 506 * @param ast The ast to compare. 507 * @return true if enhanced for-loop variable should be checked. 508 */ 509 private boolean shouldCheckEnhancedForLoopVariable(DetailAST ast) { 510 return validateEnhancedForLoopVariable 511 || ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE; 512 } 513 514 /** 515 * Determines whether unnamed variable should be checked or not. 516 * 517 * @param ast The ast to compare. 518 * @return true if unnamed variable should be checked. 519 */ 520 private boolean shouldCheckUnnamedVariable(DetailAST ast) { 521 return validateUnnamedVariables 522 || !"_".equals(ast.findFirstToken(TokenTypes.IDENT).getText()); 523 } 524 525 /** 526 * Insert a parameter at the topmost scope stack. 527 * 528 * @param ast the variable to insert. 529 */ 530 private void insertParameter(DetailAST ast) { 531 final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope; 532 final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT); 533 scope.put(astNode.getText(), new FinalVariableCandidate(astNode)); 534 } 535 536 /** 537 * Insert a variable at the topmost scope stack. 538 * 539 * @param ast the variable to insert. 540 */ 541 private void insertVariable(DetailAST ast) { 542 final Map<String, FinalVariableCandidate> scope = scopeStack.peek().scope; 543 final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT); 544 final FinalVariableCandidate candidate = new FinalVariableCandidate(astNode); 545 // for-each variables are implicitly assigned 546 candidate.assigned = ast.getParent().getType() == TokenTypes.FOR_EACH_CLAUSE; 547 scope.put(astNode.getText(), candidate); 548 if (!isInitialized(astNode)) { 549 scopeStack.peek().uninitializedVariables.add(astNode); 550 } 551 } 552 553 /** 554 * Check if VARIABLE_DEF is initialized or not. 555 * 556 * @param ast VARIABLE_DEF to be checked 557 * @return true if initialized 558 */ 559 private static boolean isInitialized(DetailAST ast) { 560 return ast.getParent().getLastChild().getType() == TokenTypes.ASSIGN; 561 } 562 563 /** 564 * Whether the ast is the first child of its parent. 565 * 566 * @param ast the ast to check. 567 * @return true if the ast is the first child of its parent. 568 */ 569 private static boolean isFirstChild(DetailAST ast) { 570 return ast.getPreviousSibling() == null; 571 } 572 573 /** 574 * Removes the final variable candidate from the Stack. 575 * 576 * @param ast variable to remove. 577 */ 578 private void removeFinalVariableCandidateFromStack(DetailAST ast) { 579 final Iterator<ScopeData> iterator = scopeStack.descendingIterator(); 580 while (iterator.hasNext()) { 581 final ScopeData scopeData = iterator.next(); 582 final Map<String, FinalVariableCandidate> scope = scopeData.scope; 583 final FinalVariableCandidate candidate = scope.get(ast.getText()); 584 DetailAST storedVariable = null; 585 if (candidate != null) { 586 storedVariable = candidate.variableIdent; 587 } 588 if (storedVariable != null && isSameVariables(storedVariable, ast)) { 589 if (shouldRemoveFinalVariableCandidate(scopeData, ast)) { 590 scope.remove(ast.getText()); 591 } 592 break; 593 } 594 } 595 } 596 597 /** 598 * Check if given parameter definition is a multiple type catch. 599 * 600 * @param parameterDefAst parameter definition 601 * @return true if it is a multiple type catch, false otherwise 602 */ 603 private static boolean isMultipleTypeCatch(DetailAST parameterDefAst) { 604 final DetailAST typeAst = parameterDefAst.findFirstToken(TokenTypes.TYPE); 605 return typeAst.findFirstToken(TokenTypes.BOR) != null; 606 } 607 608 /** 609 * Whether the final variable candidate should be removed from the list of final local variable 610 * candidates. 611 * 612 * @param scopeData the scope data of the variable. 613 * @param ast the variable ast. 614 * @return true, if the variable should be removed. 615 */ 616 private static boolean shouldRemoveFinalVariableCandidate(ScopeData scopeData, DetailAST ast) { 617 boolean shouldRemove = true; 618 for (DetailAST variable : scopeData.uninitializedVariables) { 619 if (variable.getText().equals(ast.getText())) { 620 // if the variable is declared outside the loop and initialized inside 621 // the loop, then it cannot be declared final, as it can be initialized 622 // more than once in this case 623 final DetailAST currAstLoopAstParent = getParentLoop(ast); 624 final DetailAST currVarLoopAstParent = getParentLoop(variable); 625 if (currAstLoopAstParent == currVarLoopAstParent) { 626 final FinalVariableCandidate candidate = scopeData.scope.get(ast.getText()); 627 shouldRemove = candidate.alreadyAssigned; 628 } 629 scopeData.uninitializedVariables.remove(variable); 630 break; 631 } 632 } 633 return shouldRemove; 634 } 635 636 /** 637 * Get the ast node of type {@link FinalVariableCandidate#LOOP_TYPES} that is the ancestor 638 * of the current ast node, if there is no such node, null is returned. 639 * 640 * @param ast ast node 641 * @return ast node of type {@link FinalVariableCandidate#LOOP_TYPES} that is the ancestor 642 * of the current ast node, null if no such node exists 643 */ 644 private static DetailAST getParentLoop(DetailAST ast) { 645 DetailAST parentLoop = ast; 646 while (parentLoop != null 647 && !isLoopAst(parentLoop.getType())) { 648 parentLoop = parentLoop.getParent(); 649 } 650 return parentLoop; 651 } 652 653 /** 654 * Is Arithmetic operator. 655 * 656 * @param parentType token AST 657 * @return true is token type is in arithmetic operator 658 */ 659 private static boolean isAssignOperator(int parentType) { 660 return ASSIGN_OPERATOR_TYPES.get(parentType); 661 } 662 663 /** 664 * Checks if current variable is defined in 665 * {@link TokenTypes#FOR_INIT for-loop init}, e.g.: 666 * 667 * <p> 668 * {@code 669 * for (int i = 0, j = 0; i < j; i++) { . . . } 670 * } 671 * </p> 672 * {@code i, j} are defined in {@link TokenTypes#FOR_INIT for-loop init} 673 * 674 * @param variableDef variable definition node. 675 * @return true if variable is defined in {@link TokenTypes#FOR_INIT for-loop init} 676 */ 677 private static boolean isVariableInForInit(DetailAST variableDef) { 678 return variableDef.getParent().getType() == TokenTypes.FOR_INIT; 679 } 680 681 /** 682 * Checks if a parameter is within a method that has no implementation body. 683 * 684 * @param parameterDefAst the AST node representing the parameter definition 685 * @return true if the parameter is in a method without a body 686 */ 687 private static boolean isInMethodWithoutBody(DetailAST parameterDefAst) { 688 final DetailAST methodDefAst = parameterDefAst.getParent().getParent(); 689 return methodDefAst.findFirstToken(TokenTypes.SLIST) == null; 690 } 691 692 /** 693 * Check if current param is lambda's param. 694 * 695 * @param paramDef {@link TokenTypes#PARAMETER_DEF parameter def}. 696 * @return true if current param is lambda's param. 697 */ 698 private static boolean isInLambda(DetailAST paramDef) { 699 return paramDef.getParent().getParent().getType() == TokenTypes.LAMBDA; 700 } 701 702 /** 703 * Find the Class, Constructor, Enum, Method, or Field in which it is defined. 704 * 705 * @param ast Variable for which we want to find the scope in which it is defined 706 * @return ast The Class or Constructor or Method in which it is defined. 707 */ 708 private static DetailAST findFirstUpperNamedBlock(DetailAST ast) { 709 DetailAST astTraverse = ast; 710 while (!TokenUtil.isOfType(astTraverse, TokenTypes.METHOD_DEF, TokenTypes.CLASS_DEF, 711 TokenTypes.ENUM_DEF, TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF) 712 && !ScopeUtil.isClassFieldDef(astTraverse)) { 713 astTraverse = astTraverse.getParent(); 714 } 715 return astTraverse; 716 } 717 718 /** 719 * Check if both the Variables are same. 720 * 721 * @param ast1 Variable to compare 722 * @param ast2 Variable to compare 723 * @return true if both the variables are same, otherwise false 724 */ 725 private static boolean isSameVariables(DetailAST ast1, DetailAST ast2) { 726 final DetailAST classOrMethodOfAst1 = 727 findFirstUpperNamedBlock(ast1); 728 final DetailAST classOrMethodOfAst2 = 729 findFirstUpperNamedBlock(ast2); 730 return classOrMethodOfAst1 == classOrMethodOfAst2 && ast1.getText().equals(ast2.getText()); 731 } 732 733 /** 734 * Checks whether the ast is a loop. 735 * 736 * @param ast the ast to check. 737 * @return true if the ast is a loop. 738 */ 739 private static boolean isLoopAst(int ast) { 740 return LOOP_TYPES.get(ast); 741 } 742 743 /** 744 * Holder for the scope data. 745 */ 746 private static final class ScopeData { 747 748 /** Contains variable definitions. */ 749 private final Map<String, FinalVariableCandidate> scope = new HashMap<>(); 750 751 /** Contains definitions of uninitialized variables. */ 752 private final Deque<DetailAST> uninitializedVariables = new ArrayDeque<>(); 753 754 /** Contains definitions of previous scope uninitialized variables. */ 755 private Deque<DetailAST> prevScopeUninitializedVariables = new ArrayDeque<>(); 756 757 /** Whether there is a {@code break} in the scope. */ 758 private boolean containsBreak; 759 760 /** 761 * Searches for final local variable candidate for ast in the scope. 762 * 763 * @param ast ast. 764 * @return Optional of {@link FinalVariableCandidate}. 765 */ 766 public Optional<FinalVariableCandidate> findFinalVariableCandidateForAst(DetailAST ast) { 767 Optional<FinalVariableCandidate> result = Optional.empty(); 768 DetailAST storedVariable = null; 769 final Optional<FinalVariableCandidate> candidate = 770 Optional.ofNullable(scope.get(ast.getText())); 771 if (candidate.isPresent()) { 772 storedVariable = candidate.orElseThrow().variableIdent; 773 } 774 if (storedVariable != null && isSameVariables(storedVariable, ast)) { 775 result = candidate; 776 } 777 return result; 778 } 779 780 } 781 782 /** Represents information about final local variable candidate. */ 783 private static final class FinalVariableCandidate { 784 785 /** Identifier token. */ 786 private final DetailAST variableIdent; 787 /** Whether the variable is assigned. */ 788 private boolean assigned; 789 /** Whether the variable is already assigned. */ 790 private boolean alreadyAssigned; 791 792 /** 793 * Creates new instance. 794 * 795 * @param variableIdent variable identifier. 796 */ 797 private FinalVariableCandidate(DetailAST variableIdent) { 798 this.variableIdent = variableIdent; 799 } 800 801 } 802 803}