001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks; 021 022import java.util.BitSet; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 029import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 030import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 031 032/** 033 * <p> 034 * Checks that parameters for methods, constructors, catch and for-each blocks are final. 035 * Interface, abstract, and native methods are not checked: the final keyword 036 * does not make sense for interface, abstract, and native method parameters as 037 * there is no code that could modify the parameter. 038 * </p> 039 * <p> 040 * Rationale: Changing the value of parameters during the execution of the method's 041 * algorithm can be confusing and should be avoided. A great way to let the Java compiler 042 * prevent this coding style is to declare parameters final. 043 * </p> 044 * <ul> 045 * <li> 046 * Property {@code ignorePrimitiveTypes} - Ignore primitive types as parameters. 047 * Type is {@code boolean}. 048 * Default value is {@code false}. 049 * </li> 050 * <li> 051 * Property {@code tokens} - tokens to check 052 * Type is {@code java.lang.String[]}. 053 * Validation type is {@code tokenSet}. 054 * Default value is: 055 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 056 * METHOD_DEF</a>, 057 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 058 * CTOR_DEF</a>. 059 * </li> 060 * </ul> 061 * <p> 062 * To configure the check to enforce final parameters for methods and constructors: 063 * </p> 064 * <pre> 065 * <module name="FinalParameters"/> 066 * </pre> 067 * <p> 068 * Example: 069 * </p> 070 * <pre> 071 * public class Point { 072 * public Point() { } // ok 073 * public Point(final int m) { } // ok 074 * public Point(final int m,int n) { } // violation, n should be final 075 * public void methodOne(final int x) { } // ok 076 * public void methodTwo(int x) { } // violation, x should be final 077 * public static void main(String[] args) { } // violation, args should be final 078 * } 079 * </pre> 080 * <p> 081 * To configure the check to enforce final parameters only for constructors: 082 * </p> 083 * <pre> 084 * <module name="FinalParameters"> 085 * <property name="tokens" value="CTOR_DEF"/> 086 * </module> 087 * </pre> 088 * <p> 089 * Example: 090 * </p> 091 * <pre> 092 * public class Point { 093 * public Point() { } // ok 094 * public Point(final int m) { } // ok 095 * public Point(final int m,int n) { } // violation, n should be final 096 * public void methodOne(final int x) { } // ok 097 * public void methodTwo(int x) { } // ok 098 * public static void main(String[] args) { } // ok 099 * } 100 * </pre> 101 * <p> 102 * To configure the check to allow ignoring 103 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html"> 104 * primitive datatypes</a> as parameters: 105 * </p> 106 * <pre> 107 * <module name="FinalParameters"> 108 * <property name="ignorePrimitiveTypes" value="true"/> 109 * </module> 110 * </pre> 111 * <p> 112 * Example: 113 * </p> 114 * <pre> 115 * public class Point { 116 * public Point() { } // ok 117 * public Point(final int m) { } // ok 118 * public Point(final int m,int n) { } // ok 119 * public void methodOne(final int x) { } // ok 120 * public void methodTwo(int x) { } // ok 121 * public static void main(String[] args) { } // violation, args should be final 122 * } 123 * </pre> 124 * <p> 125 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 126 * </p> 127 * <p> 128 * Violation Message Keys: 129 * </p> 130 * <ul> 131 * <li> 132 * {@code final.parameter} 133 * </li> 134 * </ul> 135 * 136 * @since 3.0 137 */ 138@StatelessCheck 139public class FinalParametersCheck extends AbstractCheck { 140 141 /** 142 * A key is pointing to the warning message text in "messages.properties" 143 * file. 144 */ 145 public static final String MSG_KEY = "final.parameter"; 146 147 /** 148 * Contains 149 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html"> 150 * primitive datatypes</a>. 151 */ 152 private final BitSet primitiveDataTypes = TokenUtil.asBitSet( 153 TokenTypes.LITERAL_BYTE, 154 TokenTypes.LITERAL_SHORT, 155 TokenTypes.LITERAL_INT, 156 TokenTypes.LITERAL_LONG, 157 TokenTypes.LITERAL_FLOAT, 158 TokenTypes.LITERAL_DOUBLE, 159 TokenTypes.LITERAL_BOOLEAN, 160 TokenTypes.LITERAL_CHAR 161 ); 162 163 /** 164 * Ignore primitive types as parameters. 165 */ 166 private boolean ignorePrimitiveTypes; 167 168 /** 169 * Setter to ignore primitive types as parameters. 170 * 171 * @param ignorePrimitiveTypes true or false. 172 */ 173 public void setIgnorePrimitiveTypes(boolean ignorePrimitiveTypes) { 174 this.ignorePrimitiveTypes = ignorePrimitiveTypes; 175 } 176 177 @Override 178 public int[] getDefaultTokens() { 179 return new int[] { 180 TokenTypes.METHOD_DEF, 181 TokenTypes.CTOR_DEF, 182 }; 183 } 184 185 @Override 186 public int[] getAcceptableTokens() { 187 return new int[] { 188 TokenTypes.METHOD_DEF, 189 TokenTypes.CTOR_DEF, 190 TokenTypes.LITERAL_CATCH, 191 TokenTypes.FOR_EACH_CLAUSE, 192 }; 193 } 194 195 @Override 196 public int[] getRequiredTokens() { 197 return CommonUtil.EMPTY_INT_ARRAY; 198 } 199 200 @Override 201 public void visitToken(DetailAST ast) { 202 // don't flag interfaces 203 final DetailAST container = ast.getParent().getParent(); 204 if (container.getType() != TokenTypes.INTERFACE_DEF) { 205 if (ast.getType() == TokenTypes.LITERAL_CATCH) { 206 visitCatch(ast); 207 } 208 else if (ast.getType() == TokenTypes.FOR_EACH_CLAUSE) { 209 visitForEachClause(ast); 210 } 211 else { 212 visitMethod(ast); 213 } 214 } 215 } 216 217 /** 218 * Checks parameters of the method or ctor. 219 * 220 * @param method method or ctor to check. 221 */ 222 private void visitMethod(final DetailAST method) { 223 final DetailAST modifiers = 224 method.findFirstToken(TokenTypes.MODIFIERS); 225 226 // ignore abstract and native methods 227 if (modifiers.findFirstToken(TokenTypes.ABSTRACT) == null 228 && modifiers.findFirstToken(TokenTypes.LITERAL_NATIVE) == null) { 229 final DetailAST parameters = 230 method.findFirstToken(TokenTypes.PARAMETERS); 231 TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, this::checkParam); 232 } 233 } 234 235 /** 236 * Checks parameter of the catch block. 237 * 238 * @param catchClause catch block to check. 239 */ 240 private void visitCatch(final DetailAST catchClause) { 241 checkParam(catchClause.findFirstToken(TokenTypes.PARAMETER_DEF)); 242 } 243 244 /** 245 * Checks parameter of the for each clause. 246 * 247 * @param forEachClause for each clause to check. 248 */ 249 private void visitForEachClause(final DetailAST forEachClause) { 250 checkParam(forEachClause.findFirstToken(TokenTypes.VARIABLE_DEF)); 251 } 252 253 /** 254 * Checks if the given parameter is final. 255 * 256 * @param param parameter to check. 257 */ 258 private void checkParam(final DetailAST param) { 259 if (param.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(TokenTypes.FINAL) == null 260 && !isIgnoredParam(param) 261 && !CheckUtil.isReceiverParameter(param)) { 262 final DetailAST paramName = param.findFirstToken(TokenTypes.IDENT); 263 final DetailAST firstNode = CheckUtil.getFirstNode(param); 264 log(firstNode, 265 MSG_KEY, paramName.getText()); 266 } 267 } 268 269 /** 270 * Checks for skip current param due to <b>ignorePrimitiveTypes</b> option. 271 * 272 * @param paramDef {@link TokenTypes#PARAMETER_DEF PARAMETER_DEF} 273 * @return true if param has to be skipped. 274 */ 275 private boolean isIgnoredParam(DetailAST paramDef) { 276 boolean result = false; 277 if (ignorePrimitiveTypes) { 278 final DetailAST type = paramDef.findFirstToken(TokenTypes.TYPE); 279 final DetailAST parameterType = type.getFirstChild(); 280 final DetailAST arrayDeclarator = type 281 .findFirstToken(TokenTypes.ARRAY_DECLARATOR); 282 if (arrayDeclarator == null 283 && primitiveDataTypes.get(parameterType.getType())) { 284 result = true; 285 } 286 } 287 return result; 288 } 289 290}