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.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.FileContents.lineIsBlank(int)> calls method "
72              + "<com.puppycrawl.tools.checkstyle.utils.CommonUtil.isBlank(java.lang.String)>"
73      );
74  
75      /**
76       * The goal is to ensure all classes of a specific name pattern have non-protected methods,
77       * except for those which are annotated with {@code Override}. In the bytecode there is no
78       * trace anymore if this method was annotated with {@code Override} or not (limitation of
79       * Archunit), eventually we need to make checkstyle's Check on this.
80       */
81      @Test
82      public void nonProtectedCheckMethodsTest() {
83          // This list contains methods which have been overridden and are set to ignore in this test.
84          final String[] methodsWithOverrideAnnotation = {
85              "processFiltered",
86              "getMethodName",
87              "mustCheckName",
88              "postProcessHeaderLines",
89              "getLogMessageId",
90          };
91          final String ignoreMethodList = String.join("|", methodsWithOverrideAnnotation);
92          final JavaClasses importedClasses = new ClassFileImporter()
93                  .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
94                  .importPackages("com.puppycrawl.tools.checkstyle.checks");
95  
96          final ArchRule checkMethodsShouldNotBeProtectedRule =
97                  methods().that()
98                  .haveNameNotMatching(".*(" + ignoreMethodList + ")").and()
99                  .areDeclaredInClassesThat()
100                 .haveSimpleNameEndingWith("Check").and()
101                 .areDeclaredInClassesThat()
102                 .doNotHaveModifier(JavaModifier.ABSTRACT)
103                 .should().notBeProtected();
104 
105         checkMethodsShouldNotBeProtectedRule.check(importedClasses);
106     }
107 
108     /**
109      * The goal is to ensure all classes in api package are not dependent on classes in util
110      * packages. Changes in Util classes are not considered to be breaking changes as they are
111      * "internal". Therefore classes in api should not depend on them.
112      */
113     @Test
114     public void testClassesInApiDoNotDependOnClassesInUtil() {
115         final JavaClasses apiPackage = new ClassFileImporter()
116             .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
117             .importPackages("com.puppycrawl.tools.checkstyle.api");
118 
119         final String[] utilPackages = {
120             "com.puppycrawl.tools.checkstyle.utils",
121             "com.puppycrawl.tools.checkstyle.checks.javadoc.utils",
122         };
123 
124         final ArchRule classShouldNotDependOnUtilPackages = noClasses()
125             .should()
126             .dependOnClassesThat()
127             .resideInAnyPackage(utilPackages);
128 
129         final EvaluationResult result = classShouldNotDependOnUtilPackages.evaluate(apiPackage);
130 
131         final List<String> allDescriptions = result.getFailureReport().getDetails();
132         final List<String> outdatedSuppressions = API_PACKAGE_SUPPRESSION_DETAILS.stream()
133                 .filter(suppression -> {
134                     return allDescriptions.stream()
135                             .noneMatch(description -> description.startsWith(suppression));
136                 })
137                 .toList();
138 
139         assertWithMessage("Outdated suppressions (can be removed)")
140                 .that(outdatedSuppressions)
141                 .isEmpty();
142 
143         final EvaluationResult filtered = result.filterDescriptionsMatching(description -> {
144             return API_PACKAGE_SUPPRESSION_DETAILS.stream()
145                 .noneMatch(description::startsWith);
146         });
147 
148         assertWithMessage("api package: %s", classShouldNotDependOnUtilPackages.getDescription())
149             .that(filtered.getFailureReport().getDetails())
150             .isEmpty();
151     }
152 
153 }