View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.coding;
21  
22  import java.util.ArrayDeque;
23  import java.util.BitSet;
24  import java.util.Deque;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.Optional;
29  
30  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
31  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
32  import com.puppycrawl.tools.checkstyle.api.DetailAST;
33  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
34  import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
35  import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
36  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
37  
38  /**
39   * <div>
40   * Checks that local variables that never have their values changed are declared final.
41   * The check can be configured to also check that unchanged parameters are declared final.
42   * </div>
43   *
44   * <p>
45   * Notes:
46   * When configured to check parameters, the check ignores parameters of interface
47   * methods and abstract methods.
48   * </p>
49   *
50   * @since 3.2
51   */
52  @FileStatefulCheck
53  public class FinalLocalVariableCheck extends AbstractCheck {
54  
55      /**
56       * A key is pointing to the warning message text in "messages.properties"
57       * file.
58       */
59      public static final String MSG_KEY = "final.variable";
60  
61      /**
62       * Assign operator types.
63       */
64      private static final BitSet ASSIGN_OPERATOR_TYPES = TokenUtil.asBitSet(
65          TokenTypes.POST_INC,
66          TokenTypes.POST_DEC,
67          TokenTypes.ASSIGN,
68          TokenTypes.PLUS_ASSIGN,
69          TokenTypes.MINUS_ASSIGN,
70          TokenTypes.STAR_ASSIGN,
71          TokenTypes.DIV_ASSIGN,
72          TokenTypes.MOD_ASSIGN,
73          TokenTypes.SR_ASSIGN,
74          TokenTypes.BSR_ASSIGN,
75          TokenTypes.SL_ASSIGN,
76          TokenTypes.BAND_ASSIGN,
77          TokenTypes.BXOR_ASSIGN,
78          TokenTypes.BOR_ASSIGN,
79          TokenTypes.INC,
80          TokenTypes.DEC
81      );
82  
83      /**
84       * Loop types.
85       */
86      private static final BitSet LOOP_TYPES = TokenUtil.asBitSet(
87          TokenTypes.LITERAL_FOR,
88          TokenTypes.LITERAL_WHILE,
89          TokenTypes.LITERAL_DO
90      );
91  
92      /** Scope Deque. */
93      private final Deque<ScopeData> scopeStack = new ArrayDeque<>();
94  
95      /** Assigned variables of current scope. */
96      private final Deque<Deque<DetailAST>> currentScopeAssignedVariables =
97              new ArrayDeque<>();
98  
99      /**
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 }