View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.naming;
21  
22  import java.util.Arrays;
23  import java.util.HashSet;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Set;
27  import java.util.stream.Collectors;
28  
29  import com.puppycrawl.tools.checkstyle.StatelessCheck;
30  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
31  import com.puppycrawl.tools.checkstyle.api.DetailAST;
32  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
33  import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
34  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
35  
36  /**
37   * <div>
38   * Validates abbreviations (consecutive capital letters) length in
39   * identifier name, it also allows to enforce camel case naming. Please read more at
40   * <a href="https://checkstyle.org/styleguides/google-java-style-20220203/javaguide.html#s5.3-camel-case">
41   * Google Style Guide</a> to get to know how to avoid long abbreviations in names.
42   * </div>
43   *
44   * <p>'_' is considered as word separator in identifier name.</p>
45   *
46   * <p>
47   * {@code allowedAbbreviationLength} specifies how many consecutive capital letters are
48   * allowed in the identifier.
49   * A value of <i>3</i> indicates that up to 4 consecutive capital letters are allowed,
50   * one after the other, before a violation is printed. The identifier 'MyTEST' would be
51   * allowed, but 'MyTESTS' would not be.
52   * A value of <i>0</i> indicates that only 1 consecutive capital letter is allowed. This
53   * is what should be used to enforce strict camel casing. The identifier 'MyTest' would
54   * be allowed, but 'MyTEst' would not be.
55   * </p>
56   *
57   * <p>
58   * {@code ignoreFinal}, {@code ignoreStatic}, and {@code ignoreStaticFinal}
59   * control whether variables with the respective modifiers are to be ignored.
60   * Note that a variable that is both static and final will always be considered under
61   * {@code ignoreStaticFinal} only, regardless of the values of {@code ignoreFinal}
62   * and {@code ignoreStatic}. So for example if {@code ignoreStatic} is true but
63   * {@code ignoreStaticFinal} is false, then static final variables will not be ignored.
64   * </p>
65   * <ul>
66   * <li>
67   * Property {@code allowedAbbreviationLength} - Indicate the number of consecutive capital
68   * letters allowed in targeted identifiers (abbreviations in the classes, interfaces, variables
69   * and methods names, ... ).
70   * Type is {@code int}.
71   * Default value is {@code 3}.
72   * </li>
73   * <li>
74   * Property {@code allowedAbbreviations} - Specify abbreviations that must be skipped for checking.
75   * Type is {@code java.lang.String[]}.
76   * Default value is {@code ""}.
77   * </li>
78   * <li>
79   * Property {@code ignoreFinal} - Allow to skip variables with {@code final} modifier.
80   * Type is {@code boolean}.
81   * Default value is {@code true}.
82   * </li>
83   * <li>
84   * Property {@code ignoreOverriddenMethods} - Allow to ignore methods tagged with {@code @Override}
85   * annotation (that usually mean inherited name).
86   * Type is {@code boolean}.
87   * Default value is {@code true}.
88   * </li>
89   * <li>
90   * Property {@code ignoreStatic} - Allow to skip variables with {@code static} modifier.
91   * Type is {@code boolean}.
92   * Default value is {@code true}.
93   * </li>
94   * <li>
95   * Property {@code ignoreStaticFinal} - Allow to skip variables with both {@code static} and
96   * {@code final} modifiers.
97   * Type is {@code boolean}.
98   * Default value is {@code true}.
99   * </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
146 public 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 }