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" file. 053 */ 054 public static final String MSG_KEY = "final.parameter"; 055 056 /** 057 * Contains 058 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html"> 059 * primitive datatypes</a>. 060 */ 061 private final BitSet primitiveDataTypes = TokenUtil.asBitSet( 062 TokenTypes.LITERAL_BYTE, 063 TokenTypes.LITERAL_SHORT, 064 TokenTypes.LITERAL_INT, 065 TokenTypes.LITERAL_LONG, 066 TokenTypes.LITERAL_FLOAT, 067 TokenTypes.LITERAL_DOUBLE, 068 TokenTypes.LITERAL_BOOLEAN, 069 TokenTypes.LITERAL_CHAR 070 ); 071 072 /** 073 * Ignore primitive types as parameters. 074 */ 075 private boolean ignorePrimitiveTypes; 076 077 /** 078 * Ignore <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html"> 079 * unnamed parameters</a>. 080 */ 081 private boolean ignoreUnnamedParameters = true; 082 083 /** 084 * Creates a new {@code FinalParametersCheck} instance. 085 */ 086 public FinalParametersCheck() { 087 // no code by default 088 } 089 090 /** 091 * Setter to ignore primitive types as parameters. 092 * 093 * @param ignorePrimitiveTypes true or false. 094 * @since 6.2 095 */ 096 public void setIgnorePrimitiveTypes(boolean ignorePrimitiveTypes) { 097 this.ignorePrimitiveTypes = ignorePrimitiveTypes; 098 } 099 100 /** 101 * Setter to ignore 102 * <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html"> 103 * unnamed parameters</a>. 104 * 105 * @param ignoreUnnamedParameters true or false. 106 * @since 10.18.0 107 */ 108 public void setIgnoreUnnamedParameters(boolean ignoreUnnamedParameters) { 109 this.ignoreUnnamedParameters = ignoreUnnamedParameters; 110 } 111 112 @Override 113 public int[] getDefaultTokens() { 114 return new int[] { 115 TokenTypes.METHOD_DEF, 116 TokenTypes.CTOR_DEF, 117 }; 118 } 119 120 @Override 121 public int[] getAcceptableTokens() { 122 return new int[] { 123 TokenTypes.METHOD_DEF, 124 TokenTypes.CTOR_DEF, 125 TokenTypes.LITERAL_CATCH, 126 TokenTypes.FOR_EACH_CLAUSE, 127 TokenTypes.PATTERN_VARIABLE_DEF, 128 }; 129 } 130 131 @Override 132 public int[] getRequiredTokens() { 133 return CommonUtil.EMPTY_INT_ARRAY; 134 } 135 136 @Override 137 public void visitToken(DetailAST ast) { 138 if (ast.getType() == TokenTypes.LITERAL_CATCH) { 139 visitCatch(ast); 140 } 141 else if (ast.getType() == TokenTypes.FOR_EACH_CLAUSE) { 142 visitForEachClause(ast); 143 } 144 else if (ast.getType() == TokenTypes.PATTERN_VARIABLE_DEF) { 145 visitPatternVariableDef(ast); 146 } 147 else { 148 visitMethod(ast); 149 } 150 } 151 152 /** 153 * Checks parameter of the pattern variable definition. 154 * 155 * @param patternVariableDef pattern variable definition to check 156 */ 157 private void visitPatternVariableDef(final DetailAST patternVariableDef) { 158 checkParam(patternVariableDef); 159 } 160 161 /** 162 * Checks parameters of the method or ctor. 163 * 164 * @param method method or ctor to check. 165 */ 166 private void visitMethod(final DetailAST method) { 167 // skip if there is no method body 168 // - abstract method 169 // - interface method (not implemented) 170 // - native method 171 if (method.findFirstToken(TokenTypes.SLIST) != null) { 172 final DetailAST parameters = 173 method.findFirstToken(TokenTypes.PARAMETERS); 174 TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, this::checkParam); 175 } 176 } 177 178 /** 179 * Checks parameter of the catch block. 180 * 181 * @param catchClause catch block to check. 182 */ 183 private void visitCatch(final DetailAST catchClause) { 184 checkParam(catchClause.findFirstToken(TokenTypes.PARAMETER_DEF)); 185 } 186 187 /** 188 * Checks parameter of the for each clause. 189 * 190 * @param forEachClause for each clause to check. 191 */ 192 private void visitForEachClause(final DetailAST forEachClause) { 193 final DetailAST variableDef = forEachClause.findFirstToken(TokenTypes.VARIABLE_DEF); 194 if (variableDef != null) { 195 // can be missing for record pattern def 196 // (only available as a preview feature in Java 20, never released) 197 checkParam(variableDef); 198 } 199 } 200 201 /** 202 * Checks if the given parameter is final. 203 * 204 * @param param parameter to check. 205 */ 206 private void checkParam(final DetailAST param) { 207 if (param.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(TokenTypes.FINAL) == null 208 && !isIgnoredPrimitiveParam(param) 209 && !isIgnoredUnnamedParam(param) 210 && !CheckUtil.isReceiverParameter(param)) { 211 final DetailAST paramName = TokenUtil.getIdent(param); 212 final DetailAST firstNode = CheckUtil.getFirstNode(param); 213 log(firstNode, 214 MSG_KEY, paramName.getText()); 215 } 216 } 217 218 /** 219 * Checks for skip current param due to <b>ignorePrimitiveTypes</b> option. 220 * 221 * @param paramDef {@link TokenTypes#PARAMETER_DEF PARAMETER_DEF} 222 * @return true if param has to be skipped. 223 */ 224 private boolean isIgnoredPrimitiveParam(DetailAST paramDef) { 225 boolean result = false; 226 if (ignorePrimitiveTypes) { 227 final DetailAST type = paramDef.findFirstToken(TokenTypes.TYPE); 228 final DetailAST parameterType = type.getFirstChild(); 229 final DetailAST arrayDeclarator = type 230 .findFirstToken(TokenTypes.ARRAY_DECLARATOR); 231 if (arrayDeclarator == null 232 && primitiveDataTypes.get(parameterType.getType())) { 233 result = true; 234 } 235 } 236 return result; 237 } 238 239 /** 240 * Checks for skip current param due to <b>ignoreUnnamedParameters</b> option. 241 * 242 * @param paramDef parameter to check 243 * @return true if the parameter should be skipped due to the ignoreUnnamedParameters option. 244 */ 245 private boolean isIgnoredUnnamedParam(final DetailAST paramDef) { 246 final DetailAST paramName = paramDef.findFirstToken(TokenTypes.IDENT); 247 return ignoreUnnamedParameters && paramName != null && "_".equals(paramName.getText()); 248 } 249 250}