View Javadoc
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.utils;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck.MSG_EQUALS_AVOID_NULL;
24  import static com.puppycrawl.tools.checkstyle.checks.coding.MultipleVariableDeclarationsCheck.MSG_MULTIPLE;
25  import static com.puppycrawl.tools.checkstyle.checks.coding.NestedIfDepthCheck.MSG_KEY;
26  import static com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck.MSG_EXPECTED_TAG;
27  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.findTokenInAstByPredicate;
28  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.getExpectedThrowable;
29  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.isUtilsClassHasPrivateConstructor;
30  
31  import java.nio.file.Path;
32  import java.util.Arrays;
33  import java.util.HashSet;
34  import java.util.List;
35  import java.util.Optional;
36  import java.util.Set;
37  
38  import org.junit.jupiter.api.Test;
39  
40  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
41  import com.puppycrawl.tools.checkstyle.DetailAstImpl;
42  import com.puppycrawl.tools.checkstyle.JavaParser;
43  import com.puppycrawl.tools.checkstyle.api.DetailAST;
44  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
45  import com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck;
46  import com.puppycrawl.tools.checkstyle.checks.coding.MultipleVariableDeclarationsCheck;
47  import com.puppycrawl.tools.checkstyle.checks.coding.NestedIfDepthCheck;
48  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck;
49  import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
50  
51  public class CheckUtilTest extends AbstractModuleTestSupport {
52  
53      @Override
54      public String getPackageLocation() {
55          return "com/puppycrawl/tools/checkstyle/utils/checkutil";
56      }
57  
58      @Test
59      public void testIsProperUtilsClass() throws ReflectiveOperationException {
60          assertWithMessage("Constructor is not private")
61                  .that(isUtilsClassHasPrivateConstructor(CheckUtil.class))
62                  .isTrue();
63      }
64  
65      @Test
66      public void testParseDoubleWithIncorrectToken() {
67          final double parsedDouble = CheckUtil.parseDouble("1_02", TokenTypes.ASSIGN);
68          assertWithMessage("Invalid parse result")
69              .that(parsedDouble)
70              .isEqualTo(Double.NaN);
71      }
72  
73      @Test
74      public void testEquals() {
75          final DetailAstImpl litStatic = new DetailAstImpl();
76          litStatic.setType(TokenTypes.LITERAL_STATIC);
77  
78          final DetailAstImpl modifiers = new DetailAstImpl();
79          modifiers.setType(TokenTypes.MODIFIERS);
80          modifiers.addChild(litStatic);
81  
82          final DetailAstImpl metDef = new DetailAstImpl();
83          metDef.setType(TokenTypes.METHOD_DEF);
84          metDef.addChild(modifiers);
85  
86          assertWithMessage("Invalid result: ast is not equals method")
87                  .that(CheckUtil.isEqualsMethod(metDef))
88                  .isFalse();
89  
90          metDef.removeChildren();
91  
92          final DetailAstImpl metName = new DetailAstImpl();
93          metName.setType(TokenTypes.IDENT);
94          metName.setText("equals");
95          metDef.addChild(metName);
96  
97          final DetailAstImpl modifiers2 = new DetailAstImpl();
98          modifiers2.setType(TokenTypes.MODIFIERS);
99          metDef.addChild(modifiers2);
100 
101         final DetailAstImpl parameter1 = new DetailAstImpl();
102         final DetailAstImpl parameter2 = new DetailAstImpl();
103 
104         final DetailAstImpl parameters = new DetailAstImpl();
105         parameters.setType(TokenTypes.PARAMETERS);
106 
107         parameters.addChild(parameter2);
108 
109         parameters.addChild(parameter1);
110         metDef.addChild(parameters);
111 
112         assertWithMessage("Invalid result: ast is not equals method")
113                 .that(CheckUtil.isEqualsMethod(metDef))
114                 .isFalse();
115     }
116 
117     @Test
118     public void testGetAccessModifierFromModifiersTokenWrongTokenType() {
119         final DetailAstImpl modifiers = new DetailAstImpl();
120         modifiers.setType(TokenTypes.METHOD_DEF);
121 
122         final IllegalArgumentException exc =
123             getExpectedThrowable(IllegalArgumentException.class, () -> {
124                 CheckUtil.getAccessModifierFromModifiersToken(modifiers);
125             });
126 
127         final String expectedExceptionMsg = "expected non-null AST-token with type 'MODIFIERS'";
128         final String actualExceptionMsg = exc.getMessage();
129         assertWithMessage("Invalid exception message")
130             .that(actualExceptionMsg)
131             .isEqualTo(expectedExceptionMsg);
132     }
133 
134     @Test
135     public void testGetTypeParameterNames() throws Exception {
136         final DetailAST parameterizedClassNode = getNodeFromFile(TokenTypes.CLASS_DEF);
137         final List<String> expected = Arrays.asList("V", "C");
138         final List<String> actual = CheckUtil.getTypeParameterNames(parameterizedClassNode);
139 
140         assertWithMessage("Invalid type parameters")
141             .that(actual)
142             .isEqualTo(expected);
143     }
144 
145     @Test
146     public void testGetTypeParameterNamesFromNonGenericClass() throws Exception {
147         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
148         verifyWithInlineConfigParser(
149                 getPath("InputCheckUtilJavadocTypeNonGenericClass.java"), expected);
150     }
151 
152     @Test
153     public void testGetTypeParameters() throws Exception {
154         final DetailAST parameterizedClassNode = getNodeFromFile(TokenTypes.CLASS_DEF);
155         final DetailAST firstTypeParameter =
156                 getNode(parameterizedClassNode, TokenTypes.TYPE_PARAMETER);
157         final List<DetailAST> expected = Arrays.asList(firstTypeParameter,
158                 firstTypeParameter.getNextSibling().getNextSibling());
159         final List<DetailAST> actual = CheckUtil.getTypeParameters(parameterizedClassNode);
160 
161         assertWithMessage("Invalid type parameters")
162             .that(actual)
163             .isEqualTo(expected);
164     }
165 
166     @Test
167     public void testIsEqualsMethod() throws Exception {
168         final DetailAST equalsMethodNode = getNodeFromFile(TokenTypes.METHOD_DEF);
169         final DetailAST someOtherMethod = equalsMethodNode.getNextSibling();
170 
171         assertWithMessage("Invalid result: AST provided is not equals method")
172                 .that(CheckUtil.isEqualsMethod(equalsMethodNode))
173                 .isTrue();
174         assertWithMessage("Invalid result: AST provided is equals method")
175                 .that(CheckUtil.isEqualsMethod(someOtherMethod))
176                 .isFalse();
177     }
178 
179     @Test
180     public void testIsNonVoidMethod() throws Exception {
181         final DetailAST nonVoidMethod = getNodeFromFile(TokenTypes.METHOD_DEF);
182         final DetailAST voidMethod = nonVoidMethod.getNextSibling();
183 
184         assertWithMessage("Invalid result: AST provided is void method")
185                 .that(CheckUtil.isNonVoidMethod(nonVoidMethod))
186                 .isTrue();
187         assertWithMessage("Invalid result: AST provided is non void method")
188                 .that(CheckUtil.isNonVoidMethod(voidMethod))
189                 .isFalse();
190     }
191 
192     @Test
193     public void testGetAccessModifierFromModifiersToken() throws Exception {
194         final DetailAST interfaceDef = getNodeFromFile(TokenTypes.INTERFACE_DEF);
195         final AccessModifierOption modifierInterface = CheckUtil
196                 .getAccessModifierFromModifiersToken(interfaceDef
197                         .findFirstToken(TokenTypes.OBJBLOCK)
198                         .findFirstToken(TokenTypes.METHOD_DEF));
199         assertWithMessage("Invalid access modifier")
200             .that(modifierInterface)
201             .isEqualTo(AccessModifierOption.PUBLIC);
202 
203         final DetailAST privateVariable = getNodeFromFile(TokenTypes.VARIABLE_DEF);
204         final AccessModifierOption modifierPrivate =
205                 CheckUtil.getAccessModifierFromModifiersToken(privateVariable);
206         assertWithMessage("Invalid access modifier")
207             .that(modifierPrivate)
208             .isEqualTo(AccessModifierOption.PRIVATE);
209 
210         final DetailAST protectedVariable = privateVariable.getNextSibling();
211         final AccessModifierOption modifierProtected =
212                 CheckUtil.getAccessModifierFromModifiersToken(protectedVariable);
213         assertWithMessage("Invalid access modifier")
214             .that(modifierProtected)
215             .isEqualTo(AccessModifierOption.PROTECTED);
216 
217         final DetailAST publicVariable = protectedVariable.getNextSibling();
218         final AccessModifierOption modifierPublic =
219                 CheckUtil.getAccessModifierFromModifiersToken(publicVariable);
220         assertWithMessage("Invalid access modifier")
221             .that(modifierPublic)
222             .isEqualTo(AccessModifierOption.PUBLIC);
223 
224         final DetailAST packageVariable = publicVariable.getNextSibling();
225         final AccessModifierOption modifierPackage =
226                 CheckUtil.getAccessModifierFromModifiersToken(packageVariable);
227         assertWithMessage("Invalid access modifier")
228             .that(modifierPackage)
229             .isEqualTo(AccessModifierOption.PACKAGE);
230 
231         final DetailAST enumConstantDefinition = getNodeFromFile(TokenTypes.ENUM_CONSTANT_DEF);
232         final AccessModifierOption modifierEnumConstant = CheckUtil
233                 .getAccessModifierFromModifiersToken(enumConstantDefinition);
234         assertWithMessage("Invalid access modifier")
235                 .that(modifierEnumConstant)
236                 .isEqualTo(AccessModifierOption.PUBLIC);
237     }
238 
239     @Test
240     public void testGetFirstNode() throws Exception {
241         final DetailAST classDef = getNodeFromFile(TokenTypes.CLASS_DEF);
242 
243         final DetailAST firstChild = classDef.getFirstChild().getFirstChild();
244         final DetailAST firstNode = CheckUtil.getFirstNode(classDef);
245         assertWithMessage("Invalid first node")
246             .that(firstNode)
247             .isEqualTo(firstChild);
248     }
249 
250     @Test
251     public void testGetFirstNode1() {
252         final DetailAstImpl child = new DetailAstImpl();
253         child.setLineNo(5);
254         child.setColumnNo(6);
255 
256         final DetailAstImpl root = new DetailAstImpl();
257         root.setLineNo(5);
258         root.setColumnNo(6);
259 
260         root.addChild(child);
261 
262         final DetailAST firstNode = CheckUtil.getFirstNode(root);
263         assertWithMessage("Unexpected node")
264             .that(firstNode)
265             .isEqualTo(root);
266     }
267 
268     @Test
269     public void testGetFirstNode2() {
270         final DetailAstImpl child = new DetailAstImpl();
271         child.setLineNo(6);
272         child.setColumnNo(5);
273 
274         final DetailAstImpl root = new DetailAstImpl();
275         root.setLineNo(5);
276         root.setColumnNo(6);
277 
278         root.addChild(child);
279 
280         final DetailAST firstNode = CheckUtil.getFirstNode(root);
281         assertWithMessage("Unexpected node")
282             .that(firstNode)
283             .isEqualTo(root);
284     }
285 
286     @Test
287     public void testParseDoubleFloatingPointValues() {
288         assertWithMessage("Invalid parse result")
289             .that(CheckUtil.parseDouble("-0.05f", TokenTypes.NUM_FLOAT))
290             .isEqualTo(-0.05);
291         assertWithMessage("Invalid parse result")
292             .that(CheckUtil.parseDouble("10.0", TokenTypes.NUM_DOUBLE))
293             .isEqualTo(10.0);
294         assertWithMessage("Invalid parse result")
295             .that(CheckUtil.parseDouble("1.23e3", TokenTypes.NUM_DOUBLE))
296             .isEqualTo(1230);
297         assertWithMessage("Invalid parse result")
298             .that(CheckUtil.parseDouble("-3.21E2", TokenTypes.NUM_DOUBLE))
299             .isEqualTo(-321);
300         assertWithMessage("Invalid parse result")
301             .that(CheckUtil.parseDouble("-0.0", TokenTypes.NUM_DOUBLE))
302             .isEqualTo(-0.0);
303         assertWithMessage("Invalid parse result")
304             .that(CheckUtil.parseDouble("NaN", TokenTypes.NUM_DOUBLE))
305             .isEqualTo(Double.NaN);
306     }
307 
308     @Test
309     public void testParseDoubleIntegerValues() {
310         assertWithMessage("Invalid parse result")
311             .that(CheckUtil.parseDouble("0L", TokenTypes.NUM_LONG))
312             .isEqualTo(0.0);
313         assertWithMessage("Invalid parse result")
314             .that(CheckUtil.parseDouble("0B101", TokenTypes.NUM_INT))
315             .isEqualTo(0b101);
316         assertWithMessage("Invalid parse result")
317             .that(CheckUtil.parseDouble("0b10001010001011010000101000101L", TokenTypes.NUM_LONG))
318             .isEqualTo(289_775_941);
319         assertWithMessage("Invalid parse result")
320             .that(CheckUtil.parseDouble("1", TokenTypes.NUM_INT))
321             .isEqualTo(1.0);
322         assertWithMessage("Invalid parse result")
323             .that(CheckUtil.parseDouble("8L", TokenTypes.NUM_LONG))
324             .isEqualTo(8.0);
325         assertWithMessage("Invalid parse result")
326             .that(CheckUtil.parseDouble("-21474836480", TokenTypes.NUM_LONG))
327             .isEqualTo(-2.147_483_648e10);
328         assertWithMessage("Invalid parse result")
329             .that(CheckUtil.parseDouble("-2", TokenTypes.NUM_INT))
330             .isEqualTo(-2);
331         assertWithMessage("Invalid parse result")
332             .that(CheckUtil.parseDouble("0xffffffff", TokenTypes.NUM_INT))
333             .isEqualTo(-1);
334         assertWithMessage("Invalid parse result")
335             .that(CheckUtil.parseDouble("0x0B63", TokenTypes.NUM_INT))
336             .isEqualTo(2915.0);
337         assertWithMessage("Invalid parse result")
338             .that(CheckUtil.parseDouble("21474836470", TokenTypes.NUM_LONG))
339             .isEqualTo(2.147_483_647e10);
340         assertWithMessage("Invalid parse result")
341             .that(CheckUtil.parseDouble("073l", TokenTypes.NUM_LONG))
342             .isEqualTo(59.0);
343     }
344 
345     @Test
346     public void testParseClassNames() {
347         final Set<String> actual = CheckUtil.parseClassNames(
348                 "I.am.class.name.with.dot.in.the.end.", "ClassOnly", "my.Class");
349         final Set<String> expected = new HashSet<>();
350         expected.add("I.am.class.name.with.dot.in.the.end.");
351         expected.add("ClassOnly");
352         expected.add("my.Class");
353         expected.add("Class");
354         assertWithMessage("Result is not expected")
355             .that(actual)
356             .isEqualTo(expected);
357     }
358 
359     @Test
360     public void testEqualsAvoidNullCheck() throws Exception {
361 
362         final String[] expected = {
363             "14:28: " + getCheckMessage(EqualsAvoidNullCheck.class, MSG_EQUALS_AVOID_NULL),
364             "21:17: " + getCheckMessage(EqualsAvoidNullCheck.class, MSG_EQUALS_AVOID_NULL),
365         };
366         verifyWithInlineConfigParser(
367                 getPath("InputCheckUtil1.java"), expected);
368     }
369 
370     @Test
371     public void testMultipleVariableDeclarationsCheck() throws Exception {
372         final String[] expected = {
373             "11:5: " + getCheckMessage(MultipleVariableDeclarationsCheck.class, MSG_MULTIPLE),
374             "14:5: " + getCheckMessage(MultipleVariableDeclarationsCheck.class, MSG_MULTIPLE),
375         };
376         verifyWithInlineConfigParser(
377                 getPath("InputCheckUtil2.java"),
378                expected);
379     }
380 
381     @Test
382     public void testNestedIfDepth() throws Exception {
383         final String[] expected = {
384             "26:17: " + getCheckMessage(NestedIfDepthCheck.class, MSG_KEY, 2, 1),
385             "52:17: " + getCheckMessage(NestedIfDepthCheck.class, MSG_KEY, 2, 1),
386         };
387         verifyWithInlineConfigParser(
388                 getPath("InputCheckUtil3.java"), expected);
389     }
390 
391     @Test
392     public void testJavaDocMethod() throws Exception {
393         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
394         verifyWithInlineConfigParser(
395                 getPath("InputCheckUtil4.java"), expected);
396     }
397 
398     @Test
399     public void testJavaDocMethod2() throws Exception {
400         final String[] expected = {
401             "21:25: " + getCheckMessage(JavadocMethodCheck.class,
402                   MSG_EXPECTED_TAG, "@param", "i"),
403         };
404         verifyWithInlineConfigParser(
405                 getPath("InputCheckUtil5.java"), expected);
406     }
407 
408     @Test
409     public void testJavadoc() throws Exception {
410         final String[] expected = {
411             "26:39: " + getCheckMessage(JavadocMethodCheck.class,
412                   MSG_EXPECTED_TAG, "@param", "i"),
413         };
414         verifyWithInlineConfigParser(
415                 getPath("InputCheckUtil7.java"), expected);
416     }
417 
418     private DetailAST getNodeFromFile(int type) throws Exception {
419         final Path path = Path.of(getPath("InputCheckUtilTest.java"));
420         return getNode(JavaParser.parseFile(path.toFile(), JavaParser.Options.WITH_COMMENTS), type);
421     }
422 
423     /**
424      * Retrieves the AST node from a specific file based on the specified token type.
425      *
426      * @param filePath The file from which the AST node should be retrieved.
427      * @param type The token type to search for in the file.
428      *             This parameter determines the type of AST node to retrieve.
429      * @return The AST node associated with the specified token type from the given file.
430      * @throws Exception If there's an issue reading or parsing the file.
431      */
432     public static DetailAST getNode(Path filePath, int type) throws Exception {
433         return getNode(JavaParser.parseFile(filePath.toFile(),
434                 JavaParser.Options.WITH_COMMENTS), type);
435     }
436 
437     /**
438      * Temporary java doc.
439      *
440      * @param root of type DetailAST
441      * @param type of type int
442      * @return call to get() from node
443      */
444     private static DetailAST getNode(DetailAST root, int type) {
445         final Optional<DetailAST> node = findTokenInAstByPredicate(root,
446             ast -> ast.getType() == type);
447 
448         assertWithMessage("Cannot find node of specified type: %s", type)
449             .that(node.isPresent())
450             .isTrue();
451 
452         return node.orElseThrow();
453     }
454 
455     @Test
456     public void testPackageInfo() {
457         assertWithMessage("Expected isPackageInfo() to return false for ('/')")
458                 .that(CheckUtil.isPackageInfo("/"))
459                 .isFalse();
460 
461         assertWithMessage("Expected isPackageInfo() to return true for package-info.java file")
462                 .that(CheckUtil.isPackageInfo("src/main/java/com/example/package-info.java"))
463                 .isTrue();
464 
465         assertWithMessage("Expected isPackageInfo() to return false for regular Java file")
466                 .that(CheckUtil.isPackageInfo("src/main/java/com/example/SomeClass.java"))
467                 .isFalse();
468     }
469 
470 }