1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle;
21
22 import java.util.Set;
23
24 import org.antlr.v4.runtime.BaseErrorListener;
25 import org.antlr.v4.runtime.CharStreams;
26 import org.antlr.v4.runtime.CommonTokenStream;
27 import org.antlr.v4.runtime.RecognitionException;
28 import org.antlr.v4.runtime.Recognizer;
29 import org.antlr.v4.runtime.atn.PredictionMode;
30 import org.antlr.v4.runtime.misc.ParseCancellationException;
31
32 import com.puppycrawl.tools.checkstyle.api.DetailAST;
33 import com.puppycrawl.tools.checkstyle.api.DetailNode;
34 import com.puppycrawl.tools.checkstyle.grammar.SimpleToken;
35 import com.puppycrawl.tools.checkstyle.grammar.javadoc.JavadocCommentsLexer;
36 import com.puppycrawl.tools.checkstyle.grammar.javadoc.JavadocCommentsParser;
37 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
38
39
40
41
42
43 public class JavadocDetailNodeParser {
44
45
46
47
48 public static final String MSG_JAVADOC_PARSE_RULE_ERROR = "javadoc.parse.rule.error";
49
50
51
52
53 public static final String MSG_UNCLOSED_HTML_TAG = "javadoc.unclosedHtml";
54
55
56 private static final String JAVADOC_START = "/**";
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 public ParseStatus parseJavadocComment(DetailAST javadocCommentAst) {
78 final int blockCommentLineNumber = javadocCommentAst.getLineNo();
79
80 final String javadocComment = JavadocUtil.getJavadocCommentContent(javadocCommentAst);
81 final ParseStatus result = new ParseStatus();
82
83
84
85
86 final DescriptiveErrorListener errorListener = new DescriptiveErrorListener();
87
88
89
90
91 errorListener.setOffset(javadocCommentAst.getLineNo() - 1);
92
93 final JavadocCommentsLexer lexer =
94 new JavadocCommentsLexer(CharStreams.fromString(javadocComment), true);
95
96 lexer.removeErrorListeners();
97 lexer.addErrorListener(errorListener);
98
99 final CommonTokenStream tokens = new CommonTokenStream(lexer);
100 tokens.fill();
101
102 final Set<SimpleToken> unclosedTags = lexer.getUnclosedTagNameTokens();
103 final JavadocCommentsParser parser = new JavadocCommentsParser(tokens, unclosedTags);
104
105
106 parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
107
108
109 parser.removeErrorListeners();
110
111 parser.addErrorListener(errorListener);
112
113
114
115 parser.setErrorHandler(new CheckstyleParserErrorStrategy());
116
117 try {
118 final JavadocCommentsParser.JavadocContext javadoc = parser.javadoc();
119 final int javadocColumnNumber = javadocCommentAst.getColumnNo()
120 + JAVADOC_START.length();
121
122 final JavadocCommentsAstVisitor visitor = new JavadocCommentsAstVisitor(
123 tokens, blockCommentLineNumber, javadocColumnNumber);
124 final DetailNode tree = visitor.visit(javadoc);
125
126 result.setTree(tree);
127
128 result.firstNonTightHtmlTag = visitor.getFirstNonTightHtmlTag();
129
130 result.setParseErrorMessage(errorListener.getErrorMessage());
131 }
132 catch (ParseCancellationException | IllegalArgumentException exc) {
133 result.setParseErrorMessage(errorListener.getErrorMessage());
134 }
135
136 return result;
137 }
138
139
140
141
142 private static final class DescriptiveErrorListener extends BaseErrorListener {
143
144
145
146
147
148
149 private int offset;
150
151
152
153
154 private ParseErrorMessage errorMessage;
155
156
157
158
159
160
161 private ParseErrorMessage getErrorMessage() {
162 return errorMessage;
163 }
164
165
166
167
168
169
170
171
172
173 public void setOffset(int offset) {
174 this.offset = offset;
175 }
176
177
178
179
180
181
182
183
184
185
186
187 @Override
188 public void syntaxError(
189 Recognizer<?, ?> recognizer, Object offendingSymbol,
190 int line, int charPositionInLine,
191 String msg, RecognitionException ex) {
192 final int lineNumber = offset + line;
193
194 final String target;
195 if (recognizer instanceof JavadocCommentsLexer lexer) {
196 target = lexer.getPreviousToken().getText();
197 }
198 else {
199 final int ruleIndex = ex.getCtx().getRuleIndex();
200 final String ruleName = recognizer.getRuleNames()[ruleIndex];
201 target = convertUpperCamelToUpperUnderscore(ruleName);
202 }
203
204 errorMessage = new ParseErrorMessage(lineNumber,
205 MSG_JAVADOC_PARSE_RULE_ERROR, charPositionInLine, msg, target);
206
207 }
208
209
210
211
212
213
214
215
216 private static String convertUpperCamelToUpperUnderscore(String text) {
217 final StringBuilder result = new StringBuilder(20);
218 for (char letter : text.toCharArray()) {
219 if (Character.isUpperCase(letter)) {
220 result.append('_');
221 }
222 result.append(Character.toUpperCase(letter));
223 }
224 return result.toString();
225 }
226 }
227
228
229
230
231
232 public static class ParseStatus {
233
234
235
236
237 private DetailNode tree;
238
239
240
241
242 private ParseErrorMessage parseErrorMessage;
243
244
245
246
247
248
249
250
251 private DetailNode firstNonTightHtmlTag;
252
253
254
255
256
257
258 public DetailNode getTree() {
259 return tree;
260 }
261
262
263
264
265
266
267 public void setTree(DetailNode tree) {
268 this.tree = tree;
269 }
270
271
272
273
274
275
276 public ParseErrorMessage getParseErrorMessage() {
277 return parseErrorMessage;
278 }
279
280
281
282
283
284
285 public void setParseErrorMessage(ParseErrorMessage parseErrorMessage) {
286 this.parseErrorMessage = parseErrorMessage;
287 }
288
289
290
291
292
293
294
295
296
297 public boolean isNonTight() {
298 return firstNonTightHtmlTag != null;
299 }
300
301
302
303
304
305
306
307
308
309 public DetailNode getFirstNonTightHtmlTag() {
310 return firstNonTightHtmlTag;
311 }
312
313 }
314
315
316
317
318 public static class ParseErrorMessage {
319
320
321
322
323 private final int lineNumber;
324
325
326
327
328 private final String messageKey;
329
330
331
332
333 private final Object[] messageArguments;
334
335
336
337
338
339
340
341
342 ParseErrorMessage(int lineNumber, String messageKey,
343 Object... messageArguments) {
344 this.lineNumber = lineNumber;
345 this.messageKey = messageKey;
346 this.messageArguments = messageArguments.clone();
347 }
348
349
350
351
352
353
354 public int getLineNumber() {
355 return lineNumber;
356 }
357
358
359
360
361
362
363 public String getMessageKey() {
364 return messageKey;
365 }
366
367
368
369
370
371
372 public Object[] getMessageArguments() {
373 return messageArguments.clone();
374 }
375
376 }
377 }