1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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.ArrayList;
23 import java.util.Arrays;
24 import java.util.HashSet;
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-20250426/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 *
66 * @since 5.8
67 */
68 @StatelessCheck
69 public class AbbreviationAsWordInNameCheck extends AbstractCheck {
70
71 /**
72 * Warning message key.
73 */
74 public static final String MSG_KEY = "abbreviation.as.word";
75
76 /**
77 * The default value of "allowedAbbreviationLength" option.
78 */
79 private static final int DEFAULT_ALLOWED_ABBREVIATIONS_LENGTH = 3;
80
81 /**
82 * Indicate the number of consecutive capital letters allowed in
83 * targeted identifiers (abbreviations in the classes, interfaces, variables
84 * and methods names, ... ).
85 */
86 private int allowedAbbreviationLength =
87 DEFAULT_ALLOWED_ABBREVIATIONS_LENGTH;
88
89 /**
90 * Specify abbreviations that must be skipped for checking.
91 */
92 private Set<String> allowedAbbreviations = new HashSet<>();
93
94 /** Allow to skip variables with {@code final} modifier. */
95 private boolean ignoreFinal = true;
96
97 /** Allow to skip variables with {@code static} modifier. */
98 private boolean ignoreStatic = true;
99
100 /** Allow to skip variables with both {@code static} and {@code final} modifiers. */
101 private boolean ignoreStaticFinal = true;
102
103 /**
104 * Allow to ignore methods tagged with {@code @Override} annotation (that
105 * usually mean inherited name).
106 */
107 private boolean ignoreOverriddenMethods = true;
108
109 /**
110 * Creates a new {@code AbbreviationAsWordInNameCheck} instance.
111 */
112 public AbbreviationAsWordInNameCheck() {
113 // no code by default
114 }
115
116 /**
117 * Setter to allow to skip variables with {@code final} modifier.
118 *
119 * @param ignoreFinal
120 * Defines if ignore variables with 'final' modifier or not.
121 * @since 5.8
122 */
123 public void setIgnoreFinal(boolean ignoreFinal) {
124 this.ignoreFinal = ignoreFinal;
125 }
126
127 /**
128 * Setter to allow to skip variables with {@code static} modifier.
129 *
130 * @param ignoreStatic
131 * Defines if ignore variables with 'static' modifier or not.
132 * @since 5.8
133 */
134 public void setIgnoreStatic(boolean ignoreStatic) {
135 this.ignoreStatic = ignoreStatic;
136 }
137
138 /**
139 * Setter to allow to skip variables with both {@code static} and {@code final} modifiers.
140 *
141 * @param ignoreStaticFinal
142 * Defines if ignore variables with both 'static' and 'final' modifiers or not.
143 * @since 8.32
144 */
145 public void setIgnoreStaticFinal(boolean ignoreStaticFinal) {
146 this.ignoreStaticFinal = ignoreStaticFinal;
147 }
148
149 /**
150 * Setter to allow to ignore methods tagged with {@code @Override}
151 * annotation (that usually mean inherited name).
152 *
153 * @param ignoreOverriddenMethods
154 * Defines if ignore methods with "@Override" annotation or not.
155 * @since 5.8
156 */
157 public void setIgnoreOverriddenMethods(boolean ignoreOverriddenMethods) {
158 this.ignoreOverriddenMethods = ignoreOverriddenMethods;
159 }
160
161 /**
162 * Setter to indicate the number of consecutive capital letters allowed
163 * in targeted identifiers (abbreviations in the classes, interfaces,
164 * variables and methods names, ... ).
165 *
166 * @param allowedAbbreviationLength amount of allowed capital letters in
167 * abbreviation.
168 * @since 5.8
169 */
170 public void setAllowedAbbreviationLength(int allowedAbbreviationLength) {
171 this.allowedAbbreviationLength = allowedAbbreviationLength;
172 }
173
174 /**
175 * Setter to specify abbreviations that must be skipped for checking.
176 *
177 * @param allowedAbbreviations abbreviations that must be
178 * skipped from checking.
179 * @since 5.8
180 */
181 public void setAllowedAbbreviations(String... allowedAbbreviations) {
182 if (allowedAbbreviations != null) {
183 this.allowedAbbreviations =
184 Arrays.stream(allowedAbbreviations).collect(Collectors.toUnmodifiableSet());
185 }
186 }
187
188 @Override
189 public int[] getDefaultTokens() {
190 return new int[] {
191 TokenTypes.CLASS_DEF,
192 TokenTypes.INTERFACE_DEF,
193 TokenTypes.ENUM_DEF,
194 TokenTypes.ANNOTATION_DEF,
195 TokenTypes.ANNOTATION_FIELD_DEF,
196 TokenTypes.PARAMETER_DEF,
197 TokenTypes.VARIABLE_DEF,
198 TokenTypes.METHOD_DEF,
199 TokenTypes.PATTERN_VARIABLE_DEF,
200 TokenTypes.RECORD_DEF,
201 TokenTypes.RECORD_COMPONENT_DEF,
202 };
203 }
204
205 @Override
206 public int[] getAcceptableTokens() {
207 return new int[] {
208 TokenTypes.CLASS_DEF,
209 TokenTypes.INTERFACE_DEF,
210 TokenTypes.ENUM_DEF,
211 TokenTypes.ANNOTATION_DEF,
212 TokenTypes.ANNOTATION_FIELD_DEF,
213 TokenTypes.PARAMETER_DEF,
214 TokenTypes.VARIABLE_DEF,
215 TokenTypes.METHOD_DEF,
216 TokenTypes.ENUM_CONSTANT_DEF,
217 TokenTypes.PATTERN_VARIABLE_DEF,
218 TokenTypes.RECORD_DEF,
219 TokenTypes.RECORD_COMPONENT_DEF,
220 };
221 }
222
223 @Override
224 public int[] getRequiredTokens() {
225 return CommonUtil.EMPTY_INT_ARRAY;
226 }
227
228 @Override
229 public void visitToken(DetailAST ast) {
230 if (!isIgnoreSituation(ast)) {
231 final DetailAST nameAst = ast.findFirstToken(TokenTypes.IDENT);
232 final String typeName = nameAst.getText();
233
234 final String abbr = getDisallowedAbbreviation(typeName);
235 if (abbr != null) {
236 log(nameAst, MSG_KEY, typeName, allowedAbbreviationLength + 1);
237 }
238 }
239 }
240
241 /**
242 * Checks if it is an ignore situation.
243 *
244 * @param ast input DetailAST node.
245 * @return true if it is an ignore situation found for given input DetailAST
246 * node.
247 */
248 private boolean isIgnoreSituation(DetailAST ast) {
249 final DetailAST modifiers = ast.getFirstChild();
250
251 final boolean result;
252 if (ast.getType() == TokenTypes.VARIABLE_DEF) {
253 if (isInterfaceDeclaration(ast)) {
254 // field declarations in interface are static/final
255 result = ignoreStaticFinal;
256 }
257 else {
258 result = hasIgnoredModifiers(modifiers);
259 }
260 }
261 else if (ast.getType() == TokenTypes.METHOD_DEF) {
262 result = ignoreOverriddenMethods && hasOverrideAnnotation(modifiers);
263 }
264 else {
265 result = CheckUtil.isReceiverParameter(ast);
266 }
267 return result;
268 }
269
270 /**
271 * Checks if a variable is to be ignored based on its modifiers.
272 *
273 * @param modifiers modifiers of the variable to be checked
274 * @return true if there is a modifier to be ignored
275 */
276 private boolean hasIgnoredModifiers(DetailAST modifiers) {
277 final boolean isStatic = modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
278 final boolean isFinal = modifiers.findFirstToken(TokenTypes.FINAL) != null;
279 final boolean result;
280 if (isStatic && isFinal) {
281 result = ignoreStaticFinal;
282 }
283 else {
284 result = ignoreStatic && isStatic || ignoreFinal && isFinal;
285 }
286 return result;
287 }
288
289 /**
290 * Check that variable definition in interface or @interface definition.
291 *
292 * @param variableDefAst variable definition.
293 * @return true if variable definition(variableDefAst) is in interface
294 * or @interface definition.
295 */
296 private static boolean isInterfaceDeclaration(DetailAST variableDefAst) {
297 boolean result = false;
298 final DetailAST astBlock = variableDefAst.getParent();
299
300 if (astBlock.getType() != TokenTypes.COMPACT_COMPILATION_UNIT) {
301 final DetailAST astParent2 = astBlock.getParent();
302
303 if (astParent2.getType() == TokenTypes.INTERFACE_DEF
304 || astParent2.getType() == TokenTypes.ANNOTATION_DEF) {
305 result = true;
306 }
307 }
308 return result;
309 }
310
311 /**
312 * Checks that the method has "@Override" annotation.
313 *
314 * @param methodModifiersAST
315 * A DetailAST nod is related to the given method modifiers
316 * (MODIFIERS type).
317 * @return true if method has "@Override" annotation.
318 */
319 private static boolean hasOverrideAnnotation(DetailAST methodModifiersAST) {
320 boolean result = false;
321 for (DetailAST child : getChildren(methodModifiersAST)) {
322 final DetailAST annotationIdent = child.findFirstToken(TokenTypes.IDENT);
323
324 if (annotationIdent != null && "Override".equals(annotationIdent.getText())) {
325 result = true;
326 break;
327 }
328 }
329 return result;
330 }
331
332 /**
333 * Gets the disallowed abbreviation contained in given String.
334 *
335 * @param str
336 * the given String.
337 * @return the disallowed abbreviation contained in given String as a
338 * separate String.
339 */
340 private String getDisallowedAbbreviation(String str) {
341 int beginIndex = 0;
342 boolean abbrStarted = false;
343 String result = null;
344
345 for (int index = 0; index < str.length(); index++) {
346 final char symbol = str.charAt(index);
347
348 if (Character.isUpperCase(symbol)) {
349 if (!abbrStarted) {
350 abbrStarted = true;
351 beginIndex = index;
352 }
353 }
354 else if (abbrStarted) {
355 abbrStarted = false;
356
357 final int endIndex;
358 final int allowedLength;
359 if (symbol == '_') {
360 endIndex = index;
361 allowedLength = allowedAbbreviationLength + 1;
362 }
363 else {
364 endIndex = index - 1;
365 allowedLength = allowedAbbreviationLength;
366 }
367 result = getAbbreviationIfIllegal(str, beginIndex, endIndex, allowedLength);
368 if (result != null) {
369 break;
370 }
371 beginIndex = -1;
372 }
373 }
374 // if abbreviation at the end of name (example: scaleX)
375 if (abbrStarted) {
376 final int endIndex = str.length() - 1;
377 result = getAbbreviationIfIllegal(str, beginIndex, endIndex, allowedAbbreviationLength);
378 }
379 return result;
380 }
381
382 /**
383 * Get Abbreviation if it is illegal, where {@code beginIndex} and {@code endIndex} are
384 * inclusive indexes of a sequence of consecutive upper-case characters.
385 *
386 * @param str name
387 * @param beginIndex begin index
388 * @param endIndex end index
389 * @param allowedLength maximum allowed length for Abbreviation
390 * @return the abbreviation if it is bigger than required and not in the
391 * ignore list, otherwise {@code null}
392 */
393 private String getAbbreviationIfIllegal(String str, int beginIndex, int endIndex,
394 int allowedLength) {
395 String result = null;
396 final int abbrLength = endIndex - beginIndex;
397 if (abbrLength > allowedLength) {
398 final String abbr = getAbbreviation(str, beginIndex, endIndex);
399 if (!allowedAbbreviations.contains(abbr)) {
400 result = abbr;
401 }
402 }
403 return result;
404 }
405
406 /**
407 * Gets the abbreviation, where {@code beginIndex} and {@code endIndex} are
408 * inclusive indexes of a sequence of consecutive upper-case characters.
409 *
410 * <p>
411 * The character at {@code endIndex} is only included in the abbreviation if
412 * it is the last character in the string; otherwise it is usually the first
413 * capital in the next word.
414 * </p>
415 *
416 * <p>
417 * For example, {@code getAbbreviation("getXMLParser", 3, 6)} returns "XML"
418 * (not "XMLP"), and so does {@code getAbbreviation("parseXML", 5, 7)}.
419 * </p>
420 *
421 * @param str name
422 * @param beginIndex begin index
423 * @param endIndex end index
424 * @return the specified abbreviation
425 */
426 private static String getAbbreviation(String str, int beginIndex, int endIndex) {
427 final String result;
428 if (endIndex == str.length() - 1) {
429 result = str.substring(beginIndex);
430 }
431 else {
432 result = str.substring(beginIndex, endIndex);
433 }
434 return result;
435 }
436
437 /**
438 * Gets all the children which are one level below on the current DetailAST
439 * parent node.
440 *
441 * @param node
442 * Current parent node.
443 * @return The list of children one level below on the current parent node.
444 */
445 private static List<DetailAST> getChildren(final DetailAST node) {
446 final List<DetailAST> result = new ArrayList<>();
447 DetailAST curNode = node.getFirstChild();
448 while (curNode != null) {
449 result.add(curNode);
450 curNode = curNode.getNextSibling();
451 }
452 return result;
453 }
454
455 }