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.Paths;
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 AbstractModuleTestSupport {
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 private static Integer[] getLinesWithWarnAndCheckComments(String aFileName,
64 final int tabWidth)
65 throws IOException {
66 final List<Integer> result = new ArrayList<>();
67 try (BufferedReader br = Files.newBufferedReader(
68 Paths.get(aFileName), StandardCharsets.UTF_8)) {
69 int lineNumber = 1;
70 for (String line = br.readLine(); line != null; line = br.readLine()) {
71 final Matcher match = LINE_WITH_COMMENT_REGEX.matcher(line);
72 if (match.matches()) {
73 final String comment = match.group(1);
74 final int indentInComment = getIndentFromComment(comment);
75 final int actualIndent = getLineStart(line, tabWidth);
76
77 if (actualIndent != indentInComment) {
78 throw new IllegalStateException(String.format(Locale.ROOT,
79 "File \"%1$s\" has incorrect indentation in comment."
80 + "Line %2$d: comment:%3$d, actual:%4$d.",
81 aFileName,
82 lineNumber,
83 indentInComment,
84 actualIndent));
85 }
86
87 if (isWarnComment(comment)) {
88 result.add(lineNumber);
89 }
90
91 if (!isCommentConsistent(comment)) {
92 throw new IllegalStateException(String.format(Locale.ROOT,
93 "File \"%1$s\" has inconsistent comment on line %2$d",
94 aFileName,
95 lineNumber));
96 }
97 }
98 else if (NONEMPTY_LINE_REGEX.matcher(line).matches()) {
99 throw new IllegalStateException(String.format(Locale.ROOT,
100 "File \"%1$s\" has no indentation comment or its format "
101 + "malformed. Error on line: %2$d(%3$s)",
102 aFileName,
103 lineNumber,
104 line));
105 }
106 lineNumber++;
107 }
108 }
109 return result.toArray(new Integer[0]);
110 }
111
112 private static int getIndentFromComment(String comment) {
113 final Matcher match = GET_INDENT_FROM_COMMENT_REGEX.matcher(comment);
114 match.matches();
115 return Integer.parseInt(match.group(1));
116 }
117
118 private static boolean isWarnComment(String comment) {
119 return comment.endsWith(" warn");
120 }
121
122 private static boolean isCommentConsistent(String comment) {
123 final int indentInComment = getIndentFromComment(comment);
124 final boolean isWarnComment = isWarnComment(comment);
125
126 final boolean result;
127 final CommentType type = getCommentType(comment);
128 switch (type) {
129 case MULTILEVEL:
130 result = isMultiLevelCommentConsistent(comment, indentInComment, isWarnComment);
131 break;
132
133 case SINGLE_LEVEL:
134 result = isSingleLevelCommentConsistent(comment, indentInComment, isWarnComment);
135 break;
136
137 case NON_STRICT_LEVEL:
138 result = isNonStrictCommentConsistent(comment, indentInComment, isWarnComment);
139 break;
140
141 case UNKNOWN:
142 throw new IllegalArgumentException("Cannot determine comment consistent");
143
144 default:
145 throw new IllegalStateException("Cannot determine comment is consistent");
146 }
147 return result;
148 }
149
150 private static boolean isNonStrictCommentConsistent(String comment,
151 int indentInComment, boolean isWarnComment) {
152 final Matcher nonStrictLevelMatch = NON_STRICT_LEVEL_COMMENT_REGEX.matcher(comment);
153 nonStrictLevelMatch.matches();
154 final int expectedMinimalIndent = Integer.parseInt(nonStrictLevelMatch.group(1));
155
156 return indentInComment >= expectedMinimalIndent && !isWarnComment
157 || indentInComment < expectedMinimalIndent && isWarnComment;
158 }
159
160 private static boolean isSingleLevelCommentConsistent(String comment,
161 int indentInComment, boolean isWarnComment) {
162 final Matcher singleLevelMatch = SINGLE_LEVEL_COMMENT_REGEX.matcher(comment);
163 singleLevelMatch.matches();
164 final int expectedLevel = Integer.parseInt(singleLevelMatch.group(1));
165
166 return expectedLevel == indentInComment && !isWarnComment
167 || expectedLevel != indentInComment && isWarnComment;
168 }
169
170 private static boolean isMultiLevelCommentConsistent(String comment,
171 int indentInComment, boolean isWarnComment) {
172 final Matcher multilevelMatch = MULTILEVEL_COMMENT_REGEX.matcher(comment);
173 multilevelMatch.matches();
174 final String[] levels = multilevelMatch.group(1).split(",");
175 final String indentInCommentStr = String.valueOf(indentInComment);
176 final boolean containsActualLevel =
177 Arrays.asList(levels).contains(indentInCommentStr);
178
179 return containsActualLevel && !isWarnComment
180 || !containsActualLevel && isWarnComment;
181 }
182
183 private static CommentType getCommentType(String comment) {
184 CommentType result = CommentType.UNKNOWN;
185 final Matcher multilevelMatch = MULTILEVEL_COMMENT_REGEX.matcher(comment);
186 if (multilevelMatch.matches()) {
187 result = CommentType.MULTILEVEL;
188 }
189 else {
190 final Matcher singleLevelMatch = SINGLE_LEVEL_COMMENT_REGEX.matcher(comment);
191 if (singleLevelMatch.matches()) {
192 result = CommentType.SINGLE_LEVEL;
193 }
194 else {
195 final Matcher nonStrictLevelMatch = NON_STRICT_LEVEL_COMMENT_REGEX.matcher(comment);
196 if (nonStrictLevelMatch.matches()) {
197 result = CommentType.NON_STRICT_LEVEL;
198 }
199 }
200 }
201 return result;
202 }
203
204 private static int getLineStart(String line, final int tabWidth) {
205 int lineStart = 0;
206 for (int index = 0; index < line.length(); ++index) {
207 if (!Character.isWhitespace(line.charAt(index))) {
208 lineStart = CommonUtil.lengthExpandedTabs(line, index, tabWidth);
209 break;
210 }
211 }
212 return lineStart;
213 }
214
215 private enum CommentType {
216
217 MULTILEVEL,
218 SINGLE_LEVEL,
219 NON_STRICT_LEVEL,
220 UNKNOWN,
221
222 }
223
224 }