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.ArrayDeque;
23 import java.util.Deque;
24 import java.util.List;
25
26 import org.antlr.v4.runtime.BailErrorStrategy;
27 import org.antlr.v4.runtime.BaseErrorListener;
28 import org.antlr.v4.runtime.BufferedTokenStream;
29 import org.antlr.v4.runtime.CharStreams;
30 import org.antlr.v4.runtime.CommonToken;
31 import org.antlr.v4.runtime.CommonTokenStream;
32 import org.antlr.v4.runtime.FailedPredicateException;
33 import org.antlr.v4.runtime.InputMismatchException;
34 import org.antlr.v4.runtime.NoViableAltException;
35 import org.antlr.v4.runtime.Parser;
36 import org.antlr.v4.runtime.ParserRuleContext;
37 import org.antlr.v4.runtime.RecognitionException;
38 import org.antlr.v4.runtime.Recognizer;
39 import org.antlr.v4.runtime.Token;
40 import org.antlr.v4.runtime.misc.Interval;
41 import org.antlr.v4.runtime.misc.ParseCancellationException;
42 import org.antlr.v4.runtime.tree.ParseTree;
43 import org.antlr.v4.runtime.tree.TerminalNode;
44
45 import com.puppycrawl.tools.checkstyle.api.DetailAST;
46 import com.puppycrawl.tools.checkstyle.api.DetailNode;
47 import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
48 import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocNodeImpl;
49 import com.puppycrawl.tools.checkstyle.grammar.javadoc.JavadocLexer;
50 import com.puppycrawl.tools.checkstyle.grammar.javadoc.JavadocParser;
51 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
52
53
54
55
56
57 public class JavadocDetailNodeParser {
58
59
60
61
62
63
64
65
66 public static final String MSG_JAVADOC_MISSED_HTML_CLOSE = "javadoc.missed.html.close";
67
68
69
70
71 public static final String MSG_JAVADOC_WRONG_SINGLETON_TAG =
72 "javadoc.wrong.singleton.html.tag";
73
74
75
76
77 public static final String MSG_JAVADOC_PARSE_RULE_ERROR = "javadoc.parse.rule.error";
78
79
80
81
82 public static final String MSG_UNCLOSED_HTML_TAG = "javadoc.unclosedHtml";
83
84
85 private static final String JAVADOC_START = "/**";
86
87
88
89
90 private int blockCommentLineNumber;
91
92
93
94
95 private DescriptiveErrorListener errorListener;
96
97
98
99
100
101
102
103 public ParseStatus parseJavadocAsDetailNode(DetailAST javadocCommentAst) {
104 blockCommentLineNumber = javadocCommentAst.getLineNo();
105
106 final String javadocComment = JavadocUtil.getJavadocCommentContent(javadocCommentAst);
107
108
109
110
111 errorListener = new DescriptiveErrorListener();
112
113
114
115
116 errorListener.setOffset(javadocCommentAst.getLineNo() - 1);
117
118 final ParseStatus result = new ParseStatus();
119
120 try {
121 final JavadocParser javadocParser = createJavadocParser(javadocComment);
122
123 final ParseTree javadocParseTree = javadocParser.javadoc();
124
125 final DetailNode tree = convertParseTreeToDetailNode(javadocParseTree);
126
127 adjustFirstLineToJavadocIndent(tree,
128 javadocCommentAst.getColumnNo()
129 + JAVADOC_START.length());
130 result.setTree(tree);
131 result.firstNonTightHtmlTag = getFirstNonTightHtmlTag(javadocParser);
132 }
133 catch (ParseCancellationException | IllegalArgumentException ex) {
134 ParseErrorMessage parseErrorMessage = null;
135
136 if (ex.getCause() instanceof FailedPredicateException
137 || ex.getCause() instanceof NoViableAltException) {
138 final RecognitionException recognitionEx = (RecognitionException) ex.getCause();
139 if (recognitionEx.getCtx() instanceof JavadocParser.HtmlTagContext) {
140 final Token htmlTagNameStart = getMissedHtmlTag(recognitionEx);
141 parseErrorMessage = new ParseErrorMessage(
142 errorListener.offset + htmlTagNameStart.getLine(),
143 MSG_JAVADOC_MISSED_HTML_CLOSE,
144 htmlTagNameStart.getCharPositionInLine(),
145 htmlTagNameStart.getText());
146 }
147 }
148
149 if (parseErrorMessage == null) {
150
151
152
153 parseErrorMessage = errorListener.getErrorMessage();
154 }
155
156 result.setParseErrorMessage(parseErrorMessage);
157 }
158
159 return result;
160 }
161
162
163
164
165
166
167
168 private JavadocParser createJavadocParser(String blockComment) {
169 final JavadocLexer lexer = new JavadocLexer(CharStreams.fromString(blockComment));
170
171 final CommonTokenStream tokens = new CommonTokenStream(lexer);
172
173 final JavadocParser parser = new JavadocParser(tokens);
174
175
176 parser.removeErrorListeners();
177
178
179 parser.addErrorListener(errorListener);
180
181
182
183 parser.setErrorHandler(new JavadocParserErrorStrategy());
184
185 return parser;
186 }
187
188
189
190
191
192
193
194
195 private DetailNode convertParseTreeToDetailNode(ParseTree parseTreeNode) {
196 final JavadocNodeImpl rootJavadocNode = createRootJavadocNode(parseTreeNode);
197
198 JavadocNodeImpl currentJavadocParent = rootJavadocNode;
199 ParseTree parseTreeParent = parseTreeNode;
200
201 while (currentJavadocParent != null) {
202
203 if (currentJavadocParent.getType() == JavadocTokenTypes.TEXT) {
204 currentJavadocParent
205 .setChildren((DetailNode[]) JavadocNodeImpl.EMPTY_DETAIL_NODE_ARRAY);
206 }
207
208 final JavadocNodeImpl[] children =
209 (JavadocNodeImpl[]) currentJavadocParent.getChildren();
210
211 insertChildrenNodes(children, parseTreeParent);
212
213 if (children.length > 0) {
214 currentJavadocParent = children[0];
215 parseTreeParent = parseTreeParent.getChild(0);
216 }
217 else {
218 JavadocNodeImpl nextJavadocSibling = (JavadocNodeImpl) JavadocUtil
219 .getNextSibling(currentJavadocParent);
220
221 ParseTree nextParseTreeSibling = getNextSibling(parseTreeParent);
222
223 while (nextJavadocSibling == null) {
224 currentJavadocParent =
225 (JavadocNodeImpl) currentJavadocParent.getParent();
226
227 parseTreeParent = parseTreeParent.getParent();
228
229 if (currentJavadocParent == null) {
230 break;
231 }
232
233 nextJavadocSibling = (JavadocNodeImpl) JavadocUtil
234 .getNextSibling(currentJavadocParent);
235
236 nextParseTreeSibling = getNextSibling(parseTreeParent);
237 }
238 currentJavadocParent = nextJavadocSibling;
239 parseTreeParent = nextParseTreeSibling;
240 }
241 }
242
243 return rootJavadocNode;
244 }
245
246
247
248
249
250
251 private void insertChildrenNodes(final JavadocNodeImpl[] nodes, ParseTree parseTreeParent) {
252 for (int i = 0; i < nodes.length; i++) {
253 final JavadocNodeImpl currentJavadocNode = nodes[i];
254 final ParseTree currentParseTreeNodeChild = parseTreeParent.getChild(i);
255 final JavadocNodeImpl[] subChildren =
256 createChildrenNodes(currentJavadocNode, currentParseTreeNodeChild);
257 currentJavadocNode.setChildren((DetailNode[]) subChildren);
258 }
259 }
260
261
262
263
264
265
266
267 private JavadocNodeImpl[]
268 createChildrenNodes(JavadocNodeImpl parentJavadocNode, ParseTree parseTreeNode) {
269 final JavadocNodeImpl[] children =
270 new JavadocNodeImpl[parseTreeNode.getChildCount()];
271
272 for (int j = 0; j < children.length; j++) {
273 final JavadocNodeImpl child =
274 createJavadocNode(parseTreeNode.getChild(j), parentJavadocNode, j);
275
276 children[j] = child;
277 }
278 return children;
279 }
280
281
282
283
284
285
286 private JavadocNodeImpl createRootJavadocNode(ParseTree parseTreeNode) {
287 final JavadocNodeImpl rootJavadocNode = createJavadocNode(parseTreeNode, null, -1);
288
289 final int childCount = parseTreeNode.getChildCount();
290 final DetailNode[] children = rootJavadocNode.getChildren();
291
292 for (int i = 0; i < childCount; i++) {
293 final JavadocNodeImpl child = createJavadocNode(parseTreeNode.getChild(i),
294 rootJavadocNode, i);
295 children[i] = child;
296 }
297 rootJavadocNode.setChildren(children);
298 return rootJavadocNode;
299 }
300
301
302
303
304
305
306
307
308
309 private JavadocNodeImpl createJavadocNode(ParseTree parseTree, DetailNode parent, int index) {
310 final JavadocNodeImpl node = new JavadocNodeImpl();
311 if (parseTree.getChildCount() == 0
312 || "Text".equals(getNodeClassNameWithoutContext(parseTree))) {
313 node.setText(parseTree.getText());
314 }
315 else {
316 node.setText(getFormattedNodeClassNameWithoutContext(parseTree));
317 }
318 node.setColumnNumber(getColumn(parseTree));
319 node.setLineNumber(getLine(parseTree) + blockCommentLineNumber);
320 node.setIndex(index);
321 node.setType(getTokenType(parseTree));
322 node.setParent(parent);
323 node.setChildren((DetailNode[]) new JavadocNodeImpl[parseTree.getChildCount()]);
324 return node;
325 }
326
327
328
329
330
331
332 private void adjustFirstLineToJavadocIndent(DetailNode tree, int javadocColumnNumber) {
333 if (tree.getLineNumber() == blockCommentLineNumber) {
334 ((JavadocNodeImpl) tree).setColumnNumber(tree.getColumnNumber() + javadocColumnNumber);
335 final DetailNode[] children = tree.getChildren();
336 for (DetailNode child : children) {
337 adjustFirstLineToJavadocIndent(child, javadocColumnNumber);
338 }
339 }
340 }
341
342
343
344
345
346
347
348 private static int getLine(ParseTree tree) {
349 final int line;
350 if (tree instanceof TerminalNode) {
351 line = ((TerminalNode) tree).getSymbol().getLine() - 1;
352 }
353 else {
354 final ParserRuleContext rule = (ParserRuleContext) tree;
355 line = rule.start.getLine() - 1;
356 }
357 return line;
358 }
359
360
361
362
363
364
365
366 private static int getColumn(ParseTree tree) {
367 final int column;
368 if (tree instanceof TerminalNode) {
369 column = ((TerminalNode) tree).getSymbol().getCharPositionInLine();
370 }
371 else {
372 final ParserRuleContext rule = (ParserRuleContext) tree;
373 column = rule.start.getCharPositionInLine();
374 }
375 return column;
376 }
377
378
379
380
381
382
383 private static ParseTree getNextSibling(ParseTree node) {
384 ParseTree nextSibling = null;
385
386 if (node.getParent() != null) {
387 final ParseTree parent = node.getParent();
388 int index = 0;
389 while (true) {
390 final ParseTree currentNode = parent.getChild(index);
391 if (currentNode.equals(node)) {
392 nextSibling = parent.getChild(index + 1);
393 break;
394 }
395 index++;
396 }
397 }
398 return nextSibling;
399 }
400
401
402
403
404
405
406 private static int getTokenType(ParseTree node) {
407 final int tokenType;
408
409 if (node.getChildCount() == 0) {
410 tokenType = ((TerminalNode) node).getSymbol().getType();
411 }
412 else {
413 final String className = getNodeClassNameWithoutContext(node);
414 tokenType = JavadocUtil.getTokenId(convertUpperCamelToUpperUnderscore(className));
415 }
416
417 return tokenType;
418 }
419
420
421
422
423
424
425
426
427 private static String getFormattedNodeClassNameWithoutContext(ParseTree node) {
428 final String classNameWithoutContext = getNodeClassNameWithoutContext(node);
429 return convertUpperCamelToUpperUnderscore(classNameWithoutContext);
430 }
431
432
433
434
435
436
437
438
439 private static String getNodeClassNameWithoutContext(ParseTree node) {
440 final String className = node.getClass().getSimpleName();
441
442 final int contextLength = 7;
443 return className.substring(0, className.length() - contextLength);
444 }
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473 private static Token getMissedHtmlTag(RecognitionException exception) {
474 Token htmlTagNameStart = null;
475 final Interval sourceInterval = exception.getCtx().getSourceInterval();
476 final List<Token> tokenList = ((BufferedTokenStream) exception.getInputStream())
477 .getTokens(sourceInterval.a, sourceInterval.b);
478 final Deque<Token> stack = new ArrayDeque<>();
479 int prevTokenType = JavadocTokenTypes.EOF;
480 for (final Token token : tokenList) {
481 final int tokenType = token.getType();
482 if (tokenType == JavadocTokenTypes.HTML_TAG_NAME
483 && prevTokenType == JavadocTokenTypes.START) {
484 stack.push(token);
485 }
486 else if (tokenType == JavadocTokenTypes.HTML_TAG_NAME && !stack.isEmpty()) {
487 if (stack.peek().getText().equals(token.getText())) {
488 stack.pop();
489 }
490 else {
491 htmlTagNameStart = stack.pop();
492 }
493 }
494 prevTokenType = tokenType;
495 }
496 if (htmlTagNameStart == null) {
497 htmlTagNameStart = stack.pop();
498 }
499 return htmlTagNameStart;
500 }
501
502
503
504
505
506
507
508
509
510
511
512 private Token getFirstNonTightHtmlTag(JavadocParser javadocParser) {
513 final CommonToken offendingToken;
514 final ParserRuleContext nonTightTagStartContext = javadocParser.nonTightTagStartContext;
515 if (nonTightTagStartContext == null) {
516 offendingToken = null;
517 }
518 else {
519 final Token token = ((TerminalNode) nonTightTagStartContext.getChild(1))
520 .getSymbol();
521 offendingToken = new CommonToken(token);
522 offendingToken.setLine(offendingToken.getLine() + errorListener.offset);
523 }
524 return offendingToken;
525 }
526
527
528
529
530
531
532
533 private static String convertUpperCamelToUpperUnderscore(String text) {
534 final StringBuilder result = new StringBuilder(20);
535 boolean first = true;
536 for (char letter : text.toCharArray()) {
537 if (!first && Character.isUpperCase(letter)) {
538 result.append('_');
539 }
540 result.append(Character.toUpperCase(letter));
541 first = false;
542 }
543 return result.toString();
544 }
545
546
547
548
549 private static class DescriptiveErrorListener extends BaseErrorListener {
550
551
552
553
554
555
556 private int offset;
557
558
559
560
561 private ParseErrorMessage errorMessage;
562
563
564
565
566
567 private ParseErrorMessage getErrorMessage() {
568 return errorMessage;
569 }
570
571
572
573
574
575
576
577
578 public void setOffset(int offset) {
579 this.offset = offset;
580 }
581
582
583
584
585
586
587
588
589
590
591
592 @Override
593 public void syntaxError(
594 Recognizer<?, ?> recognizer, Object offendingSymbol,
595 int line, int charPositionInLine,
596 String msg, RecognitionException ex) {
597 final int lineNumber = offset + line;
598
599 if (MSG_JAVADOC_WRONG_SINGLETON_TAG.equals(msg)) {
600 errorMessage = new ParseErrorMessage(lineNumber,
601 MSG_JAVADOC_WRONG_SINGLETON_TAG, charPositionInLine,
602 ((Token) offendingSymbol).getText());
603
604 throw new IllegalArgumentException(msg);
605 }
606 else {
607 final int ruleIndex = ex.getCtx().getRuleIndex();
608 final String ruleName = recognizer.getRuleNames()[ruleIndex];
609 final String upperCaseRuleName = convertUpperCamelToUpperUnderscore(ruleName);
610
611 errorMessage = new ParseErrorMessage(lineNumber,
612 MSG_JAVADOC_PARSE_RULE_ERROR, charPositionInLine, msg, upperCaseRuleName);
613 }
614 }
615
616 }
617
618
619
620
621
622 public static class ParseStatus {
623
624
625
626
627 private DetailNode tree;
628
629
630
631
632 private ParseErrorMessage parseErrorMessage;
633
634
635
636
637
638
639
640
641 private Token firstNonTightHtmlTag;
642
643
644
645
646
647 public DetailNode getTree() {
648 return tree;
649 }
650
651
652
653
654
655 public void setTree(DetailNode tree) {
656 this.tree = tree;
657 }
658
659
660
661
662
663 public ParseErrorMessage getParseErrorMessage() {
664 return parseErrorMessage;
665 }
666
667
668
669
670
671 public void setParseErrorMessage(ParseErrorMessage parseErrorMessage) {
672 this.parseErrorMessage = parseErrorMessage;
673 }
674
675
676
677
678
679
680
681
682
683 public boolean isNonTight() {
684 return firstNonTightHtmlTag != null;
685 }
686
687
688
689
690
691
692
693 public Token getFirstNonTightHtmlTag() {
694 return firstNonTightHtmlTag;
695 }
696
697 }
698
699
700
701
702 public static class ParseErrorMessage {
703
704
705
706
707 private final int lineNumber;
708
709
710
711
712 private final String messageKey;
713
714
715
716
717 private final Object[] messageArguments;
718
719
720
721
722
723
724
725
726 ParseErrorMessage(int lineNumber, String messageKey,
727 Object... messageArguments) {
728 this.lineNumber = lineNumber;
729 this.messageKey = messageKey;
730 this.messageArguments = messageArguments.clone();
731 }
732
733
734
735
736
737 public int getLineNumber() {
738 return lineNumber;
739 }
740
741
742
743
744
745 public String getMessageKey() {
746 return messageKey;
747 }
748
749
750
751
752
753 public Object[] getMessageArguments() {
754 return messageArguments.clone();
755 }
756
757 }
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773 private static class JavadocParserErrorStrategy extends BailErrorStrategy {
774
775 @Override
776 public Token recoverInline(Parser recognizer) {
777 reportError(recognizer, new InputMismatchException(recognizer));
778 return super.recoverInline(recognizer);
779 }
780
781 }
782
783 }