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.naming; 021 022import java.util.Arrays; 023import java.util.Optional; 024 025import com.puppycrawl.tools.checkstyle.api.DetailAST; 026import com.puppycrawl.tools.checkstyle.api.TokenTypes; 027import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 028import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 029 030/** 031 * <p> 032 * Checks that method parameter names conform to a specified pattern. 033 * By using {@code accessModifiers} property it is possible 034 * to specify different formats for methods at different visibility levels. 035 * </p> 036 * <p> 037 * To validate {@code catch} parameters please use 038 * <a href="https://checkstyle.org/config_naming.html#CatchParameterName">CatchParameterName</a>. 039 * </p> 040 * <p> 041 * To validate lambda parameters please use 042 * <a href="https://checkstyle.org/config_naming.html#LambdaParameterName">LambdaParameterName</a>. 043 * </p> 044 * <ul> 045 * <li> 046 * Property {@code format} - Specifies valid identifiers. Default value is 047 * {@code "^[a-z][a-zA-Z0-9]*$"}. 048 * </li> 049 * <li> 050 * Property {@code ignoreOverridden} - Allows to skip methods with Override annotation from 051 * validation. For example, the following method's parameter will be skipped from validation, 052 * if ignoreOverridden is true: 053 * <pre> 054 * @Override 055 * public boolean equals(Object o) { 056 * return super.equals(o); 057 * } 058 * </pre> 059 * Default value is {@code false}. 060 * </li> 061 * <li> 062 * Property {@code accessModifiers} - Access modifiers of methods where parameters are checked. 063 * Default value is {@code public, protected, package, private}. 064 * </li> 065 * </ul> 066 * <p> 067 * An example of how to configure the check: 068 * </p> 069 * <pre> 070 * <module name="ParameterName"/> 071 * </pre> 072 * <p>Code Example:</p> 073 * <pre> 074 * class MyClass { 075 * void method1(int v1) {} // OK 076 * void method2(int V2) {} // violation, name 'V2' must match pattern '^[a-z][a-zA-Z0-9]*$' 077 * } 078 * </pre> 079 * <p> 080 * An example of how to configure the check for names that begin with 081 * a lower case letter, followed by letters, digits, and underscores: 082 * </p> 083 * <pre> 084 * <module name="ParameterName"> 085 * <property name="format" value="^[a-z][_a-zA-Z0-9]+$"/> 086 * </module> 087 * </pre> 088 * <p>Code Example:</p> 089 * <pre> 090 * class MyClass { 091 * void method1(int v1) {} // OK 092 * void method2(int v_2) {} // OK 093 * void method3(int V3) {} // violation, name 'V3' must match pattern '^[a-z][_a-zA-Z0-9]+$' 094 * } 095 * </pre> 096 * <p> 097 * An example of how to configure the check to skip methods with Override annotation from 098 * validation: 099 * </p> 100 * <pre> 101 * <module name="ParameterName"> 102 * <property name="ignoreOverridden" value="true"/> 103 * </module> 104 * </pre> 105 * <p>Code Example:</p> 106 * <pre> 107 * class MyClass { 108 * void method1(int v1) {} // OK 109 * void method2(int V2) {} // violation, name 'V2' must match pattern '^[a-z][a-zA-Z0-9]*$' 110 * @Override 111 * public boolean equals(Object V3) { // OK 112 * return true; 113 * } 114 * } 115 * </pre> 116 * <p> 117 * An example of how to configure the check for names that begin with a lower case letter, followed 118 * by letters and digits is: 119 * </p> 120 * <pre> 121 * <module name="ParameterName"> 122 * <property name="format" value="^[a-z][a-zA-Z0-9]+$"/> 123 * </module> 124 * </pre> 125 * <p>Code Example:</p> 126 * <pre> 127 * class MyClass { 128 * void method1(int v1) {} // OK 129 * void method2(int v_2) {} // violation, name 'v_2' must match pattern '^[a-z][a-zA-Z0-9]+$' 130 * void method3(int V3) {} // violation, name 'V3' must match pattern '^[a-z][a-zA-Z0-9]+$' 131 * } 132 * </pre> 133 * <p> 134 * The following configuration checks that the parameters always start with two lowercase 135 * characters and, in addition, that public method parameters cannot be one character long: 136 * </p> 137 * <pre> 138 * <module name="ParameterName"> 139 * <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> 140 * <property name="accessModifiers" 141 * value="protected, package, private"/> 142 * <message key="name.invalidPattern" 143 * value="Parameter name ''{0}'' must match pattern ''{1}''"/> 144 * </module> 145 * <module name="ParameterName"> 146 * <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/> 147 * <property name="accessModifiers" value="public"/> 148 * <message key="name.invalidPattern" 149 * value="Parameter name ''{0}'' must match pattern ''{1}''"/> 150 * </module> 151 * </pre> 152 * <p>Code Example:</p> 153 * <pre> 154 * class MyClass { 155 * void method1(int v1) {} // OK 156 * protected method2(int V2) {} // violation, Parameter name 'V2' 157 * // must match pattern '^[a-z]([a-z0-9][a-zA-Z0-9]*)?$' 158 * private method3(int a) {} // OK 159 * public method4(int b) {} // violation, Parameter name 'b' 160 * // must match pattern '^[a-z][a-z0-9][a-zA-Z0-9]*$' 161 * } 162 * </pre> 163 * 164 * @since 3.0 165 */ 166public class ParameterNameCheck extends AbstractNameCheck { 167 168 /** 169 * Allows to skip methods with Override annotation from validation. For example, the following 170 * method's parameter will be skipped from validation, if ignoreOverridden is true: 171 * <pre> 172 * @Override 173 * public boolean equals(Object o) { 174 * return super.equals(o); 175 * } 176 * </pre> 177 */ 178 private boolean ignoreOverridden; 179 180 /** Access modifiers of methods where parameters are checked. */ 181 private AccessModifier[] accessModifiers = { 182 AccessModifier.PUBLIC, 183 AccessModifier.PROTECTED, 184 AccessModifier.PACKAGE, 185 AccessModifier.PRIVATE, 186 }; 187 188 /** 189 * Creates a new {@code ParameterNameCheck} instance. 190 */ 191 public ParameterNameCheck() { 192 super("^[a-z][a-zA-Z0-9]*$"); 193 } 194 195 /** 196 * Setter to allows to skip methods with Override annotation from validation. For example, the 197 * following method's parameter will be skipped from validation, if ignoreOverridden is true: 198 * <pre> 199 * @Override 200 * public boolean equals(Object o) { 201 * return super.equals(o); 202 * } 203 * </pre> 204 * 205 * @param ignoreOverridden Flag for skipping methods with Override annotation. 206 */ 207 public void setIgnoreOverridden(boolean ignoreOverridden) { 208 this.ignoreOverridden = ignoreOverridden; 209 } 210 211 /** 212 * Setter to access modifiers of methods where parameters are checked. 213 * 214 * @param accessModifiers access modifiers of methods which should be checked. 215 */ 216 public void setAccessModifiers(AccessModifier... accessModifiers) { 217 this.accessModifiers = 218 Arrays.copyOf(accessModifiers, accessModifiers.length); 219 } 220 221 @Override 222 public int[] getDefaultTokens() { 223 return getRequiredTokens(); 224 } 225 226 @Override 227 public int[] getAcceptableTokens() { 228 return getRequiredTokens(); 229 } 230 231 @Override 232 public int[] getRequiredTokens() { 233 return new int[] {TokenTypes.PARAMETER_DEF}; 234 } 235 236 @Override 237 protected boolean mustCheckName(DetailAST ast) { 238 boolean checkName = true; 239 if (ignoreOverridden && isOverriddenMethod(ast) 240 || ast.getParent().getType() == TokenTypes.LITERAL_CATCH 241 || ast.getParent().getParent().getType() == TokenTypes.LAMBDA 242 || CheckUtil.isReceiverParameter(ast) 243 || !matchAccessModifiers(getAccessModifier(ast))) { 244 checkName = false; 245 } 246 return checkName; 247 } 248 249 /** 250 * Returns the access modifier of the method/constructor at the specified AST. If 251 * the method is in an interface or annotation block, the access modifier is assumed 252 * to be public. 253 * 254 * @param ast the token of the method/constructor. 255 * @return the access modifier of the method/constructor. 256 */ 257 private static AccessModifier getAccessModifier(final DetailAST ast) { 258 final AccessModifier accessModifier; 259 260 if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) { 261 accessModifier = AccessModifier.PUBLIC; 262 } 263 else { 264 final DetailAST params = ast.getParent(); 265 final DetailAST meth = params.getParent(); 266 final DetailAST modsToken = meth.findFirstToken(TokenTypes.MODIFIERS); 267 accessModifier = CheckUtil.getAccessModifierFromModifiersToken(modsToken); 268 } 269 270 return accessModifier; 271 } 272 273 /** 274 * Checks whether a method has the correct access modifier to be checked. 275 * 276 * @param accessModifier the access modifier of the method. 277 * @return whether the method matches the expected access modifier. 278 */ 279 private boolean matchAccessModifiers(final AccessModifier accessModifier) { 280 return Arrays.stream(accessModifiers).anyMatch(modifier -> modifier == accessModifier); 281 } 282 283 /** 284 * Checks whether a method is annotated with Override annotation. 285 * 286 * @param ast method parameter definition token. 287 * @return true if a method is annotated with Override annotation. 288 */ 289 private static boolean isOverriddenMethod(DetailAST ast) { 290 boolean overridden = false; 291 292 final DetailAST parent = ast.getParent().getParent(); 293 final Optional<DetailAST> annotation = 294 Optional.ofNullable(parent.getFirstChild().getFirstChild()); 295 296 if (annotation.isPresent()) { 297 final Optional<DetailAST> overrideToken = 298 Optional.ofNullable(annotation.get().findFirstToken(TokenTypes.IDENT)); 299 if (overrideToken.isPresent() && "Override".equals(overrideToken.get().getText())) { 300 overridden = true; 301 } 302 } 303 return overridden; 304 } 305 306}