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