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  
24  import java.beans.PropertyDescriptor;
25  import java.io.File;
26  import java.io.IOException;
27  import java.nio.file.Files;
28  import java.nio.file.Path;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.stream.Collectors;
36  import java.util.stream.Stream;
37  
38  import org.apache.commons.beanutils.PropertyUtils;
39  import org.junit.jupiter.api.Test;
40  
41  import com.puppycrawl.tools.checkstyle.AbstractPathTestSupport;
42  import com.puppycrawl.tools.checkstyle.internal.utils.CheckUtil;
43  import com.puppycrawl.tools.checkstyle.internal.utils.XdocUtil;
44  
45  public class XdocsExampleFileTest {
46  
47      private static final Set<String> COMMON_PROPERTIES = Set.of(
48          "severity",
49          "id",
50          "fileExtensions",
51          "tabWidth",
52          "fileContents",
53          "tokens",
54          "javadocTokens",
55          "violateExecutionOnNonTightHtml"
56      );
57  
58      // This list is temporarily suppressed.
59      // Until: https://github.com/checkstyle/checkstyle/issues/17449
60      private static final Map<String, Set<String>> SUPPRESSED_PROPERTIES_BY_CHECK = Map.ofEntries(
61              Map.entry("JavadocStyleCheck", Set.of("endOfSentenceFormat")),
62              Map.entry("SuppressWarningsHolder", Set.of("aliasList")),
63              Map.entry("IndentationCheck", Set.of(
64                      "basicOffset",
65                      "lineWrappingIndentation",
66                      "throwsIndent",
67                      "arrayInitIndent",
68                      "braceAdjustment"
69              )),
70              Map.entry("ClassMemberImpliedModifierCheck", Set.of(
71                      "violateImpliedStaticOnNestedEnum",
72                      "violateImpliedStaticOnNestedRecord",
73                      "violateImpliedStaticOnNestedInterface"
74              )),
75              Map.entry("InterfaceMemberImpliedModifierCheck", Set.of(
76                      "violateImpliedFinalField",
77                      "violateImpliedPublicField",
78                      "violateImpliedStaticField",
79                      "violateImpliedPublicMethod",
80                      "violateImpliedAbstractMethod"
81              ))
82      );
83  
84      @Test
85      public void testAllCheckPropertiesAreUsedInXdocsExamples() throws Exception {
86          final Map<String, Set<String>> usedPropertiesByCheck =
87              XdocUtil.extractUsedPropertiesFromXdocsExamples();
88          final List<String> failures = new ArrayList<>();
89  
90          for (Class<?> checkClass : CheckUtil.getCheckstyleChecks()) {
91              final String checkSimpleName = checkClass.getSimpleName();
92  
93              final Set<String> definedProperties = Arrays.stream(
94                      PropertyUtils.getPropertyDescriptors(checkClass))
95                  .filter(descriptor -> descriptor.getWriteMethod() != null)
96                  .map(PropertyDescriptor::getName)
97                  .filter(property -> !COMMON_PROPERTIES.contains(property))
98                  .collect(Collectors.toUnmodifiableSet());
99  
100             final Set<String> usedProperties =
101                 usedPropertiesByCheck.getOrDefault(checkSimpleName, Collections.emptySet());
102 
103             final Set<String> suppressedProps =
104                 SUPPRESSED_PROPERTIES_BY_CHECK.getOrDefault(
105                     checkSimpleName, Collections.emptySet());
106 
107             for (String property : definedProperties) {
108                 if (!usedProperties.contains(property)
109                         && !suppressedProps.contains(property)) {
110                     failures.add("Missing property in xdoc: '"
111                             + property + "' of " + checkSimpleName);
112                 }
113             }
114         }
115         if (!failures.isEmpty()) {
116             assertWithMessage("Xdocs are missing properties:\n" + String.join("\n", failures))
117                     .fail();
118         }
119     }
120 
121     @Test
122     public void testAllExampleFilesHaveCorrespondingTestMethods() throws Exception {
123         final Path examplesResources = Path.of("src/xdocs-examples/resources");
124         final Path examplesNonCompilable = Path.of("src/xdocs-examples/resources-noncompilable");
125         final Path examplesTestRoot = Path.of(
126             "src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks");
127         final List<String> failures = new ArrayList<>();
128 
129         try (Stream<Path> testFiles = Files.walk(examplesTestRoot)) {
130             testFiles
131                 .filter(path -> path.toString().endsWith("ExamplesTest.java"))
132                 .forEach(testFile -> {
133                     try {
134                         scanFile(testFile, examplesResources, examplesNonCompilable, failures);
135                     }
136                     catch (IOException exception) {
137                         throw new IllegalStateException("Error processing: "
138                                      + testFile, exception);
139                     }
140                 });
141         }
142         if (!failures.isEmpty()) {
143             assertWithMessage("Example files are missing corresponding test methods:\n"
144                     + String.join("\n", failures))
145                     .fail();
146         }
147     }
148 
149     private static void scanFile(Path testFile, Path examplesResources, Path examplesNonCompilable,
150             List<String> failures)
151             throws IOException {
152         final String testContent = Files.readString(testFile);
153 
154         final String className = Path.of("src/xdocs-examples/java").toAbsolutePath()
155                 .relativize(testFile.toAbsolutePath()).toString()
156                 .replace(File.separator, ".")
157                 .replaceFirst("\\.java$", "");
158 
159         try {
160             final Class<?> testClass = Class.forName(className);
161             final AbstractPathTestSupport instance = (AbstractPathTestSupport) testClass
162                     .getDeclaredConstructor().newInstance();
163             final String packageLocation = instance.getPackageLocation();
164 
165             scanExampleDirectory(examplesResources.resolve(packageLocation),
166                     testContent, testFile, failures);
167             scanExampleDirectory(examplesNonCompilable.resolve(packageLocation),
168                     testContent, testFile, failures);
169         }
170         catch (ReflectiveOperationException exception) {
171             throw new IllegalStateException("Failed to instantiate " + className, exception);
172         }
173     }
174 
175     private static void scanExampleDirectory(Path exampleDir, String testContent,
176             Path testFile, List<String> failures) throws IOException {
177         if (Files.exists(exampleDir) && Files.isDirectory(exampleDir)) {
178             try (Stream<Path> exampleFiles = Files.list(exampleDir)) {
179                 exampleFiles
180                     .filter(path -> {
181                         final String fileName = path.getFileName()
182                                 .toString();
183                         return fileName.matches("Example\\d+\\.java");
184                     })
185                     .forEach(exampleFile -> {
186                         final String fileName = exampleFile.getFileName()
187                                 .toString();
188                         if (!testContent.contains("\"" + fileName + "\"")) {
189                             failures.add("Missing test for " + fileName + " in "
190                                         + testFile.getFileName());
191                         }
192                     });
193             }
194         }
195     }
196 }