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

Thu, 28 Feb 2013 14:00:52 +0000

author
mcimadamore
date
Thu, 28 Feb 2013 14:00:52 +0000
changeset 1608
133a0a0c2cbc
parent 1552
153d20d0cac5
child 1650
74d7f9bcac93
permissions
-rw-r--r--

8008723: Graph Inference: bad graph calculation leads to assertion error
Summary: Dependencies are not propagated correctly through merged nodes during inference graph initialization
Reviewed-by: jjg

     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         REPORTED_BAD_INLINE
   100     }
   102     static class TagStackItem {
   103         final DocTree tree; // typically, but not always, StartElementTree
   104         final HtmlTag tag;
   105         final Set<HtmlTag.Attr> attrs;
   106         final Set<Flag> flags;
   107         TagStackItem(DocTree tree, HtmlTag tag) {
   108             this.tree = tree;
   109             this.tag = tag;
   110             attrs = EnumSet.noneOf(HtmlTag.Attr.class);
   111             flags = EnumSet.noneOf(Flag.class);
   112         }
   113         @Override
   114         public String toString() {
   115             return String.valueOf(tag);
   116         }
   117     }
   119     private Deque<TagStackItem> tagStack; // TODO: maybe want to record starting tree as well
   120     private HtmlTag currHeaderTag;
   122     // <editor-fold defaultstate="collapsed" desc="Top level">
   124     Checker(Env env) {
   125         env.getClass();
   126         this.env = env;
   127         tagStack = new LinkedList<TagStackItem>();
   128     }
   130     public Void scan(DocCommentTree tree, TreePath p) {
   131         env.setCurrent(p, tree);
   133         boolean isOverridingMethod = !env.currOverriddenMethods.isEmpty();
   135         if (tree == null) {
   136             if (!isSynthetic() && !isOverridingMethod)
   137                 reportMissing("dc.missing.comment");
   138             return null;
   139         }
   141         tagStack.clear();
   142         currHeaderTag = null;
   144         foundParams.clear();
   145         foundThrows.clear();
   146         foundInheritDoc = false;
   147         foundReturn = false;
   149         scan(tree, (Void) null);
   151         if (!isOverridingMethod) {
   152             switch (env.currElement.getKind()) {
   153                 case METHOD:
   154                 case CONSTRUCTOR: {
   155                     ExecutableElement ee = (ExecutableElement) env.currElement;
   156                     checkParamsDocumented(ee.getTypeParameters());
   157                     checkParamsDocumented(ee.getParameters());
   158                     switch (ee.getReturnType().getKind()) {
   159                         case VOID:
   160                         case NONE:
   161                             break;
   162                         default:
   163                             if (!foundReturn
   164                                     && !foundInheritDoc
   165                                     && !env.types.isSameType(ee.getReturnType(), env.java_lang_Void)) {
   166                                 reportMissing("dc.missing.return");
   167                             }
   168                     }
   169                     checkThrowsDocumented(ee.getThrownTypes());
   170                 }
   171             }
   172         }
   174         return null;
   175     }
   177     private void reportMissing(String code, Object... args) {
   178         env.messages.report(MISSING, Kind.WARNING, env.currPath.getLeaf(), code, args);
   179     }
   181     @Override
   182     public Void visitDocComment(DocCommentTree tree, Void ignore) {
   183         super.visitDocComment(tree, ignore);
   184         for (TagStackItem tsi: tagStack) {
   185             if (tsi.tree.getKind() == DocTree.Kind.START_ELEMENT
   186                     && tsi.tag.endKind == HtmlTag.EndKind.REQUIRED) {
   187                 StartElementTree t = (StartElementTree) tsi.tree;
   188                 env.messages.error(HTML, t, "dc.tag.not.closed", t.getName());
   189             }
   190         }
   191         return null;
   192     }
   193     // </editor-fold>
   195     // <editor-fold defaultstate="collapsed" desc="Text and entities.">
   197     @Override
   198     public Void visitText(TextTree tree, Void ignore) {
   199         if (hasNonWhitespace(tree)) {
   200             checkAllowsText(tree);
   201             markEnclosingTag(Flag.HAS_TEXT);
   202         }
   203         return null;
   204     }
   206     @Override
   207     public Void visitEntity(EntityTree tree, Void ignore) {
   208         checkAllowsText(tree);
   209         markEnclosingTag(Flag.HAS_TEXT);
   210         String name = tree.getName().toString();
   211         if (name.startsWith("#")) {
   212             int v = name.toLowerCase().startsWith("#x")
   213                     ? Integer.parseInt(name.substring(2), 16)
   214                     : Integer.parseInt(name.substring(1), 10);
   215             if (!Entity.isValid(v)) {
   216                 env.messages.error(HTML, tree, "dc.entity.invalid", name);
   217             }
   218         } else if (!Entity.isValid(name)) {
   219             env.messages.error(HTML, tree, "dc.entity.invalid", name);
   220         }
   221         return null;
   222     }
   224     void checkAllowsText(DocTree tree) {
   225         TagStackItem top = tagStack.peek();
   226         if (top != null
   227                 && top.tree.getKind() == DocTree.Kind.START_ELEMENT
   228                 && !top.tag.acceptsText()) {
   229             if (top.flags.add(Flag.REPORTED_BAD_INLINE)) {
   230                 env.messages.error(HTML, tree, "dc.text.not.allowed",
   231                         ((StartElementTree) top.tree).getName());
   232             }
   233         }
   234     }
   236     // </editor-fold>
   238     // <editor-fold defaultstate="collapsed" desc="HTML elements">
   240     @Override
   241     public Void visitStartElement(StartElementTree tree, Void ignore) {
   242         markEnclosingTag(Flag.HAS_ELEMENT);
   243         final Name treeName = tree.getName();
   244         final HtmlTag t = HtmlTag.get(treeName);
   245         if (t == null) {
   246             env.messages.error(HTML, tree, "dc.tag.unknown", treeName);
   247         } else {
   248             boolean done = false;
   249             for (TagStackItem tsi: tagStack) {
   250                 if (tsi.tag.accepts(t)) {
   251                     while (tagStack.peek() != tsi) tagStack.pop();
   252                     done = true;
   253                     break;
   254                 } else if (tsi.tag.endKind != HtmlTag.EndKind.OPTIONAL) {
   255                     done = true;
   256                     break;
   257                 }
   258             }
   259             if (!done && HtmlTag.BODY.accepts(t)) {
   260                 tagStack.clear();
   261             }
   263             checkStructure(tree, t);
   265             // tag specific checks
   266             switch (t) {
   267                 // check for out of sequence headers, such as <h1>...</h1>  <h3>...</h3>
   268                 case H1: case H2: case H3: case H4: case H5: case H6:
   269                     checkHeader(tree, t);
   270                     break;
   271             }
   273             if (t.flags.contains(HtmlTag.Flag.NO_NEST)) {
   274                 for (TagStackItem i: tagStack) {
   275                     if (t == i.tag) {
   276                         env.messages.warning(HTML, tree, "dc.tag.nested.not.allowed", treeName);
   277                         break;
   278                     }
   279                 }
   280             }
   281         }
   283         // check for self closing tags, such as <a id="name"/>
   284         if (tree.isSelfClosing()) {
   285             env.messages.error(HTML, tree, "dc.tag.self.closing", treeName);
   286         }
   288         try {
   289             TagStackItem parent = tagStack.peek();
   290             TagStackItem top = new TagStackItem(tree, t);
   291             tagStack.push(top);
   293             super.visitStartElement(tree, ignore);
   295             // handle attributes that may or may not have been found in start element
   296             if (t != null) {
   297                 switch (t) {
   298                     case CAPTION:
   299                         if (parent != null && parent.tag == HtmlTag.TABLE)
   300                             parent.flags.add(Flag.TABLE_HAS_CAPTION);
   301                         break;
   303                     case IMG:
   304                         if (!top.attrs.contains(HtmlTag.Attr.ALT))
   305                             env.messages.error(ACCESSIBILITY, tree, "dc.no.alt.attr.for.image");
   306                         break;
   307                 }
   308             }
   310             return null;
   311         } finally {
   313             if (t == null || t.endKind == HtmlTag.EndKind.NONE)
   314                 tagStack.pop();
   315         }
   316     }
   318     private void checkStructure(StartElementTree tree, HtmlTag t) {
   319         Name treeName = tree.getName();
   320         TagStackItem top = tagStack.peek();
   321         switch (t.blockType) {
   322             case BLOCK:
   323                 if (top == null || top.tag.accepts(t))
   324                     return;
   326                 switch (top.tree.getKind()) {
   327                     case START_ELEMENT: {
   328                         if (top.tag.blockType == HtmlTag.BlockType.INLINE) {
   329                             Name name = ((StartElementTree) top.tree).getName();
   330                             env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.element",
   331                                     treeName, name);
   332                             return;
   333                         }
   334                     }
   335                     break;
   337                     case LINK:
   338                     case LINK_PLAIN: {
   339                         String name = top.tree.getKind().tagName;
   340                         env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.tag",
   341                                 treeName, name);
   342                         return;
   343                     }
   344                 }
   345                 break;
   347             case INLINE:
   348                 if (top == null || top.tag.accepts(t))
   349                     return;
   350                 break;
   352             case LIST_ITEM:
   353             case TABLE_ITEM:
   354                 if (top != null) {
   355                     // reset this flag so subsequent bad inline content gets reported
   356                     top.flags.remove(Flag.REPORTED_BAD_INLINE);
   357                     if (top.tag.accepts(t))
   358                         return;
   359                 }
   360                 break;
   362             case OTHER:
   363                 env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName);
   364                 return;
   365         }
   367         env.messages.error(HTML, tree, "dc.tag.not.allowed.here", treeName);
   368     }
   370     private void checkHeader(StartElementTree tree, HtmlTag tag) {
   371         // verify the new tag
   372         if (getHeaderLevel(tag) > getHeaderLevel(currHeaderTag) + 1) {
   373             if (currHeaderTag == null) {
   374                 env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.1", tag);
   375             } else {
   376                 env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.2",
   377                     tag, currHeaderTag);
   378             }
   379         }
   381         currHeaderTag = tag;
   382     }
   384     private int getHeaderLevel(HtmlTag tag) {
   385         if (tag == null)
   386             return 0;
   387         switch (tag) {
   388             case H1: return 1;
   389             case H2: return 2;
   390             case H3: return 3;
   391             case H4: return 4;
   392             case H5: return 5;
   393             case H6: return 6;
   394             default: throw new IllegalArgumentException();
   395         }
   396     }
   398     @Override
   399     public Void visitEndElement(EndElementTree tree, Void ignore) {
   400         final Name treeName = tree.getName();
   401         final HtmlTag t = HtmlTag.get(treeName);
   402         if (t == null) {
   403             env.messages.error(HTML, tree, "dc.tag.unknown", treeName);
   404         } else if (t.endKind == HtmlTag.EndKind.NONE) {
   405             env.messages.error(HTML, tree, "dc.tag.end.not.permitted", treeName);
   406         } else {
   407             boolean done = false;
   408             while (!tagStack.isEmpty()) {
   409                 TagStackItem top = tagStack.peek();
   410                 if (t == top.tag) {
   411                     switch (t) {
   412                         case TABLE:
   413                             if (!top.attrs.contains(HtmlTag.Attr.SUMMARY)
   414                                     && !top.flags.contains(Flag.TABLE_HAS_CAPTION)) {
   415                                 env.messages.error(ACCESSIBILITY, tree,
   416                                         "dc.no.summary.or.caption.for.table");
   417                             }
   418                     }
   419                     if (t.flags.contains(HtmlTag.Flag.EXPECT_CONTENT)
   420                             && !top.flags.contains(Flag.HAS_TEXT)
   421                             && !top.flags.contains(Flag.HAS_ELEMENT)) {
   422                         env.messages.warning(HTML, tree, "dc.tag.empty", treeName);
   423                     }
   424                     tagStack.pop();
   425                     done = true;
   426                     break;
   427                 } else if (top.tag == null || top.tag.endKind != HtmlTag.EndKind.REQUIRED) {
   428                     tagStack.pop();
   429                 } else {
   430                     boolean found = false;
   431                     for (TagStackItem si: tagStack) {
   432                         if (si.tag == t) {
   433                             found = true;
   434                             break;
   435                         }
   436                     }
   437                     if (found && top.tree.getKind() == DocTree.Kind.START_ELEMENT) {
   438                         env.messages.error(HTML, top.tree, "dc.tag.start.unmatched",
   439                                 ((StartElementTree) top.tree).getName());
   440                         tagStack.pop();
   441                     } else {
   442                         env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName);
   443                         done = true;
   444                         break;
   445                     }
   446                 }
   447             }
   449             if (!done && tagStack.isEmpty()) {
   450                 env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName);
   451             }
   452         }
   454         return super.visitEndElement(tree, ignore);
   455     }
   456     // </editor-fold>
   458     // <editor-fold defaultstate="collapsed" desc="HTML attributes">
   460     @Override @SuppressWarnings("fallthrough")
   461     public Void visitAttribute(AttributeTree tree, Void ignore) {
   462         HtmlTag currTag = tagStack.peek().tag;
   463         if (currTag != null) {
   464             Name name = tree.getName();
   465             HtmlTag.Attr attr = currTag.getAttr(name);
   466             if (attr != null) {
   467                 boolean first = tagStack.peek().attrs.add(attr);
   468                 if (!first)
   469                     env.messages.error(HTML, tree, "dc.attr.repeated", name);
   470             }
   471             AttrKind k = currTag.getAttrKind(name);
   472             switch (k) {
   473                 case OK:
   474                     break;
   476                 case INVALID:
   477                     env.messages.error(HTML, tree, "dc.attr.unknown", name);
   478                     break;
   480                 case OBSOLETE:
   481                     env.messages.warning(ACCESSIBILITY, tree, "dc.attr.obsolete", name);
   482                     break;
   484                 case USE_CSS:
   485                     env.messages.warning(ACCESSIBILITY, tree, "dc.attr.obsolete.use.css", name);
   486                     break;
   487             }
   489             if (attr != null) {
   490                 switch (attr) {
   491                     case NAME:
   492                         if (currTag != HtmlTag.A) {
   493                             break;
   494                         }
   495                         // fallthrough
   496                     case ID:
   497                         String value = getAttrValue(tree);
   498                         if (value == null) {
   499                             env.messages.error(HTML, tree, "dc.anchor.value.missing");
   500                         } else {
   501                             if (!validName.matcher(value).matches()) {
   502                                 env.messages.error(HTML, tree, "dc.invalid.anchor", value);
   503                             }
   504                             if (!foundAnchors.add(value)) {
   505                                 env.messages.error(HTML, tree, "dc.anchor.already.defined", value);
   506                             }
   507                         }
   508                         break;
   510                     case HREF:
   511                         if (currTag == HtmlTag.A) {
   512                             String v = getAttrValue(tree);
   513                             if (v == null || v.isEmpty()) {
   514                                 env.messages.error(HTML, tree, "dc.attr.lacks.value");
   515                             } else {
   516                                 Matcher m = docRoot.matcher(v);
   517                                 if (m.matches()) {
   518                                     String rest = m.group(2);
   519                                     if (!rest.isEmpty())
   520                                         checkURI(tree, rest);
   521                                 } else {
   522                                     checkURI(tree, v);
   523                                 }
   524                             }
   525                         }
   526                         break;
   527                 }
   528             }
   529         }
   531         // TODO: basic check on value
   533         return super.visitAttribute(tree, ignore);
   534     }
   536     // http://www.w3.org/TR/html401/types.html#type-name
   537     private static final Pattern validName = Pattern.compile("[A-Za-z][A-Za-z0-9-_:.]*");
   539     // pattern to remove leading {@docRoot}/?
   540     private static final Pattern docRoot = Pattern.compile("(?i)(\\{@docRoot *\\}/?)?(.*)");
   542     private String getAttrValue(AttributeTree tree) {
   543         if (tree.getValue() == null)
   544             return null;
   546         StringWriter sw = new StringWriter();
   547         try {
   548             new DocPretty(sw).print(tree.getValue());
   549         } catch (IOException e) {
   550             // cannot happen
   551         }
   552         // ignore potential use of entities for now
   553         return sw.toString();
   554     }
   556     private void checkURI(AttributeTree tree, String uri) {
   557         try {
   558             URI u = new URI(uri);
   559         } catch (URISyntaxException e) {
   560             env.messages.error(HTML, tree, "dc.invalid.uri", uri);
   561         }
   562     }
   563     // </editor-fold>
   565     // <editor-fold defaultstate="collapsed" desc="javadoc tags">
   567     @Override
   568     public Void visitAuthor(AuthorTree tree, Void ignore) {
   569         warnIfEmpty(tree, tree.getName());
   570         return super.visitAuthor(tree, ignore);
   571     }
   573     @Override
   574     public Void visitInheritDoc(InheritDocTree tree, Void ignore) {
   575         // TODO: verify on overridden method
   576         foundInheritDoc = true;
   577         return super.visitInheritDoc(tree, ignore);
   578     }
   580     @Override
   581     public Void visitLink(LinkTree tree, Void ignore) {
   582         // simulate inline context on tag stack
   583         HtmlTag t = (tree.getKind() == DocTree.Kind.LINK)
   584                 ? HtmlTag.CODE : HtmlTag.SPAN;
   585         tagStack.push(new TagStackItem(tree, t));
   586         try {
   587             return super.visitLink(tree, ignore);
   588         } finally {
   589             tagStack.pop();
   590         }
   591     }
   593     @Override
   594     public Void visitLiteral(LiteralTree tree, Void ignore) {
   595         if (tree.getKind() == DocTree.Kind.CODE) {
   596             for (TagStackItem tsi: tagStack) {
   597                 if (tsi.tag == HtmlTag.CODE) {
   598                     env.messages.warning(HTML, tree, "dc.tag.code.within.code");
   599                     break;
   600                 }
   601             }
   602         }
   603         return super.visitLiteral(tree, ignore);
   604     }
   606     @Override
   607     public Void visitParam(ParamTree tree, Void ignore) {
   608         boolean typaram = tree.isTypeParameter();
   609         IdentifierTree nameTree = tree.getName();
   610         Element e = env.currElement;
   611         switch (e.getKind()) {
   612             case METHOD: case CONSTRUCTOR: {
   613                 ExecutableElement ee = (ExecutableElement) e;
   614                 checkParamDeclared(nameTree, typaram ? ee.getTypeParameters() : ee.getParameters());
   615                 break;
   616             }
   618             case CLASS: case INTERFACE: {
   619                 TypeElement te = (TypeElement) e;
   620                 if (typaram) {
   621                     checkParamDeclared(nameTree, te.getTypeParameters());
   622                 } else {
   623                     env.messages.error(REFERENCE, tree, "dc.invalid.param");
   624                 }
   625                 break;
   626             }
   628             default:
   629                 env.messages.error(REFERENCE, tree, "dc.invalid.param");
   630                 break;
   631         }
   632         warnIfEmpty(tree, tree.getDescription());
   633         return super.visitParam(tree, ignore);
   634     }
   635     // where
   636     private void checkParamDeclared(IdentifierTree nameTree, List<? extends Element> list) {
   637         Name name = nameTree.getName();
   638         boolean found = false;
   639         for (Element e: list) {
   640             if (name.equals(e.getSimpleName())) {
   641                 foundParams.add(e);
   642                 found = true;
   643             }
   644         }
   645         if (!found)
   646             env.messages.error(REFERENCE, nameTree, "dc.param.name.not.found");
   647     }
   649     private void checkParamsDocumented(List<? extends Element> list) {
   650         if (foundInheritDoc)
   651             return;
   653         for (Element e: list) {
   654             if (!foundParams.contains(e)) {
   655                 CharSequence paramName = (e.getKind() == ElementKind.TYPE_PARAMETER)
   656                         ? "<" + e.getSimpleName() + ">"
   657                         : e.getSimpleName();
   658                 reportMissing("dc.missing.param", paramName);
   659             }
   660         }
   661     }
   663     @Override
   664     public Void visitReference(ReferenceTree tree, Void ignore) {
   665         Element e = env.trees.getElement(env.currPath, tree);
   666         if (e == null)
   667             env.messages.error(REFERENCE, tree, "dc.ref.not.found");
   668         return super.visitReference(tree, ignore);
   669     }
   671     @Override
   672     public Void visitReturn(ReturnTree tree, Void ignore) {
   673         Element e = env.trees.getElement(env.currPath);
   674         if (e.getKind() != ElementKind.METHOD
   675                 || ((ExecutableElement) e).getReturnType().getKind() == TypeKind.VOID)
   676             env.messages.error(REFERENCE, tree, "dc.invalid.return");
   677         foundReturn = true;
   678         warnIfEmpty(tree, tree.getDescription());
   679         return super.visitReturn(tree, ignore);
   680     }
   682     @Override
   683     public Void visitSerialData(SerialDataTree tree, Void ignore) {
   684         warnIfEmpty(tree, tree.getDescription());
   685         return super.visitSerialData(tree, ignore);
   686     }
   688     @Override
   689     public Void visitSerialField(SerialFieldTree tree, Void ignore) {
   690         warnIfEmpty(tree, tree.getDescription());
   691         return super.visitSerialField(tree, ignore);
   692     }
   694     @Override
   695     public Void visitSince(SinceTree tree, Void ignore) {
   696         warnIfEmpty(tree, tree.getBody());
   697         return super.visitSince(tree, ignore);
   698     }
   700     @Override
   701     public Void visitThrows(ThrowsTree tree, Void ignore) {
   702         ReferenceTree exName = tree.getExceptionName();
   703         Element ex = env.trees.getElement(env.currPath, exName);
   704         if (ex == null) {
   705             env.messages.error(REFERENCE, tree, "dc.ref.not.found");
   706         } else if (ex.asType().getKind() == TypeKind.DECLARED
   707                 && env.types.isAssignable(ex.asType(), env.java_lang_Throwable)) {
   708             switch (env.currElement.getKind()) {
   709                 case CONSTRUCTOR:
   710                 case METHOD:
   711                     if (isCheckedException(ex.asType())) {
   712                         ExecutableElement ee = (ExecutableElement) env.currElement;
   713                         checkThrowsDeclared(exName, ex.asType(), ee.getThrownTypes());
   714                     }
   715                     break;
   716                 default:
   717                     env.messages.error(REFERENCE, tree, "dc.invalid.throws");
   718             }
   719         } else {
   720             env.messages.error(REFERENCE, tree, "dc.invalid.throws");
   721         }
   722         warnIfEmpty(tree, tree.getDescription());
   723         return scan(tree.getDescription(), ignore);
   724     }
   726     private void checkThrowsDeclared(ReferenceTree tree, TypeMirror t, List<? extends TypeMirror> list) {
   727         boolean found = false;
   728         for (TypeMirror tl : list) {
   729             if (env.types.isAssignable(t, tl)) {
   730                 foundThrows.add(tl);
   731                 found = true;
   732             }
   733         }
   734         if (!found)
   735             env.messages.error(REFERENCE, tree, "dc.exception.not.thrown", t);
   736     }
   738     private void checkThrowsDocumented(List<? extends TypeMirror> list) {
   739         if (foundInheritDoc)
   740             return;
   742         for (TypeMirror tl: list) {
   743             if (isCheckedException(tl) && !foundThrows.contains(tl))
   744                 reportMissing("dc.missing.throws", tl);
   745         }
   746     }
   748     @Override
   749     public Void visitVersion(VersionTree tree, Void ignore) {
   750         warnIfEmpty(tree, tree.getBody());
   751         return super.visitVersion(tree, ignore);
   752     }
   754     @Override
   755     public Void visitErroneous(ErroneousTree tree, Void ignore) {
   756         env.messages.error(SYNTAX, tree, null, tree.getDiagnostic().getMessage(null));
   757         return null;
   758     }
   759     // </editor-fold>
   761     // <editor-fold defaultstate="collapsed" desc="Utility methods">
   763     private boolean isCheckedException(TypeMirror t) {
   764         return !(env.types.isAssignable(t, env.java_lang_Error)
   765                 || env.types.isAssignable(t, env.java_lang_RuntimeException));
   766     }
   768     private boolean isSynthetic() {
   769         switch (env.currElement.getKind()) {
   770             case CONSTRUCTOR:
   771                 // A synthetic default constructor has the same pos as the
   772                 // enclosing class
   773                 TreePath p = env.currPath;
   774                 return env.getPos(p) == env.getPos(p.getParentPath());
   775         }
   776         return false;
   777     }
   779     void markEnclosingTag(Flag flag) {
   780         TagStackItem top = tagStack.peek();
   781         if (top != null)
   782             top.flags.add(flag);
   783     }
   785     String toString(TreePath p) {
   786         StringBuilder sb = new StringBuilder("TreePath[");
   787         toString(p, sb);
   788         sb.append("]");
   789         return sb.toString();
   790     }
   792     void toString(TreePath p, StringBuilder sb) {
   793         TreePath parent = p.getParentPath();
   794         if (parent != null) {
   795             toString(parent, sb);
   796             sb.append(",");
   797         }
   798        sb.append(p.getLeaf().getKind()).append(":").append(env.getPos(p)).append(":S").append(env.getStartPos(p));
   799     }
   801     void warnIfEmpty(DocTree tree, List<? extends DocTree> list) {
   802         for (DocTree d: list) {
   803             switch (d.getKind()) {
   804                 case TEXT:
   805                     if (hasNonWhitespace((TextTree) d))
   806                         return;
   807                     break;
   808                 default:
   809                     return;
   810             }
   811         }
   812         env.messages.warning(SYNTAX, tree, "dc.empty", tree.getKind().tagName);
   813     }
   815     boolean hasNonWhitespace(TextTree tree) {
   816         String s = tree.getBody();
   817         for (int i = 0; i < s.length(); i++) {
   818             if (!Character.isWhitespace(s.charAt(i)))
   819                 return true;
   820         }
   821         return false;
   822     }
   824     // </editor-fold>
   826 }

mercurial