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