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