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.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 
226     @Test
227     public void testGetFirstNode() throws Exception {
228         final DetailAST classDef = getNodeFromFile(TokenTypes.CLASS_DEF);
229 
230         final DetailAST firstChild = classDef.getFirstChild().getFirstChild();
231         final DetailAST firstNode = CheckUtil.getFirstNode(classDef);
232         assertWithMessage("Invalid first node")
233             .that(firstNode)
234             .isEqualTo(firstChild);
235     }
236 
237     @Test
238     public void testGetFirstNode1() {
239         final DetailAstImpl child = new DetailAstImpl();
240         child.setLineNo(5);
241         child.setColumnNo(6);
242 
243         final DetailAstImpl root = new DetailAstImpl();
244         root.setLineNo(5);
245         root.setColumnNo(6);
246 
247         root.addChild(child);
248 
249         final DetailAST firstNode = CheckUtil.getFirstNode(root);
250         assertWithMessage("Unexpected node")
251             .that(firstNode)
252             .isEqualTo(root);
253     }
254 
255     @Test
256     public void testGetFirstNode2() {
257         final DetailAstImpl child = new DetailAstImpl();
258         child.setLineNo(6);
259         child.setColumnNo(5);
260 
261         final DetailAstImpl root = new DetailAstImpl();
262         root.setLineNo(5);
263         root.setColumnNo(6);
264 
265         root.addChild(child);
266 
267         final DetailAST firstNode = CheckUtil.getFirstNode(root);
268         assertWithMessage("Unexpected node")
269             .that(firstNode)
270             .isEqualTo(root);
271     }
272 
273     @Test
274     public void testParseDoubleFloatingPointValues() {
275         assertWithMessage("Invalid parse result")
276             .that(CheckUtil.parseDouble("-0.05f", TokenTypes.NUM_FLOAT))
277             .isEqualTo(-0.05);
278         assertWithMessage("Invalid parse result")
279             .that(CheckUtil.parseDouble("10.0", TokenTypes.NUM_DOUBLE))
280             .isEqualTo(10.0);
281         assertWithMessage("Invalid parse result")
282             .that(CheckUtil.parseDouble("1.23e3", TokenTypes.NUM_DOUBLE))
283             .isEqualTo(1230);
284         assertWithMessage("Invalid parse result")
285             .that(CheckUtil.parseDouble("-3.21E2", TokenTypes.NUM_DOUBLE))
286             .isEqualTo(-321);
287         assertWithMessage("Invalid parse result")
288             .that(CheckUtil.parseDouble("-0.0", TokenTypes.NUM_DOUBLE))
289             .isEqualTo(-0.0);
290         assertWithMessage("Invalid parse result")
291             .that(CheckUtil.parseDouble("NaN", TokenTypes.NUM_DOUBLE))
292             .isEqualTo(Double.NaN);
293     }
294 
295     @Test
296     public void testParseDoubleIntegerValues() {
297         assertWithMessage("Invalid parse result")
298             .that(CheckUtil.parseDouble("0L", TokenTypes.NUM_LONG))
299             .isEqualTo(0.0);
300         assertWithMessage("Invalid parse result")
301             .that(CheckUtil.parseDouble("0B101", TokenTypes.NUM_INT))
302             .isEqualTo(0b101);
303         assertWithMessage("Invalid parse result")
304             .that(CheckUtil.parseDouble("0b10001010001011010000101000101L", TokenTypes.NUM_LONG))
305             .isEqualTo(289_775_941);
306         assertWithMessage("Invalid parse result")
307             .that(CheckUtil.parseDouble("1", TokenTypes.NUM_INT))
308             .isEqualTo(1.0);
309         assertWithMessage("Invalid parse result")
310             .that(CheckUtil.parseDouble("8L", TokenTypes.NUM_LONG))
311             .isEqualTo(8.0);
312         assertWithMessage("Invalid parse result")
313             .that(CheckUtil.parseDouble("-21474836480", TokenTypes.NUM_LONG))
314             .isEqualTo(-2.147_483_648E10);
315         assertWithMessage("Invalid parse result")
316             .that(CheckUtil.parseDouble("-2", TokenTypes.NUM_INT))
317             .isEqualTo(-2);
318         assertWithMessage("Invalid parse result")
319             .that(CheckUtil.parseDouble("0xffffffff", TokenTypes.NUM_INT))
320             .isEqualTo(-1);
321         assertWithMessage("Invalid parse result")
322             .that(CheckUtil.parseDouble("0x0B63", TokenTypes.NUM_INT))
323             .isEqualTo(2915.0);
324         assertWithMessage("Invalid parse result")
325             .that(CheckUtil.parseDouble("21474836470", TokenTypes.NUM_LONG))
326             .isEqualTo(2.147_483_647E10);
327         assertWithMessage("Invalid parse result")
328             .that(CheckUtil.parseDouble("073l", TokenTypes.NUM_LONG))
329             .isEqualTo(59.0);
330     }
331 
332     @Test
333     public void testParseClassNames() {
334         final Set<String> actual = CheckUtil.parseClassNames(
335                 "I.am.class.name.with.dot.in.the.end.", "ClassOnly", "my.Class");
336         final Set<String> expected = new HashSet<>();
337         expected.add("I.am.class.name.with.dot.in.the.end.");
338         expected.add("ClassOnly");
339         expected.add("my.Class");
340         expected.add("Class");
341         assertWithMessage("Result is not expected")
342             .that(actual)
343             .isEqualTo(expected);
344     }
345 
346     @Test
347     public void testEqualsAvoidNullCheck() throws Exception {
348 
349         final String[] expected = {
350             "14:28: " + getCheckMessage(EqualsAvoidNullCheck.class, MSG_EQUALS_AVOID_NULL),
351             "21:17: " + getCheckMessage(EqualsAvoidNullCheck.class, MSG_EQUALS_AVOID_NULL),
352         };
353         verifyWithInlineConfigParser(
354                 getPath("InputCheckUtil1.java"), expected);
355     }
356 
357     @Test
358     public void testMultipleVariableDeclarationsCheck() throws Exception {
359         final String[] expected = {
360             "11:5: " + getCheckMessage(MultipleVariableDeclarationsCheck.class, MSG_MULTIPLE),
361             "14:5: " + getCheckMessage(MultipleVariableDeclarationsCheck.class, MSG_MULTIPLE),
362         };
363         verifyWithInlineConfigParser(
364                 getPath("InputCheckUtil2.java"),
365                expected);
366     }
367 
368     @Test
369     public void testNestedIfDepth() throws Exception {
370         final String[] expected = {
371             "26:17: " + getCheckMessage(NestedIfDepthCheck.class, MSG_KEY, 2, 1),
372             "52:17: " + getCheckMessage(NestedIfDepthCheck.class, MSG_KEY, 2, 1),
373         };
374         verifyWithInlineConfigParser(
375                 getPath("InputCheckUtil3.java"), expected);
376     }
377 
378     @Test
379     public void testJavaDocMethod() throws Exception {
380         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
381         verifyWithInlineConfigParser(
382                 getPath("InputCheckUtil4.java"), expected);
383     }
384 
385     @Test
386     public void testJavaDocMethod2() throws Exception {
387         final String[] expected = {
388             "14:25: " + getCheckMessage(JavadocMethodCheck.class,
389                   MSG_EXPECTED_TAG, "@param", "i"),
390         };
391         verifyWithInlineConfigParser(
392                 getPath("InputCheckUtil5.java"), expected);
393     }
394 
395     @Test
396     public void testJavadoc() throws Exception {
397         final String[] expected = {
398             "25:39: " + getCheckMessage(JavadocMethodCheck.class,
399                   MSG_EXPECTED_TAG, "@param", "i"),
400         };
401         verifyWithInlineConfigParser(
402                 getPath("InputCheckUtil7.java"), expected);
403     }
404 
405     private DetailAST getNodeFromFile(int type) throws Exception {
406         return getNode(JavaParser.parseFile(new File(getPath("InputCheckUtilTest.java")),
407             JavaParser.Options.WITH_COMMENTS), type);
408     }
409 
410     /**
411      * Retrieves the AST node from a specific file based on the specified token type.
412      *
413      * @param type The token type to search for in the file.
414      *             This parameter determines the type of AST node to retrieve.
415      * @param file The file from which the AST node should be retrieved.
416      * @return The AST node associated with the specified token type from the given file.
417      * @throws Exception If there's an issue reading or parsing the file.
418      */
419     public static DetailAST getNode(File file, int type) throws Exception {
420         return getNode(JavaParser.parseFile(file, JavaParser.Options.WITH_COMMENTS), type);
421     }
422 
423     /**
424      * Temporary java doc.
425      *
426      * @param root of type DetailAST
427      * @param type of type int
428      * @return call to get() from node
429      */
430     private static DetailAST getNode(DetailAST root, int type) {
431         final Optional<DetailAST> node = findTokenInAstByPredicate(root,
432             ast -> ast.getType() == type);
433 
434         assertWithMessage("Cannot find node of specified type: %s", type)
435             .that(node.isPresent())
436             .isTrue();
437 
438         return node.orElseThrow();
439     }
440 }