001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.Arrays; 023import java.util.Collections; 024import java.util.Set; 025import java.util.stream.Collectors; 026 027import com.puppycrawl.tools.checkstyle.StatelessCheck; 028import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.TokenTypes; 031import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 032import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 033 034/** 035 * <p> 036 * Checks that parameters for methods, constructors, catch and for-each blocks are final. 037 * Interface, abstract, and native methods are not checked: the final keyword 038 * does not make sense for interface, abstract, and native method parameters as 039 * there is no code that could modify the parameter. 040 * </p> 041 * <p> 042 * Rationale: Changing the value of parameters during the execution of the method's 043 * algorithm can be confusing and should be avoided. A great way to let the Java compiler 044 * prevent this coding style is to declare parameters final. 045 * </p> 046 * <ul> 047 * <li> 048 * Property {@code ignorePrimitiveTypes} - Ignore primitive types as parameters. 049 * Type is {@code boolean}. 050 * Default value is {@code false}. 051 * </li> 052 * <li> 053 * Property {@code tokens} - tokens to check 054 * Type is {@code int[]}. 055 * Default value is: 056 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 057 * METHOD_DEF</a>, 058 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 059 * CTOR_DEF</a>. 060 * </li> 061 * </ul> 062 * <p> 063 * To configure the check to enforce final parameters for methods and constructors: 064 * </p> 065 * <pre> 066 * <module name="FinalParameters"/> 067 * </pre> 068 * <p> 069 * To configure the check to enforce final parameters only for constructors: 070 * </p> 071 * <pre> 072 * <module name="FinalParameters"> 073 * <property name="tokens" value="CTOR_DEF"/> 074 * </module> 075 * </pre> 076 * <p> 077 * To configure the check to allow ignoring 078 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html"> 079 * primitive datatypes</a> as parameters: 080 * </p> 081 * <pre> 082 * <module name="FinalParameters"> 083 * <property name="ignorePrimitiveTypes" value="true"/> 084 * </module> 085 * </pre> 086 * <p> 087 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 088 * </p> 089 * <p> 090 * Violation Message Keys: 091 * </p> 092 * <ul> 093 * <li> 094 * {@code final.parameter} 095 * </li> 096 * </ul> 097 * 098 * @since 3.0 099 */ 100@StatelessCheck 101public class FinalParametersCheck extends AbstractCheck { 102 103 /** 104 * A key is pointing to the warning message text in "messages.properties" 105 * file. 106 */ 107 public static final String MSG_KEY = "final.parameter"; 108 109 /** 110 * Contains 111 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html"> 112 * primitive datatypes</a>. 113 */ 114 private final Set<Integer> primitiveDataTypes = Collections.unmodifiableSet( 115 Arrays.stream(new Integer[] { 116 TokenTypes.LITERAL_BYTE, 117 TokenTypes.LITERAL_SHORT, 118 TokenTypes.LITERAL_INT, 119 TokenTypes.LITERAL_LONG, 120 TokenTypes.LITERAL_FLOAT, 121 TokenTypes.LITERAL_DOUBLE, 122 TokenTypes.LITERAL_BOOLEAN, 123 TokenTypes.LITERAL_CHAR, }) 124 .collect(Collectors.toSet())); 125 126 /** 127 * Ignore primitive types as parameters. 128 */ 129 private boolean ignorePrimitiveTypes; 130 131 /** 132 * Setter to ignore primitive types as parameters. 133 * 134 * @param ignorePrimitiveTypes true or false. 135 */ 136 public void setIgnorePrimitiveTypes(boolean ignorePrimitiveTypes) { 137 this.ignorePrimitiveTypes = ignorePrimitiveTypes; 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 DetailAST child = parameters.getFirstChild(); 195 while (child != null) { 196 // children are PARAMETER_DEF and COMMA 197 if (child.getType() == TokenTypes.PARAMETER_DEF) { 198 checkParam(child); 199 } 200 child = child.getNextSibling(); 201 } 202 } 203 } 204 205 /** 206 * Checks parameter of the catch block. 207 * 208 * @param catchClause catch block to check. 209 */ 210 private void visitCatch(final DetailAST catchClause) { 211 checkParam(catchClause.findFirstToken(TokenTypes.PARAMETER_DEF)); 212 } 213 214 /** 215 * Checks parameter of the for each clause. 216 * 217 * @param forEachClause for each clause to check. 218 */ 219 private void visitForEachClause(final DetailAST forEachClause) { 220 checkParam(forEachClause.findFirstToken(TokenTypes.VARIABLE_DEF)); 221 } 222 223 /** 224 * Checks if the given parameter is final. 225 * 226 * @param param parameter to check. 227 */ 228 private void checkParam(final DetailAST param) { 229 if (param.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(TokenTypes.FINAL) == null 230 && !isIgnoredParam(param) 231 && !CheckUtil.isReceiverParameter(param)) { 232 final DetailAST paramName = param.findFirstToken(TokenTypes.IDENT); 233 final DetailAST firstNode = CheckUtil.getFirstNode(param); 234 log(firstNode, 235 MSG_KEY, paramName.getText()); 236 } 237 } 238 239 /** 240 * Checks for skip current param due to <b>ignorePrimitiveTypes</b> option. 241 * 242 * @param paramDef {@link TokenTypes#PARAMETER_DEF PARAMETER_DEF} 243 * @return true if param has to be skipped. 244 */ 245 private boolean isIgnoredParam(DetailAST paramDef) { 246 boolean result = false; 247 if (ignorePrimitiveTypes) { 248 final DetailAST parameterType = paramDef 249 .findFirstToken(TokenTypes.TYPE).getFirstChild(); 250 if (primitiveDataTypes.contains(parameterType.getType())) { 251 result = true; 252 } 253 } 254 return result; 255 } 256 257}