src/share/classes/com/sun/tools/doclint/Checker.java

Tue, 17 Sep 2013 14:17:13 -0700

author
jjg
date
Tue, 17 Sep 2013 14:17:13 -0700
changeset 2033
fdfbc5f0c4ed
parent 1917
2fbe77c38802
child 2052
503338f16d2b
permissions
-rw-r--r--

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 }

mercurial