1
2
3
4
5 package com.qulice.checkstyle;
6
7 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
8 import com.puppycrawl.tools.checkstyle.api.DetailAST;
9 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
10 import java.util.Locale;
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 public final class JavadocNoIndentCheck extends AbstractCheck {
34
35
36
37
38 public JavadocNoIndentCheck() {
39
40 }
41
42 @Override
43 public int[] getDefaultTokens() {
44 return new int[] {
45 TokenTypes.PACKAGE_DEF,
46 TokenTypes.CLASS_DEF,
47 TokenTypes.INTERFACE_DEF,
48 TokenTypes.ANNOTATION_DEF,
49 TokenTypes.ANNOTATION_FIELD_DEF,
50 TokenTypes.ENUM_DEF,
51 TokenTypes.ENUM_CONSTANT_DEF,
52 TokenTypes.VARIABLE_DEF,
53 TokenTypes.CTOR_DEF,
54 TokenTypes.METHOD_DEF,
55 };
56 }
57
58 @Override
59 public int[] getAcceptableTokens() {
60 return this.getDefaultTokens();
61 }
62
63 @Override
64 public int[] getRequiredTokens() {
65 return this.getDefaultTokens();
66 }
67
68 @Override
69 public void visitToken(final DetailAST ast) {
70 final String[] lines = this.getLines();
71 final int current = ast.getLineNo();
72 final int start =
73 JavadocNoIndentCheck.findCommentStart(lines, current) + 1;
74 if (JavadocNoIndentCheck.isNodeHavingJavadoc(ast, start)
75 && start < lines.length) {
76 this.check(
77 lines, start,
78 JavadocNoIndentCheck.findCommentEnd(lines, current) - 1
79 );
80 }
81 }
82
83 private void check(final String[] lines, final int start, final int end) {
84 boolean pre = false;
85 boolean tagged = false;
86 int depth = 0;
87 for (int pos = start; pos <= end; pos += 1) {
88 final String line = lines[pos];
89 final String body = JavadocNoIndentCheck.afterAsterisk(line);
90 final boolean region = JavadocNoIndentCheck.region(pre, depth, line);
91 if (!region && body.trim().startsWith("@")) {
92 tagged = true;
93 }
94 if (!region && !tagged
95 && JavadocNoIndentCheck.overIndented(body)) {
96 this.log(
97 pos + 1,
98 "Extra indentation is not allowed in Javadoc"
99 );
100 }
101 pre = JavadocNoIndentCheck.nextPre(pre, line);
102 depth = JavadocNoIndentCheck.nextDepth(depth, line);
103 }
104 }
105
106 private static boolean region(final boolean pre, final int depth,
107 final String line) {
108 return pre || depth > 0 || line.contains("{@snippet");
109 }
110
111 private static String afterAsterisk(final String line) {
112 final int star = line.indexOf('*');
113 final String result;
114 if (star < 0) {
115 result = "";
116 } else {
117 result = line.substring(star + 1);
118 }
119 return result;
120 }
121
122 private static boolean overIndented(final String body) {
123 int spaces = 0;
124 while (spaces < body.length() && body.charAt(spaces) == ' ') {
125 spaces += 1;
126 }
127 return spaces > 1 && spaces < body.length();
128 }
129
130 private static boolean nextPre(final boolean pre, final String line) {
131 final String lower = line.toLowerCase(Locale.ENGLISH);
132 final boolean result;
133 if (pre) {
134 result = !lower.contains("</pre>");
135 } else {
136 result = lower.contains("<pre>") && !lower.contains("</pre>");
137 }
138 return result;
139 }
140
141 private static int nextDepth(final int depth, final String line) {
142 final int result;
143 if (depth > 0) {
144 result = Math.max(0, depth + JavadocNoIndentCheck.braces(line));
145 } else {
146 final int idx = line.indexOf("{@snippet");
147 if (idx < 0) {
148 result = 0;
149 } else {
150 result = Math.max(
151 0, JavadocNoIndentCheck.braces(line.substring(idx))
152 );
153 }
154 }
155 return result;
156 }
157
158 private static int braces(final String text) {
159 int delta = 0;
160 for (int pos = 0; pos < text.length(); pos += 1) {
161 final char chr = text.charAt(pos);
162 if (chr == '{') {
163 delta += 1;
164 } else if (chr == '}') {
165 delta -= 1;
166 }
167 }
168 return delta;
169 }
170
171 private static boolean isNodeHavingJavadoc(final DetailAST node,
172 final int start) {
173 return start > JavadocNoIndentCheck.getLineNoOfPreviousNode(node);
174 }
175
176 private static int getLineNoOfPreviousNode(final DetailAST node) {
177 int start = 0;
178 final DetailAST previous = node.getPreviousSibling();
179 if (previous != null) {
180 start = previous.getLineNo();
181 }
182 return start;
183 }
184
185 private static int findCommentStart(final String[] lines, final int start) {
186 return JavadocNoIndentCheck.findTrimmedTextUp(lines, start, "/**");
187 }
188
189 private static int findCommentEnd(final String[] lines, final int start) {
190 int found = -1;
191 for (int pos = start - 1; pos >= 0; pos -= 1) {
192 final String trimmed = lines[pos].trim();
193 if ("*/".equals(trimmed) || "**/".equals(trimmed)) {
194 found = pos;
195 break;
196 }
197 }
198 return found;
199 }
200
201 private static int findTrimmedTextUp(final String[] lines,
202 final int start, final String text) {
203 int found = -1;
204 for (int pos = start - 1; pos >= 0; pos -= 1) {
205 if (lines[pos].trim().equals(text)) {
206 found = pos;
207 break;
208 }
209 }
210 return found;
211 }
212 }