Checker.java revision 2846:072008f47620
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 */ 25 26package com.sun.tools.doclint; 27 28import java.io.IOException; 29import java.io.StringWriter; 30import java.net.URI; 31import java.net.URISyntaxException; 32import java.util.Deque; 33import java.util.EnumSet; 34import java.util.HashMap; 35import java.util.HashSet; 36import java.util.LinkedList; 37import java.util.List; 38import java.util.Map; 39import java.util.Set; 40import java.util.regex.Matcher; 41import java.util.regex.Pattern; 42 43import javax.lang.model.element.Element; 44import javax.lang.model.element.ElementKind; 45import javax.lang.model.element.ExecutableElement; 46import javax.lang.model.element.Name; 47import javax.lang.model.element.VariableElement; 48import javax.lang.model.type.TypeKind; 49import javax.lang.model.type.TypeMirror; 50import javax.tools.Diagnostic.Kind; 51import javax.tools.JavaFileObject; 52 53import com.sun.source.doctree.AttributeTree; 54import com.sun.source.doctree.AuthorTree; 55import com.sun.source.doctree.DocCommentTree; 56import com.sun.source.doctree.DocRootTree; 57import com.sun.source.doctree.DocTree; 58import com.sun.source.doctree.EndElementTree; 59import com.sun.source.doctree.EntityTree; 60import com.sun.source.doctree.ErroneousTree; 61import com.sun.source.doctree.IdentifierTree; 62import com.sun.source.doctree.InheritDocTree; 63import com.sun.source.doctree.LinkTree; 64import com.sun.source.doctree.LiteralTree; 65import com.sun.source.doctree.ParamTree; 66import com.sun.source.doctree.ReferenceTree; 67import com.sun.source.doctree.ReturnTree; 68import com.sun.source.doctree.SerialDataTree; 69import com.sun.source.doctree.SerialFieldTree; 70import com.sun.source.doctree.SinceTree; 71import com.sun.source.doctree.StartElementTree; 72import com.sun.source.doctree.TextTree; 73import com.sun.source.doctree.ThrowsTree; 74import com.sun.source.doctree.UnknownBlockTagTree; 75import com.sun.source.doctree.UnknownInlineTagTree; 76import com.sun.source.doctree.ValueTree; 77import com.sun.source.doctree.VersionTree; 78import com.sun.source.tree.Tree; 79import com.sun.source.util.DocTreePath; 80import com.sun.source.util.DocTreePathScanner; 81import com.sun.source.util.TreePath; 82import com.sun.tools.doclint.HtmlTag.AttrKind; 83import com.sun.tools.javac.tree.DocPretty; 84import com.sun.tools.javac.util.Assert; 85import com.sun.tools.javac.util.DefinedBy; 86import com.sun.tools.javac.util.DefinedBy.Api; 87import com.sun.tools.javac.util.StringUtils; 88import static com.sun.tools.doclint.Messages.Group.*; 89 90 91/** 92 * Validate a doc comment. 93 * 94 * <p><b>This is NOT part of any supported API. 95 * If you write code that depends on this, you do so at your own 96 * risk. This code and its internal interfaces are subject to change 97 * or deletion without notice.</b></p> 98 */ 99public class Checker extends DocTreePathScanner<Void, Void> { 100 final Env env; 101 102 Set<Element> foundParams = new HashSet<>(); 103 Set<TypeMirror> foundThrows = new HashSet<>(); 104 Map<Element, Set<String>> foundAnchors = new HashMap<>(); 105 boolean foundInheritDoc = false; 106 boolean foundReturn = false; 107 108 public enum Flag { 109 TABLE_HAS_CAPTION, 110 HAS_ELEMENT, 111 HAS_INLINE_TAG, 112 HAS_TEXT, 113 REPORTED_BAD_INLINE 114 } 115 116 static class TagStackItem { 117 final DocTree tree; // typically, but not always, StartElementTree 118 final HtmlTag tag; 119 final Set<HtmlTag.Attr> attrs; 120 final Set<Flag> flags; 121 TagStackItem(DocTree tree, HtmlTag tag) { 122 this.tree = tree; 123 this.tag = tag; 124 attrs = EnumSet.noneOf(HtmlTag.Attr.class); 125 flags = EnumSet.noneOf(Flag.class); 126 } 127 @Override 128 public String toString() { 129 return String.valueOf(tag); 130 } 131 } 132 133 private Deque<TagStackItem> tagStack; // TODO: maybe want to record starting tree as well 134 private HtmlTag currHeaderTag; 135 136 private final int implicitHeaderLevel; 137 138 // <editor-fold defaultstate="collapsed" desc="Top level"> 139 140 Checker(Env env) { 141 this.env = Assert.checkNonNull(env); 142 tagStack = new LinkedList<>(); 143 implicitHeaderLevel = env.implicitHeaderLevel; 144 } 145 146 public Void scan(DocCommentTree tree, TreePath p) { 147 env.initTypes(); 148 env.setCurrent(p, tree); 149 150 boolean isOverridingMethod = !env.currOverriddenMethods.isEmpty(); 151 152 if (p.getLeaf().getKind() == Tree.Kind.PACKAGE) { 153 // If p points to a package, the implied declaration is the 154 // package declaration (if any) for the compilation unit. 155 // Handle this case specially, because doc comments are only 156 // expected in package-info files. 157 JavaFileObject fo = p.getCompilationUnit().getSourceFile(); 158 boolean isPkgInfo = fo.isNameCompatible("package-info", JavaFileObject.Kind.SOURCE); 159 if (tree == null) { 160 if (isPkgInfo) 161 reportMissing("dc.missing.comment"); 162 return null; 163 } else { 164 if (!isPkgInfo) 165 reportReference("dc.unexpected.comment"); 166 } 167 } else { 168 if (tree == null) { 169 if (!isSynthetic() && !isOverridingMethod) 170 reportMissing("dc.missing.comment"); 171 return null; 172 } 173 } 174 175 tagStack.clear(); 176 currHeaderTag = null; 177 178 foundParams.clear(); 179 foundThrows.clear(); 180 foundInheritDoc = false; 181 foundReturn = false; 182 183 scan(new DocTreePath(p, tree), null); 184 185 if (!isOverridingMethod) { 186 switch (env.currElement.getKind()) { 187 case METHOD: 188 case CONSTRUCTOR: { 189 ExecutableElement ee = (ExecutableElement) env.currElement; 190 checkParamsDocumented(ee.getTypeParameters()); 191 checkParamsDocumented(ee.getParameters()); 192 switch (ee.getReturnType().getKind()) { 193 case VOID: 194 case NONE: 195 break; 196 default: 197 if (!foundReturn 198 && !foundInheritDoc 199 && !env.types.isSameType(ee.getReturnType(), env.java_lang_Void)) { 200 reportMissing("dc.missing.return"); 201 } 202 } 203 checkThrowsDocumented(ee.getThrownTypes()); 204 } 205 } 206 } 207 208 return null; 209 } 210 211 private void reportMissing(String code, Object... args) { 212 env.messages.report(MISSING, Kind.WARNING, env.currPath.getLeaf(), code, args); 213 } 214 215 private void reportReference(String code, Object... args) { 216 env.messages.report(REFERENCE, Kind.WARNING, env.currPath.getLeaf(), code, args); 217 } 218 219 @Override @DefinedBy(Api.COMPILER_TREE) 220 public Void visitDocComment(DocCommentTree tree, Void ignore) { 221 super.visitDocComment(tree, ignore); 222 for (TagStackItem tsi: tagStack) { 223 warnIfEmpty(tsi, null); 224 if (tsi.tree.getKind() == DocTree.Kind.START_ELEMENT 225 && tsi.tag.endKind == HtmlTag.EndKind.REQUIRED) { 226 StartElementTree t = (StartElementTree) tsi.tree; 227 env.messages.error(HTML, t, "dc.tag.not.closed", t.getName()); 228 } 229 } 230 return null; 231 } 232 // </editor-fold> 233 234 // <editor-fold defaultstate="collapsed" desc="Text and entities."> 235 236 @Override @DefinedBy(Api.COMPILER_TREE) 237 public Void visitText(TextTree tree, Void ignore) { 238 if (hasNonWhitespace(tree)) { 239 checkAllowsText(tree); 240 markEnclosingTag(Flag.HAS_TEXT); 241 } 242 return null; 243 } 244 245 @Override @DefinedBy(Api.COMPILER_TREE) 246 public Void visitEntity(EntityTree tree, Void ignore) { 247 checkAllowsText(tree); 248 markEnclosingTag(Flag.HAS_TEXT); 249 String name = tree.getName().toString(); 250 if (name.startsWith("#")) { 251 int v = StringUtils.toLowerCase(name).startsWith("#x") 252 ? Integer.parseInt(name.substring(2), 16) 253 : Integer.parseInt(name.substring(1), 10); 254 if (!Entity.isValid(v)) { 255 env.messages.error(HTML, tree, "dc.entity.invalid", name); 256 } 257 } else if (!Entity.isValid(name)) { 258 env.messages.error(HTML, tree, "dc.entity.invalid", name); 259 } 260 return null; 261 } 262 263 void checkAllowsText(DocTree tree) { 264 TagStackItem top = tagStack.peek(); 265 if (top != null 266 && top.tree.getKind() == DocTree.Kind.START_ELEMENT 267 && !top.tag.acceptsText()) { 268 if (top.flags.add(Flag.REPORTED_BAD_INLINE)) { 269 env.messages.error(HTML, tree, "dc.text.not.allowed", 270 ((StartElementTree) top.tree).getName()); 271 } 272 } 273 } 274 275 // </editor-fold> 276 277 // <editor-fold defaultstate="collapsed" desc="HTML elements"> 278 279 @Override @DefinedBy(Api.COMPILER_TREE) 280 public Void visitStartElement(StartElementTree tree, Void ignore) { 281 final Name treeName = tree.getName(); 282 final HtmlTag t = HtmlTag.get(treeName); 283 if (t == null) { 284 env.messages.error(HTML, tree, "dc.tag.unknown", treeName); 285 } else { 286 boolean done = false; 287 for (TagStackItem tsi: tagStack) { 288 if (tsi.tag.accepts(t)) { 289 while (tagStack.peek() != tsi) { 290 warnIfEmpty(tagStack.peek(), null); 291 tagStack.pop(); 292 } 293 done = true; 294 break; 295 } else if (tsi.tag.endKind != HtmlTag.EndKind.OPTIONAL) { 296 done = true; 297 break; 298 } 299 } 300 if (!done && HtmlTag.BODY.accepts(t)) { 301 while (!tagStack.isEmpty()) { 302 warnIfEmpty(tagStack.peek(), null); 303 tagStack.pop(); 304 } 305 } 306 307 markEnclosingTag(Flag.HAS_ELEMENT); 308 checkStructure(tree, t); 309 310 // tag specific checks 311 switch (t) { 312 // check for out of sequence headers, such as <h1>...</h1> <h3>...</h3> 313 case H1: case H2: case H3: case H4: case H5: case H6: 314 checkHeader(tree, t); 315 break; 316 } 317 318 if (t.flags.contains(HtmlTag.Flag.NO_NEST)) { 319 for (TagStackItem i: tagStack) { 320 if (t == i.tag) { 321 env.messages.warning(HTML, tree, "dc.tag.nested.not.allowed", treeName); 322 break; 323 } 324 } 325 } 326 } 327 328 // check for self closing tags, such as <a id="name"/> 329 if (tree.isSelfClosing()) { 330 env.messages.error(HTML, tree, "dc.tag.self.closing", treeName); 331 } 332 333 try { 334 TagStackItem parent = tagStack.peek(); 335 TagStackItem top = new TagStackItem(tree, t); 336 tagStack.push(top); 337 338 super.visitStartElement(tree, ignore); 339 340 // handle attributes that may or may not have been found in start element 341 if (t != null) { 342 switch (t) { 343 case CAPTION: 344 if (parent != null && parent.tag == HtmlTag.TABLE) 345 parent.flags.add(Flag.TABLE_HAS_CAPTION); 346 break; 347 348 case IMG: 349 if (!top.attrs.contains(HtmlTag.Attr.ALT)) 350 env.messages.error(ACCESSIBILITY, tree, "dc.no.alt.attr.for.image"); 351 break; 352 } 353 } 354 355 return null; 356 } finally { 357 358 if (t == null || t.endKind == HtmlTag.EndKind.NONE) 359 tagStack.pop(); 360 } 361 } 362 363 private void checkStructure(StartElementTree tree, HtmlTag t) { 364 Name treeName = tree.getName(); 365 TagStackItem top = tagStack.peek(); 366 switch (t.blockType) { 367 case BLOCK: 368 if (top == null || top.tag.accepts(t)) 369 return; 370 371 switch (top.tree.getKind()) { 372 case START_ELEMENT: { 373 if (top.tag.blockType == HtmlTag.BlockType.INLINE) { 374 Name name = ((StartElementTree) top.tree).getName(); 375 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.element", 376 treeName, name); 377 return; 378 } 379 } 380 break; 381 382 case LINK: 383 case LINK_PLAIN: { 384 String name = top.tree.getKind().tagName; 385 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.tag", 386 treeName, name); 387 return; 388 } 389 } 390 break; 391 392 case INLINE: 393 if (top == null || top.tag.accepts(t)) 394 return; 395 break; 396 397 case LIST_ITEM: 398 case TABLE_ITEM: 399 if (top != null) { 400 // reset this flag so subsequent bad inline content gets reported 401 top.flags.remove(Flag.REPORTED_BAD_INLINE); 402 if (top.tag.accepts(t)) 403 return; 404 } 405 break; 406 407 case OTHER: 408 env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName); 409 return; 410 } 411 412 env.messages.error(HTML, tree, "dc.tag.not.allowed.here", treeName); 413 } 414 415 private void checkHeader(StartElementTree tree, HtmlTag tag) { 416 // verify the new tag 417 if (getHeaderLevel(tag) > getHeaderLevel(currHeaderTag) + 1) { 418 if (currHeaderTag == null) { 419 env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.1", tag); 420 } else { 421 env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.2", 422 tag, currHeaderTag); 423 } 424 } 425 426 currHeaderTag = tag; 427 } 428 429 private int getHeaderLevel(HtmlTag tag) { 430 if (tag == null) 431 return implicitHeaderLevel; 432 switch (tag) { 433 case H1: return 1; 434 case H2: return 2; 435 case H3: return 3; 436 case H4: return 4; 437 case H5: return 5; 438 case H6: return 6; 439 default: throw new IllegalArgumentException(); 440 } 441 } 442 443 @Override @DefinedBy(Api.COMPILER_TREE) 444 public Void visitEndElement(EndElementTree tree, Void ignore) { 445 final Name treeName = tree.getName(); 446 final HtmlTag t = HtmlTag.get(treeName); 447 if (t == null) { 448 env.messages.error(HTML, tree, "dc.tag.unknown", treeName); 449 } else if (t.endKind == HtmlTag.EndKind.NONE) { 450 env.messages.error(HTML, tree, "dc.tag.end.not.permitted", treeName); 451 } else { 452 boolean done = false; 453 while (!tagStack.isEmpty()) { 454 TagStackItem top = tagStack.peek(); 455 if (t == top.tag) { 456 switch (t) { 457 case TABLE: 458 if (!top.attrs.contains(HtmlTag.Attr.SUMMARY) 459 && !top.flags.contains(Flag.TABLE_HAS_CAPTION)) { 460 env.messages.error(ACCESSIBILITY, tree, 461 "dc.no.summary.or.caption.for.table"); 462 } 463 } 464 warnIfEmpty(top, tree); 465 tagStack.pop(); 466 done = true; 467 break; 468 } else if (top.tag == null || top.tag.endKind != HtmlTag.EndKind.REQUIRED) { 469 tagStack.pop(); 470 } else { 471 boolean found = false; 472 for (TagStackItem si: tagStack) { 473 if (si.tag == t) { 474 found = true; 475 break; 476 } 477 } 478 if (found && top.tree.getKind() == DocTree.Kind.START_ELEMENT) { 479 env.messages.error(HTML, top.tree, "dc.tag.start.unmatched", 480 ((StartElementTree) top.tree).getName()); 481 tagStack.pop(); 482 } else { 483 env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName); 484 done = true; 485 break; 486 } 487 } 488 } 489 490 if (!done && tagStack.isEmpty()) { 491 env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName); 492 } 493 } 494 495 return super.visitEndElement(tree, ignore); 496 } 497 498 void warnIfEmpty(TagStackItem tsi, DocTree endTree) { 499 if (tsi.tag != null && tsi.tree instanceof StartElementTree) { 500 if (tsi.tag.flags.contains(HtmlTag.Flag.EXPECT_CONTENT) 501 && !tsi.flags.contains(Flag.HAS_TEXT) 502 && !tsi.flags.contains(Flag.HAS_ELEMENT) 503 && !tsi.flags.contains(Flag.HAS_INLINE_TAG)) { 504 DocTree tree = (endTree != null) ? endTree : tsi.tree; 505 Name treeName = ((StartElementTree) tsi.tree).getName(); 506 env.messages.warning(HTML, tree, "dc.tag.empty", treeName); 507 } 508 } 509 } 510 511 // </editor-fold> 512 513 // <editor-fold defaultstate="collapsed" desc="HTML attributes"> 514 515 @Override @DefinedBy(Api.COMPILER_TREE) @SuppressWarnings("fallthrough") 516 public Void visitAttribute(AttributeTree tree, Void ignore) { 517 HtmlTag currTag = tagStack.peek().tag; 518 if (currTag != null) { 519 Name name = tree.getName(); 520 HtmlTag.Attr attr = currTag.getAttr(name); 521 if (attr != null) { 522 boolean first = tagStack.peek().attrs.add(attr); 523 if (!first) 524 env.messages.error(HTML, tree, "dc.attr.repeated", name); 525 } 526 AttrKind k = currTag.getAttrKind(name); 527 switch (k) { 528 case OK: 529 break; 530 531 case INVALID: 532 env.messages.error(HTML, tree, "dc.attr.unknown", name); 533 break; 534 535 case OBSOLETE: 536 env.messages.warning(ACCESSIBILITY, tree, "dc.attr.obsolete", name); 537 break; 538 539 case USE_CSS: 540 env.messages.warning(ACCESSIBILITY, tree, "dc.attr.obsolete.use.css", name); 541 break; 542 } 543 544 if (attr != null) { 545 switch (attr) { 546 case NAME: 547 if (currTag != HtmlTag.A) { 548 break; 549 } 550 // fallthrough 551 case ID: 552 String value = getAttrValue(tree); 553 if (value == null) { 554 env.messages.error(HTML, tree, "dc.anchor.value.missing"); 555 } else { 556 if (!validName.matcher(value).matches()) { 557 env.messages.error(HTML, tree, "dc.invalid.anchor", value); 558 } 559 if (!checkAnchor(value)) { 560 env.messages.error(HTML, tree, "dc.anchor.already.defined", value); 561 } 562 } 563 break; 564 565 case HREF: 566 if (currTag == HtmlTag.A) { 567 String v = getAttrValue(tree); 568 if (v == null || v.isEmpty()) { 569 env.messages.error(HTML, tree, "dc.attr.lacks.value"); 570 } else { 571 Matcher m = docRoot.matcher(v); 572 if (m.matches()) { 573 String rest = m.group(2); 574 if (!rest.isEmpty()) 575 checkURI(tree, rest); 576 } else { 577 checkURI(tree, v); 578 } 579 } 580 } 581 break; 582 583 case VALUE: 584 if (currTag == HtmlTag.LI) { 585 String v = getAttrValue(tree); 586 if (v == null || v.isEmpty()) { 587 env.messages.error(HTML, tree, "dc.attr.lacks.value"); 588 } else if (!validNumber.matcher(v).matches()) { 589 env.messages.error(HTML, tree, "dc.attr.not.number"); 590 } 591 } 592 break; 593 } 594 } 595 } 596 597 // TODO: basic check on value 598 599 return super.visitAttribute(tree, ignore); 600 } 601 602 private boolean checkAnchor(String name) { 603 Element e = getEnclosingPackageOrClass(env.currElement); 604 if (e == null) 605 return true; 606 Set<String> set = foundAnchors.get(e); 607 if (set == null) 608 foundAnchors.put(e, set = new HashSet<>()); 609 return set.add(name); 610 } 611 612 private Element getEnclosingPackageOrClass(Element e) { 613 while (e != null) { 614 switch (e.getKind()) { 615 case CLASS: 616 case ENUM: 617 case INTERFACE: 618 case PACKAGE: 619 return e; 620 default: 621 e = e.getEnclosingElement(); 622 } 623 } 624 return e; 625 } 626 627 // http://www.w3.org/TR/html401/types.html#type-name 628 private static final Pattern validName = Pattern.compile("[A-Za-z][A-Za-z0-9-_:.]*"); 629 630 private static final Pattern validNumber = Pattern.compile("-?[0-9]+"); 631 632 // pattern to remove leading {@docRoot}/? 633 private static final Pattern docRoot = Pattern.compile("(?i)(\\{@docRoot *\\}/?)?(.*)"); 634 635 private String getAttrValue(AttributeTree tree) { 636 if (tree.getValue() == null) 637 return null; 638 639 StringWriter sw = new StringWriter(); 640 try { 641 new DocPretty(sw).print(tree.getValue()); 642 } catch (IOException e) { 643 // cannot happen 644 } 645 // ignore potential use of entities for now 646 return sw.toString(); 647 } 648 649 private void checkURI(AttributeTree tree, String uri) { 650 try { 651 URI u = new URI(uri); 652 } catch (URISyntaxException e) { 653 env.messages.error(HTML, tree, "dc.invalid.uri", uri); 654 } 655 } 656 // </editor-fold> 657 658 // <editor-fold defaultstate="collapsed" desc="javadoc tags"> 659 660 @Override @DefinedBy(Api.COMPILER_TREE) 661 public Void visitAuthor(AuthorTree tree, Void ignore) { 662 warnIfEmpty(tree, tree.getName()); 663 return super.visitAuthor(tree, ignore); 664 } 665 666 @Override @DefinedBy(Api.COMPILER_TREE) 667 public Void visitDocRoot(DocRootTree tree, Void ignore) { 668 markEnclosingTag(Flag.HAS_INLINE_TAG); 669 return super.visitDocRoot(tree, ignore); 670 } 671 672 @Override @DefinedBy(Api.COMPILER_TREE) 673 public Void visitInheritDoc(InheritDocTree tree, Void ignore) { 674 markEnclosingTag(Flag.HAS_INLINE_TAG); 675 // TODO: verify on overridden method 676 foundInheritDoc = true; 677 return super.visitInheritDoc(tree, ignore); 678 } 679 680 @Override @DefinedBy(Api.COMPILER_TREE) 681 public Void visitLink(LinkTree tree, Void ignore) { 682 markEnclosingTag(Flag.HAS_INLINE_TAG); 683 // simulate inline context on tag stack 684 HtmlTag t = (tree.getKind() == DocTree.Kind.LINK) 685 ? HtmlTag.CODE : HtmlTag.SPAN; 686 tagStack.push(new TagStackItem(tree, t)); 687 try { 688 return super.visitLink(tree, ignore); 689 } finally { 690 tagStack.pop(); 691 } 692 } 693 694 @Override @DefinedBy(Api.COMPILER_TREE) 695 public Void visitLiteral(LiteralTree tree, Void ignore) { 696 markEnclosingTag(Flag.HAS_INLINE_TAG); 697 if (tree.getKind() == DocTree.Kind.CODE) { 698 for (TagStackItem tsi: tagStack) { 699 if (tsi.tag == HtmlTag.CODE) { 700 env.messages.warning(HTML, tree, "dc.tag.code.within.code"); 701 break; 702 } 703 } 704 } 705 return super.visitLiteral(tree, ignore); 706 } 707 708 @Override @DefinedBy(Api.COMPILER_TREE) 709 @SuppressWarnings("fallthrough") 710 public Void visitParam(ParamTree tree, Void ignore) { 711 boolean typaram = tree.isTypeParameter(); 712 IdentifierTree nameTree = tree.getName(); 713 Element paramElement = nameTree != null ? env.trees.getElement(new DocTreePath(getCurrentPath(), nameTree)) : null; 714 715 if (paramElement == null) { 716 switch (env.currElement.getKind()) { 717 case CLASS: case INTERFACE: { 718 if (!typaram) { 719 env.messages.error(REFERENCE, tree, "dc.invalid.param"); 720 break; 721 } 722 } 723 case METHOD: case CONSTRUCTOR: { 724 env.messages.error(REFERENCE, nameTree, "dc.param.name.not.found"); 725 break; 726 } 727 728 default: 729 env.messages.error(REFERENCE, tree, "dc.invalid.param"); 730 break; 731 } 732 } else { 733 foundParams.add(paramElement); 734 } 735 736 warnIfEmpty(tree, tree.getDescription()); 737 return super.visitParam(tree, ignore); 738 } 739 740 private void checkParamsDocumented(List<? extends Element> list) { 741 if (foundInheritDoc) 742 return; 743 744 for (Element e: list) { 745 if (!foundParams.contains(e)) { 746 CharSequence paramName = (e.getKind() == ElementKind.TYPE_PARAMETER) 747 ? "<" + e.getSimpleName() + ">" 748 : e.getSimpleName(); 749 reportMissing("dc.missing.param", paramName); 750 } 751 } 752 } 753 754 @Override @DefinedBy(Api.COMPILER_TREE) 755 public Void visitReference(ReferenceTree tree, Void ignore) { 756 String sig = tree.getSignature(); 757 if (sig.contains("<") || sig.contains(">")) 758 env.messages.error(REFERENCE, tree, "dc.type.arg.not.allowed"); 759 760 Element e = env.trees.getElement(getCurrentPath()); 761 if (e == null) 762 env.messages.error(REFERENCE, tree, "dc.ref.not.found"); 763 return super.visitReference(tree, ignore); 764 } 765 766 @Override @DefinedBy(Api.COMPILER_TREE) 767 public Void visitReturn(ReturnTree tree, Void ignore) { 768 Element e = env.trees.getElement(env.currPath); 769 if (e.getKind() != ElementKind.METHOD 770 || ((ExecutableElement) e).getReturnType().getKind() == TypeKind.VOID) 771 env.messages.error(REFERENCE, tree, "dc.invalid.return"); 772 foundReturn = true; 773 warnIfEmpty(tree, tree.getDescription()); 774 return super.visitReturn(tree, ignore); 775 } 776 777 @Override @DefinedBy(Api.COMPILER_TREE) 778 public Void visitSerialData(SerialDataTree tree, Void ignore) { 779 warnIfEmpty(tree, tree.getDescription()); 780 return super.visitSerialData(tree, ignore); 781 } 782 783 @Override @DefinedBy(Api.COMPILER_TREE) 784 public Void visitSerialField(SerialFieldTree tree, Void ignore) { 785 warnIfEmpty(tree, tree.getDescription()); 786 return super.visitSerialField(tree, ignore); 787 } 788 789 @Override @DefinedBy(Api.COMPILER_TREE) 790 public Void visitSince(SinceTree tree, Void ignore) { 791 warnIfEmpty(tree, tree.getBody()); 792 return super.visitSince(tree, ignore); 793 } 794 795 @Override @DefinedBy(Api.COMPILER_TREE) 796 public Void visitThrows(ThrowsTree tree, Void ignore) { 797 ReferenceTree exName = tree.getExceptionName(); 798 Element ex = env.trees.getElement(new DocTreePath(getCurrentPath(), exName)); 799 if (ex == null) { 800 env.messages.error(REFERENCE, tree, "dc.ref.not.found"); 801 } else if (isThrowable(ex.asType())) { 802 switch (env.currElement.getKind()) { 803 case CONSTRUCTOR: 804 case METHOD: 805 if (isCheckedException(ex.asType())) { 806 ExecutableElement ee = (ExecutableElement) env.currElement; 807 checkThrowsDeclared(exName, ex.asType(), ee.getThrownTypes()); 808 } 809 break; 810 default: 811 env.messages.error(REFERENCE, tree, "dc.invalid.throws"); 812 } 813 } else { 814 env.messages.error(REFERENCE, tree, "dc.invalid.throws"); 815 } 816 warnIfEmpty(tree, tree.getDescription()); 817 return scan(tree.getDescription(), ignore); 818 } 819 820 private boolean isThrowable(TypeMirror tm) { 821 switch (tm.getKind()) { 822 case DECLARED: 823 case TYPEVAR: 824 return env.types.isAssignable(tm, env.java_lang_Throwable); 825 } 826 return false; 827 } 828 829 private void checkThrowsDeclared(ReferenceTree tree, TypeMirror t, List<? extends TypeMirror> list) { 830 boolean found = false; 831 for (TypeMirror tl : list) { 832 if (env.types.isAssignable(t, tl)) { 833 foundThrows.add(tl); 834 found = true; 835 } 836 } 837 if (!found) 838 env.messages.error(REFERENCE, tree, "dc.exception.not.thrown", t); 839 } 840 841 private void checkThrowsDocumented(List<? extends TypeMirror> list) { 842 if (foundInheritDoc) 843 return; 844 845 for (TypeMirror tl: list) { 846 if (isCheckedException(tl) && !foundThrows.contains(tl)) 847 reportMissing("dc.missing.throws", tl); 848 } 849 } 850 851 @Override @DefinedBy(Api.COMPILER_TREE) 852 public Void visitUnknownBlockTag(UnknownBlockTagTree tree, Void ignore) { 853 checkUnknownTag(tree, tree.getTagName()); 854 return super.visitUnknownBlockTag(tree, ignore); 855 } 856 857 @Override @DefinedBy(Api.COMPILER_TREE) 858 public Void visitUnknownInlineTag(UnknownInlineTagTree tree, Void ignore) { 859 checkUnknownTag(tree, tree.getTagName()); 860 return super.visitUnknownInlineTag(tree, ignore); 861 } 862 863 private void checkUnknownTag(DocTree tree, String tagName) { 864 if (env.customTags != null && !env.customTags.contains(tagName)) 865 env.messages.error(SYNTAX, tree, "dc.tag.unknown", tagName); 866 } 867 868 @Override @DefinedBy(Api.COMPILER_TREE) 869 public Void visitValue(ValueTree tree, Void ignore) { 870 ReferenceTree ref = tree.getReference(); 871 if (ref == null || ref.getSignature().isEmpty()) { 872 if (!isConstant(env.currElement)) 873 env.messages.error(REFERENCE, tree, "dc.value.not.allowed.here"); 874 } else { 875 Element e = env.trees.getElement(new DocTreePath(getCurrentPath(), ref)); 876 if (!isConstant(e)) 877 env.messages.error(REFERENCE, tree, "dc.value.not.a.constant"); 878 } 879 880 markEnclosingTag(Flag.HAS_INLINE_TAG); 881 return super.visitValue(tree, ignore); 882 } 883 884 private boolean isConstant(Element e) { 885 if (e == null) 886 return false; 887 888 switch (e.getKind()) { 889 case FIELD: 890 Object value = ((VariableElement) e).getConstantValue(); 891 return (value != null); // can't distinguish "not a constant" from "constant is null" 892 default: 893 return false; 894 } 895 } 896 897 @Override @DefinedBy(Api.COMPILER_TREE) 898 public Void visitVersion(VersionTree tree, Void ignore) { 899 warnIfEmpty(tree, tree.getBody()); 900 return super.visitVersion(tree, ignore); 901 } 902 903 @Override @DefinedBy(Api.COMPILER_TREE) 904 public Void visitErroneous(ErroneousTree tree, Void ignore) { 905 env.messages.error(SYNTAX, tree, null, tree.getDiagnostic().getMessage(null)); 906 return null; 907 } 908 // </editor-fold> 909 910 // <editor-fold defaultstate="collapsed" desc="Utility methods"> 911 912 private boolean isCheckedException(TypeMirror t) { 913 return !(env.types.isAssignable(t, env.java_lang_Error) 914 || env.types.isAssignable(t, env.java_lang_RuntimeException)); 915 } 916 917 private boolean isSynthetic() { 918 switch (env.currElement.getKind()) { 919 case CONSTRUCTOR: 920 // A synthetic default constructor has the same pos as the 921 // enclosing class 922 TreePath p = env.currPath; 923 return env.getPos(p) == env.getPos(p.getParentPath()); 924 } 925 return false; 926 } 927 928 void markEnclosingTag(Flag flag) { 929 TagStackItem top = tagStack.peek(); 930 if (top != null) 931 top.flags.add(flag); 932 } 933 934 String toString(TreePath p) { 935 StringBuilder sb = new StringBuilder("TreePath["); 936 toString(p, sb); 937 sb.append("]"); 938 return sb.toString(); 939 } 940 941 void toString(TreePath p, StringBuilder sb) { 942 TreePath parent = p.getParentPath(); 943 if (parent != null) { 944 toString(parent, sb); 945 sb.append(","); 946 } 947 sb.append(p.getLeaf().getKind()).append(":").append(env.getPos(p)).append(":S").append(env.getStartPos(p)); 948 } 949 950 void warnIfEmpty(DocTree tree, List<? extends DocTree> list) { 951 for (DocTree d: list) { 952 switch (d.getKind()) { 953 case TEXT: 954 if (hasNonWhitespace((TextTree) d)) 955 return; 956 break; 957 default: 958 return; 959 } 960 } 961 env.messages.warning(SYNTAX, tree, "dc.empty", tree.getKind().tagName); 962 } 963 964 boolean hasNonWhitespace(TextTree tree) { 965 String s = tree.getBody(); 966 for (int i = 0; i < s.length(); i++) { 967 if (!Character.isWhitespace(s.charAt(i))) 968 return true; 969 } 970 return false; 971 } 972 973 // </editor-fold> 974 975} 976