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