Path: blob/aarch64-shenandoah-jdk8u272-b10/langtools/src/share/classes/com/sun/tools/doclint/Checker.java
38899 views
/*1* Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.sun.tools.doclint;2627import java.io.IOException;28import java.io.StringWriter;29import java.net.URI;30import java.net.URISyntaxException;31import java.util.Deque;32import java.util.EnumSet;33import java.util.HashMap;34import java.util.HashSet;35import java.util.LinkedList;36import java.util.List;37import java.util.Map;38import java.util.Set;39import java.util.regex.Matcher;40import java.util.regex.Pattern;4142import javax.lang.model.element.Element;43import javax.lang.model.element.ElementKind;44import javax.lang.model.element.ExecutableElement;45import javax.lang.model.element.Name;46import javax.lang.model.element.VariableElement;47import javax.lang.model.type.TypeKind;48import javax.lang.model.type.TypeMirror;49import javax.tools.Diagnostic.Kind;50import javax.tools.JavaFileObject;5152import com.sun.source.doctree.AttributeTree;53import com.sun.source.doctree.AuthorTree;54import com.sun.source.doctree.DocCommentTree;55import com.sun.source.doctree.DocRootTree;56import com.sun.source.doctree.DocTree;57import com.sun.source.doctree.EndElementTree;58import com.sun.source.doctree.EntityTree;59import com.sun.source.doctree.ErroneousTree;60import com.sun.source.doctree.IdentifierTree;61import com.sun.source.doctree.InheritDocTree;62import com.sun.source.doctree.LinkTree;63import com.sun.source.doctree.LiteralTree;64import com.sun.source.doctree.ParamTree;65import com.sun.source.doctree.ReferenceTree;66import com.sun.source.doctree.ReturnTree;67import com.sun.source.doctree.SerialDataTree;68import com.sun.source.doctree.SerialFieldTree;69import com.sun.source.doctree.SinceTree;70import com.sun.source.doctree.StartElementTree;71import com.sun.source.doctree.TextTree;72import com.sun.source.doctree.ThrowsTree;73import com.sun.source.doctree.UnknownBlockTagTree;74import com.sun.source.doctree.UnknownInlineTagTree;75import com.sun.source.doctree.ValueTree;76import com.sun.source.doctree.VersionTree;77import com.sun.source.util.DocTreePath;78import com.sun.source.util.DocTreePathScanner;79import com.sun.source.util.TreePath;80import com.sun.tools.doclint.HtmlTag.AttrKind;81import com.sun.tools.javac.tree.DocPretty;82import com.sun.tools.javac.util.StringUtils;83import static com.sun.tools.doclint.Messages.Group.*;848586/**87* Validate a doc comment.88*89* <p><b>This is NOT part of any supported API.90* If you write code that depends on this, you do so at your own91* risk. This code and its internal interfaces are subject to change92* or deletion without notice.</b></p>93*/94public class Checker extends DocTreePathScanner<Void, Void> {95final Env env;9697Set<Element> foundParams = new HashSet<>();98Set<TypeMirror> foundThrows = new HashSet<>();99Map<Element, Set<String>> foundAnchors = new HashMap<>();100boolean foundInheritDoc = false;101boolean foundReturn = false;102103public enum Flag {104TABLE_HAS_CAPTION,105HAS_ELEMENT,106HAS_INLINE_TAG,107HAS_TEXT,108REPORTED_BAD_INLINE109}110111static class TagStackItem {112final DocTree tree; // typically, but not always, StartElementTree113final HtmlTag tag;114final Set<HtmlTag.Attr> attrs;115final Set<Flag> flags;116TagStackItem(DocTree tree, HtmlTag tag) {117this.tree = tree;118this.tag = tag;119attrs = EnumSet.noneOf(HtmlTag.Attr.class);120flags = EnumSet.noneOf(Flag.class);121}122@Override123public String toString() {124return String.valueOf(tag);125}126}127128private final Deque<TagStackItem> tagStack; // TODO: maybe want to record starting tree as well129private HtmlTag currHeaderTag;130131private final int implicitHeaderLevel;132133// <editor-fold defaultstate="collapsed" desc="Top level">134135Checker(Env env) {136env.getClass();137this.env = env;138tagStack = new LinkedList<>();139implicitHeaderLevel = env.implicitHeaderLevel;140}141142public Void scan(DocCommentTree tree, TreePath p) {143env.setCurrent(p, tree);144145boolean isOverridingMethod = !env.currOverriddenMethods.isEmpty();146147if (p.getLeaf() == p.getCompilationUnit()) {148// If p points to a compilation unit, the implied declaration is the149// package declaration (if any) for the compilation unit.150// Handle this case specially, because doc comments are only151// expected in package-info files.152JavaFileObject fo = p.getCompilationUnit().getSourceFile();153boolean isPkgInfo = fo.isNameCompatible("package-info", JavaFileObject.Kind.SOURCE);154if (tree == null) {155if (isPkgInfo)156reportMissing("dc.missing.comment");157return null;158} else {159if (!isPkgInfo)160reportReference("dc.unexpected.comment");161}162} else {163if (tree == null) {164if (!isSynthetic() && !isOverridingMethod)165reportMissing("dc.missing.comment");166return null;167}168}169170tagStack.clear();171currHeaderTag = null;172173foundParams.clear();174foundThrows.clear();175foundInheritDoc = false;176foundReturn = false;177178scan(new DocTreePath(p, tree), null);179180if (!isOverridingMethod) {181switch (env.currElement.getKind()) {182case METHOD:183case CONSTRUCTOR: {184ExecutableElement ee = (ExecutableElement) env.currElement;185checkParamsDocumented(ee.getTypeParameters());186checkParamsDocumented(ee.getParameters());187switch (ee.getReturnType().getKind()) {188case VOID:189case NONE:190break;191default:192if (!foundReturn193&& !foundInheritDoc194&& !env.types.isSameType(ee.getReturnType(), env.java_lang_Void)) {195reportMissing("dc.missing.return");196}197}198checkThrowsDocumented(ee.getThrownTypes());199}200}201}202203return null;204}205206private void reportMissing(String code, Object... args) {207env.messages.report(MISSING, Kind.WARNING, env.currPath.getLeaf(), code, args);208}209210private void reportReference(String code, Object... args) {211env.messages.report(REFERENCE, Kind.WARNING, env.currPath.getLeaf(), code, args);212}213214@Override215public Void visitDocComment(DocCommentTree tree, Void ignore) {216super.visitDocComment(tree, ignore);217for (TagStackItem tsi: tagStack) {218warnIfEmpty(tsi, null);219if (tsi.tree.getKind() == DocTree.Kind.START_ELEMENT220&& tsi.tag.endKind == HtmlTag.EndKind.REQUIRED) {221StartElementTree t = (StartElementTree) tsi.tree;222env.messages.error(HTML, t, "dc.tag.not.closed", t.getName());223}224}225return null;226}227// </editor-fold>228229// <editor-fold defaultstate="collapsed" desc="Text and entities.">230231@Override232public Void visitText(TextTree tree, Void ignore) {233if (hasNonWhitespace(tree)) {234checkAllowsText(tree);235markEnclosingTag(Flag.HAS_TEXT);236}237return null;238}239240@Override241public Void visitEntity(EntityTree tree, Void ignore) {242checkAllowsText(tree);243markEnclosingTag(Flag.HAS_TEXT);244String name = tree.getName().toString();245if (name.startsWith("#")) {246int v = StringUtils.toLowerCase(name).startsWith("#x")247? Integer.parseInt(name.substring(2), 16)248: Integer.parseInt(name.substring(1), 10);249if (!Entity.isValid(v)) {250env.messages.error(HTML, tree, "dc.entity.invalid", name);251}252} else if (!Entity.isValid(name)) {253env.messages.error(HTML, tree, "dc.entity.invalid", name);254}255return null;256}257258void checkAllowsText(DocTree tree) {259TagStackItem top = tagStack.peek();260if (top != null261&& top.tree.getKind() == DocTree.Kind.START_ELEMENT262&& !top.tag.acceptsText()) {263if (top.flags.add(Flag.REPORTED_BAD_INLINE)) {264env.messages.error(HTML, tree, "dc.text.not.allowed",265((StartElementTree) top.tree).getName());266}267}268}269270// </editor-fold>271272// <editor-fold defaultstate="collapsed" desc="HTML elements">273274@Override275public Void visitStartElement(StartElementTree tree, Void ignore) {276final Name treeName = tree.getName();277final HtmlTag t = HtmlTag.get(treeName);278if (t == null) {279env.messages.error(HTML, tree, "dc.tag.unknown", treeName);280} else {281boolean done = false;282for (TagStackItem tsi: tagStack) {283if (tsi.tag.accepts(t)) {284while (tagStack.peek() != tsi) {285warnIfEmpty(tagStack.peek(), null);286tagStack.pop();287}288done = true;289break;290} else if (tsi.tag.endKind != HtmlTag.EndKind.OPTIONAL) {291done = true;292break;293}294}295if (!done && HtmlTag.BODY.accepts(t)) {296while (!tagStack.isEmpty()) {297warnIfEmpty(tagStack.peek(), null);298tagStack.pop();299}300}301302markEnclosingTag(Flag.HAS_ELEMENT);303checkStructure(tree, t);304305// tag specific checks306switch (t) {307// check for out of sequence headers, such as <h1>...</h1> <h3>...</h3>308case H1: case H2: case H3: case H4: case H5: case H6:309checkHeader(tree, t);310break;311}312313if (t.flags.contains(HtmlTag.Flag.NO_NEST)) {314for (TagStackItem i: tagStack) {315if (t == i.tag) {316env.messages.warning(HTML, tree, "dc.tag.nested.not.allowed", treeName);317break;318}319}320}321}322323// check for self closing tags, such as <a id="name"/>324if (tree.isSelfClosing()) {325env.messages.error(HTML, tree, "dc.tag.self.closing", treeName);326}327328try {329TagStackItem parent = tagStack.peek();330TagStackItem top = new TagStackItem(tree, t);331tagStack.push(top);332333super.visitStartElement(tree, ignore);334335// handle attributes that may or may not have been found in start element336if (t != null) {337switch (t) {338case CAPTION:339if (parent != null && parent.tag == HtmlTag.TABLE)340parent.flags.add(Flag.TABLE_HAS_CAPTION);341break;342343case IMG:344if (!top.attrs.contains(HtmlTag.Attr.ALT))345env.messages.error(ACCESSIBILITY, tree, "dc.no.alt.attr.for.image");346break;347}348}349350return null;351} finally {352353if (t == null || t.endKind == HtmlTag.EndKind.NONE)354tagStack.pop();355}356}357358private void checkStructure(StartElementTree tree, HtmlTag t) {359Name treeName = tree.getName();360TagStackItem top = tagStack.peek();361switch (t.blockType) {362case BLOCK:363if (top == null || top.tag.accepts(t))364return;365366switch (top.tree.getKind()) {367case START_ELEMENT: {368if (top.tag.blockType == HtmlTag.BlockType.INLINE) {369Name name = ((StartElementTree) top.tree).getName();370env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.element",371treeName, name);372return;373}374}375break;376377case LINK:378case LINK_PLAIN: {379String name = top.tree.getKind().tagName;380env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.tag",381treeName, name);382return;383}384}385break;386387case INLINE:388if (top == null || top.tag.accepts(t))389return;390break;391392case LIST_ITEM:393case TABLE_ITEM:394if (top != null) {395// reset this flag so subsequent bad inline content gets reported396top.flags.remove(Flag.REPORTED_BAD_INLINE);397if (top.tag.accepts(t))398return;399}400break;401402case OTHER:403switch (t) {404case SCRIPT:405// <script> may or may not be allowed, depending on --allow-script-in-comments406// but we allow it here, and rely on a separate scanner to detect all uses407// of JavaScript, including <script> tags, and use in attributes, etc.408break;409410default:411env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName);412}413return;414}415416env.messages.error(HTML, tree, "dc.tag.not.allowed.here", treeName);417}418419private void checkHeader(StartElementTree tree, HtmlTag tag) {420// verify the new tag421if (getHeaderLevel(tag) > getHeaderLevel(currHeaderTag) + 1) {422if (currHeaderTag == null) {423env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.1", tag);424} else {425env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.2",426tag, currHeaderTag);427}428}429430currHeaderTag = tag;431}432433private int getHeaderLevel(HtmlTag tag) {434if (tag == null)435return implicitHeaderLevel;436switch (tag) {437case H1: return 1;438case H2: return 2;439case H3: return 3;440case H4: return 4;441case H5: return 5;442case H6: return 6;443default: throw new IllegalArgumentException();444}445}446447@Override448public Void visitEndElement(EndElementTree tree, Void ignore) {449final Name treeName = tree.getName();450final HtmlTag t = HtmlTag.get(treeName);451if (t == null) {452env.messages.error(HTML, tree, "dc.tag.unknown", treeName);453} else if (t.endKind == HtmlTag.EndKind.NONE) {454env.messages.error(HTML, tree, "dc.tag.end.not.permitted", treeName);455} else {456boolean done = false;457while (!tagStack.isEmpty()) {458TagStackItem top = tagStack.peek();459if (t == top.tag) {460switch (t) {461case TABLE:462if (!top.attrs.contains(HtmlTag.Attr.SUMMARY)463&& !top.flags.contains(Flag.TABLE_HAS_CAPTION)) {464env.messages.error(ACCESSIBILITY, tree,465"dc.no.summary.or.caption.for.table");466}467}468warnIfEmpty(top, tree);469tagStack.pop();470done = true;471break;472} else if (top.tag == null || top.tag.endKind != HtmlTag.EndKind.REQUIRED) {473tagStack.pop();474} else {475boolean found = false;476for (TagStackItem si: tagStack) {477if (si.tag == t) {478found = true;479break;480}481}482if (found && top.tree.getKind() == DocTree.Kind.START_ELEMENT) {483env.messages.error(HTML, top.tree, "dc.tag.start.unmatched",484((StartElementTree) top.tree).getName());485tagStack.pop();486} else {487env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName);488done = true;489break;490}491}492}493494if (!done && tagStack.isEmpty()) {495env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName);496}497}498499return super.visitEndElement(tree, ignore);500}501502void warnIfEmpty(TagStackItem tsi, DocTree endTree) {503if (tsi.tag != null && tsi.tree instanceof StartElementTree) {504if (tsi.tag.flags.contains(HtmlTag.Flag.EXPECT_CONTENT)505&& !tsi.flags.contains(Flag.HAS_TEXT)506&& !tsi.flags.contains(Flag.HAS_ELEMENT)507&& !tsi.flags.contains(Flag.HAS_INLINE_TAG)) {508DocTree tree = (endTree != null) ? endTree : tsi.tree;509Name treeName = ((StartElementTree) tsi.tree).getName();510env.messages.warning(HTML, tree, "dc.tag.empty", treeName);511}512}513}514515// </editor-fold>516517// <editor-fold defaultstate="collapsed" desc="HTML attributes">518519@Override @SuppressWarnings("fallthrough")520public Void visitAttribute(AttributeTree tree, Void ignore) {521HtmlTag currTag = tagStack.peek().tag;522if (currTag != null) {523Name name = tree.getName();524HtmlTag.Attr attr = currTag.getAttr(name);525if (attr != null) {526boolean first = tagStack.peek().attrs.add(attr);527if (!first)528env.messages.error(HTML, tree, "dc.attr.repeated", name);529}530531// for now, doclint allows all attribute names beginning with "on" as event handler names,532// without checking the validity or applicability of the name533if (!name.toString().startsWith("on")) {534AttrKind k = currTag.getAttrKind(name);535switch (k) {536case OK:537break;538539case INVALID:540env.messages.error(HTML, tree, "dc.attr.unknown", name);541break;542543case OBSOLETE:544env.messages.warning(ACCESSIBILITY, tree, "dc.attr.obsolete", name);545break;546547case USE_CSS:548env.messages.warning(ACCESSIBILITY, tree, "dc.attr.obsolete.use.css", name);549break;550}551}552553if (attr != null) {554switch (attr) {555case NAME:556if (currTag != HtmlTag.A) {557break;558}559// fallthrough560case ID:561String value = getAttrValue(tree);562if (value == null) {563env.messages.error(HTML, tree, "dc.anchor.value.missing");564} else {565if (!validName.matcher(value).matches()) {566env.messages.error(HTML, tree, "dc.invalid.anchor", value);567}568if (!checkAnchor(value)) {569env.messages.error(HTML, tree, "dc.anchor.already.defined", value);570}571}572break;573574case HREF:575if (currTag == HtmlTag.A) {576String v = getAttrValue(tree);577if (v == null || v.isEmpty()) {578env.messages.error(HTML, tree, "dc.attr.lacks.value");579} else {580Matcher m = docRoot.matcher(v);581if (m.matches()) {582String rest = m.group(2);583if (!rest.isEmpty())584checkURI(tree, rest);585} else {586checkURI(tree, v);587}588}589}590break;591592case VALUE:593if (currTag == HtmlTag.LI) {594String v = getAttrValue(tree);595if (v == null || v.isEmpty()) {596env.messages.error(HTML, tree, "dc.attr.lacks.value");597} else if (!validNumber.matcher(v).matches()) {598env.messages.error(HTML, tree, "dc.attr.not.number");599}600}601break;602}603}604}605606// TODO: basic check on value607608return super.visitAttribute(tree, ignore);609}610611private boolean checkAnchor(String name) {612Element e = getEnclosingPackageOrClass(env.currElement);613if (e == null)614return true;615Set<String> set = foundAnchors.get(e);616if (set == null)617foundAnchors.put(e, set = new HashSet<>());618return set.add(name);619}620621private Element getEnclosingPackageOrClass(Element e) {622while (e != null) {623switch (e.getKind()) {624case CLASS:625case ENUM:626case INTERFACE:627case PACKAGE:628return e;629default:630e = e.getEnclosingElement();631}632}633return e;634}635636// http://www.w3.org/TR/html401/types.html#type-name637private static final Pattern validName = Pattern.compile("[A-Za-z][A-Za-z0-9-_:.]*");638639private static final Pattern validNumber = Pattern.compile("-?[0-9]+");640641// pattern to remove leading {@docRoot}/?642private static final Pattern docRoot = Pattern.compile("(?i)(\\{@docRoot *\\}/?)?(.*)");643644private String getAttrValue(AttributeTree tree) {645if (tree.getValue() == null)646return null;647648StringWriter sw = new StringWriter();649try {650new DocPretty(sw).print(tree.getValue());651} catch (IOException e) {652// cannot happen653}654// ignore potential use of entities for now655return sw.toString();656}657658private void checkURI(AttributeTree tree, String uri) {659// allow URIs beginning with javascript:, which would otherwise be rejected by the URI API.660if (uri.startsWith("javascript:"))661return;662try {663URI u = new URI(uri);664} catch (URISyntaxException e) {665env.messages.error(HTML, tree, "dc.invalid.uri", uri);666}667}668// </editor-fold>669670// <editor-fold defaultstate="collapsed" desc="javadoc tags">671672@Override673public Void visitAuthor(AuthorTree tree, Void ignore) {674warnIfEmpty(tree, tree.getName());675return super.visitAuthor(tree, ignore);676}677678@Override679public Void visitDocRoot(DocRootTree tree, Void ignore) {680markEnclosingTag(Flag.HAS_INLINE_TAG);681return super.visitDocRoot(tree, ignore);682}683684@Override685public Void visitInheritDoc(InheritDocTree tree, Void ignore) {686markEnclosingTag(Flag.HAS_INLINE_TAG);687// TODO: verify on overridden method688foundInheritDoc = true;689return super.visitInheritDoc(tree, ignore);690}691692@Override693public Void visitLink(LinkTree tree, Void ignore) {694markEnclosingTag(Flag.HAS_INLINE_TAG);695// simulate inline context on tag stack696HtmlTag t = (tree.getKind() == DocTree.Kind.LINK)697? HtmlTag.CODE : HtmlTag.SPAN;698tagStack.push(new TagStackItem(tree, t));699try {700return super.visitLink(tree, ignore);701} finally {702tagStack.pop();703}704}705706@Override707public Void visitLiteral(LiteralTree tree, Void ignore) {708markEnclosingTag(Flag.HAS_INLINE_TAG);709if (tree.getKind() == DocTree.Kind.CODE) {710for (TagStackItem tsi: tagStack) {711if (tsi.tag == HtmlTag.CODE) {712env.messages.warning(HTML, tree, "dc.tag.code.within.code");713break;714}715}716}717return super.visitLiteral(tree, ignore);718}719720@Override721@SuppressWarnings("fallthrough")722public Void visitParam(ParamTree tree, Void ignore) {723boolean typaram = tree.isTypeParameter();724IdentifierTree nameTree = tree.getName();725Element paramElement = nameTree != null ? env.trees.getElement(new DocTreePath(getCurrentPath(), nameTree)) : null;726727if (paramElement == null) {728switch (env.currElement.getKind()) {729case CLASS: case INTERFACE: {730if (!typaram) {731env.messages.error(REFERENCE, tree, "dc.invalid.param");732break;733}734}735case METHOD: case CONSTRUCTOR: {736env.messages.error(REFERENCE, nameTree, "dc.param.name.not.found");737break;738}739740default:741env.messages.error(REFERENCE, tree, "dc.invalid.param");742break;743}744} else {745foundParams.add(paramElement);746}747748warnIfEmpty(tree, tree.getDescription());749return super.visitParam(tree, ignore);750}751752private void checkParamsDocumented(List<? extends Element> list) {753if (foundInheritDoc)754return;755756for (Element e: list) {757if (!foundParams.contains(e)) {758CharSequence paramName = (e.getKind() == ElementKind.TYPE_PARAMETER)759? "<" + e.getSimpleName() + ">"760: e.getSimpleName();761reportMissing("dc.missing.param", paramName);762}763}764}765766@Override767public Void visitReference(ReferenceTree tree, Void ignore) {768String sig = tree.getSignature();769if (sig.contains("<") || sig.contains(">"))770env.messages.error(REFERENCE, tree, "dc.type.arg.not.allowed");771772Element e = env.trees.getElement(getCurrentPath());773if (e == null)774env.messages.error(REFERENCE, tree, "dc.ref.not.found");775return super.visitReference(tree, ignore);776}777778@Override779public Void visitReturn(ReturnTree tree, Void ignore) {780Element e = env.trees.getElement(env.currPath);781if (e.getKind() != ElementKind.METHOD782|| ((ExecutableElement) e).getReturnType().getKind() == TypeKind.VOID)783env.messages.error(REFERENCE, tree, "dc.invalid.return");784foundReturn = true;785warnIfEmpty(tree, tree.getDescription());786return super.visitReturn(tree, ignore);787}788789@Override790public Void visitSerialData(SerialDataTree tree, Void ignore) {791warnIfEmpty(tree, tree.getDescription());792return super.visitSerialData(tree, ignore);793}794795@Override796public Void visitSerialField(SerialFieldTree tree, Void ignore) {797warnIfEmpty(tree, tree.getDescription());798return super.visitSerialField(tree, ignore);799}800801@Override802public Void visitSince(SinceTree tree, Void ignore) {803warnIfEmpty(tree, tree.getBody());804return super.visitSince(tree, ignore);805}806807@Override808public Void visitThrows(ThrowsTree tree, Void ignore) {809ReferenceTree exName = tree.getExceptionName();810Element ex = env.trees.getElement(new DocTreePath(getCurrentPath(), exName));811if (ex == null) {812env.messages.error(REFERENCE, tree, "dc.ref.not.found");813} else if (isThrowable(ex.asType())) {814switch (env.currElement.getKind()) {815case CONSTRUCTOR:816case METHOD:817if (isCheckedException(ex.asType())) {818ExecutableElement ee = (ExecutableElement) env.currElement;819checkThrowsDeclared(exName, ex.asType(), ee.getThrownTypes());820}821break;822default:823env.messages.error(REFERENCE, tree, "dc.invalid.throws");824}825} else {826env.messages.error(REFERENCE, tree, "dc.invalid.throws");827}828warnIfEmpty(tree, tree.getDescription());829return scan(tree.getDescription(), ignore);830}831832private boolean isThrowable(TypeMirror tm) {833switch (tm.getKind()) {834case DECLARED:835case TYPEVAR:836return env.types.isAssignable(tm, env.java_lang_Throwable);837}838return false;839}840841private void checkThrowsDeclared(ReferenceTree tree, TypeMirror t, List<? extends TypeMirror> list) {842boolean found = false;843for (TypeMirror tl : list) {844if (env.types.isAssignable(t, tl)) {845foundThrows.add(tl);846found = true;847}848}849if (!found)850env.messages.error(REFERENCE, tree, "dc.exception.not.thrown", t);851}852853private void checkThrowsDocumented(List<? extends TypeMirror> list) {854if (foundInheritDoc)855return;856857for (TypeMirror tl: list) {858if (isCheckedException(tl) && !foundThrows.contains(tl))859reportMissing("dc.missing.throws", tl);860}861}862863@Override864public Void visitUnknownBlockTag(UnknownBlockTagTree tree, Void ignore) {865checkUnknownTag(tree, tree.getTagName());866return super.visitUnknownBlockTag(tree, ignore);867}868869@Override870public Void visitUnknownInlineTag(UnknownInlineTagTree tree, Void ignore) {871checkUnknownTag(tree, tree.getTagName());872return super.visitUnknownInlineTag(tree, ignore);873}874875private void checkUnknownTag(DocTree tree, String tagName) {876if (env.customTags != null && !env.customTags.contains(tagName))877env.messages.error(SYNTAX, tree, "dc.tag.unknown", tagName);878}879880@Override881public Void visitValue(ValueTree tree, Void ignore) {882ReferenceTree ref = tree.getReference();883if (ref == null || ref.getSignature().isEmpty()) {884if (!isConstant(env.currElement))885env.messages.error(REFERENCE, tree, "dc.value.not.allowed.here");886} else {887Element e = env.trees.getElement(new DocTreePath(getCurrentPath(), ref));888if (!isConstant(e))889env.messages.error(REFERENCE, tree, "dc.value.not.a.constant");890}891892markEnclosingTag(Flag.HAS_INLINE_TAG);893return super.visitValue(tree, ignore);894}895896private boolean isConstant(Element e) {897if (e == null)898return false;899900switch (e.getKind()) {901case FIELD:902Object value = ((VariableElement) e).getConstantValue();903return (value != null); // can't distinguish "not a constant" from "constant is null"904default:905return false;906}907}908909@Override910public Void visitVersion(VersionTree tree, Void ignore) {911warnIfEmpty(tree, tree.getBody());912return super.visitVersion(tree, ignore);913}914915@Override916public Void visitErroneous(ErroneousTree tree, Void ignore) {917env.messages.error(SYNTAX, tree, null, tree.getDiagnostic().getMessage(null));918return null;919}920// </editor-fold>921922// <editor-fold defaultstate="collapsed" desc="Utility methods">923924private boolean isCheckedException(TypeMirror t) {925return !(env.types.isAssignable(t, env.java_lang_Error)926|| env.types.isAssignable(t, env.java_lang_RuntimeException));927}928929private boolean isSynthetic() {930switch (env.currElement.getKind()) {931case CONSTRUCTOR:932// A synthetic default constructor has the same pos as the933// enclosing class934TreePath p = env.currPath;935return env.getPos(p) == env.getPos(p.getParentPath());936}937return false;938}939940void markEnclosingTag(Flag flag) {941TagStackItem top = tagStack.peek();942if (top != null)943top.flags.add(flag);944}945946String toString(TreePath p) {947StringBuilder sb = new StringBuilder("TreePath[");948toString(p, sb);949sb.append("]");950return sb.toString();951}952953void toString(TreePath p, StringBuilder sb) {954TreePath parent = p.getParentPath();955if (parent != null) {956toString(parent, sb);957sb.append(",");958}959sb.append(p.getLeaf().getKind()).append(":").append(env.getPos(p)).append(":S").append(env.getStartPos(p));960}961962void warnIfEmpty(DocTree tree, List<? extends DocTree> list) {963for (DocTree d: list) {964switch (d.getKind()) {965case TEXT:966if (hasNonWhitespace((TextTree) d))967return;968break;969default:970return;971}972}973env.messages.warning(SYNTAX, tree, "dc.empty", tree.getKind().tagName);974}975976boolean hasNonWhitespace(TextTree tree) {977String s = tree.getBody();978for (int i = 0; i < s.length(); i++) {979if (!Character.isWhitespace(s.charAt(i)))980return true;981}982return false;983}984985// </editor-fold>986987}988989990