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
157 final boolean result;
158 final CommentType type = getCommentType(comment);
159 switch (type) {
160 case MULTILEVEL:
161 result = isMultiLevelCommentConsistent(comment, indentInComment, isWarnComment);
162 break;
163
164 case SINGLE_LEVEL:
165 result = isSingleLevelCommentConsistent(comment, indentInComment, isWarnComment);
166 break;
167
168 case NON_STRICT_LEVEL:
169 result = isNonStrictCommentConsistent(comment, indentInComment, isWarnComment);
170 break;
171
172 case UNKNOWN:
173 throw new IllegalArgumentException("Cannot determine comment consistent");
174
175 default:
176 throw new IllegalStateException("Cannot determine comment is consistent");
177 }
178 return result;
179 }
180
181
182
183
184
185
186
187
188
189 private static boolean isNonStrictCommentConsistent(String comment,
190 int indentInComment, boolean isWarnComment) {
191 final Matcher nonStrictLevelMatch = NON_STRICT_LEVEL_COMMENT_REGEX.matcher(comment);
192 nonStrictLevelMatch.matches();
193 final int expectedMinimalIndent = Integer.parseInt(nonStrictLevelMatch.group(1));
194
195 return indentInComment >= expectedMinimalIndent && !isWarnComment
196 || indentInComment < expectedMinimalIndent && isWarnComment;
197 }
198
199
200
201
202
203
204
205
206
207 private static boolean isSingleLevelCommentConsistent(String comment,
208 int indentInComment, boolean isWarnComment) {
209 final Matcher singleLevelMatch = SINGLE_LEVEL_COMMENT_REGEX.matcher(comment);
210 singleLevelMatch.matches();
211 final int expectedLevel = Integer.parseInt(singleLevelMatch.group(1));
212
213 return expectedLevel == indentInComment && !isWarnComment
214 || expectedLevel != indentInComment && isWarnComment;
215 }
216
217
218
219
220
221
222
223
224
225 private static boolean isMultiLevelCommentConsistent(String comment,
226 int indentInComment, boolean isWarnComment) {
227 final Matcher multilevelMatch = MULTILEVEL_COMMENT_REGEX.matcher(comment);
228 multilevelMatch.matches();
229 final String[] levels = multilevelMatch.group(1).split(",");
230 final String indentInCommentStr = String.valueOf(indentInComment);
231 final boolean containsActualLevel =
232 Arrays.asList(levels).contains(indentInCommentStr);
233
234 return containsActualLevel && !isWarnComment
235 || !containsActualLevel && isWarnComment;
236 }
237
238
239
240
241
242
243
244
245
246 private static CommentType getCommentType(String comment) {
247 CommentType result = CommentType.UNKNOWN;
248 final Matcher multilevelMatch = MULTILEVEL_COMMENT_REGEX.matcher(comment);
249 if (multilevelMatch.matches()) {
250 result = CommentType.MULTILEVEL;
251 }
252 else {
253 final Matcher singleLevelMatch = SINGLE_LEVEL_COMMENT_REGEX.matcher(comment);
254 if (singleLevelMatch.matches()) {
255 result = CommentType.SINGLE_LEVEL;
256 }
257 else {
258 final Matcher nonStrictLevelMatch = NON_STRICT_LEVEL_COMMENT_REGEX.matcher(comment);
259 if (nonStrictLevelMatch.matches()) {
260 result = CommentType.NON_STRICT_LEVEL;
261 }
262 }
263 }
264 return result;
265 }
266
267
268
269
270
271
272
273
274 private static int getLineStart(String line, final int tabWidth) {
275 int lineStart = 0;
276 for (int index = 0; index < line.length(); ++index) {
277 if (!Character.isWhitespace(line.charAt(index))) {
278 lineStart = CommonUtil.lengthExpandedTabs(line, index, tabWidth);
279 break;
280 }
281 }
282 return lineStart;
283 }
284
285 private enum CommentType {
286
287 MULTILEVEL,
288 SINGLE_LEVEL,
289 NON_STRICT_LEVEL,
290 UNKNOWN,
291
292 }
293
294 }