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 }