View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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         };
159     }
160 
161     @Override
162     public int[] getRequiredTokens() {
163         return CommonUtil.EMPTY_INT_ARRAY;
164     }
165 
166     @Override
167     public void visitToken(DetailAST ast) {
168         // don't flag interfaces
169         final DetailAST container = ast.getParent().getParent();
170         if (container.getType() != TokenTypes.INTERFACE_DEF) {
171             if (ast.getType() == TokenTypes.LITERAL_CATCH) {
172                 visitCatch(ast);
173             }
174             else if (ast.getType() == TokenTypes.FOR_EACH_CLAUSE) {
175                 visitForEachClause(ast);
176             }
177             else {
178                 visitMethod(ast);
179             }
180         }
181     }
182 
183     /**
184      * Checks parameters of the method or ctor.
185      *
186      * @param method method or ctor to check.
187      */
188     private void visitMethod(final DetailAST method) {
189         final DetailAST modifiers =
190             method.findFirstToken(TokenTypes.MODIFIERS);
191 
192         // ignore abstract and native methods
193         if (modifiers.findFirstToken(TokenTypes.ABSTRACT) == null
194                 && modifiers.findFirstToken(TokenTypes.LITERAL_NATIVE) == null) {
195             final DetailAST parameters =
196                 method.findFirstToken(TokenTypes.PARAMETERS);
197             TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, this::checkParam);
198         }
199     }
200 
201     /**
202      * Checks parameter of the catch block.
203      *
204      * @param catchClause catch block to check.
205      */
206     private void visitCatch(final DetailAST catchClause) {
207         checkParam(catchClause.findFirstToken(TokenTypes.PARAMETER_DEF));
208     }
209 
210     /**
211      * Checks parameter of the for each clause.
212      *
213      * @param forEachClause for each clause to check.
214      */
215     private void visitForEachClause(final DetailAST forEachClause) {
216         checkParam(forEachClause.findFirstToken(TokenTypes.VARIABLE_DEF));
217     }
218 
219     /**
220      * Checks if the given parameter is final.
221      *
222      * @param param parameter to check.
223      */
224     private void checkParam(final DetailAST param) {
225         if (param.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(TokenTypes.FINAL) == null
226                 && !isIgnoredPrimitiveParam(param)
227                 && !isIgnoredUnnamedParam(param)
228                 && !CheckUtil.isReceiverParameter(param)) {
229             final DetailAST paramName = param.findFirstToken(TokenTypes.IDENT);
230             final DetailAST firstNode = CheckUtil.getFirstNode(param);
231             log(firstNode,
232                 MSG_KEY, paramName.getText());
233         }
234     }
235 
236     /**
237      * Checks for skip current param due to <b>ignorePrimitiveTypes</b> option.
238      *
239      * @param paramDef {@link TokenTypes#PARAMETER_DEF PARAMETER_DEF}
240      * @return true if param has to be skipped.
241      */
242     private boolean isIgnoredPrimitiveParam(DetailAST paramDef) {
243         boolean result = false;
244         if (ignorePrimitiveTypes) {
245             final DetailAST type = paramDef.findFirstToken(TokenTypes.TYPE);
246             final DetailAST parameterType = type.getFirstChild();
247             final DetailAST arrayDeclarator = type
248                     .findFirstToken(TokenTypes.ARRAY_DECLARATOR);
249             if (arrayDeclarator == null
250                     && primitiveDataTypes.get(parameterType.getType())) {
251                 result = true;
252             }
253         }
254         return result;
255     }
256 
257     /**
258      *  Checks for skip current param due to <b>ignoreUnnamedParameters</b> option.
259      *
260      * @param paramDef parameter to check
261      * @return true if the parameter should be skipped due to the ignoreUnnamedParameters option.
262      */
263     private boolean isIgnoredUnnamedParam(final DetailAST paramDef) {
264         final DetailAST paramName = paramDef.findFirstToken(TokenTypes.IDENT);
265         return ignoreUnnamedParameters && paramName != null && "_".equals(paramName.getText());
266     }
267 
268 }