View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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   * <ul>
46   * <li>
47   * Property {@code ignorePrimitiveTypes} - Ignore primitive types as parameters.
48   * Type is {@code boolean}.
49   * Default value is {@code false}.
50   * </li>
51   * <li>
52   * Property {@code ignoreUnnamedParameters} -
53   * Ignore <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
54   * unnamed parameters</a>.
55   * Type is {@code boolean}.
56   * Default value is {@code true}.
57   * </li>
58   * <li>
59   * Property {@code tokens} - tokens to check
60   * Type is {@code java.lang.String[]}.
61   * Validation type is {@code tokenSet}.
62   * Default value is:
63   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
64   * METHOD_DEF</a>,
65   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
66   * CTOR_DEF</a>.
67   * </li>
68   * </ul>
69   *
70   * <p>
71   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
72   * </p>
73   *
74   * <p>
75   * Violation Message Keys:
76   * </p>
77   * <ul>
78   * <li>
79   * {@code final.parameter}
80   * </li>
81   * </ul>
82   *
83   * @since 3.0
84   */
85  @StatelessCheck
86  public class FinalParametersCheck extends AbstractCheck {
87  
88      /**
89       * A key is pointing to the warning message text in "messages.properties"
90       * file.
91       */
92      public static final String MSG_KEY = "final.parameter";
93  
94      /**
95       * Contains
96       * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html">
97       * primitive datatypes</a>.
98       */
99      private final BitSet primitiveDataTypes = TokenUtil.asBitSet(
100         TokenTypes.LITERAL_BYTE,
101         TokenTypes.LITERAL_SHORT,
102         TokenTypes.LITERAL_INT,
103         TokenTypes.LITERAL_LONG,
104         TokenTypes.LITERAL_FLOAT,
105         TokenTypes.LITERAL_DOUBLE,
106         TokenTypes.LITERAL_BOOLEAN,
107         TokenTypes.LITERAL_CHAR
108     );
109 
110     /**
111      * Ignore primitive types as parameters.
112      */
113     private boolean ignorePrimitiveTypes;
114 
115     /**
116      * Ignore <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
117      * unnamed parameters</a>.
118      */
119     private boolean ignoreUnnamedParameters = true;
120 
121     /**
122      * Setter to ignore primitive types as parameters.
123      *
124      * @param ignorePrimitiveTypes true or false.
125      * @since 6.2
126      */
127     public void setIgnorePrimitiveTypes(boolean ignorePrimitiveTypes) {
128         this.ignorePrimitiveTypes = ignorePrimitiveTypes;
129     }
130 
131     /**
132      * Setter to ignore
133      * <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
134      * unnamed parameters</a>.
135      *
136      * @param ignoreUnnamedParameters true or false.
137      * @since 10.18.0
138      */
139     public void setIgnoreUnnamedParameters(boolean ignoreUnnamedParameters) {
140         this.ignoreUnnamedParameters = ignoreUnnamedParameters;
141     }
142 
143     @Override
144     public int[] getDefaultTokens() {
145         return new int[] {
146             TokenTypes.METHOD_DEF,
147             TokenTypes.CTOR_DEF,
148         };
149     }
150 
151     @Override
152     public int[] getAcceptableTokens() {
153         return new int[] {
154             TokenTypes.METHOD_DEF,
155             TokenTypes.CTOR_DEF,
156             TokenTypes.LITERAL_CATCH,
157             TokenTypes.FOR_EACH_CLAUSE,
158             TokenTypes.PATTERN_VARIABLE_DEF,
159         };
160     }
161 
162     @Override
163     public int[] getRequiredTokens() {
164         return CommonUtil.EMPTY_INT_ARRAY;
165     }
166 
167     @Override
168     public void visitToken(DetailAST ast) {
169         if (ast.getType() == TokenTypes.LITERAL_CATCH) {
170             visitCatch(ast);
171         }
172         else if (ast.getType() == TokenTypes.FOR_EACH_CLAUSE) {
173             visitForEachClause(ast);
174         }
175         else if (ast.getType() == TokenTypes.PATTERN_VARIABLE_DEF) {
176             visitPatternVariableDef(ast);
177         }
178         else {
179             visitMethod(ast);
180         }
181     }
182 
183     /**
184      * Checks parameter of the pattern variable definition.
185      *
186      * @param patternVariableDef pattern variable definition to check
187      */
188     private void visitPatternVariableDef(final DetailAST patternVariableDef) {
189         checkParam(patternVariableDef);
190     }
191 
192     /**
193      * Checks parameters of the method or ctor.
194      *
195      * @param method method or ctor to check.
196      */
197     private void visitMethod(final DetailAST method) {
198         // skip if there is no method body
199         // - abstract method
200         // - interface method (not implemented)
201         // - native method
202         if (method.findFirstToken(TokenTypes.SLIST) != null) {
203             final DetailAST parameters =
204                 method.findFirstToken(TokenTypes.PARAMETERS);
205             TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, this::checkParam);
206         }
207     }
208 
209     /**
210      * Checks parameter of the catch block.
211      *
212      * @param catchClause catch block to check.
213      */
214     private void visitCatch(final DetailAST catchClause) {
215         checkParam(catchClause.findFirstToken(TokenTypes.PARAMETER_DEF));
216     }
217 
218     /**
219      * Checks parameter of the for each clause.
220      *
221      * @param forEachClause for each clause to check.
222      */
223     private void visitForEachClause(final DetailAST forEachClause) {
224         final DetailAST variableDef = forEachClause.findFirstToken(TokenTypes.VARIABLE_DEF);
225         if (variableDef != null) {
226             // can be missing for record pattern def
227             // (only available as a preview feature in Java 20, never released)
228             checkParam(variableDef);
229         }
230     }
231 
232     /**
233      * Checks if the given parameter is final.
234      *
235      * @param param parameter to check.
236      */
237     private void checkParam(final DetailAST param) {
238         if (param.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(TokenTypes.FINAL) == null
239                 && !isIgnoredPrimitiveParam(param)
240                 && !isIgnoredUnnamedParam(param)
241                 && !CheckUtil.isReceiverParameter(param)) {
242             final DetailAST paramName = param.findFirstToken(TokenTypes.IDENT);
243             final DetailAST firstNode = CheckUtil.getFirstNode(param);
244             log(firstNode,
245                 MSG_KEY, paramName.getText());
246         }
247     }
248 
249     /**
250      * Checks for skip current param due to <b>ignorePrimitiveTypes</b> option.
251      *
252      * @param paramDef {@link TokenTypes#PARAMETER_DEF PARAMETER_DEF}
253      * @return true if param has to be skipped.
254      */
255     private boolean isIgnoredPrimitiveParam(DetailAST paramDef) {
256         boolean result = false;
257         if (ignorePrimitiveTypes) {
258             final DetailAST type = paramDef.findFirstToken(TokenTypes.TYPE);
259             final DetailAST parameterType = type.getFirstChild();
260             final DetailAST arrayDeclarator = type
261                     .findFirstToken(TokenTypes.ARRAY_DECLARATOR);
262             if (arrayDeclarator == null
263                     && primitiveDataTypes.get(parameterType.getType())) {
264                 result = true;
265             }
266         }
267         return result;
268     }
269 
270     /**
271      *  Checks for skip current param due to <b>ignoreUnnamedParameters</b> option.
272      *
273      * @param paramDef parameter to check
274      * @return true if the parameter should be skipped due to the ignoreUnnamedParameters option.
275      */
276     private boolean isIgnoredUnnamedParam(final DetailAST paramDef) {
277         final DetailAST paramName = paramDef.findFirstToken(TokenTypes.IDENT);
278         return ignoreUnnamedParameters && paramName != null && "_".equals(paramName.getText());
279     }
280 
281 }