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