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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 public final class JavadocCompactParagraphCheck extends AbstractCheck {
49
50
51
52
53 public JavadocCompactParagraphCheck() {
54
55 }
56
57 @Override
58 public int[] getDefaultTokens() {
59 return new int[] {
60 TokenTypes.PACKAGE_DEF,
61 TokenTypes.CLASS_DEF,
62 TokenTypes.INTERFACE_DEF,
63 TokenTypes.ANNOTATION_DEF,
64 TokenTypes.ANNOTATION_FIELD_DEF,
65 TokenTypes.ENUM_DEF,
66 TokenTypes.ENUM_CONSTANT_DEF,
67 TokenTypes.VARIABLE_DEF,
68 TokenTypes.CTOR_DEF,
69 TokenTypes.METHOD_DEF,
70 };
71 }
72
73 @Override
74 public int[] getAcceptableTokens() {
75 return this.getDefaultTokens();
76 }
77
78 @Override
79 public int[] getRequiredTokens() {
80 return this.getDefaultTokens();
81 }
82
83 @Override
84 public void visitToken(final DetailAST ast) {
85 final String[] lines = this.getLines();
86 final int current = ast.getLineNo();
87 final int start =
88 JavadocCompactParagraphCheck.findCommentStart(lines, current) + 1;
89 final int end =
90 JavadocCompactParagraphCheck.findCommentEnd(lines, current) - 1;
91 if (JavadocCompactParagraphCheck.isNodeHavingJavadoc(ast, start)
92 && start < lines.length && end >= start) {
93 this.checkParagraphs(lines, start, end);
94 }
95 }
96
97 private void checkParagraphs(final String[] lines, final int start,
98 final int end) {
99 boolean pre = false;
100 int depth = 0;
101 for (int pos = start; pos <= end && pos < lines.length; pos += 1) {
102 final String body = JavadocCompactParagraphCheck.body(lines[pos]);
103 final String low = body.toLowerCase(Locale.ENGLISH);
104 if (depth > 0) {
105 depth += JavadocCompactParagraphCheck.braces(body);
106 } else if (low.contains("{@snippet")) {
107 depth = Math.max(0, JavadocCompactParagraphCheck.braces(body));
108 } else if (pre) {
109 pre = !low.contains("</pre>");
110 } else if (low.contains("<pre>")) {
111 pre = !low.contains("</pre>");
112 } else {
113 this.report(pos, body);
114 }
115 }
116 }
117
118 private void report(final int pos, final String body) {
119 if (body.endsWith("<p>")) {
120 this.log(
121 pos + 1,
122 "Opening paragraph tag <p> must be followed by text on the same line"
123 );
124 }
125 if (body.startsWith("</p>")) {
126 this.log(
127 pos + 1,
128 "Closing paragraph tag </p> must be preceded by text on the same line"
129 );
130 }
131 }
132
133 private static String body(final String line) {
134 String trimmed = line.trim();
135 if (trimmed.startsWith("*")) {
136 trimmed = trimmed.substring(1).trim();
137 }
138 return trimmed;
139 }
140
141 private static int braces(final String body) {
142 int delta = 0;
143 for (int pos = 0; pos < body.length(); pos += 1) {
144 final char chr = body.charAt(pos);
145 if (chr == '{') {
146 delta += 1;
147 } else if (chr == '}') {
148 delta -= 1;
149 }
150 }
151 return delta;
152 }
153
154 private static boolean isNodeHavingJavadoc(final DetailAST node,
155 final int start) {
156 return start > JavadocCompactParagraphCheck.getLineNoOfPreviousNode(
157 node
158 );
159 }
160
161 private static int getLineNoOfPreviousNode(final DetailAST node) {
162 int start = 0;
163 final DetailAST previous = node.getPreviousSibling();
164 if (previous != null) {
165 start = previous.getLineNo();
166 }
167 return start;
168 }
169
170 private static int findCommentStart(final String[] lines, final int start) {
171 return JavadocCompactParagraphCheck.findTrimmedTextUp(
172 lines, start, "/**"
173 );
174 }
175
176 private static int findCommentEnd(final String[] lines, final int start) {
177 int found = -1;
178 for (int pos = start - 1; pos >= 0; pos -= 1) {
179 final String trimmed = lines[pos].trim();
180 if ("*/".equals(trimmed) || "**/".equals(trimmed)) {
181 found = pos;
182 break;
183 }
184 }
185 return found;
186 }
187
188 private static int findTrimmedTextUp(final String[] lines,
189 final int start, final String text) {
190 int found = -1;
191 for (int pos = start - 1; pos >= 0; pos -= 1) {
192 if (lines[pos].trim().equals(text)) {
193 found = pos;
194 break;
195 }
196 }
197 return found;
198 }
199 }