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   *
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       * Setter to ignore primitive types as parameters.
86       *
87       * @param ignorePrimitiveTypes true or false.
88       * @since 6.2
89       */
90      public void setIgnorePrimitiveTypes(boolean ignorePrimitiveTypes) {
91          this.ignorePrimitiveTypes = ignorePrimitiveTypes;
92      }
93  
94      /**
95       * Setter to ignore
96       * <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
97       * unnamed parameters</a>.
98       *
99       * @param ignoreUnnamedParameters true or false.
100      * @since 10.18.0
101      */
102     public void setIgnoreUnnamedParameters(boolean ignoreUnnamedParameters) {
103         this.ignoreUnnamedParameters = ignoreUnnamedParameters;
104     }
105 
106     @Override
107     public int[] getDefaultTokens() {
108         return new int[] {
109             TokenTypes.METHOD_DEF,
110             TokenTypes.CTOR_DEF,
111         };
112     }
113 
114     @Override
115     public int[] getAcceptableTokens() {
116         return new int[] {
117             TokenTypes.METHOD_DEF,
118             TokenTypes.CTOR_DEF,
119             TokenTypes.LITERAL_CATCH,
120             TokenTypes.FOR_EACH_CLAUSE,
121             TokenTypes.PATTERN_VARIABLE_DEF,
122         };
123     }
124 
125     @Override
126     public int[] getRequiredTokens() {
127         return CommonUtil.EMPTY_INT_ARRAY;
128     }
129 
130     @Override
131     public void visitToken(DetailAST ast) {
132         if (ast.getType() == TokenTypes.LITERAL_CATCH) {
133             visitCatch(ast);
134         }
135         else if (ast.getType() == TokenTypes.FOR_EACH_CLAUSE) {
136             visitForEachClause(ast);
137         }
138         else if (ast.getType() == TokenTypes.PATTERN_VARIABLE_DEF) {
139             visitPatternVariableDef(ast);
140         }
141         else {
142             visitMethod(ast);
143         }
144     }
145 
146     /**
147      * Checks parameter of the pattern variable definition.
148      *
149      * @param patternVariableDef pattern variable definition to check
150      */
151     private void visitPatternVariableDef(final DetailAST patternVariableDef) {
152         checkParam(patternVariableDef);
153     }
154 
155     /**
156      * Checks parameters of the method or ctor.
157      *
158      * @param method method or ctor to check.
159      */
160     private void visitMethod(final DetailAST method) {
161         // skip if there is no method body
162         // - abstract method
163         // - interface method (not implemented)
164         // - native method
165         if (method.findFirstToken(TokenTypes.SLIST) != null) {
166             final DetailAST parameters =
167                 method.findFirstToken(TokenTypes.PARAMETERS);
168             TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, this::checkParam);
169         }
170     }
171 
172     /**
173      * Checks parameter of the catch block.
174      *
175      * @param catchClause catch block to check.
176      */
177     private void visitCatch(final DetailAST catchClause) {
178         checkParam(catchClause.findFirstToken(TokenTypes.PARAMETER_DEF));
179     }
180 
181     /**
182      * Checks parameter of the for each clause.
183      *
184      * @param forEachClause for each clause to check.
185      */
186     private void visitForEachClause(final DetailAST forEachClause) {
187         final DetailAST variableDef = forEachClause.findFirstToken(TokenTypes.VARIABLE_DEF);
188         if (variableDef != null) {
189             // can be missing for record pattern def
190             // (only available as a preview feature in Java 20, never released)
191             checkParam(variableDef);
192         }
193     }
194 
195     /**
196      * Checks if the given parameter is final.
197      *
198      * @param param parameter to check.
199      */
200     private void checkParam(final DetailAST param) {
201         if (param.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(TokenTypes.FINAL) == null
202                 && !isIgnoredPrimitiveParam(param)
203                 && !isIgnoredUnnamedParam(param)
204                 && !CheckUtil.isReceiverParameter(param)) {
205             final DetailAST paramName = param.findFirstToken(TokenTypes.IDENT);
206             final DetailAST firstNode = CheckUtil.getFirstNode(param);
207             log(firstNode,
208                 MSG_KEY, paramName.getText());
209         }
210     }
211 
212     /**
213      * Checks for skip current param due to <b>ignorePrimitiveTypes</b> option.
214      *
215      * @param paramDef {@link TokenTypes#PARAMETER_DEF PARAMETER_DEF}
216      * @return true if param has to be skipped.
217      */
218     private boolean isIgnoredPrimitiveParam(DetailAST paramDef) {
219         boolean result = false;
220         if (ignorePrimitiveTypes) {
221             final DetailAST type = paramDef.findFirstToken(TokenTypes.TYPE);
222             final DetailAST parameterType = type.getFirstChild();
223             final DetailAST arrayDeclarator = type
224                     .findFirstToken(TokenTypes.ARRAY_DECLARATOR);
225             if (arrayDeclarator == null
226                     && primitiveDataTypes.get(parameterType.getType())) {
227                 result = true;
228             }
229         }
230         return result;
231     }
232 
233     /**
234      *  Checks for skip current param due to <b>ignoreUnnamedParameters</b> option.
235      *
236      * @param paramDef parameter to check
237      * @return true if the parameter should be skipped due to the ignoreUnnamedParameters option.
238      */
239     private boolean isIgnoredUnnamedParam(final DetailAST paramDef) {
240         final DetailAST paramName = paramDef.findFirstToken(TokenTypes.IDENT);
241         return ignoreUnnamedParameters && paramName != null && "_".equals(paramName.getText());
242     }
243 
244 }