001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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.HashSet; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Set; 027import java.util.stream.Collectors; 028 029import com.puppycrawl.tools.checkstyle.StatelessCheck; 030import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 031import com.puppycrawl.tools.checkstyle.api.DetailAST; 032import com.puppycrawl.tools.checkstyle.api.TokenTypes; 033import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 034import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 035 036/** 037 * <div> 038 * Validates abbreviations (consecutive capital letters) length in 039 * identifier name, it also allows to enforce camel case naming. Please read more at 040 * <a href="https://checkstyle.org/styleguides/google-java-style-20220203/javaguide.html#s5.3-camel-case"> 041 * Google Style Guide</a> to get to know how to avoid long abbreviations in names. 042 * </div> 043 * 044 * <p>'_' is considered as word separator in identifier name.</p> 045 * 046 * <p> 047 * {@code allowedAbbreviationLength} specifies how many consecutive capital letters are 048 * allowed in the identifier. 049 * A value of <i>3</i> indicates that up to 4 consecutive capital letters are allowed, 050 * one after the other, before a violation is printed. The identifier 'MyTEST' would be 051 * allowed, but 'MyTESTS' would not be. 052 * A value of <i>0</i> indicates that only 1 consecutive capital letter is allowed. This 053 * is what should be used to enforce strict camel casing. The identifier 'MyTest' would 054 * be allowed, but 'MyTEst' would not be. 055 * </p> 056 * 057 * <p> 058 * {@code ignoreFinal}, {@code ignoreStatic}, and {@code ignoreStaticFinal} 059 * control whether variables with the respective modifiers are to be ignored. 060 * Note that a variable that is both static and final will always be considered under 061 * {@code ignoreStaticFinal} only, regardless of the values of {@code ignoreFinal} 062 * and {@code ignoreStatic}. So for example if {@code ignoreStatic} is true but 063 * {@code ignoreStaticFinal} is false, then static final variables will not be ignored. 064 * </p> 065 * <ul> 066 * <li> 067 * Property {@code allowedAbbreviationLength} - Indicate the number of consecutive capital 068 * letters allowed in targeted identifiers (abbreviations in the classes, interfaces, variables 069 * and methods names, ... ). 070 * Type is {@code int}. 071 * Default value is {@code 3}. 072 * </li> 073 * <li> 074 * Property {@code allowedAbbreviations} - Specify abbreviations that must be skipped for checking. 075 * Type is {@code java.lang.String[]}. 076 * Default value is {@code ""}. 077 * </li> 078 * <li> 079 * Property {@code ignoreFinal} - Allow to skip variables with {@code final} modifier. 080 * Type is {@code boolean}. 081 * Default value is {@code true}. 082 * </li> 083 * <li> 084 * Property {@code ignoreOverriddenMethods} - Allow to ignore methods tagged with {@code @Override} 085 * annotation (that usually mean inherited name). 086 * Type is {@code boolean}. 087 * Default value is {@code true}. 088 * </li> 089 * <li> 090 * Property {@code ignoreStatic} - Allow to skip variables with {@code static} modifier. 091 * Type is {@code boolean}. 092 * Default value is {@code true}. 093 * </li> 094 * <li> 095 * Property {@code ignoreStaticFinal} - Allow to skip variables with both {@code static} and 096 * {@code final} modifiers. 097 * Type is {@code boolean}. 098 * Default value is {@code true}. 099 * </li> 100 * <li> 101 * Property {@code tokens} - tokens to check 102 * Type is {@code java.lang.String[]}. 103 * Validation type is {@code tokenSet}. 104 * Default value is: 105 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 106 * CLASS_DEF</a>, 107 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 108 * INTERFACE_DEF</a>, 109 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 110 * ENUM_DEF</a>, 111 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF"> 112 * ANNOTATION_DEF</a>, 113 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF"> 114 * ANNOTATION_FIELD_DEF</a>, 115 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PARAMETER_DEF"> 116 * PARAMETER_DEF</a>, 117 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 118 * VARIABLE_DEF</a>, 119 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 120 * METHOD_DEF</a>, 121 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PATTERN_VARIABLE_DEF"> 122 * PATTERN_VARIABLE_DEF</a>, 123 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF"> 124 * RECORD_DEF</a>, 125 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_COMPONENT_DEF"> 126 * RECORD_COMPONENT_DEF</a>. 127 * </li> 128 * </ul> 129 * 130 * <p> 131 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 132 * </p> 133 * 134 * <p> 135 * Violation Message Keys: 136 * </p> 137 * <ul> 138 * <li> 139 * {@code abbreviation.as.word} 140 * </li> 141 * </ul> 142 * 143 * @since 5.8 144 */ 145@StatelessCheck 146public class AbbreviationAsWordInNameCheck extends AbstractCheck { 147 148 /** 149 * Warning message key. 150 */ 151 public static final String MSG_KEY = "abbreviation.as.word"; 152 153 /** 154 * The default value of "allowedAbbreviationLength" option. 155 */ 156 private static final int DEFAULT_ALLOWED_ABBREVIATIONS_LENGTH = 3; 157 158 /** 159 * Indicate the number of consecutive capital letters allowed in 160 * targeted identifiers (abbreviations in the classes, interfaces, variables 161 * and methods names, ... ). 162 */ 163 private int allowedAbbreviationLength = 164 DEFAULT_ALLOWED_ABBREVIATIONS_LENGTH; 165 166 /** 167 * Specify abbreviations that must be skipped for checking. 168 */ 169 private Set<String> allowedAbbreviations = new HashSet<>(); 170 171 /** Allow to skip variables with {@code final} modifier. */ 172 private boolean ignoreFinal = true; 173 174 /** Allow to skip variables with {@code static} modifier. */ 175 private boolean ignoreStatic = true; 176 177 /** Allow to skip variables with both {@code static} and {@code final} modifiers. */ 178 private boolean ignoreStaticFinal = true; 179 180 /** 181 * Allow to ignore methods tagged with {@code @Override} annotation (that 182 * usually mean inherited name). 183 */ 184 private boolean ignoreOverriddenMethods = true; 185 186 /** 187 * Setter to allow to skip variables with {@code final} modifier. 188 * 189 * @param ignoreFinal 190 * Defines if ignore variables with 'final' modifier or not. 191 * @since 5.8 192 */ 193 public void setIgnoreFinal(boolean ignoreFinal) { 194 this.ignoreFinal = ignoreFinal; 195 } 196 197 /** 198 * Setter to allow to skip variables with {@code static} modifier. 199 * 200 * @param ignoreStatic 201 * Defines if ignore variables with 'static' modifier or not. 202 * @since 5.8 203 */ 204 public void setIgnoreStatic(boolean ignoreStatic) { 205 this.ignoreStatic = ignoreStatic; 206 } 207 208 /** 209 * Setter to allow to skip variables with both {@code static} and {@code final} modifiers. 210 * 211 * @param ignoreStaticFinal 212 * Defines if ignore variables with both 'static' and 'final' modifiers or not. 213 * @since 8.32 214 */ 215 public void setIgnoreStaticFinal(boolean ignoreStaticFinal) { 216 this.ignoreStaticFinal = ignoreStaticFinal; 217 } 218 219 /** 220 * Setter to allow to ignore methods tagged with {@code @Override} 221 * annotation (that usually mean inherited name). 222 * 223 * @param ignoreOverriddenMethods 224 * Defines if ignore methods with "@Override" annotation or not. 225 * @since 5.8 226 */ 227 public void setIgnoreOverriddenMethods(boolean ignoreOverriddenMethods) { 228 this.ignoreOverriddenMethods = ignoreOverriddenMethods; 229 } 230 231 /** 232 * Setter to indicate the number of consecutive capital letters allowed 233 * in targeted identifiers (abbreviations in the classes, interfaces, 234 * variables and methods names, ... ). 235 * 236 * @param allowedAbbreviationLength amount of allowed capital letters in 237 * abbreviation. 238 * @since 5.8 239 */ 240 public void setAllowedAbbreviationLength(int allowedAbbreviationLength) { 241 this.allowedAbbreviationLength = allowedAbbreviationLength; 242 } 243 244 /** 245 * Setter to specify abbreviations that must be skipped for checking. 246 * 247 * @param allowedAbbreviations abbreviations that must be 248 * skipped from checking. 249 * @since 5.8 250 */ 251 public void setAllowedAbbreviations(String... allowedAbbreviations) { 252 if (allowedAbbreviations != null) { 253 this.allowedAbbreviations = 254 Arrays.stream(allowedAbbreviations).collect(Collectors.toUnmodifiableSet()); 255 } 256 } 257 258 @Override 259 public int[] getDefaultTokens() { 260 return new int[] { 261 TokenTypes.CLASS_DEF, 262 TokenTypes.INTERFACE_DEF, 263 TokenTypes.ENUM_DEF, 264 TokenTypes.ANNOTATION_DEF, 265 TokenTypes.ANNOTATION_FIELD_DEF, 266 TokenTypes.PARAMETER_DEF, 267 TokenTypes.VARIABLE_DEF, 268 TokenTypes.METHOD_DEF, 269 TokenTypes.PATTERN_VARIABLE_DEF, 270 TokenTypes.RECORD_DEF, 271 TokenTypes.RECORD_COMPONENT_DEF, 272 }; 273 } 274 275 @Override 276 public int[] getAcceptableTokens() { 277 return new int[] { 278 TokenTypes.CLASS_DEF, 279 TokenTypes.INTERFACE_DEF, 280 TokenTypes.ENUM_DEF, 281 TokenTypes.ANNOTATION_DEF, 282 TokenTypes.ANNOTATION_FIELD_DEF, 283 TokenTypes.PARAMETER_DEF, 284 TokenTypes.VARIABLE_DEF, 285 TokenTypes.METHOD_DEF, 286 TokenTypes.ENUM_CONSTANT_DEF, 287 TokenTypes.PATTERN_VARIABLE_DEF, 288 TokenTypes.RECORD_DEF, 289 TokenTypes.RECORD_COMPONENT_DEF, 290 }; 291 } 292 293 @Override 294 public int[] getRequiredTokens() { 295 return CommonUtil.EMPTY_INT_ARRAY; 296 } 297 298 @Override 299 public void visitToken(DetailAST ast) { 300 if (!isIgnoreSituation(ast)) { 301 final DetailAST nameAst = ast.findFirstToken(TokenTypes.IDENT); 302 final String typeName = nameAst.getText(); 303 304 final String abbr = getDisallowedAbbreviation(typeName); 305 if (abbr != null) { 306 log(nameAst, MSG_KEY, typeName, allowedAbbreviationLength + 1); 307 } 308 } 309 } 310 311 /** 312 * Checks if it is an ignore situation. 313 * 314 * @param ast input DetailAST node. 315 * @return true if it is an ignore situation found for given input DetailAST 316 * node. 317 */ 318 private boolean isIgnoreSituation(DetailAST ast) { 319 final DetailAST modifiers = ast.getFirstChild(); 320 321 final boolean result; 322 if (ast.getType() == TokenTypes.VARIABLE_DEF) { 323 if (isInterfaceDeclaration(ast)) { 324 // field declarations in interface are static/final 325 result = ignoreStaticFinal; 326 } 327 else { 328 result = hasIgnoredModifiers(modifiers); 329 } 330 } 331 else if (ast.getType() == TokenTypes.METHOD_DEF) { 332 result = ignoreOverriddenMethods && hasOverrideAnnotation(modifiers); 333 } 334 else { 335 result = CheckUtil.isReceiverParameter(ast); 336 } 337 return result; 338 } 339 340 /** 341 * Checks if a variable is to be ignored based on its modifiers. 342 * 343 * @param modifiers modifiers of the variable to be checked 344 * @return true if there is a modifier to be ignored 345 */ 346 private boolean hasIgnoredModifiers(DetailAST modifiers) { 347 final boolean isStatic = modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 348 final boolean isFinal = modifiers.findFirstToken(TokenTypes.FINAL) != null; 349 final boolean result; 350 if (isStatic && isFinal) { 351 result = ignoreStaticFinal; 352 } 353 else { 354 result = ignoreStatic && isStatic || ignoreFinal && isFinal; 355 } 356 return result; 357 } 358 359 /** 360 * Check that variable definition in interface or @interface definition. 361 * 362 * @param variableDefAst variable definition. 363 * @return true if variable definition(variableDefAst) is in interface 364 * or @interface definition. 365 */ 366 private static boolean isInterfaceDeclaration(DetailAST variableDefAst) { 367 boolean result = false; 368 final DetailAST astBlock = variableDefAst.getParent(); 369 final DetailAST astParent2 = astBlock.getParent(); 370 371 if (astParent2.getType() == TokenTypes.INTERFACE_DEF 372 || astParent2.getType() == TokenTypes.ANNOTATION_DEF) { 373 result = true; 374 } 375 return result; 376 } 377 378 /** 379 * Checks that the method has "@Override" annotation. 380 * 381 * @param methodModifiersAST 382 * A DetailAST nod is related to the given method modifiers 383 * (MODIFIERS type). 384 * @return true if method has "@Override" annotation. 385 */ 386 private static boolean hasOverrideAnnotation(DetailAST methodModifiersAST) { 387 boolean result = false; 388 for (DetailAST child : getChildren(methodModifiersAST)) { 389 final DetailAST annotationIdent = child.findFirstToken(TokenTypes.IDENT); 390 391 if (annotationIdent != null && "Override".equals(annotationIdent.getText())) { 392 result = true; 393 break; 394 } 395 } 396 return result; 397 } 398 399 /** 400 * Gets the disallowed abbreviation contained in given String. 401 * 402 * @param str 403 * the given String. 404 * @return the disallowed abbreviation contained in given String as a 405 * separate String. 406 */ 407 private String getDisallowedAbbreviation(String str) { 408 int beginIndex = 0; 409 boolean abbrStarted = false; 410 String result = null; 411 412 for (int index = 0; index < str.length(); index++) { 413 final char symbol = str.charAt(index); 414 415 if (Character.isUpperCase(symbol)) { 416 if (!abbrStarted) { 417 abbrStarted = true; 418 beginIndex = index; 419 } 420 } 421 else if (abbrStarted) { 422 abbrStarted = false; 423 424 final int endIndex; 425 final int allowedLength; 426 if (symbol == '_') { 427 endIndex = index; 428 allowedLength = allowedAbbreviationLength + 1; 429 } 430 else { 431 endIndex = index - 1; 432 allowedLength = allowedAbbreviationLength; 433 } 434 result = getAbbreviationIfIllegal(str, beginIndex, endIndex, allowedLength); 435 if (result != null) { 436 break; 437 } 438 beginIndex = -1; 439 } 440 } 441 // if abbreviation at the end of name (example: scaleX) 442 if (abbrStarted) { 443 final int endIndex = str.length() - 1; 444 result = getAbbreviationIfIllegal(str, beginIndex, endIndex, allowedAbbreviationLength); 445 } 446 return result; 447 } 448 449 /** 450 * Get Abbreviation if it is illegal, where {@code beginIndex} and {@code endIndex} are 451 * inclusive indexes of a sequence of consecutive upper-case characters. 452 * 453 * @param str name 454 * @param beginIndex begin index 455 * @param endIndex end index 456 * @param allowedLength maximum allowed length for Abbreviation 457 * @return the abbreviation if it is bigger than required and not in the 458 * ignore list, otherwise {@code null} 459 */ 460 private String getAbbreviationIfIllegal(String str, int beginIndex, int endIndex, 461 int allowedLength) { 462 String result = null; 463 final int abbrLength = endIndex - beginIndex; 464 if (abbrLength > allowedLength) { 465 final String abbr = getAbbreviation(str, beginIndex, endIndex); 466 if (!allowedAbbreviations.contains(abbr)) { 467 result = abbr; 468 } 469 } 470 return result; 471 } 472 473 /** 474 * Gets the abbreviation, where {@code beginIndex} and {@code endIndex} are 475 * inclusive indexes of a sequence of consecutive upper-case characters. 476 * 477 * <p> 478 * The character at {@code endIndex} is only included in the abbreviation if 479 * it is the last character in the string; otherwise it is usually the first 480 * capital in the next word. 481 * </p> 482 * 483 * <p> 484 * For example, {@code getAbbreviation("getXMLParser", 3, 6)} returns "XML" 485 * (not "XMLP"), and so does {@code getAbbreviation("parseXML", 5, 7)}. 486 * </p> 487 * 488 * @param str name 489 * @param beginIndex begin index 490 * @param endIndex end index 491 * @return the specified abbreviation 492 */ 493 private static String getAbbreviation(String str, int beginIndex, int endIndex) { 494 final String result; 495 if (endIndex == str.length() - 1) { 496 result = str.substring(beginIndex); 497 } 498 else { 499 result = str.substring(beginIndex, endIndex); 500 } 501 return result; 502 } 503 504 /** 505 * Gets all the children which are one level below on the current DetailAST 506 * parent node. 507 * 508 * @param node 509 * Current parent node. 510 * @return The list of children one level below on the current parent node. 511 */ 512 private static List<DetailAST> getChildren(final DetailAST node) { 513 final List<DetailAST> result = new LinkedList<>(); 514 DetailAST curNode = node.getFirstChild(); 515 while (curNode != null) { 516 result.add(curNode); 517 curNode = curNode.getNextSibling(); 518 } 519 return result; 520 } 521 522}