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