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 testGetTypeParameters() throws Exception {
147         final DetailAST parameterizedClassNode = getNodeFromFile(TokenTypes.CLASS_DEF);
148         final DetailAST firstTypeParameter =
149                 getNode(parameterizedClassNode, TokenTypes.TYPE_PARAMETER);
150         final List<DetailAST> expected = Arrays.asList(firstTypeParameter,
151                 firstTypeParameter.getNextSibling().getNextSibling());
152         final List<DetailAST> actual = CheckUtil.getTypeParameters(parameterizedClassNode);
153 
154         assertWithMessage("Invalid type parameters")
155             .that(actual)
156             .isEqualTo(expected);
157     }
158 
159     @Test
160     public void testIsEqualsMethod() throws Exception {
161         final DetailAST equalsMethodNode = getNodeFromFile(TokenTypes.METHOD_DEF);
162         final DetailAST someOtherMethod = equalsMethodNode.getNextSibling();
163 
164         assertWithMessage("Invalid result: AST provided is not equals method")
165                 .that(CheckUtil.isEqualsMethod(equalsMethodNode))
166                 .isTrue();
167         assertWithMessage("Invalid result: AST provided is equals method")
168                 .that(CheckUtil.isEqualsMethod(someOtherMethod))
169                 .isFalse();
170     }
171 
172     @Test
173     public void testIsNonVoidMethod() throws Exception {
174         final DetailAST nonVoidMethod = getNodeFromFile(TokenTypes.METHOD_DEF);
175         final DetailAST voidMethod = nonVoidMethod.getNextSibling();
176 
177         assertWithMessage("Invalid result: AST provided is void method")
178                 .that(CheckUtil.isNonVoidMethod(nonVoidMethod))
179                 .isTrue();
180         assertWithMessage("Invalid result: AST provided is non void method")
181                 .that(CheckUtil.isNonVoidMethod(voidMethod))
182                 .isFalse();
183     }
184 
185     @Test
186     public void testGetAccessModifierFromModifiersToken() throws Exception {
187         final DetailAST interfaceDef = getNodeFromFile(TokenTypes.INTERFACE_DEF);
188         final AccessModifierOption modifierInterface = CheckUtil
189                 .getAccessModifierFromModifiersToken(interfaceDef
190                         .findFirstToken(TokenTypes.OBJBLOCK)
191                         .findFirstToken(TokenTypes.METHOD_DEF));
192         assertWithMessage("Invalid access modifier")
193             .that(modifierInterface)
194             .isEqualTo(AccessModifierOption.PUBLIC);
195 
196         final DetailAST privateVariable = getNodeFromFile(TokenTypes.VARIABLE_DEF);
197         final AccessModifierOption modifierPrivate =
198                 CheckUtil.getAccessModifierFromModifiersToken(privateVariable);
199         assertWithMessage("Invalid access modifier")
200             .that(modifierPrivate)
201             .isEqualTo(AccessModifierOption.PRIVATE);
202 
203         final DetailAST protectedVariable = privateVariable.getNextSibling();
204         final AccessModifierOption modifierProtected =
205                 CheckUtil.getAccessModifierFromModifiersToken(protectedVariable);
206         assertWithMessage("Invalid access modifier")
207             .that(modifierProtected)
208             .isEqualTo(AccessModifierOption.PROTECTED);
209 
210         final DetailAST publicVariable = protectedVariable.getNextSibling();
211         final AccessModifierOption modifierPublic =
212                 CheckUtil.getAccessModifierFromModifiersToken(publicVariable);
213         assertWithMessage("Invalid access modifier")
214             .that(modifierPublic)
215             .isEqualTo(AccessModifierOption.PUBLIC);
216 
217         final DetailAST packageVariable = publicVariable.getNextSibling();
218         final AccessModifierOption modifierPackage =
219                 CheckUtil.getAccessModifierFromModifiersToken(packageVariable);
220         assertWithMessage("Invalid access modifier")
221             .that(modifierPackage)
222             .isEqualTo(AccessModifierOption.PACKAGE);
223 
224         final DetailAST enumConstantDefinition = getNodeFromFile(TokenTypes.ENUM_CONSTANT_DEF);
225         final AccessModifierOption modifierEnumConstant = CheckUtil
226                 .getAccessModifierFromModifiersToken(enumConstantDefinition);
227         assertWithMessage("Invalid access modifier")
228                 .that(modifierEnumConstant)
229                 .isEqualTo(AccessModifierOption.PUBLIC);
230     }
231 
232     @Test
233     public void testGetFirstNode() throws Exception {
234         final DetailAST classDef = getNodeFromFile(TokenTypes.CLASS_DEF);
235 
236         final DetailAST firstChild = classDef.getFirstChild().getFirstChild();
237         final DetailAST firstNode = CheckUtil.getFirstNode(classDef);
238         assertWithMessage("Invalid first node")
239             .that(firstNode)
240             .isEqualTo(firstChild);
241     }
242 
243     @Test
244     public void testGetFirstNode1() {
245         final DetailAstImpl child = new DetailAstImpl();
246         child.setLineNo(5);
247         child.setColumnNo(6);
248 
249         final DetailAstImpl root = new DetailAstImpl();
250         root.setLineNo(5);
251         root.setColumnNo(6);
252 
253         root.addChild(child);
254 
255         final DetailAST firstNode = CheckUtil.getFirstNode(root);
256         assertWithMessage("Unexpected node")
257             .that(firstNode)
258             .isEqualTo(root);
259     }
260 
261     @Test
262     public void testGetFirstNode2() {
263         final DetailAstImpl child = new DetailAstImpl();
264         child.setLineNo(6);
265         child.setColumnNo(5);
266 
267         final DetailAstImpl root = new DetailAstImpl();
268         root.setLineNo(5);
269         root.setColumnNo(6);
270 
271         root.addChild(child);
272 
273         final DetailAST firstNode = CheckUtil.getFirstNode(root);
274         assertWithMessage("Unexpected node")
275             .that(firstNode)
276             .isEqualTo(root);
277     }
278 
279     @Test
280     public void testParseDoubleFloatingPointValues() {
281         assertWithMessage("Invalid parse result")
282             .that(CheckUtil.parseDouble("-0.05f", TokenTypes.NUM_FLOAT))
283             .isEqualTo(-0.05);
284         assertWithMessage("Invalid parse result")
285             .that(CheckUtil.parseDouble("10.0", TokenTypes.NUM_DOUBLE))
286             .isEqualTo(10.0);
287         assertWithMessage("Invalid parse result")
288             .that(CheckUtil.parseDouble("1.23e3", TokenTypes.NUM_DOUBLE))
289             .isEqualTo(1230);
290         assertWithMessage("Invalid parse result")
291             .that(CheckUtil.parseDouble("-3.21E2", TokenTypes.NUM_DOUBLE))
292             .isEqualTo(-321);
293         assertWithMessage("Invalid parse result")
294             .that(CheckUtil.parseDouble("-0.0", TokenTypes.NUM_DOUBLE))
295             .isEqualTo(-0.0);
296         assertWithMessage("Invalid parse result")
297             .that(CheckUtil.parseDouble("NaN", TokenTypes.NUM_DOUBLE))
298             .isEqualTo(Double.NaN);
299     }
300 
301     @Test
302     public void testParseDoubleIntegerValues() {
303         assertWithMessage("Invalid parse result")
304             .that(CheckUtil.parseDouble("0L", TokenTypes.NUM_LONG))
305             .isEqualTo(0.0);
306         assertWithMessage("Invalid parse result")
307             .that(CheckUtil.parseDouble("0B101", TokenTypes.NUM_INT))
308             .isEqualTo(0b101);
309         assertWithMessage("Invalid parse result")
310             .that(CheckUtil.parseDouble("0b10001010001011010000101000101L", TokenTypes.NUM_LONG))
311             .isEqualTo(289_775_941);
312         assertWithMessage("Invalid parse result")
313             .that(CheckUtil.parseDouble("1", TokenTypes.NUM_INT))
314             .isEqualTo(1.0);
315         assertWithMessage("Invalid parse result")
316             .that(CheckUtil.parseDouble("8L", TokenTypes.NUM_LONG))
317             .isEqualTo(8.0);
318         assertWithMessage("Invalid parse result")
319             .that(CheckUtil.parseDouble("-21474836480", TokenTypes.NUM_LONG))
320             .isEqualTo(-2.147_483_648e10);
321         assertWithMessage("Invalid parse result")
322             .that(CheckUtil.parseDouble("-2", TokenTypes.NUM_INT))
323             .isEqualTo(-2);
324         assertWithMessage("Invalid parse result")
325             .that(CheckUtil.parseDouble("0xffffffff", TokenTypes.NUM_INT))
326             .isEqualTo(-1);
327         assertWithMessage("Invalid parse result")
328             .that(CheckUtil.parseDouble("0x0B63", TokenTypes.NUM_INT))
329             .isEqualTo(2915.0);
330         assertWithMessage("Invalid parse result")
331             .that(CheckUtil.parseDouble("21474836470", TokenTypes.NUM_LONG))
332             .isEqualTo(2.147_483_647e10);
333         assertWithMessage("Invalid parse result")
334             .that(CheckUtil.parseDouble("073l", TokenTypes.NUM_LONG))
335             .isEqualTo(59.0);
336     }
337 
338     @Test
339     public void testParseClassNames() {
340         final Set<String> actual = CheckUtil.parseClassNames(
341                 "I.am.class.name.with.dot.in.the.end.", "ClassOnly", "my.Class");
342         final Set<String> expected = new HashSet<>();
343         expected.add("I.am.class.name.with.dot.in.the.end.");
344         expected.add("ClassOnly");
345         expected.add("my.Class");
346         expected.add("Class");
347         assertWithMessage("Result is not expected")
348             .that(actual)
349             .isEqualTo(expected);
350     }
351 
352     @Test
353     public void testEqualsAvoidNullCheck() throws Exception {
354 
355         final String[] expected = {
356             "14:28: " + getCheckMessage(EqualsAvoidNullCheck.class, MSG_EQUALS_AVOID_NULL),
357             "21:17: " + getCheckMessage(EqualsAvoidNullCheck.class, MSG_EQUALS_AVOID_NULL),
358         };
359         verifyWithInlineConfigParser(
360                 getPath("InputCheckUtil1.java"), expected);
361     }
362 
363     @Test
364     public void testMultipleVariableDeclarationsCheck() throws Exception {
365         final String[] expected = {
366             "11:5: " + getCheckMessage(MultipleVariableDeclarationsCheck.class, MSG_MULTIPLE),
367             "14:5: " + getCheckMessage(MultipleVariableDeclarationsCheck.class, MSG_MULTIPLE),
368         };
369         verifyWithInlineConfigParser(
370                 getPath("InputCheckUtil2.java"),
371                expected);
372     }
373 
374     @Test
375     public void testNestedIfDepth() throws Exception {
376         final String[] expected = {
377             "26:17: " + getCheckMessage(NestedIfDepthCheck.class, MSG_KEY, 2, 1),
378             "52:17: " + getCheckMessage(NestedIfDepthCheck.class, MSG_KEY, 2, 1),
379         };
380         verifyWithInlineConfigParser(
381                 getPath("InputCheckUtil3.java"), expected);
382     }
383 
384     @Test
385     public void testJavaDocMethod() throws Exception {
386         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
387         verifyWithInlineConfigParser(
388                 getPath("InputCheckUtil4.java"), expected);
389     }
390 
391     @Test
392     public void testJavaDocMethod2() throws Exception {
393         final String[] expected = {
394             "20:25: " + getCheckMessage(JavadocMethodCheck.class,
395                   MSG_EXPECTED_TAG, "@param", "i"),
396         };
397         verifyWithInlineConfigParser(
398                 getPath("InputCheckUtil5.java"), expected);
399     }
400 
401     @Test
402     public void testJavadoc() throws Exception {
403         final String[] expected = {
404             "26:39: " + getCheckMessage(JavadocMethodCheck.class,
405                   MSG_EXPECTED_TAG, "@param", "i"),
406         };
407         verifyWithInlineConfigParser(
408                 getPath("InputCheckUtil7.java"), expected);
409     }
410 
411     private DetailAST getNodeFromFile(int type) throws Exception {
412         final Path path = Path.of(getPath("InputCheckUtilTest.java"));
413         return getNode(JavaParser.parseFile(path.toFile(), JavaParser.Options.WITH_COMMENTS), type);
414     }
415 
416     /**
417      * Retrieves the AST node from a specific file based on the specified token type.
418      *
419      * @param type The token type to search for in the file.
420      *             This parameter determines the type of AST node to retrieve.
421      * @param filePath The file from which the AST node should be retrieved.
422      * @return The AST node associated with the specified token type from the given file.
423      * @throws Exception If there's an issue reading or parsing the file.
424      */
425     public static DetailAST getNode(Path filePath, int type) throws Exception {
426         return getNode(JavaParser.parseFile(filePath.toFile(),
427                 JavaParser.Options.WITH_COMMENTS), type);
428     }
429 
430     /**
431      * Temporary java doc.
432      *
433      * @param root of type DetailAST
434      * @param type of type int
435      * @return call to get() from node
436      */
437     private static DetailAST getNode(DetailAST root, int type) {
438         final Optional<DetailAST> node = findTokenInAstByPredicate(root,
439             ast -> ast.getType() == type);
440 
441         assertWithMessage("Cannot find node of specified type: %s", type)
442             .that(node.isPresent())
443             .isTrue();
444 
445         return node.orElseThrow();
446     }
447 
448     @Test
449     public void testPackageInfo() {
450         final boolean result = CheckUtil.isPackageInfo("/");
451 
452         assertWithMessage("Expected isPackageInfo() to return false for ('/')")
453                 .that(result)
454                 .isFalse();
455     }
456 
457 }