Tue, 17 Sep 2013 14:17:13 -0700
8024538: -Xdoclint + -Xprefer:source + incremental compilation == FAIL
Reviewed-by: darcy
1 /*
2 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 package com.sun.tools.doclint;
28 import java.io.IOException;
29 import java.io.StringWriter;
30 import java.net.URI;
31 import java.net.URISyntaxException;
32 import java.util.Deque;
33 import java.util.EnumSet;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.LinkedList;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Set;
40 import java.util.regex.Matcher;
41 import java.util.regex.Pattern;
43 import javax.lang.model.element.Element;
44 import javax.lang.model.element.ElementKind;
45 import javax.lang.model.element.ExecutableElement;
46 import javax.lang.model.element.Name;
47 import javax.lang.model.type.TypeKind;
48 import javax.lang.model.type.TypeMirror;
49 import javax.tools.Diagnostic.Kind;
50 import javax.tools.JavaFileObject;
52 import com.sun.source.doctree.AttributeTree;
53 import com.sun.source.doctree.AuthorTree;
54 import com.sun.source.doctree.DocCommentTree;
55 import com.sun.source.doctree.DocRootTree;
56 import com.sun.source.doctree.DocTree;
57 import com.sun.source.doctree.EndElementTree;
58 import com.sun.source.doctree.EntityTree;
59 import com.sun.source.doctree.ErroneousTree;
60 import com.sun.source.doctree.IdentifierTree;
61 import com.sun.source.doctree.InheritDocTree;
62 import com.sun.source.doctree.LinkTree;
63 import com.sun.source.doctree.LiteralTree;
64 import com.sun.source.doctree.ParamTree;
65 import com.sun.source.doctree.ReferenceTree;
66 import com.sun.source.doctree.ReturnTree;
67 import com.sun.source.doctree.SerialDataTree;
68 import com.sun.source.doctree.SerialFieldTree;
69 import com.sun.source.doctree.SinceTree;
70 import com.sun.source.doctree.StartElementTree;
71 import com.sun.source.doctree.TextTree;
72 import com.sun.source.doctree.ThrowsTree;
73 import com.sun.source.doctree.ValueTree;
74 import com.sun.source.doctree.VersionTree;
75 import com.sun.source.util.DocTreePath;
76 import com.sun.source.util.DocTreePathScanner;
77 import com.sun.source.util.TreePath;
78 import com.sun.tools.doclint.HtmlTag.AttrKind;
79 import com.sun.tools.javac.tree.DocPretty;
80 import static com.sun.tools.doclint.Messages.Group.*;
83 /**
84 * Validate a doc comment.
85 *
86 * <p><b>This is NOT part of any supported API.
87 * If you write code that depends on this, you do so at your own
88 * risk. This code and its internal interfaces are subject to change
89 * or deletion without notice.</b></p>
90 */
91 public class Checker extends DocTreePathScanner<Void, Void> {
92 final Env env;
94 Set<Element> foundParams = new HashSet<>();
95 Set<TypeMirror> foundThrows = new HashSet<>();
96 Map<JavaFileObject, Set<String>> foundAnchors = new HashMap<>();
97 boolean foundInheritDoc = false;
98 boolean foundReturn = false;
100 public enum Flag {
101 TABLE_HAS_CAPTION,
102 HAS_ELEMENT,
103 HAS_INLINE_TAG,
104 HAS_TEXT,
105 REPORTED_BAD_INLINE
106 }
108 static class TagStackItem {
109 final DocTree tree; // typically, but not always, StartElementTree
110 final HtmlTag tag;
111 final Set<HtmlTag.Attr> attrs;
112 final Set<Flag> flags;
113 TagStackItem(DocTree tree, HtmlTag tag) {
114 this.tree = tree;
115 this.tag = tag;
116 attrs = EnumSet.noneOf(HtmlTag.Attr.class);
117 flags = EnumSet.noneOf(Flag.class);
118 }
119 @Override
120 public String toString() {
121 return String.valueOf(tag);
122 }
123 }
125 private Deque<TagStackItem> tagStack; // TODO: maybe want to record starting tree as well
126 private HtmlTag currHeaderTag;
128 private final int implicitHeaderLevel;
130 // <editor-fold defaultstate="collapsed" desc="Top level">
132 Checker(Env env) {
133 env.getClass();
134 this.env = env;
135 tagStack = new LinkedList<>();
136 implicitHeaderLevel = env.implicitHeaderLevel;
137 }
139 public Void scan(DocCommentTree tree, TreePath p) {
140 env.setCurrent(p, tree);
142 boolean isOverridingMethod = !env.currOverriddenMethods.isEmpty();
144 if (p.getLeaf() == p.getCompilationUnit()) {
145 // If p points to a compilation unit, the implied declaration is the
146 // package declaration (if any) for the compilation unit.
147 // Handle this case specially, because doc comments are only
148 // expected in package-info files.
149 JavaFileObject fo = p.getCompilationUnit().getSourceFile();
150 boolean isPkgInfo = fo.isNameCompatible("package-info", JavaFileObject.Kind.SOURCE);
151 if (tree == null) {
152 if (isPkgInfo)
153 reportMissing("dc.missing.comment");
154 return null;
155 } else {
156 if (!isPkgInfo)
157 reportReference("dc.unexpected.comment");
158 }
159 } else {
160 if (tree == null) {
161 if (!isSynthetic() && !isOverridingMethod)
162 reportMissing("dc.missing.comment");
163 return null;
164 }
165 }
167 tagStack.clear();
168 currHeaderTag = null;
170 foundParams.clear();
171 foundThrows.clear();
172 foundInheritDoc = false;
173 foundReturn = false;
175 scan(new DocTreePath(p, tree), null);
177 if (!isOverridingMethod) {
178 switch (env.currElement.getKind()) {
179 case METHOD:
180 case CONSTRUCTOR: {
181 ExecutableElement ee = (ExecutableElement) env.currElement;
182 checkParamsDocumented(ee.getTypeParameters());
183 checkParamsDocumented(ee.getParameters());
184 switch (ee.getReturnType().getKind()) {
185 case VOID:
186 case NONE:
187 break;
188 default:
189 if (!foundReturn
190 && !foundInheritDoc
191 && !env.types.isSameType(ee.getReturnType(), env.java_lang_Void)) {
192 reportMissing("dc.missing.return");
193 }
194 }
195 checkThrowsDocumented(ee.getThrownTypes());
196 }
197 }
198 }
200 return null;
201 }
203 private void reportMissing(String code, Object... args) {
204 env.messages.report(MISSING, Kind.WARNING, env.currPath.getLeaf(), code, args);
205 }
207 private void reportReference(String code, Object... args) {
208 env.messages.report(REFERENCE, Kind.WARNING, env.currPath.getLeaf(), code, args);
209 }
211 @Override
212 public Void visitDocComment(DocCommentTree tree, Void ignore) {
213 super.visitDocComment(tree, ignore);
214 for (TagStackItem tsi: tagStack) {
215 if (tsi.tree.getKind() == DocTree.Kind.START_ELEMENT
216 && tsi.tag.endKind == HtmlTag.EndKind.REQUIRED) {
217 StartElementTree t = (StartElementTree) tsi.tree;
218 env.messages.error(HTML, t, "dc.tag.not.closed", t.getName());
219 }
220 }
221 return null;
222 }
223 // </editor-fold>
225 // <editor-fold defaultstate="collapsed" desc="Text and entities.">
227 @Override
228 public Void visitText(TextTree tree, Void ignore) {
229 if (hasNonWhitespace(tree)) {
230 checkAllowsText(tree);
231 markEnclosingTag(Flag.HAS_TEXT);
232 }
233 return null;
234 }
236 @Override
237 public Void visitEntity(EntityTree tree, Void ignore) {
238 checkAllowsText(tree);
239 markEnclosingTag(Flag.HAS_TEXT);
240 String name = tree.getName().toString();
241 if (name.startsWith("#")) {
242 int v = name.toLowerCase().startsWith("#x")
243 ? Integer.parseInt(name.substring(2), 16)
244 : Integer.parseInt(name.substring(1), 10);
245 if (!Entity.isValid(v)) {
246 env.messages.error(HTML, tree, "dc.entity.invalid", name);
247 }
248 } else if (!Entity.isValid(name)) {
249 env.messages.error(HTML, tree, "dc.entity.invalid", name);
250 }
251 return null;
252 }
254 void checkAllowsText(DocTree tree) {
255 TagStackItem top = tagStack.peek();
256 if (top != null
257 && top.tree.getKind() == DocTree.Kind.START_ELEMENT
258 && !top.tag.acceptsText()) {
259 if (top.flags.add(Flag.REPORTED_BAD_INLINE)) {
260 env.messages.error(HTML, tree, "dc.text.not.allowed",
261 ((StartElementTree) top.tree).getName());
262 }
263 }
264 }
266 // </editor-fold>
268 // <editor-fold defaultstate="collapsed" desc="HTML elements">
270 @Override
271 public Void visitStartElement(StartElementTree tree, Void ignore) {
272 markEnclosingTag(Flag.HAS_ELEMENT);
273 final Name treeName = tree.getName();
274 final HtmlTag t = HtmlTag.get(treeName);
275 if (t == null) {
276 env.messages.error(HTML, tree, "dc.tag.unknown", treeName);
277 } else {
278 boolean done = false;
279 for (TagStackItem tsi: tagStack) {
280 if (tsi.tag.accepts(t)) {
281 while (tagStack.peek() != tsi) tagStack.pop();
282 done = true;
283 break;
284 } else if (tsi.tag.endKind != HtmlTag.EndKind.OPTIONAL) {
285 done = true;
286 break;
287 }
288 }
289 if (!done && HtmlTag.BODY.accepts(t)) {
290 tagStack.clear();
291 }
293 checkStructure(tree, t);
295 // tag specific checks
296 switch (t) {
297 // check for out of sequence headers, such as <h1>...</h1> <h3>...</h3>
298 case H1: case H2: case H3: case H4: case H5: case H6:
299 checkHeader(tree, t);
300 break;
301 }
303 if (t.flags.contains(HtmlTag.Flag.NO_NEST)) {
304 for (TagStackItem i: tagStack) {
305 if (t == i.tag) {
306 env.messages.warning(HTML, tree, "dc.tag.nested.not.allowed", treeName);
307 break;
308 }
309 }
310 }
311 }
313 // check for self closing tags, such as <a id="name"/>
314 if (tree.isSelfClosing()) {
315 env.messages.error(HTML, tree, "dc.tag.self.closing", treeName);
316 }
318 try {
319 TagStackItem parent = tagStack.peek();
320 TagStackItem top = new TagStackItem(tree, t);
321 tagStack.push(top);
323 super.visitStartElement(tree, ignore);
325 // handle attributes that may or may not have been found in start element
326 if (t != null) {
327 switch (t) {
328 case CAPTION:
329 if (parent != null && parent.tag == HtmlTag.TABLE)
330 parent.flags.add(Flag.TABLE_HAS_CAPTION);
331 break;
333 case IMG:
334 if (!top.attrs.contains(HtmlTag.Attr.ALT))
335 env.messages.error(ACCESSIBILITY, tree, "dc.no.alt.attr.for.image");
336 break;
337 }
338 }
340 return null;
341 } finally {
343 if (t == null || t.endKind == HtmlTag.EndKind.NONE)
344 tagStack.pop();
345 }
346 }
348 private void checkStructure(StartElementTree tree, HtmlTag t) {
349 Name treeName = tree.getName();
350 TagStackItem top = tagStack.peek();
351 switch (t.blockType) {
352 case BLOCK:
353 if (top == null || top.tag.accepts(t))
354 return;
356 switch (top.tree.getKind()) {
357 case START_ELEMENT: {
358 if (top.tag.blockType == HtmlTag.BlockType.INLINE) {
359 Name name = ((StartElementTree) top.tree).getName();
360 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.element",
361 treeName, name);
362 return;
363 }
364 }
365 break;
367 case LINK:
368 case LINK_PLAIN: {
369 String name = top.tree.getKind().tagName;
370 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.tag",
371 treeName, name);
372 return;
373 }
374 }
375 break;
377 case INLINE:
378 if (top == null || top.tag.accepts(t))
379 return;
380 break;
382 case LIST_ITEM:
383 case TABLE_ITEM:
384 if (top != null) {
385 // reset this flag so subsequent bad inline content gets reported
386 top.flags.remove(Flag.REPORTED_BAD_INLINE);
387 if (top.tag.accepts(t))
388 return;
389 }
390 break;
392 case OTHER:
393 env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName);
394 return;
395 }
397 env.messages.error(HTML, tree, "dc.tag.not.allowed.here", treeName);
398 }
400 private void checkHeader(StartElementTree tree, HtmlTag tag) {
401 // verify the new tag
402 if (getHeaderLevel(tag) > getHeaderLevel(currHeaderTag) + 1) {
403 if (currHeaderTag == null) {
404 env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.1", tag);
405 } else {
406 env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.2",
407 tag, currHeaderTag);
408 }
409 }
411 currHeaderTag = tag;
412 }
414 private int getHeaderLevel(HtmlTag tag) {
415 if (tag == null)
416 return implicitHeaderLevel;
417 switch (tag) {
418 case H1: return 1;
419 case H2: return 2;
420 case H3: return 3;
421 case H4: return 4;
422 case H5: return 5;
423 case H6: return 6;
424 default: throw new IllegalArgumentException();
425 }
426 }
428 @Override
429 public Void visitEndElement(EndElementTree tree, Void ignore) {
430 final Name treeName = tree.getName();
431 final HtmlTag t = HtmlTag.get(treeName);
432 if (t == null) {
433 env.messages.error(HTML, tree, "dc.tag.unknown", treeName);
434 } else if (t.endKind == HtmlTag.EndKind.NONE) {
435 env.messages.error(HTML, tree, "dc.tag.end.not.permitted", treeName);
436 } else {
437 boolean done = false;
438 while (!tagStack.isEmpty()) {
439 TagStackItem top = tagStack.peek();
440 if (t == top.tag) {
441 switch (t) {
442 case TABLE:
443 if (!top.attrs.contains(HtmlTag.Attr.SUMMARY)
444 && !top.flags.contains(Flag.TABLE_HAS_CAPTION)) {
445 env.messages.error(ACCESSIBILITY, tree,
446 "dc.no.summary.or.caption.for.table");
447 }
448 }
449 if (t.flags.contains(HtmlTag.Flag.EXPECT_CONTENT)
450 && !top.flags.contains(Flag.HAS_TEXT)
451 && !top.flags.contains(Flag.HAS_ELEMENT)
452 && !top.flags.contains(Flag.HAS_INLINE_TAG)) {
453 env.messages.warning(HTML, tree, "dc.tag.empty", treeName);
454 }
455 tagStack.pop();
456 done = true;
457 break;
458 } else if (top.tag == null || top.tag.endKind != HtmlTag.EndKind.REQUIRED) {
459 tagStack.pop();
460 } else {
461 boolean found = false;
462 for (TagStackItem si: tagStack) {
463 if (si.tag == t) {
464 found = true;
465 break;
466 }
467 }
468 if (found && top.tree.getKind() == DocTree.Kind.START_ELEMENT) {
469 env.messages.error(HTML, top.tree, "dc.tag.start.unmatched",
470 ((StartElementTree) top.tree).getName());
471 tagStack.pop();
472 } else {
473 env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName);
474 done = true;
475 break;
476 }
477 }
478 }
480 if (!done && tagStack.isEmpty()) {
481 env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName);
482 }
483 }
485 return super.visitEndElement(tree, ignore);
486 }
487 // </editor-fold>
489 // <editor-fold defaultstate="collapsed" desc="HTML attributes">
491 @Override @SuppressWarnings("fallthrough")
492 public Void visitAttribute(AttributeTree tree, Void ignore) {
493 HtmlTag currTag = tagStack.peek().tag;
494 if (currTag != null) {
495 Name name = tree.getName();
496 HtmlTag.Attr attr = currTag.getAttr(name);
497 if (attr != null) {
498 boolean first = tagStack.peek().attrs.add(attr);
499 if (!first)
500 env.messages.error(HTML, tree, "dc.attr.repeated", name);
501 }
502 AttrKind k = currTag.getAttrKind(name);
503 switch (k) {
504 case OK:
505 break;
507 case INVALID:
508 env.messages.error(HTML, tree, "dc.attr.unknown", name);
509 break;
511 case OBSOLETE:
512 env.messages.warning(ACCESSIBILITY, tree, "dc.attr.obsolete", name);
513 break;
515 case USE_CSS:
516 env.messages.warning(ACCESSIBILITY, tree, "dc.attr.obsolete.use.css", name);
517 break;
518 }
520 if (attr != null) {
521 switch (attr) {
522 case NAME:
523 if (currTag != HtmlTag.A) {
524 break;
525 }
526 // fallthrough
527 case ID:
528 String value = getAttrValue(tree);
529 if (value == null) {
530 env.messages.error(HTML, tree, "dc.anchor.value.missing");
531 } else {
532 if (!validName.matcher(value).matches()) {
533 env.messages.error(HTML, tree, "dc.invalid.anchor", value);
534 }
535 if (!checkAnchor(value)) {
536 env.messages.error(HTML, tree, "dc.anchor.already.defined", value);
537 }
538 }
539 break;
541 case HREF:
542 if (currTag == HtmlTag.A) {
543 String v = getAttrValue(tree);
544 if (v == null || v.isEmpty()) {
545 env.messages.error(HTML, tree, "dc.attr.lacks.value");
546 } else {
547 Matcher m = docRoot.matcher(v);
548 if (m.matches()) {
549 String rest = m.group(2);
550 if (!rest.isEmpty())
551 checkURI(tree, rest);
552 } else {
553 checkURI(tree, v);
554 }
555 }
556 }
557 break;
559 case VALUE:
560 if (currTag == HtmlTag.LI) {
561 String v = getAttrValue(tree);
562 if (v == null || v.isEmpty()) {
563 env.messages.error(HTML, tree, "dc.attr.lacks.value");
564 } else if (!validNumber.matcher(v).matches()) {
565 env.messages.error(HTML, tree, "dc.attr.not.number");
566 }
567 }
568 break;
569 }
570 }
571 }
573 // TODO: basic check on value
575 return super.visitAttribute(tree, ignore);
576 }
578 private boolean checkAnchor(String name) {
579 JavaFileObject fo = env.currPath.getCompilationUnit().getSourceFile();
580 Set<String> set = foundAnchors.get(fo);
581 if (set == null)
582 foundAnchors.put(fo, set = new HashSet<>());
583 return set.add(name);
584 }
586 // http://www.w3.org/TR/html401/types.html#type-name
587 private static final Pattern validName = Pattern.compile("[A-Za-z][A-Za-z0-9-_:.]*");
589 private static final Pattern validNumber = Pattern.compile("-?[0-9]+");
591 // pattern to remove leading {@docRoot}/?
592 private static final Pattern docRoot = Pattern.compile("(?i)(\\{@docRoot *\\}/?)?(.*)");
594 private String getAttrValue(AttributeTree tree) {
595 if (tree.getValue() == null)
596 return null;
598 StringWriter sw = new StringWriter();
599 try {
600 new DocPretty(sw).print(tree.getValue());
601 } catch (IOException e) {
602 // cannot happen
603 }
604 // ignore potential use of entities for now
605 return sw.toString();
606 }
608 private void checkURI(AttributeTree tree, String uri) {
609 try {
610 URI u = new URI(uri);
611 } catch (URISyntaxException e) {
612 env.messages.error(HTML, tree, "dc.invalid.uri", uri);
613 }
614 }
615 // </editor-fold>
617 // <editor-fold defaultstate="collapsed" desc="javadoc tags">
619 @Override
620 public Void visitAuthor(AuthorTree tree, Void ignore) {
621 warnIfEmpty(tree, tree.getName());
622 return super.visitAuthor(tree, ignore);
623 }
625 @Override
626 public Void visitDocRoot(DocRootTree tree, Void ignore) {
627 markEnclosingTag(Flag.HAS_INLINE_TAG);
628 return super.visitDocRoot(tree, ignore);
629 }
631 @Override
632 public Void visitInheritDoc(InheritDocTree tree, Void ignore) {
633 markEnclosingTag(Flag.HAS_INLINE_TAG);
634 // TODO: verify on overridden method
635 foundInheritDoc = true;
636 return super.visitInheritDoc(tree, ignore);
637 }
639 @Override
640 public Void visitLink(LinkTree tree, Void ignore) {
641 markEnclosingTag(Flag.HAS_INLINE_TAG);
642 // simulate inline context on tag stack
643 HtmlTag t = (tree.getKind() == DocTree.Kind.LINK)
644 ? HtmlTag.CODE : HtmlTag.SPAN;
645 tagStack.push(new TagStackItem(tree, t));
646 try {
647 return super.visitLink(tree, ignore);
648 } finally {
649 tagStack.pop();
650 }
651 }
653 @Override
654 public Void visitLiteral(LiteralTree tree, Void ignore) {
655 markEnclosingTag(Flag.HAS_INLINE_TAG);
656 if (tree.getKind() == DocTree.Kind.CODE) {
657 for (TagStackItem tsi: tagStack) {
658 if (tsi.tag == HtmlTag.CODE) {
659 env.messages.warning(HTML, tree, "dc.tag.code.within.code");
660 break;
661 }
662 }
663 }
664 return super.visitLiteral(tree, ignore);
665 }
667 @Override
668 @SuppressWarnings("fallthrough")
669 public Void visitParam(ParamTree tree, Void ignore) {
670 boolean typaram = tree.isTypeParameter();
671 IdentifierTree nameTree = tree.getName();
672 Element paramElement = nameTree != null ? env.trees.getElement(new DocTreePath(getCurrentPath(), nameTree)) : null;
674 if (paramElement == null) {
675 switch (env.currElement.getKind()) {
676 case CLASS: case INTERFACE: {
677 if (!typaram) {
678 env.messages.error(REFERENCE, tree, "dc.invalid.param");
679 break;
680 }
681 }
682 case METHOD: case CONSTRUCTOR: {
683 env.messages.error(REFERENCE, nameTree, "dc.param.name.not.found");
684 break;
685 }
687 default:
688 env.messages.error(REFERENCE, tree, "dc.invalid.param");
689 break;
690 }
691 } else {
692 foundParams.add(paramElement);
693 }
695 warnIfEmpty(tree, tree.getDescription());
696 return super.visitParam(tree, ignore);
697 }
699 private void checkParamsDocumented(List<? extends Element> list) {
700 if (foundInheritDoc)
701 return;
703 for (Element e: list) {
704 if (!foundParams.contains(e)) {
705 CharSequence paramName = (e.getKind() == ElementKind.TYPE_PARAMETER)
706 ? "<" + e.getSimpleName() + ">"
707 : e.getSimpleName();
708 reportMissing("dc.missing.param", paramName);
709 }
710 }
711 }
713 @Override
714 public Void visitReference(ReferenceTree tree, Void ignore) {
715 Element e = env.trees.getElement(getCurrentPath());
716 if (e == null)
717 env.messages.error(REFERENCE, tree, "dc.ref.not.found");
718 return super.visitReference(tree, ignore);
719 }
721 @Override
722 public Void visitReturn(ReturnTree tree, Void ignore) {
723 Element e = env.trees.getElement(env.currPath);
724 if (e.getKind() != ElementKind.METHOD
725 || ((ExecutableElement) e).getReturnType().getKind() == TypeKind.VOID)
726 env.messages.error(REFERENCE, tree, "dc.invalid.return");
727 foundReturn = true;
728 warnIfEmpty(tree, tree.getDescription());
729 return super.visitReturn(tree, ignore);
730 }
732 @Override
733 public Void visitSerialData(SerialDataTree tree, Void ignore) {
734 warnIfEmpty(tree, tree.getDescription());
735 return super.visitSerialData(tree, ignore);
736 }
738 @Override
739 public Void visitSerialField(SerialFieldTree tree, Void ignore) {
740 warnIfEmpty(tree, tree.getDescription());
741 return super.visitSerialField(tree, ignore);
742 }
744 @Override
745 public Void visitSince(SinceTree tree, Void ignore) {
746 warnIfEmpty(tree, tree.getBody());
747 return super.visitSince(tree, ignore);
748 }
750 @Override
751 public Void visitThrows(ThrowsTree tree, Void ignore) {
752 ReferenceTree exName = tree.getExceptionName();
753 Element ex = env.trees.getElement(new DocTreePath(getCurrentPath(), exName));
754 if (ex == null) {
755 env.messages.error(REFERENCE, tree, "dc.ref.not.found");
756 } else if (isThrowable(ex.asType())) {
757 switch (env.currElement.getKind()) {
758 case CONSTRUCTOR:
759 case METHOD:
760 if (isCheckedException(ex.asType())) {
761 ExecutableElement ee = (ExecutableElement) env.currElement;
762 checkThrowsDeclared(exName, ex.asType(), ee.getThrownTypes());
763 }
764 break;
765 default:
766 env.messages.error(REFERENCE, tree, "dc.invalid.throws");
767 }
768 } else {
769 env.messages.error(REFERENCE, tree, "dc.invalid.throws");
770 }
771 warnIfEmpty(tree, tree.getDescription());
772 return scan(tree.getDescription(), ignore);
773 }
775 private boolean isThrowable(TypeMirror tm) {
776 switch (tm.getKind()) {
777 case DECLARED:
778 case TYPEVAR:
779 return env.types.isAssignable(tm, env.java_lang_Throwable);
780 }
781 return false;
782 }
784 private void checkThrowsDeclared(ReferenceTree tree, TypeMirror t, List<? extends TypeMirror> list) {
785 boolean found = false;
786 for (TypeMirror tl : list) {
787 if (env.types.isAssignable(t, tl)) {
788 foundThrows.add(tl);
789 found = true;
790 }
791 }
792 if (!found)
793 env.messages.error(REFERENCE, tree, "dc.exception.not.thrown", t);
794 }
796 private void checkThrowsDocumented(List<? extends TypeMirror> list) {
797 if (foundInheritDoc)
798 return;
800 for (TypeMirror tl: list) {
801 if (isCheckedException(tl) && !foundThrows.contains(tl))
802 reportMissing("dc.missing.throws", tl);
803 }
804 }
806 @Override
807 public Void visitValue(ValueTree tree, Void ignore) {
808 markEnclosingTag(Flag.HAS_INLINE_TAG);
809 return super.visitValue(tree, ignore);
810 }
812 @Override
813 public Void visitVersion(VersionTree tree, Void ignore) {
814 warnIfEmpty(tree, tree.getBody());
815 return super.visitVersion(tree, ignore);
816 }
818 @Override
819 public Void visitErroneous(ErroneousTree tree, Void ignore) {
820 env.messages.error(SYNTAX, tree, null, tree.getDiagnostic().getMessage(null));
821 return null;
822 }
823 // </editor-fold>
825 // <editor-fold defaultstate="collapsed" desc="Utility methods">
827 private boolean isCheckedException(TypeMirror t) {
828 return !(env.types.isAssignable(t, env.java_lang_Error)
829 || env.types.isAssignable(t, env.java_lang_RuntimeException));
830 }
832 private boolean isSynthetic() {
833 switch (env.currElement.getKind()) {
834 case CONSTRUCTOR:
835 // A synthetic default constructor has the same pos as the
836 // enclosing class
837 TreePath p = env.currPath;
838 return env.getPos(p) == env.getPos(p.getParentPath());
839 }
840 return false;
841 }
843 void markEnclosingTag(Flag flag) {
844 TagStackItem top = tagStack.peek();
845 if (top != null)
846 top.flags.add(flag);
847 }
849 String toString(TreePath p) {
850 StringBuilder sb = new StringBuilder("TreePath[");
851 toString(p, sb);
852 sb.append("]");
853 return sb.toString();
854 }
856 void toString(TreePath p, StringBuilder sb) {
857 TreePath parent = p.getParentPath();
858 if (parent != null) {
859 toString(parent, sb);
860 sb.append(",");
861 }
862 sb.append(p.getLeaf().getKind()).append(":").append(env.getPos(p)).append(":S").append(env.getStartPos(p));
863 }
865 void warnIfEmpty(DocTree tree, List<? extends DocTree> list) {
866 for (DocTree d: list) {
867 switch (d.getKind()) {
868 case TEXT:
869 if (hasNonWhitespace((TextTree) d))
870 return;
871 break;
872 default:
873 return;
874 }
875 }
876 env.messages.warning(SYNTAX, tree, "dc.empty", tree.getKind().tagName);
877 }
879 boolean hasNonWhitespace(TextTree tree) {
880 String s = tree.getBody();
881 for (int i = 0; i < s.length(); i++) {
882 if (!Character.isWhitespace(s.charAt(i)))
883 return true;
884 }
885 return false;
886 }
888 // </editor-fold>
890 }