1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.google.checkstyle.test.base;
21
22 import java.io.BufferedReader;
23 import java.io.IOException;
24 import java.nio.charset.StandardCharsets;
25 import java.nio.file.Files;
26 import java.nio.file.Path;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33
34 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
35
36 public abstract class AbstractIndentationTestSupport extends AbstractGoogleModuleTestSupport {
37
38 private static final int TAB_WIDTH = 4;
39
40 private static final Pattern NONEMPTY_LINE_REGEX =
41 Pattern.compile(".*?\\S+.*?");
42
43 private static final Pattern LINE_WITH_COMMENT_REGEX =
44 Pattern.compile(".*?\\S+.*?(//indent:(\\d+) exp:((>=\\d+)|(\\d+(,\\d+)*?))( warn)?)");
45
46 private static final Pattern GET_INDENT_FROM_COMMENT_REGEX =
47 Pattern.compile("//indent:(\\d+).*?");
48
49 private static final Pattern MULTILEVEL_COMMENT_REGEX =
50 Pattern.compile("//indent:\\d+ exp:(\\d+(,\\d+)+?)( warn)?");
51
52 private static final Pattern SINGLE_LEVEL_COMMENT_REGEX =
53 Pattern.compile("//indent:\\d+ exp:(\\d+)( warn)?");
54
55 private static final Pattern NON_STRICT_LEVEL_COMMENT_REGEX =
56 Pattern.compile("//indent:\\d+ exp:>=(\\d+)( warn)?");
57
58 @Override
59 protected Integer[] getLinesWithWarn(String fileName) throws IOException {
60 return getLinesWithWarnAndCheckComments(fileName, TAB_WIDTH);
61 }
62
63
64
65
66
67
68
69
70
71
72
73 private static Integer[] getLinesWithWarnAndCheckComments(String aFileName,
74 final int tabWidth)
75 throws IOException {
76 final List<Integer> result = new ArrayList<>();
77 try (BufferedReader br = Files.newBufferedReader(
78 Path.of(aFileName), StandardCharsets.UTF_8)) {
79 int lineNumber = 1;
80 for (String line = br.readLine(); line != null; line = br.readLine()) {
81 final Matcher match = LINE_WITH_COMMENT_REGEX.matcher(line);
82 if (match.matches()) {
83 final String comment = match.group(1);
84 final int indentInComment = getIndentFromComment(comment);
85 final int actualIndent = getLineStart(line, tabWidth);
86
87 if (actualIndent != indentInComment) {
88 throw new IllegalStateException(String.format(Locale.ROOT,
89 "File \"%1$s\" has incorrect indentation in comment."
90 + "Line %2$d: comment:%3$d, actual:%4$d.",
91 aFileName,
92 lineNumber,
93 indentInComment,
94 actualIndent));
95 }
96
97 if (isWarnComment(comment)) {
98 result.add(lineNumber);
99 }
100
101 if (!isCommentConsistent(comment)) {
102 throw new IllegalStateException(String.format(Locale.ROOT,
103 "File \"%1$s\" has inconsistent comment on line %2$d",
104 aFileName,
105 lineNumber));
106 }
107 }
108 else if (NONEMPTY_LINE_REGEX.matcher(line).matches()) {
109 throw new IllegalStateException(String.format(Locale.ROOT,
110 "File \"%1$s\" has no indentation comment or its format "
111 + "malformed. Error on line: %2$d(%3$s)",
112 aFileName,
113 lineNumber,
114 line));
115 }
116 lineNumber++;
117 }
118 }
119 return result.toArray(new Integer[0]);
120 }
121
122
123
124
125
126
127
128 private static int getIndentFromComment(String comment) {
129 final Matcher match = GET_INDENT_FROM_COMMENT_REGEX.matcher(comment);
130 match.matches();
131 return Integer.parseInt(match.group(1));
132 }
133
134
135
136
137
138
139
140 private static boolean isWarnComment(String comment) {
141 return comment.endsWith(" warn");
142 }
143
144
145
146
147
148
149
150
151
152
153 private static boolean isCommentConsistent(String comment) {
154 final int indentInComment = getIndentFromComment(comment);
155 final boolean isWarnComment = isWarnComment(comment);
156 final CommentType type = getCommentType(comment);
157 return switch (type) {
158 case MULTILEVEL ->
159 isMultiLevelCommentConsistent(comment, indentInComment, isWarnComment);
160 case SINGLE_LEVEL ->
161 isSingleLevelCommentConsistent(comment, indentInComment, isWarnComment);
162 case NON_STRICT_LEVEL ->
163 isNonStrictCommentConsistent(comment, indentInComment, isWarnComment);
164 case UNKNOWN ->
165 throw new IllegalArgumentException("Cannot determine comment consistent");
166 default ->
167 throw new IllegalStateException("Cannot determine comment is consistent");
168 };
169 }
170
171
172
173
174
175
176
177
178
179 private static boolean isNonStrictCommentConsistent(String comment,
180 int indentInComment, boolean isWarnComment) {
181 final Matcher nonStrictLevelMatch = NON_STRICT_LEVEL_COMMENT_REGEX.matcher(comment);
182 nonStrictLevelMatch.matches();
183 final int expectedMinimalIndent = Integer.parseInt(nonStrictLevelMatch.group(1));
184
185 return indentInComment >= expectedMinimalIndent && !isWarnComment
186 || indentInComment < expectedMinimalIndent && isWarnComment;
187 }
188
189
190
191
192
193
194
195
196
197 private static boolean isSingleLevelCommentConsistent(String comment,
198 int indentInComment, boolean isWarnComment) {
199 final Matcher singleLevelMatch = SINGLE_LEVEL_COMMENT_REGEX.matcher(comment);
200 singleLevelMatch.matches();
201 final int expectedLevel = Integer.parseInt(singleLevelMatch.group(1));
202
203 return expectedLevel == indentInComment && !isWarnComment
204 || expectedLevel != indentInComment && isWarnComment;
205 }
206
207
208
209
210
211
212
213
214
215 private static boolean isMultiLevelCommentConsistent(String comment,
216 int indentInComment, boolean isWarnComment) {
217 final Matcher multilevelMatch = MULTILEVEL_COMMENT_REGEX.matcher(comment);
218 multilevelMatch.matches();
219 final String[] levels = multilevelMatch.group(1).split(",");
220 final String indentInCommentStr = String.valueOf(indentInComment);
221 final boolean containsActualLevel =
222 Arrays.asList(levels).contains(indentInCommentStr);
223
224 return containsActualLevel && !isWarnComment
225 || !containsActualLevel && isWarnComment;
226 }
227
228
229
230
231
232
233
234
235
236 private static CommentType getCommentType(String comment) {
237 CommentType result = CommentType.UNKNOWN;
238 final Matcher multilevelMatch = MULTILEVEL_COMMENT_REGEX.matcher(comment);
239 if (multilevelMatch.matches()) {
240 result = CommentType.MULTILEVEL;
241 }
242 else {
243 final Matcher singleLevelMatch = SINGLE_LEVEL_COMMENT_REGEX.matcher(comment);
244 if (singleLevelMatch.matches()) {
245 result = CommentType.SINGLE_LEVEL;
246 }
247 else {
248 final Matcher nonStrictLevelMatch = NON_STRICT_LEVEL_COMMENT_REGEX.matcher(comment);
249 if (nonStrictLevelMatch.matches()) {
250 result = CommentType.NON_STRICT_LEVEL;
251 }
252 }
253 }
254 return result;
255 }
256
257
258
259
260
261
262
263
264 private static int getLineStart(String line, final int tabWidth) {
265 int lineStart = 0;
266 for (int index = 0; index < line.length(); ++index) {
267 if (!Character.isWhitespace(line.charAt(index))) {
268 lineStart = CommonUtil.lengthExpandedTabs(line, index, tabWidth);
269 break;
270 }
271 }
272 return lineStart;
273 }
274
275 private enum CommentType {
276
277 MULTILEVEL,
278 SINGLE_LEVEL,
279 NON_STRICT_LEVEL,
280 UNKNOWN,
281
282 }
283
284 }