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.checks.metrics;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.checks.metrics.ClassFanOutComplexityCheck.MSG_KEY;
24  
25  import java.io.File;
26  import java.util.Collection;
27  import java.util.Map;
28  import java.util.Optional;
29  
30  import org.junit.jupiter.api.Test;
31  
32  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
33  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
34  import com.puppycrawl.tools.checkstyle.JavaParser;
35  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
36  import com.puppycrawl.tools.checkstyle.api.DetailAST;
37  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
38  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
39  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
40  
41  public class ClassFanOutComplexityCheckTest extends AbstractModuleTestSupport {
42  
43      @Override
44      protected String getPackageLocation() {
45          return "com/puppycrawl/tools/checkstyle/checks/metrics/classfanoutcomplexity";
46      }
47  
48      @Test
49      public void test() throws Exception {
50  
51          final String[] expected = {
52              "27:1: " + getCheckMessage(MSG_KEY, 3, 0),
53              "59:1: " + getCheckMessage(MSG_KEY, 1, 0),
54          };
55  
56          verifyWithInlineConfigParser(
57                  getPath("InputClassFanOutComplexity.java"), expected);
58      }
59  
60      @Test
61      public void testExcludedPackagesDirectPackages() throws Exception {
62          final String[] expected = {
63              "29:1: " + getCheckMessage(MSG_KEY, 2, 0),
64          };
65  
66          verifyWithInlineConfigParser(
67                  getPath("InputClassFanOutComplexityExcludedPackagesDirectPackages.java"), expected);
68      }
69  
70      @Test
71      public void testExcludedPackagesCommonPackages() throws Exception {
72          final String[] expected = {
73              "28:1: " + getCheckMessage(MSG_KEY, 2, 0),
74              "32:5: " + getCheckMessage(MSG_KEY, 2, 0),
75              "38:1: " + getCheckMessage(MSG_KEY, 1, 0),
76          };
77          verifyWithInlineConfigParser(
78                  getPath("InputClassFanOutComplexityExcludedPackagesCommonPackage.java"), expected);
79      }
80  
81      @Test
82      public void testExcludedPackagesCommonPackagesWithEndingDot() throws Exception {
83          final DefaultConfiguration checkConfig =
84              createModuleConfig(ClassFanOutComplexityCheck.class);
85  
86          checkConfig.addProperty("max", "0");
87          checkConfig.addProperty("excludedPackages",
88              "com.puppycrawl.tools.checkstyle.checks.metrics.inputs.a.");
89  
90          try {
91              createChecker(checkConfig);
92              assertWithMessage("exception expected").fail();
93          }
94          catch (CheckstyleException ex) {
95              assertWithMessage("Invalid exception message")
96                  .that(ex.getMessage())
97                  .isEqualTo("cannot initialize module com.puppycrawl.tools.checkstyle.TreeWalker - "
98                      + "cannot initialize module com.puppycrawl.tools.checkstyle.checks."
99                      + "metrics.ClassFanOutComplexityCheck - "
100                     + "Cannot set property 'excludedPackages' to "
101                     + "'com.puppycrawl.tools.checkstyle.checks.metrics.inputs.a.'");
102             assertWithMessage("Invalid exception message,")
103                 .that(ex.getCause().getCause().getCause().getCause().getMessage())
104                 .isEqualTo("the following values are not valid identifiers: ["
105                             + "com.puppycrawl.tools.checkstyle.checks.metrics.inputs.a.]");
106         }
107     }
108 
109     @Test
110     public void testExcludedPackagesAllIgnored() throws Exception {
111         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
112         verifyWithInlineConfigParser(
113                 getPath("InputClassFanOutComplexityExcludedPackagesAllIgnored.java"), expected);
114     }
115 
116     @Test
117     public void test15() throws Exception {
118 
119         final String[] expected = {
120             "29:1: " + getCheckMessage(MSG_KEY, 1, 0),
121         };
122 
123         verifyWithInlineConfigParser(
124                 getPath("InputClassFanOutComplexity15Extensions.java"), expected);
125     }
126 
127     @Test
128     public void testDefaultConfiguration() throws Exception {
129         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
130         verifyWithInlineConfigParser(
131                 getPath("InputClassFanOutComplexity2.java"), expected);
132     }
133 
134     @Test
135     public void testGetAcceptableTokens() {
136         final ClassFanOutComplexityCheck classFanOutComplexityCheckObj =
137             new ClassFanOutComplexityCheck();
138         final int[] actual = classFanOutComplexityCheckObj.getAcceptableTokens();
139         final int[] expected = {
140             TokenTypes.PACKAGE_DEF,
141             TokenTypes.IMPORT,
142             TokenTypes.CLASS_DEF,
143             TokenTypes.EXTENDS_CLAUSE,
144             TokenTypes.IMPLEMENTS_CLAUSE,
145             TokenTypes.ANNOTATION,
146             TokenTypes.INTERFACE_DEF,
147             TokenTypes.ENUM_DEF,
148             TokenTypes.TYPE,
149             TokenTypes.LITERAL_NEW,
150             TokenTypes.LITERAL_THROWS,
151             TokenTypes.ANNOTATION_DEF,
152             TokenTypes.RECORD_DEF,
153         };
154         assertWithMessage("Acceptable tokens should not be null")
155             .that(actual)
156             .isNotNull();
157         assertWithMessage("Invalid acceptable tokens")
158             .that(actual)
159             .isEqualTo(expected);
160     }
161 
162     @Test
163     public void testRegularExpression() throws Exception {
164 
165         final String[] expected = {
166             "44:1: " + getCheckMessage(MSG_KEY, 2, 0),
167             "76:1: " + getCheckMessage(MSG_KEY, 1, 0),
168         };
169 
170         verifyWithInlineConfigParser(
171                 getPath("InputClassFanOutComplexity3.java"), expected);
172     }
173 
174     @Test
175     public void testEmptyRegularExpression() throws Exception {
176 
177         final String[] expected = {
178             "44:1: " + getCheckMessage(MSG_KEY, 3, 0),
179             "76:1: " + getCheckMessage(MSG_KEY, 1, 0),
180         };
181 
182         verifyWithInlineConfigParser(
183                 getPath("InputClassFanOutComplexity4.java"), expected);
184     }
185 
186     @Test
187     public void testWithMultiDimensionalArray() throws Exception {
188 
189         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
190         verifyWithInlineConfigParser(
191                 getPath("InputClassFanOutComplexityMultiDimensionalArray.java"), expected);
192     }
193 
194     @Test
195     public void testPackageName() throws Exception {
196 
197         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
198         verifyWithInlineConfigParser(
199                 getPath("InputClassFanOutComplexityPackageName.java"), expected);
200     }
201 
202     @Test
203     public void testExtends() throws Exception {
204         final String[] expected = {
205             "23:1: " + getCheckMessage(MSG_KEY, 1, 0),
206         };
207         verifyWithInlineConfigParser(
208                 getPath("InputClassFanOutComplexityExtends.java"), expected);
209     }
210 
211     @Test
212     public void testImplements() throws Exception {
213         final String[] expected = {
214             "23:1: " + getCheckMessage(MSG_KEY, 1, 0),
215         };
216         verifyWithInlineConfigParser(
217                 getPath("InputClassFanOutComplexityImplements.java"), expected);
218     }
219 
220     @Test
221     public void testAnnotation() throws Exception {
222         final String[] expected = {
223             "29:1: " + getCheckMessage(MSG_KEY, 2, 0),
224             "45:5: " + getCheckMessage(MSG_KEY, 2, 0),
225             "54:5: " + getCheckMessage(MSG_KEY, 3, 0),
226             "64:5: " + getCheckMessage(MSG_KEY, 2, 0),
227             "79:1: " + getCheckMessage(MSG_KEY, 1, 0),
228             "99:1: " + getCheckMessage(MSG_KEY, 1, 0),
229             "102:1: " + getCheckMessage(MSG_KEY, 1, 0),
230         };
231         verifyWithInlineConfigParser(
232                 getPath("InputClassFanOutComplexityAnnotations.java"), expected);
233     }
234 
235     @Test
236     public void testImplementsAndNestedCount() throws Exception {
237         final String[] expected = {
238             "26:1: " + getCheckMessage(MSG_KEY, 3, 0),
239         };
240         verifyWithInlineConfigParser(
241                 getPath("InputClassFanOutComplexityImplementsAndNestedCount.java"), expected);
242     }
243 
244     @Test
245     public void testClassFanOutComplexityRecords() throws Exception {
246         final String[] expected = {
247             "32:1: " + getCheckMessage(MSG_KEY, 4, 2),
248             "53:1: " + getCheckMessage(MSG_KEY, 4, 2),
249         };
250         verifyWithInlineConfigParser(
251                 getNonCompilablePath("InputClassFanOutComplexityRecords.java"), expected);
252     }
253 
254     @Test
255     public void testClassFanOutComplexityIgnoreVar() throws Exception {
256         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
257         verifyWithInlineConfigParser(
258                 getPath("InputClassFanOutComplexityVar.java"), expected);
259     }
260 
261     @Test
262     public void testClassFanOutComplexityRemoveIncorrectAnnotationToken() throws Exception {
263         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
264         verifyWithInlineConfigParser(
265                 getPath("InputClassFanOutComplexityRemoveIncorrectAnnotationToken.java"), expected);
266     }
267 
268     @Test
269     public void testClassFanOutComplexityRemoveIncorrectTypeParameter() throws Exception {
270         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
271         verifyWithInlineConfigParser(
272                 getPath("InputClassFanOutComplexityRemoveIncorrectTypeParameter.java"), expected);
273     }
274 
275     @Test
276     public void testClassFanOutComplexityMultiCatchBitwiseOr() throws Exception {
277         final String[] expected = {
278             "27:1: " + getCheckMessage(MSG_KEY, 5, 4),
279         };
280 
281         verifyWithInlineConfigParser(
282                 getPath("InputClassFanOutComplexityMultiCatchBitwiseOr.java"), expected);
283     }
284 
285     @Test
286     public void testThrows() throws Exception {
287         final String[] expected = {
288             "25:1: " + getCheckMessage(MSG_KEY, 2, 0),
289         };
290 
291         verifyWithInlineConfigParser(
292                 getPath("InputClassFanOutComplexityThrows.java"), expected);
293     }
294 
295     @Test
296     public void testSealedClasses() throws Exception {
297         final String[] expected = {
298             "25:1: " + getCheckMessage(MSG_KEY, 2, 0),
299             "32:1: " + getCheckMessage(MSG_KEY, 1, 0),
300         };
301 
302         verifyWithInlineConfigParser(
303                 getNonCompilablePath("InputClassFanOutComplexitySealedClasses.java"),
304                 expected);
305     }
306 
307     /**
308      * We cannot reproduce situation when visitToken is called and leaveToken is not.
309      * So, we have to use reflection to be sure that even in such situation
310      * state of the field will be cleared.
311      *
312      * @throws Exception when code tested throws exception
313      */
314     @Test
315     @SuppressWarnings("unchecked")
316     public void testClearStateImportedClassPackages() throws Exception {
317         final ClassFanOutComplexityCheck check = new ClassFanOutComplexityCheck();
318         final DetailAST root = JavaParser.parseFile(
319                 new File(getPath("InputClassFanOutComplexity.java")),
320                 JavaParser.Options.WITHOUT_COMMENTS);
321         final Optional<DetailAST> importAst = TestUtil.findTokenInAstByPredicate(root,
322             ast -> ast.getType() == TokenTypes.IMPORT);
323 
324         assertWithMessage("Ast should contain IMPORT")
325                 .that(importAst.isPresent())
326                 .isTrue();
327         assertWithMessage("State is not cleared on beginTree")
328                 .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check,
329                     importAst.orElseThrow(), "importedClassPackages",
330                     importedClssPackage -> ((Map<String, String>) importedClssPackage).isEmpty()))
331                 .isTrue();
332     }
333 
334     /**
335      * We cannot reproduce situation when visitToken is called and leaveToken is not.
336      * So, we have to use reflection to be sure that even in such situation
337      * state of the field will be cleared.
338      *
339      * @throws Exception when code tested throws exception
340      */
341     @Test
342     public void testClearStateClassContexts() throws Exception {
343         final ClassFanOutComplexityCheck check = new ClassFanOutComplexityCheck();
344         final DetailAST root = JavaParser.parseFile(
345                 new File(getPath("InputClassFanOutComplexity.java")),
346                 JavaParser.Options.WITHOUT_COMMENTS);
347         final Optional<DetailAST> classDef = TestUtil.findTokenInAstByPredicate(root,
348             ast -> ast.getType() == TokenTypes.CLASS_DEF);
349 
350         assertWithMessage("Ast should contain CLASS_DEF")
351                 .that(classDef.isPresent())
352                 .isTrue();
353         assertWithMessage("State is not cleared on beginTree")
354                 .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check, classDef.orElseThrow(),
355                         "classesContexts",
356                         classContexts -> ((Collection<?>) classContexts).size() == 1))
357                 .isTrue();
358     }
359 
360     /**
361      * We cannot reproduce situation when visitToken is called and leaveToken is not.
362      * So, we have to use reflection to be sure that even in such situation
363      * state of the field will be cleared.
364      *
365      * @throws Exception when code tested throws exception
366      */
367     @Test
368     public void testClearStatePackageName() throws Exception {
369         final ClassFanOutComplexityCheck check = new ClassFanOutComplexityCheck();
370         final DetailAST root = JavaParser.parseFile(
371                 new File(getPath("InputClassFanOutComplexity.java")),
372                 JavaParser.Options.WITHOUT_COMMENTS);
373         final Optional<DetailAST> packageDef = TestUtil.findTokenInAstByPredicate(root,
374             ast -> ast.getType() == TokenTypes.PACKAGE_DEF);
375 
376         assertWithMessage("Ast should contain PACKAGE_DEF")
377                 .that(packageDef.isPresent())
378                 .isTrue();
379         assertWithMessage("State is not cleared on beginTree")
380                 .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check,
381                         packageDef.orElseThrow(), "packageName",
382                         packageName -> ((String) packageName).isEmpty()))
383                 .isTrue();
384     }
385 
386     /**
387      * Test [ClassName]::new expression. Such expression will be processed as a
388      * normal declaration of a new instance. We need to make sure this does not
389      *
390      * @throws Exception when code tested throws exception
391      */
392     @Test
393     public void testLambdaNew() throws Exception {
394         final String[] expected = {};
395         // no violation until #14787
396         verifyWithInlineConfigParser(
397                 getPath("InputClassFanOutComplexityLambdaNew.java"), expected);
398     }
399 
400 }