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