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 }