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

Mon, 21 Jan 2013 10:00:46 -0800

author
jjg
date
Mon, 21 Jan 2013 10:00:46 -0800
changeset 1506
4a3cfc970c6f
parent 1502
916143318f10
child 1507
967052c425a1
permissions
-rw-r--r--

8006263: Supplementary test cases needed for doclint
Reviewed-by: mcimadamore
Contributed-by: peter.jensen@oracle.com

     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 com.sun.source.doctree.LiteralTree;
    29 import java.util.regex.Matcher;
    30 import com.sun.source.doctree.LinkTree;
    31 import java.net.URI;
    32 import java.util.regex.Pattern;
    33 import java.io.IOException;
    34 import com.sun.tools.javac.tree.DocPretty;
    35 import java.io.StringWriter;
    36 import java.util.Deque;
    37 import java.util.EnumSet;
    38 import java.util.HashSet;
    39 import java.util.LinkedList;
    40 import java.util.List;
    41 import java.util.Set;
    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.element.TypeElement;
    48 import javax.lang.model.type.TypeKind;
    49 import javax.lang.model.type.TypeMirror;
    50 import javax.tools.Diagnostic.Kind;
    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.DocTree;
    56 import com.sun.source.doctree.EndElementTree;
    57 import com.sun.source.doctree.EntityTree;
    58 import com.sun.source.doctree.ErroneousTree;
    59 import com.sun.source.doctree.IdentifierTree;
    60 import com.sun.source.doctree.InheritDocTree;
    61 import com.sun.source.doctree.ParamTree;
    62 import com.sun.source.doctree.ReferenceTree;
    63 import com.sun.source.doctree.ReturnTree;
    64 import com.sun.source.doctree.SerialDataTree;
    65 import com.sun.source.doctree.SerialFieldTree;
    66 import com.sun.source.doctree.SinceTree;
    67 import com.sun.source.doctree.StartElementTree;
    68 import com.sun.source.doctree.TextTree;
    69 import com.sun.source.doctree.ThrowsTree;
    70 import com.sun.source.doctree.VersionTree;
    71 import com.sun.source.util.DocTreeScanner;
    72 import com.sun.source.util.TreePath;
    73 import com.sun.tools.doclint.HtmlTag.AttrKind;
    74 import java.net.URISyntaxException;
    75 import static com.sun.tools.doclint.Messages.Group.*;
    78 /**
    79  * Validate a doc comment.
    80  *
    81  * <p><b>This is NOT part of any supported API.
    82  * If you write code that depends on this, you do so at your own
    83  * risk.  This code and its internal interfaces are subject to change
    84  * or deletion without notice.</b></p>
    85  */
    86 public class Checker extends DocTreeScanner<Void, Void> {
    87     final Env env;
    89     Set<Element> foundParams = new HashSet<Element>();
    90     Set<TypeMirror> foundThrows = new HashSet<TypeMirror>();
    91     Set<String> foundAnchors = new HashSet<String>();
    92     boolean foundInheritDoc = false;
    93     boolean foundReturn = false;
    95     public enum Flag {
    96         TABLE_HAS_CAPTION,
    97         HAS_ELEMENT,
    98         HAS_TEXT
    99     }
   101     static class TagStackItem {
   102         final DocTree tree; // typically, but not always, StartElementTree
   103         final HtmlTag tag;
   104         final Set<HtmlTag.Attr> attrs;
   105         final Set<Flag> flags;
   106         TagStackItem(DocTree tree, HtmlTag tag) {
   107             this.tree = tree;
   108             this.tag = tag;
   109             attrs = EnumSet.noneOf(HtmlTag.Attr.class);
   110             flags = EnumSet.noneOf(Flag.class);
   111         }
   112         @Override
   113         public String toString() {
   114             return String.valueOf(tag);
   115         }
   116     }
   118     private Deque<TagStackItem> tagStack; // TODO: maybe want to record starting tree as well
   119     private HtmlTag currHeaderTag;
   121     // <editor-fold defaultstate="collapsed" desc="Top level">
   123     Checker(Env env) {
   124         env.getClass();
   125         this.env = env;
   126         tagStack = new LinkedList<TagStackItem>();
   127     }
   129     public Void scan(DocCommentTree tree, TreePath p) {
   130         env.setCurrent(p, tree);
   132         boolean isOverridingMethod = !env.currOverriddenMethods.isEmpty();
   134         if (tree == null) {
   135             if (!isSynthetic() && !isOverridingMethod)
   136                 reportMissing("dc.missing.comment");
   137             return null;
   138         }
   140         tagStack.clear();
   141         currHeaderTag = null;
   143         foundParams.clear();
   144         foundThrows.clear();
   145         foundInheritDoc = false;
   146         foundReturn = false;
   148         scan(tree, (Void) null);
   150         if (!isOverridingMethod) {
   151             switch (env.currElement.getKind()) {
   152                 case METHOD:
   153                 case CONSTRUCTOR: {
   154                     ExecutableElement ee = (ExecutableElement) env.currElement;
   155                     checkParamsDocumented(ee.getTypeParameters());
   156                     checkParamsDocumented(ee.getParameters());
   157                     switch (ee.getReturnType().getKind()) {
   158                         case VOID:
   159                         case NONE:
   160                             break;
   161                         default:
   162                             if (!foundReturn
   163                                     && !foundInheritDoc
   164                                     && !env.types.isSameType(ee.getReturnType(), env.java_lang_Void)) {
   165                                 reportMissing("dc.missing.return");
   166                             }
   167                     }
   168                     checkThrowsDocumented(ee.getThrownTypes());
   169                 }
   170             }
   171         }
   173         return null;
   174     }
   176     private void reportMissing(String code, Object... args) {
   177         env.messages.report(MISSING, Kind.WARNING, env.currPath.getLeaf(), code, args);
   178     }
   180     @Override
   181     public Void visitDocComment(DocCommentTree tree, Void ignore) {
   182         super.visitDocComment(tree, ignore);
   183         for (TagStackItem tsi: tagStack) {
   184             if (tsi.tree.getKind() == DocTree.Kind.START_ELEMENT
   185                     && tsi.tag.endKind == HtmlTag.EndKind.REQUIRED) {
   186                 StartElementTree t = (StartElementTree) tsi.tree;
   187                 env.messages.error(HTML, t, "dc.tag.not.closed", t.getName());
   188             }
   189         }
   190         return null;
   191     }
   192     // </editor-fold>
   194     // <editor-fold defaultstate="collapsed" desc="Text and entities.">
   196     @Override
   197     public Void visitText(TextTree tree, Void ignore) {
   198         if (!tree.getBody().trim().isEmpty()) {
   199             markEnclosingTag(Flag.HAS_TEXT);
   200         }
   201         return null;
   202     }
   204     @Override
   205     public Void visitEntity(EntityTree tree, Void ignore) {
   206         markEnclosingTag(Flag.HAS_TEXT);
   207         String name = tree.getName().toString();
   208         if (name.startsWith("#")) {
   209             int v = name.toLowerCase().startsWith("#x")
   210                     ? Integer.parseInt(name.substring(2), 16)
   211                     : Integer.parseInt(name.substring(1), 10);
   212             if (!Entity.isValid(v)) {
   213                 env.messages.error(HTML, tree, "dc.entity.invalid", name);
   214             }
   215         } else if (!Entity.isValid(name)) {
   216             env.messages.error(HTML, tree, "dc.entity.invalid", name);
   217         }
   218         return null;
   219     }
   221     // </editor-fold>
   223     // <editor-fold defaultstate="collapsed" desc="HTML elements">
   225     @Override
   226     public Void visitStartElement(StartElementTree tree, Void ignore) {
   227         markEnclosingTag(Flag.HAS_ELEMENT);
   228         final Name treeName = tree.getName();
   229         final HtmlTag t = HtmlTag.get(treeName);
   230         if (t == null) {
   231             env.messages.error(HTML, tree, "dc.tag.unknown", treeName);
   232         } else {
   233             // tag specific checks
   234             switch (t) {
   235                 // check for out of sequence headers, such as <h1>...</h1>  <h3>...</h3>
   236                 case H1: case H2: case H3: case H4: case H5: case H6:
   237                     checkHeader(tree, t);
   238                     break;
   239                 // <p> inside <pre>
   240                 case P:
   241                     TagStackItem top = tagStack.peek();
   242                     if (top != null && top.tag == HtmlTag.PRE)
   243                         env.messages.warning(HTML, tree, "dc.tag.p.in.pre");
   244                     break;
   245             }
   247             // check that only block tags and inline tags are used,
   248             // and that blocks tags are not used within inline tags
   249             switch (t.blockType) {
   250                 case INLINE:
   251                     break;
   252                 case BLOCK:
   253                     TagStackItem top = tagStack.peek();
   254                     if (top != null && top.tag != null && top.tag.blockType == HtmlTag.BlockType.INLINE) {
   255                         switch (top.tree.getKind()) {
   256                             case START_ELEMENT: {
   257                                 Name name = ((StartElementTree) top.tree).getName();
   258                                 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.element",
   259                                         treeName, name);
   260                                 break;
   261                             }
   262                             case LINK:
   263                             case LINK_PLAIN: {
   264                                 String name = top.tree.getKind().tagName;
   265                                 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.tag",
   266                                         treeName, name);
   267                                 break;
   268                             }
   269                             default:
   270                                 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.other",
   271                                         treeName);
   272                         }
   273                     }
   274                     break;
   275                 case OTHER:
   276                     env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName);
   277                     break;
   278                 default:
   279                     throw new AssertionError();
   280             }
   282             if (t.flags.contains(HtmlTag.Flag.NO_NEST)) {
   283                 for (TagStackItem i: tagStack) {
   284                     if (t == i.tag) {
   285                         env.messages.warning(HTML, tree, "dc.tag.nested.not.allowed", treeName);
   286                         break;
   287                     }
   288                 }
   289             }
   290         }
   292         // check for self closing tags, such as <a id="name"/>
   293         if (tree.isSelfClosing()) {
   294             env.messages.error(HTML, tree, "dc.tag.self.closing", treeName);
   295         }
   297         try {
   298             TagStackItem parent = tagStack.peek();
   299             TagStackItem top = new TagStackItem(tree, t);
   300             tagStack.push(top);
   302             super.visitStartElement(tree, ignore);
   304             // handle attributes that may or may not have been found in start element
   305             if (t != null) {
   306                 switch (t) {
   307                     case CAPTION:
   308                         if (parent != null && parent.tag == HtmlTag.TABLE)
   309                             parent.flags.add(Flag.TABLE_HAS_CAPTION);
   310                         break;
   312                     case IMG:
   313                         if (!top.attrs.contains(HtmlTag.Attr.ALT))
   314                             env.messages.error(ACCESSIBILITY, tree, "dc.no.alt.attr.for.image");
   315                         break;
   316                 }
   317             }
   319             return null;
   320         } finally {
   322             if (t == null || t.endKind == HtmlTag.EndKind.NONE)
   323                 tagStack.pop();
   324         }
   325     }
   327     private void checkHeader(StartElementTree tree, HtmlTag tag) {
   328         // verify the new tag
   329         if (getHeaderLevel(tag) > getHeaderLevel(currHeaderTag) + 1) {
   330             if (currHeaderTag == null) {
   331                 env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.1", tag);
   332             } else {
   333                 env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.2",
   334                     tag, currHeaderTag);
   335             }
   336         }
   338         currHeaderTag = tag;
   339     }
   341     private int getHeaderLevel(HtmlTag tag) {
   342         if (tag == null)
   343             return 0;
   344         switch (tag) {
   345             case H1: return 1;
   346             case H2: return 2;
   347             case H3: return 3;
   348             case H4: return 4;
   349             case H5: return 5;
   350             case H6: return 6;
   351             default: throw new IllegalArgumentException();
   352         }
   353     }
   355     @Override
   356     public Void visitEndElement(EndElementTree tree, Void ignore) {
   357         final Name treeName = tree.getName();
   358         final HtmlTag t = HtmlTag.get(treeName);
   359         if (t == null) {
   360             env.messages.error(HTML, tree, "dc.tag.unknown", treeName);
   361         } else if (t.endKind == HtmlTag.EndKind.NONE) {
   362             env.messages.error(HTML, tree, "dc.tag.end.not.permitted", treeName);
   363         } else {
   364             boolean done = false;
   365             while (!tagStack.isEmpty()) {
   366                 TagStackItem top = tagStack.peek();
   367                 if (t == top.tag) {
   368                     switch (t) {
   369                         case TABLE:
   370                             if (!top.attrs.contains(HtmlTag.Attr.SUMMARY)
   371                                     && !top.flags.contains(Flag.TABLE_HAS_CAPTION)) {
   372                                 env.messages.error(ACCESSIBILITY, tree,
   373                                         "dc.no.summary.or.caption.for.table");
   374                             }
   375                     }
   376                     if (t.flags.contains(HtmlTag.Flag.EXPECT_CONTENT)
   377                             && !top.flags.contains(Flag.HAS_TEXT)
   378                             && !top.flags.contains(Flag.HAS_ELEMENT)) {
   379                         env.messages.warning(HTML, tree, "dc.tag.empty", treeName);
   380                     }
   381                     if (t.flags.contains(HtmlTag.Flag.NO_TEXT)
   382                             && top.flags.contains(Flag.HAS_TEXT)) {
   383                         env.messages.error(HTML, tree, "dc.text.not.allowed", treeName);
   384                     }
   385                     tagStack.pop();
   386                     done = true;
   387                     break;
   388                 } else if (top.tag == null || top.tag.endKind != HtmlTag.EndKind.REQUIRED) {
   389                     tagStack.pop();
   390                 } else {
   391                     boolean found = false;
   392                     for (TagStackItem si: tagStack) {
   393                         if (si.tag == t) {
   394                             found = true;
   395                             break;
   396                         }
   397                     }
   398                     if (found && top.tree.getKind() == DocTree.Kind.START_ELEMENT) {
   399                         env.messages.error(HTML, top.tree, "dc.tag.start.unmatched",
   400                                 ((StartElementTree) top.tree).getName());
   401                         tagStack.pop();
   402                     } else {
   403                         env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName);
   404                         done = true;
   405                         break;
   406                     }
   407                 }
   408             }
   410             if (!done && tagStack.isEmpty()) {
   411                 env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName);
   412             }
   413         }
   415         return super.visitEndElement(tree, ignore);
   416     }
   417     // </editor-fold>
   419     // <editor-fold defaultstate="collapsed" desc="HTML attributes">
   421     @Override @SuppressWarnings("fallthrough")
   422     public Void visitAttribute(AttributeTree tree, Void ignore) {
   423         HtmlTag currTag = tagStack.peek().tag;
   424         if (currTag != null) {
   425             Name name = tree.getName();
   426             HtmlTag.Attr attr = currTag.getAttr(name);
   427             if (attr != null) {
   428                 boolean first = tagStack.peek().attrs.add(attr);
   429                 if (!first)
   430                     env.messages.error(HTML, tree, "dc.attr.repeated", name);
   431             }
   432             AttrKind k = currTag.getAttrKind(name);
   433             switch (k) {
   434                 case OK:
   435                     break;
   437                 case INVALID:
   438                     env.messages.error(HTML, tree, "dc.attr.unknown", name);
   439                     break;
   441                 case OBSOLETE:
   442                     env.messages.warning(ACCESSIBILITY, tree, "dc.attr.obsolete", name);
   443                     break;
   445                 case USE_CSS:
   446                     env.messages.warning(ACCESSIBILITY, tree, "dc.attr.obsolete.use.css", name);
   447                     break;
   448             }
   450             if (attr != null) {
   451                 switch (attr) {
   452                     case NAME:
   453                         if (currTag != HtmlTag.A) {
   454                             break;
   455                         }
   456                         // fallthrough
   457                     case ID:
   458                         String value = getAttrValue(tree);
   459                         if (value == null) {
   460                             env.messages.error(HTML, tree, "dc.anchor.value.missing");
   461                         } else {
   462                             if (!validName.matcher(value).matches()) {
   463                                 env.messages.error(HTML, tree, "dc.invalid.anchor", value);
   464                             }
   465                             if (!foundAnchors.add(value)) {
   466                                 env.messages.error(HTML, tree, "dc.anchor.already.defined", value);
   467                             }
   468                         }
   469                         break;
   471                     case HREF:
   472                         if (currTag == HtmlTag.A) {
   473                             String v = getAttrValue(tree);
   474                             if (v == null || v.isEmpty()) {
   475                                 env.messages.error(HTML, tree, "dc.attr.lacks.value");
   476                             } else {
   477                                 Matcher m = docRoot.matcher(v);
   478                                 if (m.matches()) {
   479                                     String rest = m.group(2);
   480                                     if (!rest.isEmpty())
   481                                         checkURI(tree, rest);
   482                                 } else {
   483                                     checkURI(tree, v);
   484                                 }
   485                             }
   486                         }
   487                         break;
   488                 }
   489             }
   490         }
   492         // TODO: basic check on value
   494         return super.visitAttribute(tree, ignore);
   495     }
   497     // http://www.w3.org/TR/html401/types.html#type-name
   498     private static final Pattern validName = Pattern.compile("[A-Za-z][A-Za-z0-9-_:.]*");
   500     // pattern to remove leading {@docRoot}/?
   501     private static final Pattern docRoot = Pattern.compile("(?i)(\\{@docRoot *\\}/?)?(.*)");
   503     private String getAttrValue(AttributeTree tree) {
   504         if (tree.getValue() == null)
   505             return null;
   507         StringWriter sw = new StringWriter();
   508         try {
   509             new DocPretty(sw).print(tree.getValue());
   510         } catch (IOException e) {
   511             // cannot happen
   512         }
   513         // ignore potential use of entities for now
   514         return sw.toString();
   515     }
   517     private void checkURI(AttributeTree tree, String uri) {
   518         try {
   519             URI u = new URI(uri);
   520         } catch (URISyntaxException e) {
   521             env.messages.error(HTML, tree, "dc.invalid.uri", uri);
   522         }
   523     }
   524     // </editor-fold>
   526     // <editor-fold defaultstate="collapsed" desc="javadoc tags">
   528     @Override
   529     public Void visitAuthor(AuthorTree tree, Void ignore) {
   530         warnIfEmpty(tree, tree.getName());
   531         return super.visitAuthor(tree, ignore);
   532     }
   534     @Override
   535     public Void visitInheritDoc(InheritDocTree tree, Void ignore) {
   536         // TODO: verify on overridden method
   537         foundInheritDoc = true;
   538         return super.visitInheritDoc(tree, ignore);
   539     }
   541     @Override
   542     public Void visitLink(LinkTree tree, Void ignore) {
   543         // simulate inline context on tag stack
   544         HtmlTag t = (tree.getKind() == DocTree.Kind.LINK)
   545                 ? HtmlTag.CODE : HtmlTag.SPAN;
   546         tagStack.push(new TagStackItem(tree, t));
   547         try {
   548             return super.visitLink(tree, ignore);
   549         } finally {
   550             tagStack.pop();
   551         }
   552     }
   554     @Override
   555     public Void visitLiteral(LiteralTree tree, Void ignore) {
   556         if (tree.getKind() == DocTree.Kind.CODE) {
   557             for (TagStackItem tsi: tagStack) {
   558                 if (tsi.tag == HtmlTag.CODE) {
   559                     env.messages.warning(HTML, tree, "dc.tag.code.within.code");
   560                     break;
   561                 }
   562             }
   563         }
   564         return super.visitLiteral(tree, ignore);
   565     }
   567     @Override
   568     public Void visitParam(ParamTree tree, Void ignore) {
   569         boolean typaram = tree.isTypeParameter();
   570         IdentifierTree nameTree = tree.getName();
   571         Element e = env.currElement;
   572         switch (e.getKind()) {
   573             case METHOD: case CONSTRUCTOR: {
   574                 ExecutableElement ee = (ExecutableElement) e;
   575                 checkParamDeclared(nameTree, typaram ? ee.getTypeParameters() : ee.getParameters());
   576                 break;
   577             }
   579             case CLASS: case INTERFACE: {
   580                 TypeElement te = (TypeElement) e;
   581                 if (typaram) {
   582                     checkParamDeclared(nameTree, te.getTypeParameters());
   583                 } else {
   584                     env.messages.error(REFERENCE, tree, "dc.invalid.param");
   585                 }
   586                 break;
   587             }
   589             default:
   590                 env.messages.error(REFERENCE, tree, "dc.invalid.param");
   591                 break;
   592         }
   593         warnIfEmpty(tree, tree.getDescription());
   594         return super.visitParam(tree, ignore);
   595     }
   596     // where
   597     private void checkParamDeclared(IdentifierTree nameTree, List<? extends Element> list) {
   598         Name name = nameTree.getName();
   599         boolean found = false;
   600         for (Element e: list) {
   601             if (name.equals(e.getSimpleName())) {
   602                 foundParams.add(e);
   603                 found = true;
   604             }
   605         }
   606         if (!found)
   607             env.messages.error(REFERENCE, nameTree, "dc.param.name.not.found");
   608     }
   610     private void checkParamsDocumented(List<? extends Element> list) {
   611         if (foundInheritDoc)
   612             return;
   614         for (Element e: list) {
   615             if (!foundParams.contains(e)) {
   616                 CharSequence paramName = (e.getKind() == ElementKind.TYPE_PARAMETER)
   617                         ? "<" + e.getSimpleName() + ">"
   618                         : e.getSimpleName();
   619                 reportMissing("dc.missing.param", paramName);
   620             }
   621         }
   622     }
   624     @Override
   625     public Void visitReference(ReferenceTree tree, Void ignore) {
   626         Element e = env.trees.getElement(env.currPath, tree);
   627         if (e == null)
   628             env.messages.error(REFERENCE, tree, "dc.ref.not.found");
   629         return super.visitReference(tree, ignore);
   630     }
   632     @Override
   633     public Void visitReturn(ReturnTree tree, Void ignore) {
   634         Element e = env.trees.getElement(env.currPath);
   635         if (e.getKind() != ElementKind.METHOD
   636                 || ((ExecutableElement) e).getReturnType().getKind() == TypeKind.VOID)
   637             env.messages.error(REFERENCE, tree, "dc.invalid.return");
   638         foundReturn = true;
   639         warnIfEmpty(tree, tree.getDescription());
   640         return super.visitReturn(tree, ignore);
   641     }
   643     @Override
   644     public Void visitSerialData(SerialDataTree tree, Void ignore) {
   645         warnIfEmpty(tree, tree.getDescription());
   646         return super.visitSerialData(tree, ignore);
   647     }
   649     @Override
   650     public Void visitSerialField(SerialFieldTree tree, Void ignore) {
   651         warnIfEmpty(tree, tree.getDescription());
   652         return super.visitSerialField(tree, ignore);
   653     }
   655     @Override
   656     public Void visitSince(SinceTree tree, Void ignore) {
   657         warnIfEmpty(tree, tree.getBody());
   658         return super.visitSince(tree, ignore);
   659     }
   661     @Override
   662     public Void visitThrows(ThrowsTree tree, Void ignore) {
   663         ReferenceTree exName = tree.getExceptionName();
   664         Element ex = env.trees.getElement(env.currPath, exName);
   665         if (ex == null) {
   666             env.messages.error(REFERENCE, tree, "dc.ref.not.found");
   667         } else if (ex.asType().getKind() == TypeKind.DECLARED
   668                 && env.types.isAssignable(ex.asType(), env.java_lang_Throwable)) {
   669             switch (env.currElement.getKind()) {
   670                 case CONSTRUCTOR:
   671                 case METHOD:
   672                     if (isCheckedException(ex.asType())) {
   673                         ExecutableElement ee = (ExecutableElement) env.currElement;
   674                         checkThrowsDeclared(exName, ex.asType(), ee.getThrownTypes());
   675                     }
   676                     break;
   677                 default:
   678                     env.messages.error(REFERENCE, tree, "dc.invalid.throws");
   679             }
   680         } else {
   681             env.messages.error(REFERENCE, tree, "dc.invalid.throws");
   682         }
   683         warnIfEmpty(tree, tree.getDescription());
   684         return scan(tree.getDescription(), ignore);
   685     }
   687     private void checkThrowsDeclared(ReferenceTree tree, TypeMirror t, List<? extends TypeMirror> list) {
   688         boolean found = false;
   689         for (TypeMirror tl : list) {
   690             if (env.types.isAssignable(t, tl)) {
   691                 foundThrows.add(tl);
   692                 found = true;
   693             }
   694         }
   695         if (!found)
   696             env.messages.error(REFERENCE, tree, "dc.exception.not.thrown", t);
   697     }
   699     private void checkThrowsDocumented(List<? extends TypeMirror> list) {
   700         if (foundInheritDoc)
   701             return;
   703         for (TypeMirror tl: list) {
   704             if (isCheckedException(tl) && !foundThrows.contains(tl))
   705                 reportMissing("dc.missing.throws", tl);
   706         }
   707     }
   709     @Override
   710     public Void visitVersion(VersionTree tree, Void ignore) {
   711         warnIfEmpty(tree, tree.getBody());
   712         return super.visitVersion(tree, ignore);
   713     }
   715     @Override
   716     public Void visitErroneous(ErroneousTree tree, Void ignore) {
   717         env.messages.error(SYNTAX, tree, null, tree.getDiagnostic().getMessage(null));
   718         return null;
   719     }
   720     // </editor-fold>
   722     // <editor-fold defaultstate="collapsed" desc="Utility methods">
   724     private boolean isCheckedException(TypeMirror t) {
   725         return !(env.types.isAssignable(t, env.java_lang_Error)
   726                 || env.types.isAssignable(t, env.java_lang_RuntimeException));
   727     }
   729     private boolean isSynthetic() {
   730         switch (env.currElement.getKind()) {
   731             case CONSTRUCTOR:
   732                 // A synthetic default constructor has the same pos as the
   733                 // enclosing class
   734                 TreePath p = env.currPath;
   735                 return env.getPos(p) == env.getPos(p.getParentPath());
   736         }
   737         return false;
   738     }
   740     void markEnclosingTag(Flag flag) {
   741         TagStackItem top = tagStack.peek();
   742         if (top != null)
   743             top.flags.add(flag);
   744     }
   746     String toString(TreePath p) {
   747         StringBuilder sb = new StringBuilder("TreePath[");
   748         toString(p, sb);
   749         sb.append("]");
   750         return sb.toString();
   751     }
   753     void toString(TreePath p, StringBuilder sb) {
   754         TreePath parent = p.getParentPath();
   755         if (parent != null) {
   756             toString(parent, sb);
   757             sb.append(",");
   758         }
   759        sb.append(p.getLeaf().getKind()).append(":").append(env.getPos(p)).append(":S").append(env.getStartPos(p));
   760     }
   762     void warnIfEmpty(DocTree tree, List<? extends DocTree> list) {
   763         for (DocTree d: list) {
   764             switch (d.getKind()) {
   765                 case TEXT:
   766                     if (!((TextTree) d).getBody().trim().isEmpty())
   767                         return;
   768                     break;
   769                 default:
   770                     return;
   771             }
   772         }
   773         env.messages.warning(SYNTAX, tree, "dc.empty", tree.getKind().tagName);
   774     }
   775     // </editor-fold>
   777 }

mercurial