View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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.google.checkstyle.test.base;
21  
22  import java.io.BufferedReader;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.io.InputStreamReader;
26  import java.nio.charset.StandardCharsets;
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.CommonUtils;
35  
36  public abstract class AbstractIndentationTestSupport extends AbstractModuleTestSupport {
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      private static Integer[] getLinesWithWarnAndCheckComments(String aFileName,
63              final int tabWidth)
64                      throws IOException {
65          final List<Integer> result = new ArrayList<>();
66          try (BufferedReader br = new BufferedReader(new InputStreamReader(
67                  new FileInputStream(aFileName), StandardCharsets.UTF_8))) {
68              int lineNumber = 1;
69              for (String line = br.readLine(); line != null; line = br.readLine()) {
70                  final Matcher match = LINE_WITH_COMMENT_REGEX.matcher(line);
71                  if (match.matches()) {
72                      final String comment = match.group(1);
73                      final int indentInComment = getIndentFromComment(comment);
74                      final int actualIndent = getLineStart(line, tabWidth);
75  
76                      if (actualIndent != indentInComment) {
77                          throw new IllegalStateException(String.format(Locale.ROOT,
78                                          "File \"%1$s\" has incorrect indentation in comment."
79                                                          + "Line %2$d: comment:%3$d, actual:%4$d.",
80                                          aFileName,
81                                          lineNumber,
82                                          indentInComment,
83                                          actualIndent));
84                      }
85  
86                      if (isWarnComment(comment)) {
87                          result.add(lineNumber);
88                      }
89  
90                      if (!isCommentConsistent(comment)) {
91                          throw new IllegalStateException(String.format(Locale.ROOT,
92                                          "File \"%1$s\" has inconsistent comment on line %2$d",
93                                          aFileName,
94                                          lineNumber));
95                      }
96                  }
97                  else if (NONEMPTY_LINE_REGEX.matcher(line).matches()) {
98                      throw new IllegalStateException(String.format(Locale.ROOT,
99                                      "File \"%1$s\" has no indentation comment or its format "
100                                                     + "malformed. Error on line: %2$d(%3$s)",
101                                     aFileName,
102                                     lineNumber,
103                                     line));
104                 }
105                 lineNumber++;
106             }
107         }
108         return result.toArray(new Integer[result.size()]);
109     }
110 
111     private static int getIndentFromComment(String comment) {
112         final Matcher match = GET_INDENT_FROM_COMMENT_REGEX.matcher(comment);
113         match.matches();
114         return Integer.parseInt(match.group(1));
115     }
116 
117     private static boolean isWarnComment(String comment) {
118         return comment.endsWith(" warn");
119     }
120 
121     private static boolean isCommentConsistent(String comment) {
122         final int indentInComment = getIndentFromComment(comment);
123         final boolean isWarnComment = isWarnComment(comment);
124 
125         final boolean result;
126         final CommentType type = getCommentType(comment);
127         switch (type) {
128             case MULTILEVEL:
129                 result = isMultiLevelCommentConsistent(comment, indentInComment, isWarnComment);
130                 break;
131 
132             case SINGLE_LEVEL:
133                 result = isSingleLevelCommentConsistent(comment, indentInComment, isWarnComment);
134                 break;
135 
136             case NON_STRICT_LEVEL:
137                 result = isNonStrictCommentConsistent(comment, indentInComment, isWarnComment);
138                 break;
139 
140             case UNKNOWN:
141                 throw new IllegalArgumentException("Cannot determine comment consistent");
142 
143             default:
144                 throw new IllegalStateException("Cannot determine comment is consistent");
145 
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 = CommonUtils.lengthExpandedTabs(line, index, tabWidth);
209                 break;
210             }
211         }
212         return lineStart;
213     }
214 
215     private enum CommentType {
216         MULTILEVEL, SINGLE_LEVEL, NON_STRICT_LEVEL, UNKNOWN
217     }
218 }