001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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 * <div> 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 * </div> 039 * 040 * <p> 041 * Rationale: Changing the value of parameters during the execution of the method's 042 * algorithm can be confusing and should be avoided. A great way to let the Java compiler 043 * prevent this coding style is to declare parameters final. 044 * </p> 045 * 046 * @since 3.0 047 */ 048@StatelessCheck 049public class FinalParametersCheck extends AbstractCheck { 050 051 /** 052 * A key is pointing to the warning message text in "messages.properties" 053 * file. 054 */ 055 public static final String MSG_KEY = "final.parameter"; 056 057 /** 058 * Contains 059 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html"> 060 * primitive datatypes</a>. 061 */ 062 private final BitSet primitiveDataTypes = TokenUtil.asBitSet( 063 TokenTypes.LITERAL_BYTE, 064 TokenTypes.LITERAL_SHORT, 065 TokenTypes.LITERAL_INT, 066 TokenTypes.LITERAL_LONG, 067 TokenTypes.LITERAL_FLOAT, 068 TokenTypes.LITERAL_DOUBLE, 069 TokenTypes.LITERAL_BOOLEAN, 070 TokenTypes.LITERAL_CHAR 071 ); 072 073 /** 074 * Ignore primitive types as parameters. 075 */ 076 private boolean ignorePrimitiveTypes; 077 078 /** 079 * Ignore <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html"> 080 * unnamed parameters</a>. 081 */ 082 private boolean ignoreUnnamedParameters = true; 083 084 /** 085 * Creates a new {@code FinalParametersCheck} instance. 086 */ 087 public FinalParametersCheck() { 088 // no code by default 089 } 090 091 /** 092 * Setter to ignore primitive types as parameters. 093 * 094 * @param ignorePrimitiveTypes true or false. 095 * @since 6.2 096 */ 097 public void setIgnorePrimitiveTypes(boolean ignorePrimitiveTypes) { 098 this.ignorePrimitiveTypes = ignorePrimitiveTypes; 099 } 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}