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;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  
24  import java.io.IOException;
25  import java.nio.file.Files;
26  import java.nio.file.Path;
27  import java.util.List;
28  import java.util.Optional;
29  import java.util.stream.Stream;
30  
31  import org.junit.jupiter.params.ParameterizedTest;
32  import org.junit.jupiter.params.provider.MethodSource;
33  
34  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
35  import com.puppycrawl.tools.checkstyle.api.Configuration;
36  import com.puppycrawl.tools.checkstyle.checks.indentation.IndentationCheck;
37  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
38  
39  public class IndentationTrailingCommentsVerticalAlignmentTest {
40  
41      private static final String INDENTATION_TEST_FILES_PATH =
42          "com/puppycrawl/tools/checkstyle/checks/indentation/indentation";
43  
44      @MethodSource("indentationTestFiles")
45      @ParameterizedTest
46      public final void testTrailingCommentsAlignment(Path testFile) throws Exception {
47          final int currentTabWidth = getTabWidth(testFile);
48          final List<String> lines = Files.readAllLines(testFile);
49          int expectedStartIndex = -1;
50  
51          for (int idx = 0; idx < lines.size(); idx++) {
52              final String line = lines.get(idx);
53              if (line.trim().startsWith("import ") || line.trim().startsWith("package ")) {
54                  continue;
55              }
56              final int commentStartIndex = line.indexOf("//indent:");
57              if (commentStartIndex > 0) {
58                  final String codePart = line.substring(0, commentStartIndex);
59                  if (!codePart.isBlank()) {
60                      int actualStartIndex =
61                          CommonUtil.lengthExpandedTabs(line, commentStartIndex, currentTabWidth);
62  
63                      // for unicode characters having supplementary code points
64                      final long extraWidth = codePart.codePoints().filter(
65                          Character::isSupplementaryCodePoint).count();
66                      actualStartIndex -= Math.toIntExact(extraWidth);
67  
68                      if (expectedStartIndex == -1) {
69                          expectedStartIndex = actualStartIndex;
70                      }
71                      else {
72                          assertWithMessage(
73                                  "Trailing comment alignment mismatch in file: %s on line %s",
74                                  testFile, idx + 1)
75                                  .that(actualStartIndex)
76                                  .isEqualTo(expectedStartIndex);
77                      }
78                  }
79              }
80          }
81      }
82  
83      private static int getTabWidth(Path testFile) throws Exception {
84          final Optional<Path> matchingConfig = findConfigForFile(testFile);
85          int result = 4;
86  
87          if (matchingConfig.isPresent()) {
88              final Configuration config = ConfigurationLoader.loadConfiguration(
89                  matchingConfig.get().toString(),
90                  new PropertiesExpander(System.getProperties()));
91              result = extractTabWidthFromConfig(config);
92          }
93          return result;
94      }
95  
96      private static Optional<Path> findConfigForFile(Path testFile) throws IOException {
97          final String fileName = testFile.getFileName().toString();
98          final Path configDir = Path.of("src", "test", "resources",
99              "com", "puppycrawl", "tools", "checkstyle",
100             "checks", "indentation", "indentation");
101         final Optional<Path> result;
102 
103         try (Stream<Path> configs = Files.walk(configDir)) {
104             result = configs
105                 .filter(path -> path.toString().endsWith(".xml"))
106                 .filter(path -> isConfigFileContainingFileName(path, fileName))
107                 .findFirst();
108         }
109         return result;
110     }
111 
112     private static boolean isConfigFileContainingFileName(Path configPath, String fileName) {
113         boolean result;
114         try {
115             result = Files.readString(configPath).contains(fileName);
116         }
117         catch (IOException exception) {
118             result = false;
119         }
120         return result;
121     }
122 
123     private static int extractTabWidthFromConfig(Configuration config) throws CheckstyleException {
124         int result = 4;
125         for (Configuration child : config.getChildren()) {
126             if ("TreeWalker".equals(child.getName())) {
127                 for (Configuration module : child.getChildren()) {
128                     if ("IndentationCheck".equals(module.getName())
129                             || "Indentation".equals(module.getName())) {
130                         final IndentationCheck check = new IndentationCheck();
131                         check.configure(module);
132                         result = check.getIndentationTabWidth();
133                     }
134                 }
135             }
136         }
137         return result;
138     }
139 
140     private static Stream<Path> indentationTestFiles() {
141         final Path resourcesDir = Path.of("src", "test", "resources");
142         final Path indentationDir = resourcesDir.resolve(INDENTATION_TEST_FILES_PATH);
143         Stream<Path> result;
144         try (Stream<Path> testFiles = Files.walk(indentationDir)) {
145             final List<Path> collected = testFiles
146                 .filter(path -> {
147                     final String fileName = path.getFileName().toString();
148                     return fileName.startsWith("InputIndentation")
149                             && fileName.endsWith(".java");
150                 }).toList();
151             result = collected.stream();
152         }
153         catch (IOException exception) {
154             assertWithMessage("Failed to find indentation test files")
155                     .that(exception)
156                     .isNull();
157             result = Stream.empty();
158         }
159         return result;
160     }
161 }