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