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.internal;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
24  import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
25  
26  import java.util.List;
27  
28  import org.junit.jupiter.api.Test;
29  
30  import com.tngtech.archunit.core.domain.JavaClasses;
31  import com.tngtech.archunit.core.domain.JavaModifier;
32  import com.tngtech.archunit.core.importer.ClassFileImporter;
33  import com.tngtech.archunit.core.importer.ImportOption;
34  import com.tngtech.archunit.lang.ArchRule;
35  import com.tngtech.archunit.lang.EvaluationResult;
36  
37  public class ArchUnitTest {
38  
39      /**
40       * Suppression list containing violations from {@code classShouldNotDependOnUtilPackages}
41       * ArchRule. Location of the violation (eg - {@code in (AbstractAutomaticBean.java:372)})
42       * has been omitted as line number can change with modifications to the file.
43       */
44      private static final List<String> API_PACKAGE_SUPPRESSION_DETAILS = List.of(
45          "Constructor <com.puppycrawl.tools.checkstyle.api.FileText.<init>(java.io.File, java.lang"
46              + ".String)> gets field <com.puppycrawl.tools.checkstyle.utils.CommonUtil"
47              + ".EMPTY_STRING_ARRAY>",
48          "Constructor <com.puppycrawl.tools.checkstyle.api.Violation.<init>(int, int, int, int,"
49              + " java.lang.String, java.lang.String, [Ljava.lang.Object;,"
50              + " com.puppycrawl.tools.checkstyle.api.SeverityLevel, java.lang.String,"
51              + " java.lang.Class, java.lang.String)> calls method"
52              + " <com.puppycrawl.tools.checkstyle.utils.UnmodifiableCollectionUtil.copyOfArray"
53              + "([Ljava.lang.Object;, int)>",
54          "Constructor <com.puppycrawl.tools.checkstyle.api.FileText.<init>(java.io.File, java.util"
55              + ".List)> gets field <com.puppycrawl.tools.checkstyle.utils.CommonUtil"
56              + ".EMPTY_STRING_ARRAY>",
57          "Method <com.puppycrawl.tools.checkstyle.api.AbstractCheck.log(com.puppycrawl.tools"
58              + ".checkstyle.api.DetailAST, java.lang.String, [Ljava.lang.Object;)> calls method "
59              + "<com.puppycrawl.tools.checkstyle.utils.CommonUtil.lengthExpandedTabs(java.lang"
60              + ".String, int, int)>",
61          "Method <com.puppycrawl.tools.checkstyle.api.AbstractCheck.log(int, int, java.lang"
62              + ".String, [Ljava.lang.Object;)> calls method <com.puppycrawl.tools.checkstyle.utils"
63              + ".CommonUtil.lengthExpandedTabs(java.lang.String, int, int)>",
64          "Method <com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck.log(int, int, java.lang"
65              + ".String, [Ljava.lang.Object;)> calls method <com.puppycrawl.tools.checkstyle.utils"
66              + ".CommonUtil.lengthExpandedTabs(java.lang.String, int, int)>",
67          "Method <com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck.process(java.io.File, "
68              + "com.puppycrawl.tools.checkstyle.api.FileText)> calls method <com.puppycrawl.tools"
69              + ".checkstyle.utils.CommonUtil.matchesFileExtension(java.io.File, [Ljava.lang"
70              + ".String;)>",
71          "Method <com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck.setFileExtensions"
72              + "([Ljava.lang.String;)> calls method <com.puppycrawl.tools.checkstyle.utils"
73              + ".CommonUtil.startsWithChar(java.lang.String, char)>",
74          "Method <com.puppycrawl.tools.checkstyle.AbstractAutomaticBean$PatternConverter"
75              + ".convert(java.lang.Class, java.lang.Object)> calls method <com.puppycrawl.tools"
76              + ".checkstyle.utils.CommonUtil.createPattern(java.lang.String)>",
77          "Method <com.puppycrawl.tools.checkstyle.AbstractAutomaticBean$RelaxedStringArray"
78              + "Converter.convert(java.lang.Class, java.lang.Object)> gets field <com.puppycrawl"
79              + ".tools.checkstyle.utils.CommonUtil.EMPTY_STRING_ARRAY>",
80          "Method <com.puppycrawl.tools.checkstyle.AbstractAutomaticBean$UriConverter.convert("
81              + "java.lang.Class, java.lang.Object)> calls method <com.puppycrawl.tools.checkstyle"
82              + ".utils.CommonUtil.getUriByFilename(java.lang.String)>",
83          "Method <com.puppycrawl.tools.checkstyle.AbstractAutomaticBean$UriConverter.convert("
84              + "java.lang.Class, java.lang.Object)> calls method <com.puppycrawl.tools.checkstyle"
85              + ".utils.CommonUtil.isBlank(java.lang.String)>",
86          "Method <com.puppycrawl.tools.checkstyle.api.FileContents.lineIsBlank(int)> calls method "
87              + "<com.puppycrawl.tools.checkstyle.utils.CommonUtil.isBlank(java.lang.String)>"
88      );
89  
90      /**
91       * The goal is to ensure all classes of a specific name pattern have non-protected methods,
92       * except for those which are annotated with {@code Override}. In the bytecode there is no
93       * trace anymore if this method was annotated with {@code Override} or not (limitation of
94       * Archunit), eventually we need to make checkstyle's Check on this.
95       */
96      @Test
97      public void nonProtectedCheckMethodsTest() {
98          // This list contains methods which have been overridden and are set to ignore in this test.
99          final String[] methodsWithOverrideAnnotation = {
100             "processFiltered",
101             "getMethodName",
102             "mustCheckName",
103             "postProcessHeaderLines",
104             "getLogMessageId",
105         };
106         final String ignoreMethodList = String.join("|", methodsWithOverrideAnnotation);
107         final JavaClasses importedClasses = new ClassFileImporter()
108                 .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
109                 .importPackages("com.puppycrawl.tools.checkstyle.checks");
110 
111         final ArchRule checkMethodsShouldNotBeProtectedRule =
112                 methods().that()
113                 .haveNameNotMatching(".*(" + ignoreMethodList + ")").and()
114                 .areDeclaredInClassesThat()
115                 .haveSimpleNameEndingWith("Check").and()
116                 .areDeclaredInClassesThat()
117                 .doNotHaveModifier(JavaModifier.ABSTRACT)
118                 .should().notBeProtected();
119 
120         checkMethodsShouldNotBeProtectedRule.check(importedClasses);
121     }
122 
123     /**
124      * The goal is to ensure all classes in api package are not dependent on classes in util
125      * packages. Changes in Util classes are not considered to be breaking changes as they are
126      * "internal". Therefore classes in api should not depend on them.
127      */
128     @Test
129     public void testClassesInApiDoNotDependOnClassesInUtil() {
130         final JavaClasses apiPackage = new ClassFileImporter()
131             .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
132             .importPackages("com.puppycrawl.tools.checkstyle.api");
133 
134         final String[] utilPackages = {
135             "com.puppycrawl.tools.checkstyle.utils",
136             "com.puppycrawl.tools.checkstyle.checks.javadoc.utils",
137         };
138 
139         final ArchRule classShouldNotDependOnUtilPackages = noClasses()
140             .should()
141             .dependOnClassesThat()
142             .resideInAnyPackage(utilPackages);
143 
144         final EvaluationResult result = classShouldNotDependOnUtilPackages.evaluate(apiPackage);
145         final EvaluationResult filtered = result.filterDescriptionsMatching(description -> {
146             return API_PACKAGE_SUPPRESSION_DETAILS.stream()
147                 .noneMatch(description::startsWith);
148         });
149 
150         assertWithMessage("api package: " + classShouldNotDependOnUtilPackages.getDescription())
151             .that(filtered.getFailureReport().getDetails())
152             .isEmpty();
153     }
154 
155 }