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.element.VariableElement;
48 import javax.lang.model.type.TypeKind;
49 import javax.lang.model.type.TypeMirror;
50 import javax.tools.Diagnostic.Kind;
51 import javax.tools.JavaFileObject;
52
53 import com.sun.source.doctree.AttributeTree;
54 import com.sun.source.doctree.AuthorTree;
55 import com.sun.source.doctree.DocCommentTree;
56 import com.sun.source.doctree.DocRootTree;
57 import com.sun.source.doctree.DocTree;
58 import com.sun.source.doctree.EndElementTree;
59 import com.sun.source.doctree.EntityTree;
60 import com.sun.source.doctree.ErroneousTree;
61 import com.sun.source.doctree.IdentifierTree;
62 import com.sun.source.doctree.IndexTree;
63 import com.sun.source.doctree.InheritDocTree;
64 import com.sun.source.doctree.InlineTagTree;
65 import com.sun.source.doctree.LinkTree;
66 import com.sun.source.doctree.LiteralTree;
67 import com.sun.source.doctree.ParamTree;
68 import com.sun.source.doctree.ProvidesTree;
69 import com.sun.source.doctree.ReferenceTree;
70 import com.sun.source.doctree.ReturnTree;
71 import com.sun.source.doctree.SerialDataTree;
72 import com.sun.source.doctree.SerialFieldTree;
73 import com.sun.source.doctree.SinceTree;
74 import com.sun.source.doctree.StartElementTree;
75 import com.sun.source.doctree.SummaryTree;
76 import com.sun.source.doctree.SystemPropertyTree;
77 import com.sun.source.doctree.TextTree;
78 import com.sun.source.doctree.ThrowsTree;
79 import com.sun.source.doctree.UnknownBlockTagTree;
80 import com.sun.source.doctree.UnknownInlineTagTree;
81 import com.sun.source.doctree.UsesTree;
82 import com.sun.source.doctree.ValueTree;
83 import com.sun.source.doctree.VersionTree;
84 import com.sun.source.tree.Tree;
123 }
124
125 static class TagStackItem {
126 final DocTree tree; // typically, but not always, StartElementTree
127 final HtmlTag tag;
128 final Set<HtmlTag.Attr> attrs;
129 final Set<Flag> flags;
130 TagStackItem(DocTree tree, HtmlTag tag) {
131 this.tree = tree;
132 this.tag = tag;
133 attrs = EnumSet.noneOf(HtmlTag.Attr.class);
134 flags = EnumSet.noneOf(Flag.class);
135 }
136 @Override
137 public String toString() {
138 return String.valueOf(tag);
139 }
140 }
141
142 private final Deque<TagStackItem> tagStack; // TODO: maybe want to record starting tree as well
143 private HtmlTag currHeaderTag;
144
145 private final int implicitHeaderLevel;
146
147 // <editor-fold defaultstate="collapsed" desc="Top level">
148
149 Checker(Env env) {
150 this.env = Assert.checkNonNull(env);
151 tagStack = new LinkedList<>();
152 implicitHeaderLevel = env.implicitHeaderLevel;
153 }
154
155 public Void scan(DocCommentTree tree, TreePath p) {
156 env.initTypes();
157 env.setCurrent(p, tree);
158
159 boolean isOverridingMethod = !env.currOverriddenMethods.isEmpty();
160 JavaFileObject fo = p.getCompilationUnit().getSourceFile();
161
162 if (p.getLeaf().getKind() == Tree.Kind.PACKAGE) {
163 // If p points to a package, the implied declaration is the
164 // package declaration (if any) for the compilation unit.
165 // Handle this case specially, because doc comments are only
166 // expected in package-info files.
167 boolean isPkgInfo = fo.isNameCompatible("package-info", JavaFileObject.Kind.SOURCE);
168 if (tree == null) {
169 if (isPkgInfo)
170 reportMissing("dc.missing.comment");
171 return null;
172 } else {
173 if (!isPkgInfo)
174 reportReference("dc.unexpected.comment");
175 }
176 } else if (tree != null && fo.isNameCompatible("package", JavaFileObject.Kind.HTML)) {
177 // a package.html file with a DocCommentTree
178 if (tree.getFullBody().isEmpty()) {
179 reportMissing("dc.missing.comment");
180 return null;
181 }
182 } else {
183 if (tree == null) {
184 if (!isSynthetic() && !isOverridingMethod)
185 reportMissing("dc.missing.comment");
186 return null;
187 }
188 }
189
190 tagStack.clear();
191 currHeaderTag = null;
192
193 foundParams.clear();
194 foundThrows.clear();
195 foundInheritDoc = false;
196 foundReturn = false;
197 hasNonWhitespaceText = false;
198
199 scan(new DocTreePath(p, tree), null);
200
201 if (!isOverridingMethod) {
202 switch (env.currElement.getKind()) {
203 case METHOD:
204 case CONSTRUCTOR: {
205 ExecutableElement ee = (ExecutableElement) env.currElement;
206 checkParamsDocumented(ee.getTypeParameters());
207 checkParamsDocumented(ee.getParameters());
208 switch (ee.getReturnType().getKind()) {
209 case VOID:
210 case NONE:
211 break;
212 default:
213 if (!foundReturn
214 && !foundInheritDoc
215 && !env.types.isSameType(ee.getReturnType(), env.java_lang_Void)) {
216 reportMissing("dc.missing.return");
217 }
218 }
311 }
312 done = true;
313 break;
314 } else if (tsi.tag.endKind != HtmlTag.EndKind.OPTIONAL) {
315 done = true;
316 break;
317 }
318 }
319 if (!done && HtmlTag.BODY.accepts(t)) {
320 while (!tagStack.isEmpty()) {
321 warnIfEmpty(tagStack.peek(), null);
322 tagStack.pop();
323 }
324 }
325
326 markEnclosingTag(Flag.HAS_ELEMENT);
327 checkStructure(tree, t);
328
329 // tag specific checks
330 switch (t) {
331 // check for out of sequence headers, such as <h1>...</h1> <h3>...</h3>
332 case H1: case H2: case H3: case H4: case H5: case H6:
333 checkHeader(tree, t);
334 break;
335 }
336
337 if (t.flags.contains(HtmlTag.Flag.NO_NEST)) {
338 for (TagStackItem i: tagStack) {
339 if (t == i.tag) {
340 env.messages.warning(HTML, tree, "dc.tag.nested.not.allowed", treeName);
341 break;
342 }
343 }
344 }
345 }
346
347 // check for self closing tags, such as <a id="name"/>
348 if (tree.isSelfClosing()) {
349 env.messages.error(HTML, tree, "dc.tag.self.closing", treeName);
350 }
351
352 try {
353 TagStackItem parent = tagStack.peek();
429 }
430 break;
431
432 case OTHER:
433 switch (t) {
434 case SCRIPT:
435 // <script> may or may not be allowed, depending on --allow-script-in-comments
436 // but we allow it here, and rely on a separate scanner to detect all uses
437 // of JavaScript, including <script> tags, and use in attributes, etc.
438 break;
439
440 default:
441 env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName);
442 }
443 return;
444 }
445
446 env.messages.error(HTML, tree, "dc.tag.not.allowed.here", treeName);
447 }
448
449 private void checkHeader(StartElementTree tree, HtmlTag tag) {
450 // verify the new tag
451 if (getHeaderLevel(tag) > getHeaderLevel(currHeaderTag) + 1) {
452 if (currHeaderTag == null) {
453 env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.1", tag);
454 } else {
455 env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.2",
456 tag, currHeaderTag);
457 }
458 }
459
460 currHeaderTag = tag;
461 }
462
463 private int getHeaderLevel(HtmlTag tag) {
464 if (tag == null)
465 return implicitHeaderLevel;
466 switch (tag) {
467 case H1: return 1;
468 case H2: return 2;
469 case H3: return 3;
470 case H4: return 4;
471 case H5: return 5;
472 case H6: return 6;
473 default: throw new IllegalArgumentException();
474 }
475 }
476
477 @Override @DefinedBy(Api.COMPILER_TREE)
478 public Void visitEndElement(EndElementTree tree, Void ignore) {
479 final Name treeName = tree.getName();
480 final HtmlTag t = HtmlTag.get(treeName);
481 if (t == null) {
482 env.messages.error(HTML, tree, "dc.tag.unknown", treeName);
483 } else if (t.endKind == HtmlTag.EndKind.NONE) {
484 env.messages.error(HTML, tree, "dc.tag.end.not.permitted", treeName);
485 } else {
649 }
650 }
651 }
652
653 // TODO: basic check on value
654
655 return super.visitAttribute(tree, ignore);
656 }
657
658 private void validateHtml4Attrs(AttributeTree tree, Name name, AttrKind k) {
659 switch (k) {
660 case ALL:
661 case HTML4:
662 break;
663
664 case INVALID:
665 env.messages.error(HTML, tree, "dc.attr.unknown", name);
666 break;
667
668 case OBSOLETE:
669 env.messages.warning(ACCESSIBILITY, tree, "dc.attr.obsolete", name);
670 break;
671
672 case USE_CSS:
673 env.messages.warning(ACCESSIBILITY, tree, "dc.attr.obsolete.use.css", name);
674 break;
675
676 case HTML5:
677 env.messages.error(HTML, tree, "dc.attr.not.supported.html4", name);
678 break;
679 }
680 }
681
682 private void validateHtml5Attrs(AttributeTree tree, Name name, AttrKind k) {
683 switch (k) {
684 case ALL:
685 case HTML5:
686 break;
687
688 case INVALID:
689 case OBSOLETE:
690 case USE_CSS:
691 case HTML4:
692 env.messages.error(HTML, tree, "dc.attr.not.supported.html5", name);
693 break;
|
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.element.VariableElement;
48 import javax.lang.model.type.TypeKind;
49 import javax.lang.model.type.TypeMirror;
50 import javax.tools.Diagnostic.Kind;
51 import javax.tools.JavaFileObject;
52
53 import com.sun.source.doctree.AttributeTree;
54 import com.sun.source.doctree.AuthorTree;
55 import com.sun.source.doctree.DocCommentTree;
56 import com.sun.source.doctree.DocRootTree;
57 import com.sun.source.doctree.DocTree;
58 import com.sun.source.doctree.EndElementTree;
59 import com.sun.source.doctree.EntityTree;
60 import com.sun.source.doctree.ErroneousTree;
61 import com.sun.source.doctree.IdentifierTree;
62 import com.sun.source.doctree.IndexTree;
63 import com.sun.source.doctree.InheritDocTree;
64 import com.sun.source.doctree.LinkTree;
65 import com.sun.source.doctree.LiteralTree;
66 import com.sun.source.doctree.ParamTree;
67 import com.sun.source.doctree.ProvidesTree;
68 import com.sun.source.doctree.ReferenceTree;
69 import com.sun.source.doctree.ReturnTree;
70 import com.sun.source.doctree.SerialDataTree;
71 import com.sun.source.doctree.SerialFieldTree;
72 import com.sun.source.doctree.SinceTree;
73 import com.sun.source.doctree.StartElementTree;
74 import com.sun.source.doctree.SummaryTree;
75 import com.sun.source.doctree.SystemPropertyTree;
76 import com.sun.source.doctree.TextTree;
77 import com.sun.source.doctree.ThrowsTree;
78 import com.sun.source.doctree.UnknownBlockTagTree;
79 import com.sun.source.doctree.UnknownInlineTagTree;
80 import com.sun.source.doctree.UsesTree;
81 import com.sun.source.doctree.ValueTree;
82 import com.sun.source.doctree.VersionTree;
83 import com.sun.source.tree.Tree;
122 }
123
124 static class TagStackItem {
125 final DocTree tree; // typically, but not always, StartElementTree
126 final HtmlTag tag;
127 final Set<HtmlTag.Attr> attrs;
128 final Set<Flag> flags;
129 TagStackItem(DocTree tree, HtmlTag tag) {
130 this.tree = tree;
131 this.tag = tag;
132 attrs = EnumSet.noneOf(HtmlTag.Attr.class);
133 flags = EnumSet.noneOf(Flag.class);
134 }
135 @Override
136 public String toString() {
137 return String.valueOf(tag);
138 }
139 }
140
141 private final Deque<TagStackItem> tagStack; // TODO: maybe want to record starting tree as well
142 private HtmlTag currHeadingTag;
143
144 private int implicitHeadingRank;
145
146 // <editor-fold defaultstate="collapsed" desc="Top level">
147
148 Checker(Env env) {
149 this.env = Assert.checkNonNull(env);
150 tagStack = new LinkedList<>();
151 }
152
153 public Void scan(DocCommentTree tree, TreePath p) {
154 env.initTypes();
155 env.setCurrent(p, tree);
156
157 boolean isOverridingMethod = !env.currOverriddenMethods.isEmpty();
158 JavaFileObject fo = p.getCompilationUnit().getSourceFile();
159
160 if (p.getLeaf().getKind() == Tree.Kind.PACKAGE) {
161 // If p points to a package, the implied declaration is the
162 // package declaration (if any) for the compilation unit.
163 // Handle this case specially, because doc comments are only
164 // expected in package-info files.
165 boolean isPkgInfo = fo.isNameCompatible("package-info", JavaFileObject.Kind.SOURCE);
166 if (tree == null) {
167 if (isPkgInfo)
168 reportMissing("dc.missing.comment");
169 return null;
170 } else {
171 if (!isPkgInfo)
172 reportReference("dc.unexpected.comment");
173 }
174 } else if (tree != null && fo.isNameCompatible("package", JavaFileObject.Kind.HTML)) {
175 // a package.html file with a DocCommentTree
176 if (tree.getFullBody().isEmpty()) {
177 reportMissing("dc.missing.comment");
178 return null;
179 }
180 } else {
181 if (tree == null) {
182 if (!isSynthetic() && !isOverridingMethod)
183 reportMissing("dc.missing.comment");
184 return null;
185 }
186 }
187
188 tagStack.clear();
189 currHeadingTag = null;
190
191 foundParams.clear();
192 foundThrows.clear();
193 foundInheritDoc = false;
194 foundReturn = false;
195 hasNonWhitespaceText = false;
196
197 switch (p.getLeaf().getKind()) {
198 // the following are for declarations that have their own top-level page,
199 // and so the doc comment comes after the <h1> page title.
200 case MODULE:
201 case PACKAGE:
202 case CLASS:
203 case INTERFACE:
204 case ENUM:
205 case ANNOTATION_TYPE:
206 implicitHeadingRank = 1;
207 break;
208
209 // this is for html files
210 // ... if it is a legacy package.html, the doc comment comes after the <h1> page title
211 // ... otherwise, (e.g. overview file and doc-files/*.html files) no additional headings are inserted
212 case COMPILATION_UNIT:
213 implicitHeadingRank = fo.isNameCompatible("package", JavaFileObject.Kind.HTML) ? 1 : 0;
214 break;
215
216 // the following are for member declarations, which appear in the page
217 // for the enclosing type, and so appear after the <h2> "Members"
218 // aggregate heading and the specific <h3> "Member signature" heading.
219 case METHOD:
220 case VARIABLE:
221 implicitHeadingRank = 3;
222 break;
223
224 default:
225 Assert.error("unexpected tree kind: " + p.getLeaf().getKind() + " " + fo);
226 }
227
228 scan(new DocTreePath(p, tree), null);
229
230 if (!isOverridingMethod) {
231 switch (env.currElement.getKind()) {
232 case METHOD:
233 case CONSTRUCTOR: {
234 ExecutableElement ee = (ExecutableElement) env.currElement;
235 checkParamsDocumented(ee.getTypeParameters());
236 checkParamsDocumented(ee.getParameters());
237 switch (ee.getReturnType().getKind()) {
238 case VOID:
239 case NONE:
240 break;
241 default:
242 if (!foundReturn
243 && !foundInheritDoc
244 && !env.types.isSameType(ee.getReturnType(), env.java_lang_Void)) {
245 reportMissing("dc.missing.return");
246 }
247 }
340 }
341 done = true;
342 break;
343 } else if (tsi.tag.endKind != HtmlTag.EndKind.OPTIONAL) {
344 done = true;
345 break;
346 }
347 }
348 if (!done && HtmlTag.BODY.accepts(t)) {
349 while (!tagStack.isEmpty()) {
350 warnIfEmpty(tagStack.peek(), null);
351 tagStack.pop();
352 }
353 }
354
355 markEnclosingTag(Flag.HAS_ELEMENT);
356 checkStructure(tree, t);
357
358 // tag specific checks
359 switch (t) {
360 // check for out of sequence headings, such as <h1>...</h1> <h3>...</h3>
361 case H1: case H2: case H3: case H4: case H5: case H6:
362 checkHeading(tree, t);
363 break;
364 }
365
366 if (t.flags.contains(HtmlTag.Flag.NO_NEST)) {
367 for (TagStackItem i: tagStack) {
368 if (t == i.tag) {
369 env.messages.warning(HTML, tree, "dc.tag.nested.not.allowed", treeName);
370 break;
371 }
372 }
373 }
374 }
375
376 // check for self closing tags, such as <a id="name"/>
377 if (tree.isSelfClosing()) {
378 env.messages.error(HTML, tree, "dc.tag.self.closing", treeName);
379 }
380
381 try {
382 TagStackItem parent = tagStack.peek();
458 }
459 break;
460
461 case OTHER:
462 switch (t) {
463 case SCRIPT:
464 // <script> may or may not be allowed, depending on --allow-script-in-comments
465 // but we allow it here, and rely on a separate scanner to detect all uses
466 // of JavaScript, including <script> tags, and use in attributes, etc.
467 break;
468
469 default:
470 env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName);
471 }
472 return;
473 }
474
475 env.messages.error(HTML, tree, "dc.tag.not.allowed.here", treeName);
476 }
477
478 private void checkHeading(StartElementTree tree, HtmlTag tag) {
479 // verify the new tag
480 if (getHeadingRank(tag) > getHeadingRank(currHeadingTag) + 1) {
481 if (currHeadingTag == null) {
482 env.messages.error(ACCESSIBILITY, tree, "dc.tag.heading.sequence.1",
483 tag, implicitHeadingRank);
484 } else {
485 env.messages.error(ACCESSIBILITY, tree, "dc.tag.heading.sequence.2",
486 tag, currHeadingTag);
487 }
488 } else if (getHeadingRank(tag) <= implicitHeadingRank) {
489 env.messages.error(ACCESSIBILITY, tree, "dc.tag.heading.sequence.3",
490 tag, implicitHeadingRank);
491 }
492
493 currHeadingTag = tag;
494 }
495
496 private int getHeadingRank(HtmlTag tag) {
497 if (tag == null)
498 return implicitHeadingRank;
499 switch (tag) {
500 case H1: return 1;
501 case H2: return 2;
502 case H3: return 3;
503 case H4: return 4;
504 case H5: return 5;
505 case H6: return 6;
506 default: throw new IllegalArgumentException();
507 }
508 }
509
510 @Override @DefinedBy(Api.COMPILER_TREE)
511 public Void visitEndElement(EndElementTree tree, Void ignore) {
512 final Name treeName = tree.getName();
513 final HtmlTag t = HtmlTag.get(treeName);
514 if (t == null) {
515 env.messages.error(HTML, tree, "dc.tag.unknown", treeName);
516 } else if (t.endKind == HtmlTag.EndKind.NONE) {
517 env.messages.error(HTML, tree, "dc.tag.end.not.permitted", treeName);
518 } else {
682 }
683 }
684 }
685
686 // TODO: basic check on value
687
688 return super.visitAttribute(tree, ignore);
689 }
690
691 private void validateHtml4Attrs(AttributeTree tree, Name name, AttrKind k) {
692 switch (k) {
693 case ALL:
694 case HTML4:
695 break;
696
697 case INVALID:
698 env.messages.error(HTML, tree, "dc.attr.unknown", name);
699 break;
700
701 case OBSOLETE:
702 env.messages.warning(HTML, tree, "dc.attr.obsolete", name);
703 break;
704
705 case USE_CSS:
706 env.messages.warning(HTML, tree, "dc.attr.obsolete.use.css", name);
707 break;
708
709 case HTML5:
710 env.messages.error(HTML, tree, "dc.attr.not.supported.html4", name);
711 break;
712 }
713 }
714
715 private void validateHtml5Attrs(AttributeTree tree, Name name, AttrKind k) {
716 switch (k) {
717 case ALL:
718 case HTML5:
719 break;
720
721 case INVALID:
722 case OBSOLETE:
723 case USE_CSS:
724 case HTML4:
725 env.messages.error(HTML, tree, "dc.attr.not.supported.html5", name);
726 break;
|