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;
21  
22  import java.util.BitSet;
23  
24  import com.puppycrawl.tools.checkstyle.StatelessCheck;
25  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
26  import com.puppycrawl.tools.checkstyle.api.DetailAST;
27  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
28  import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
29  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
30  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
31  
32  /**
33   * <div>
34   * Checks that parameters for methods, constructors, catch and for-each blocks are final.
35   * Interface, abstract, and native methods are not checked: the final keyword
36   * does not make sense for interface, abstract, and native method parameters as
37   * there is no code that could modify the parameter.
38   * </div>
39   *
40   * <p>
41   * Rationale: Changing the value of parameters during the execution of the method's
42   * algorithm can be confusing and should be avoided. A great way to let the Java compiler
43   * prevent this coding style is to declare parameters final.
44   * </p>
45   *
46   * @since 3.0
47   */
48  @StatelessCheck
49  public class FinalParametersCheck extends AbstractCheck {
50  
51      /**
52       * A key is pointing to the warning message text in "messages.properties"
53       * file.
54       */
55      public static final String MSG_KEY = "final.parameter";
56  
57      /**
58       * Contains
59       * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html">
60       * primitive datatypes</a>.
61       */
62      private final BitSet primitiveDataTypes = TokenUtil.asBitSet(
63          TokenTypes.LITERAL_BYTE,
64          TokenTypes.LITERAL_SHORT,
65          TokenTypes.LITERAL_INT,
66          TokenTypes.LITERAL_LONG,
67          TokenTypes.LITERAL_FLOAT,
68          TokenTypes.LITERAL_DOUBLE,
69          TokenTypes.LITERAL_BOOLEAN,
70          TokenTypes.LITERAL_CHAR
71      );
72  
73      /**
74       * Ignore primitive types as parameters.
75       */
76      private boolean ignorePrimitiveTypes;
77  
78      /**
79       * Ignore <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
80       * unnamed parameters</a>.
81       */
82      private boolean ignoreUnnamedParameters = true;
83  
84      /**
85       * Creates a new {@code FinalParametersCheck} instance.
86       */
87      public FinalParametersCheck() {
88          // no code by default
89      }
90  
91      /**
92       * Setter to ignore primitive types as parameters.
93       *
94       * @param ignorePrimitiveTypes true or false.
95       * @since 6.2
96       */
97      public void setIgnorePrimitiveTypes(boolean ignorePrimitiveTypes) {
98          this.ignorePrimitiveTypes = ignorePrimitiveTypes;
99      }
100 
101     /**
102      * Setter to ignore
103      * <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
104      * unnamed parameters</a>.
105      *
106      * @param ignoreUnnamedParameters true or false.
107      * @since 10.18.0
108      */
109     public void setIgnoreUnnamedParameters(boolean ignoreUnnamedParameters) {
110         this.ignoreUnnamedParameters = ignoreUnnamedParameters;
111     }
112 
113     @Override
114     public int[] getDefaultTokens() {
115         return new int[] {
116             TokenTypes.METHOD_DEF,
117             TokenTypes.CTOR_DEF,
118         };
119     }
120 
121     @Override
122     public int[] getAcceptableTokens() {
123         return new int[] {
124             TokenTypes.METHOD_DEF,
125             TokenTypes.CTOR_DEF,
126             TokenTypes.LITERAL_CATCH,
127             TokenTypes.FOR_EACH_CLAUSE,
128             TokenTypes.PATTERN_VARIABLE_DEF,
129         };
130     }
131 
132     @Override
133     public int[] getRequiredTokens() {
134         return CommonUtil.EMPTY_INT_ARRAY;
135     }
136 
137     @Override
138     public void visitToken(DetailAST ast) {
139         if (ast.getType() == TokenTypes.LITERAL_CATCH) {
140             visitCatch(ast);
141         }
142         else if (ast.getType() == TokenTypes.FOR_EACH_CLAUSE) {
143             visitForEachClause(ast);
144         }
145         else if (ast.getType() == TokenTypes.PATTERN_VARIABLE_DEF) {
146             visitPatternVariableDef(ast);
147         }
148         else {
149             visitMethod(ast);
150         }
151     }
152 
153     /**
154      * Checks parameter of the pattern variable definition.
155      *
156      * @param patternVariableDef pattern variable definition to check
157      */
158     private void visitPatternVariableDef(final DetailAST patternVariableDef) {
159         checkParam(patternVariableDef);
160     }
161 
162     /**
163      * Checks parameters of the method or ctor.
164      *
165      * @param method method or ctor to check.
166      */
167     private void visitMethod(final DetailAST method) {
168         // skip if there is no method body
169         // - abstract method
170         // - interface method (not implemented)
171         // - native method
172         if (method.findFirstToken(TokenTypes.SLIST) != null) {
173             final DetailAST parameters =
174                 method.findFirstToken(TokenTypes.PARAMETERS);
175             TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, this::checkParam);
176         }
177     }
178 
179     /**
180      * Checks parameter of the catch block.
181      *
182      * @param catchClause catch block to check.
183      */
184     private void visitCatch(final DetailAST catchClause) {
185         checkParam(catchClause.findFirstToken(TokenTypes.PARAMETER_DEF));
186     }
187 
188     /**
189      * Checks parameter of the for each clause.
190      *
191      * @param forEachClause for each clause to check.
192      */
193     private void visitForEachClause(final DetailAST forEachClause) {
194         final DetailAST variableDef = forEachClause.findFirstToken(TokenTypes.VARIABLE_DEF);
195         if (variableDef != null) {
196             // can be missing for record pattern def
197             // (only available as a preview feature in Java 20, never released)
198             checkParam(variableDef);
199         }
200     }
201 
202     /**
203      * Checks if the given parameter is final.
204      *
205      * @param param parameter to check.
206      */
207     private void checkParam(final DetailAST param) {
208         if (param.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(TokenTypes.FINAL) == null
209                 && !isIgnoredPrimitiveParam(param)
210                 && !isIgnoredUnnamedParam(param)
211                 && !CheckUtil.isReceiverParameter(param)) {
212             final DetailAST paramName = TokenUtil.getIdent(param);
213             final DetailAST firstNode = CheckUtil.getFirstNode(param);
214             log(firstNode,
215                 MSG_KEY, paramName.getText());
216         }
217     }
218 
219     /**
220      * Checks for skip current param due to <b>ignorePrimitiveTypes</b> option.
221      *
222      * @param paramDef {@link TokenTypes#PARAMETER_DEF PARAMETER_DEF}
223      * @return true if param has to be skipped.
224      */
225     private boolean isIgnoredPrimitiveParam(DetailAST paramDef) {
226         boolean result = false;
227         if (ignorePrimitiveTypes) {
228             final DetailAST type = paramDef.findFirstToken(TokenTypes.TYPE);
229             final DetailAST parameterType = type.getFirstChild();
230             final DetailAST arrayDeclarator = type
231                     .findFirstToken(TokenTypes.ARRAY_DECLARATOR);
232             if (arrayDeclarator == null
233                     && primitiveDataTypes.get(parameterType.getType())) {
234                 result = true;
235             }
236         }
237         return result;
238     }
239 
240     /**
241      *  Checks for skip current param due to <b>ignoreUnnamedParameters</b> option.
242      *
243      * @param paramDef parameter to check
244      * @return true if the parameter should be skipped due to the ignoreUnnamedParameters option.
245      */
246     private boolean isIgnoredUnnamedParam(final DetailAST paramDef) {
247         final DetailAST paramName = paramDef.findFirstToken(TokenTypes.IDENT);
248         return ignoreUnnamedParameters && paramName != null && "_".equals(paramName.getText());
249     }
250 
251 }