Path: blob/master/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint/Checker.java
41161 views
/*1* Copyright (c) 2012, 2021, 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 jdk.javadoc.internal.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.Objects;39import java.util.Set;40import java.util.regex.Matcher;41import java.util.regex.Pattern;4243import javax.lang.model.element.Element;44import javax.lang.model.element.ElementKind;45import javax.lang.model.element.ExecutableElement;46import javax.lang.model.element.Name;47import javax.lang.model.element.VariableElement;48import javax.lang.model.type.TypeKind;49import javax.lang.model.type.TypeMirror;50import javax.tools.Diagnostic.Kind;51import javax.tools.JavaFileObject;5253import com.sun.source.doctree.AttributeTree;54import com.sun.source.doctree.AuthorTree;55import com.sun.source.doctree.DocCommentTree;56import com.sun.source.doctree.DocRootTree;57import com.sun.source.doctree.DocTree;58import com.sun.source.doctree.EndElementTree;59import com.sun.source.doctree.EntityTree;60import com.sun.source.doctree.ErroneousTree;61import com.sun.source.doctree.IdentifierTree;62import com.sun.source.doctree.IndexTree;63import com.sun.source.doctree.InheritDocTree;64import com.sun.source.doctree.LinkTree;65import com.sun.source.doctree.LiteralTree;66import com.sun.source.doctree.ParamTree;67import com.sun.source.doctree.ProvidesTree;68import com.sun.source.doctree.ReferenceTree;69import com.sun.source.doctree.ReturnTree;70import com.sun.source.doctree.SerialDataTree;71import com.sun.source.doctree.SerialFieldTree;72import com.sun.source.doctree.SinceTree;73import com.sun.source.doctree.StartElementTree;74import com.sun.source.doctree.SummaryTree;75import com.sun.source.doctree.SystemPropertyTree;76import com.sun.source.doctree.TextTree;77import com.sun.source.doctree.ThrowsTree;78import com.sun.source.doctree.UnknownBlockTagTree;79import com.sun.source.doctree.UnknownInlineTagTree;80import com.sun.source.doctree.UsesTree;81import com.sun.source.doctree.ValueTree;82import com.sun.source.doctree.VersionTree;83import com.sun.source.tree.Tree;84import com.sun.source.util.DocTreePath;85import com.sun.source.util.DocTreePathScanner;86import com.sun.source.util.TreePath;87import com.sun.tools.javac.tree.DocPretty;88import com.sun.tools.javac.util.Assert;89import com.sun.tools.javac.util.DefinedBy;90import com.sun.tools.javac.util.DefinedBy.Api;9192import jdk.javadoc.internal.doclint.HtmlTag.AttrKind;93import jdk.javadoc.internal.doclint.HtmlTag.ElemKind;94import static jdk.javadoc.internal.doclint.Messages.Group.*;959697/**98* Validate a doc comment.99*100* <p><b>This is NOT part of any supported API.101* If you write code that depends on this, you do so at your own102* risk. This code and its internal interfaces are subject to change103* or deletion without notice.</b></p>104*/105public class Checker extends DocTreePathScanner<Void, Void> {106final Env env;107108Set<Element> foundParams = new HashSet<>();109Set<TypeMirror> foundThrows = new HashSet<>();110Map<Element, Set<String>> foundAnchors = new HashMap<>();111boolean foundInheritDoc = false;112boolean foundReturn = false;113boolean hasNonWhitespaceText = false;114115public enum Flag {116TABLE_HAS_CAPTION,117TABLE_IS_PRESENTATION,118HAS_ELEMENT,119HAS_HEADING,120HAS_INLINE_TAG,121HAS_TEXT,122REPORTED_BAD_INLINE123}124125static class TagStackItem {126final DocTree tree; // typically, but not always, StartElementTree127final HtmlTag tag;128final Set<HtmlTag.Attr> attrs;129final Set<Flag> flags;130TagStackItem(DocTree tree, HtmlTag tag) {131this.tree = tree;132this.tag = tag;133attrs = EnumSet.noneOf(HtmlTag.Attr.class);134flags = EnumSet.noneOf(Flag.class);135}136@Override137public String toString() {138return String.valueOf(tag);139}140}141142private final Deque<TagStackItem> tagStack; // TODO: maybe want to record starting tree as well143private HtmlTag currHeadingTag;144145private int implicitHeadingRank;146private boolean inIndex;147private boolean inLink;148private boolean inSummary;149150// <editor-fold defaultstate="collapsed" desc="Top level">151152Checker(Env env) {153this.env = Assert.checkNonNull(env);154tagStack = new LinkedList<>();155}156157public Void scan(DocCommentTree tree, TreePath p) {158env.initTypes();159env.setCurrent(p, tree);160161boolean isOverridingMethod = !env.currOverriddenMethods.isEmpty();162JavaFileObject fo = p.getCompilationUnit().getSourceFile();163164if (p.getLeaf().getKind() == Tree.Kind.PACKAGE) {165// If p points to a package, the implied declaration is the166// package declaration (if any) for the compilation unit.167// Handle this case specially, because doc comments are only168// expected in package-info files.169boolean isPkgInfo = fo.isNameCompatible("package-info", JavaFileObject.Kind.SOURCE);170if (tree == null) {171if (isPkgInfo)172reportMissing("dc.missing.comment");173return null;174} else {175if (!isPkgInfo)176reportReference("dc.unexpected.comment");177}178} else if (tree != null && fo.isNameCompatible("package", JavaFileObject.Kind.HTML)) {179// a package.html file with a DocCommentTree180if (tree.getFullBody().isEmpty()) {181reportMissing("dc.missing.comment");182return null;183}184} else {185if (tree == null) {186if (!isSynthetic() && !isOverridingMethod)187reportMissing("dc.missing.comment");188return null;189}190}191192tagStack.clear();193currHeadingTag = null;194195foundParams.clear();196foundThrows.clear();197foundInheritDoc = false;198foundReturn = false;199hasNonWhitespaceText = false;200201switch (p.getLeaf().getKind()) {202// the following are for declarations that have their own top-level page,203// and so the doc comment comes after the <h1> page title.204case MODULE:205case PACKAGE:206case CLASS:207case INTERFACE:208case ENUM:209case ANNOTATION_TYPE:210case RECORD:211implicitHeadingRank = 1;212break;213214// this is for html files215// ... if it is a legacy package.html, the doc comment comes after the <h1> page title216// ... otherwise, (e.g. overview file and doc-files/*.html files) no additional headings are inserted217case COMPILATION_UNIT:218implicitHeadingRank = fo.isNameCompatible("package", JavaFileObject.Kind.HTML) ? 1 : 0;219break;220221// the following are for member declarations, which appear in the page222// for the enclosing type, and so appear after the <h2> "Members"223// aggregate heading and the specific <h3> "Member signature" heading.224case METHOD:225case VARIABLE:226implicitHeadingRank = 3;227break;228229default:230Assert.error("unexpected tree kind: " + p.getLeaf().getKind() + " " + fo);231}232233scan(new DocTreePath(p, tree), null);234235if (!isOverridingMethod) {236switch (env.currElement.getKind()) {237case METHOD:238case CONSTRUCTOR: {239ExecutableElement ee = (ExecutableElement) env.currElement;240checkParamsDocumented(ee.getTypeParameters());241checkParamsDocumented(ee.getParameters());242switch (ee.getReturnType().getKind()) {243case VOID:244case NONE:245break;246default:247if (!foundReturn248&& !foundInheritDoc249&& !env.types.isSameType(ee.getReturnType(), env.java_lang_Void)) {250reportMissing("dc.missing.return");251}252}253checkThrowsDocumented(ee.getThrownTypes());254}255}256}257258return null;259}260261private void reportMissing(String code, Object... args) {262env.messages.report(MISSING, Kind.WARNING, env.currPath.getLeaf(), code, args);263}264265private void reportReference(String code, Object... args) {266env.messages.report(REFERENCE, Kind.WARNING, env.currPath.getLeaf(), code, args);267}268269@Override @DefinedBy(Api.COMPILER_TREE)270public Void visitDocComment(DocCommentTree tree, Void ignore) {271scan(tree.getFirstSentence(), ignore);272scan(tree.getBody(), ignore);273checkTagStack();274275for (DocTree blockTag : tree.getBlockTags()) {276tagStack.clear();277scan(blockTag, ignore);278checkTagStack();279}280281return null;282}283284private void checkTagStack() {285for (TagStackItem tsi: tagStack) {286warnIfEmpty(tsi, null);287if (tsi.tree.getKind() == DocTree.Kind.START_ELEMENT288&& tsi.tag.endKind == HtmlTag.EndKind.REQUIRED) {289StartElementTree t = (StartElementTree) tsi.tree;290env.messages.error(HTML, t, "dc.tag.not.closed", t.getName());291}292}293}294// </editor-fold>295296// <editor-fold defaultstate="collapsed" desc="Text and entities.">297298@Override @DefinedBy(Api.COMPILER_TREE)299public Void visitText(TextTree tree, Void ignore) {300hasNonWhitespaceText = hasNonWhitespace(tree);301if (hasNonWhitespaceText) {302checkAllowsText(tree);303markEnclosingTag(Flag.HAS_TEXT);304}305return null;306}307308@Override @DefinedBy(Api.COMPILER_TREE)309public Void visitEntity(EntityTree tree, Void ignore) {310checkAllowsText(tree);311markEnclosingTag(Flag.HAS_TEXT);312String s = env.trees.getCharacters(tree);313if (s == null) {314env.messages.error(HTML, tree, "dc.entity.invalid", tree.getName());315}316return null;317318}319320void checkAllowsText(DocTree tree) {321TagStackItem top = tagStack.peek();322if (top != null323&& top.tree.getKind() == DocTree.Kind.START_ELEMENT324&& !top.tag.acceptsText()) {325if (top.flags.add(Flag.REPORTED_BAD_INLINE)) {326env.messages.error(HTML, tree, "dc.text.not.allowed",327((StartElementTree) top.tree).getName());328}329}330}331332// </editor-fold>333334// <editor-fold defaultstate="collapsed" desc="HTML elements">335336@Override @DefinedBy(Api.COMPILER_TREE)337public Void visitStartElement(StartElementTree tree, Void ignore) {338final Name treeName = tree.getName();339final HtmlTag t = HtmlTag.get(treeName);340if (t == null) {341env.messages.error(HTML, tree, "dc.tag.unknown", treeName);342} else if (t.elemKind == ElemKind.HTML4) {343env.messages.error(HTML, tree, "dc.tag.not.supported.html5", treeName);344} else {345boolean done = false;346for (TagStackItem tsi: tagStack) {347if (tsi.tag.accepts(t)) {348while (tagStack.peek() != tsi) {349warnIfEmpty(tagStack.peek(), null);350tagStack.pop();351}352done = true;353break;354} else if (tsi.tag.endKind != HtmlTag.EndKind.OPTIONAL) {355done = true;356break;357}358}359if (!done && HtmlTag.BODY.accepts(t)) {360while (!tagStack.isEmpty()) {361warnIfEmpty(tagStack.peek(), null);362tagStack.pop();363}364}365366markEnclosingTag(Flag.HAS_ELEMENT);367checkStructure(tree, t);368369// tag specific checks370switch (t) {371// check for out of sequence headings, such as <h1>...</h1> <h3>...</h3>372case H1: case H2: case H3: case H4: case H5: case H6:373checkHeading(tree, t);374break;375}376377if (t.flags.contains(HtmlTag.Flag.NO_NEST)) {378for (TagStackItem i: tagStack) {379if (t == i.tag) {380env.messages.warning(HTML, tree, "dc.tag.nested.not.allowed", treeName);381break;382}383}384}385}386387// check for self closing tags, such as <a id="name"/>388if (tree.isSelfClosing() && !isSelfClosingAllowed(t)) {389env.messages.error(HTML, tree, "dc.tag.self.closing", treeName);390}391392try {393TagStackItem parent = tagStack.peek();394TagStackItem top = new TagStackItem(tree, t);395tagStack.push(top);396397super.visitStartElement(tree, ignore);398399// handle attributes that may or may not have been found in start element400if (t != null) {401switch (t) {402case CAPTION:403if (parent != null && parent.tag == HtmlTag.TABLE)404parent.flags.add(Flag.TABLE_HAS_CAPTION);405break;406407case H1: case H2: case H3: case H4: case H5: case H6:408if (parent != null && (parent.tag == HtmlTag.SECTION || parent.tag == HtmlTag.ARTICLE)) {409parent.flags.add(Flag.HAS_HEADING);410}411break;412413case IMG:414if (!top.attrs.contains(HtmlTag.Attr.ALT))415env.messages.error(ACCESSIBILITY, tree, "dc.no.alt.attr.for.image");416break;417}418}419420return null;421} finally {422423if (t == null || t.endKind == HtmlTag.EndKind.NONE)424tagStack.pop();425}426}427428// so-called "self-closing" tags are only permitted in HTML 5, for void elements429// https://html.spec.whatwg.org/multipage/syntax.html#start-tags430private boolean isSelfClosingAllowed(HtmlTag tag) {431return tag.endKind == HtmlTag.EndKind.NONE;432}433434private void checkStructure(StartElementTree tree, HtmlTag t) {435Name treeName = tree.getName();436TagStackItem top = tagStack.peek();437switch (t.blockType) {438case BLOCK:439if (top == null || top.tag.accepts(t))440return;441442switch (top.tree.getKind()) {443case START_ELEMENT: {444if (top.tag.blockType == HtmlTag.BlockType.INLINE) {445Name name = ((StartElementTree) top.tree).getName();446env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.element",447treeName, name);448return;449}450}451break;452453case LINK:454case LINK_PLAIN: {455String name = top.tree.getKind().tagName;456env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.tag",457treeName, name);458return;459}460}461break;462463case INLINE:464if (top == null || top.tag.accepts(t))465return;466break;467468case LIST_ITEM:469case TABLE_ITEM:470if (top != null) {471// reset this flag so subsequent bad inline content gets reported472top.flags.remove(Flag.REPORTED_BAD_INLINE);473if (top.tag.accepts(t))474return;475}476break;477478case OTHER:479switch (t) {480case SCRIPT:481// <script> may or may not be allowed, depending on --allow-script-in-comments482// but we allow it here, and rely on a separate scanner to detect all uses483// of JavaScript, including <script> tags, and use in attributes, etc.484break;485486default:487env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName);488}489return;490}491492env.messages.error(HTML, tree, "dc.tag.not.allowed.here", treeName);493}494495private void checkHeading(StartElementTree tree, HtmlTag tag) {496// verify the new tag497if (getHeadingRank(tag) > getHeadingRank(currHeadingTag) + 1) {498if (currHeadingTag == null) {499env.messages.error(ACCESSIBILITY, tree, "dc.tag.heading.sequence.1",500tag, implicitHeadingRank);501} else {502env.messages.error(ACCESSIBILITY, tree, "dc.tag.heading.sequence.2",503tag, currHeadingTag);504}505} else if (getHeadingRank(tag) <= implicitHeadingRank) {506env.messages.error(ACCESSIBILITY, tree, "dc.tag.heading.sequence.3",507tag, implicitHeadingRank);508}509510currHeadingTag = tag;511}512513private int getHeadingRank(HtmlTag tag) {514if (tag == null)515return implicitHeadingRank;516switch (tag) {517case H1: return 1;518case H2: return 2;519case H3: return 3;520case H4: return 4;521case H5: return 5;522case H6: return 6;523default: throw new IllegalArgumentException();524}525}526527@Override @DefinedBy(Api.COMPILER_TREE)528public Void visitEndElement(EndElementTree tree, Void ignore) {529final Name treeName = tree.getName();530final HtmlTag t = HtmlTag.get(treeName);531if (t == null) {532env.messages.error(HTML, tree, "dc.tag.unknown", treeName);533} else if (t.endKind == HtmlTag.EndKind.NONE) {534env.messages.error(HTML, tree, "dc.tag.end.not.permitted", treeName);535} else {536boolean done = false;537while (!tagStack.isEmpty()) {538TagStackItem top = tagStack.peek();539if (t == top.tag) {540switch (t) {541case TABLE:542if (!top.flags.contains(Flag.TABLE_IS_PRESENTATION)543&& !top.attrs.contains(HtmlTag.Attr.SUMMARY)544&& !top.flags.contains(Flag.TABLE_HAS_CAPTION)) {545env.messages.error(ACCESSIBILITY, tree,546"dc.no.summary.or.caption.for.table");547}548break;549550case SECTION:551case ARTICLE:552if (!top.flags.contains(Flag.HAS_HEADING)) {553env.messages.error(HTML, tree, "dc.tag.requires.heading", treeName);554}555break;556}557warnIfEmpty(top, tree);558tagStack.pop();559done = true;560break;561} else if (top.tag == null || top.tag.endKind != HtmlTag.EndKind.REQUIRED) {562warnIfEmpty(top, null);563tagStack.pop();564} else {565boolean found = false;566for (TagStackItem si: tagStack) {567if (si.tag == t) {568found = true;569break;570}571}572if (found && top.tree.getKind() == DocTree.Kind.START_ELEMENT) {573env.messages.error(HTML, top.tree, "dc.tag.start.unmatched",574((StartElementTree) top.tree).getName());575tagStack.pop();576} else {577env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName);578done = true;579break;580}581}582}583584if (!done && tagStack.isEmpty()) {585env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName);586}587}588589return super.visitEndElement(tree, ignore);590}591592void warnIfEmpty(TagStackItem tsi, DocTree endTree) {593if (tsi.tag != null && tsi.tree instanceof StartElementTree startTree) {594if (tsi.tag.flags.contains(HtmlTag.Flag.EXPECT_CONTENT)595&& !tsi.flags.contains(Flag.HAS_TEXT)596&& !tsi.flags.contains(Flag.HAS_ELEMENT)597&& !tsi.flags.contains(Flag.HAS_INLINE_TAG)598&& !(tsi.tag.elemKind == ElemKind.HTML4)) {599DocTree tree = (endTree != null) ? endTree : startTree;600Name treeName = startTree.getName();601env.messages.warning(HTML, tree, "dc.tag.empty", treeName);602}603}604}605606// </editor-fold>607608// <editor-fold defaultstate="collapsed" desc="HTML attributes">609610@Override @DefinedBy(Api.COMPILER_TREE) @SuppressWarnings("fallthrough")611public Void visitAttribute(AttributeTree tree, Void ignore) {612HtmlTag currTag = tagStack.peek().tag;613if (currTag != null && currTag.elemKind != ElemKind.HTML4) {614Name name = tree.getName();615HtmlTag.Attr attr = currTag.getAttr(name);616if (attr != null) {617boolean first = tagStack.peek().attrs.add(attr);618if (!first)619env.messages.error(HTML, tree, "dc.attr.repeated", name);620}621// for now, doclint allows all attribute names beginning with "on" as event handler names,622// without checking the validity or applicability of the name623if (!name.toString().startsWith("on")) {624AttrKind k = currTag.getAttrKind(name);625switch (k) {626case OK:627break;628case OBSOLETE:629env.messages.warning(HTML, tree, "dc.attr.obsolete", name);630break;631case HTML4:632env.messages.error(HTML, tree, "dc.attr.not.supported.html5", name);633break;634case INVALID:635env.messages.error(HTML, tree, "dc.attr.unknown", name);636break;637}638}639640if (attr != null) {641switch (attr) {642case ID:643String value = getAttrValue(tree);644if (value == null) {645env.messages.error(HTML, tree, "dc.anchor.value.missing");646} else {647if (!validId.matcher(value).matches()) {648env.messages.error(HTML, tree, "dc.invalid.anchor", value);649}650if (!checkAnchor(value)) {651env.messages.error(HTML, tree, "dc.anchor.already.defined", value);652}653}654break;655656case HREF:657if (currTag == HtmlTag.A) {658String v = getAttrValue(tree);659if (v == null || v.isEmpty()) {660env.messages.error(HTML, tree, "dc.attr.lacks.value");661} else {662Matcher m = docRoot.matcher(v);663if (m.matches()) {664String rest = m.group(2);665if (!rest.isEmpty())666checkURI(tree, rest);667} else {668checkURI(tree, v);669}670}671}672break;673674case VALUE:675if (currTag == HtmlTag.LI) {676String v = getAttrValue(tree);677if (v == null || v.isEmpty()) {678env.messages.error(HTML, tree, "dc.attr.lacks.value");679} else if (!validNumber.matcher(v).matches()) {680env.messages.error(HTML, tree, "dc.attr.not.number");681}682}683break;684685case BORDER:686if (currTag == HtmlTag.TABLE) {687String v = getAttrValue(tree);688try {689if (v == null || (!v.isEmpty() && Integer.parseInt(v) != 1)) {690env.messages.error(HTML, tree, "dc.attr.table.border.not.valid", attr);691}692} catch (NumberFormatException ex) {693env.messages.error(HTML, tree, "dc.attr.table.border.not.number", attr);694}695} else if (currTag == HtmlTag.IMG) {696String v = getAttrValue(tree);697try {698if (v == null || (!v.isEmpty() && Integer.parseInt(v) != 0)) {699env.messages.error(HTML, tree, "dc.attr.img.border.not.valid", attr);700}701} catch (NumberFormatException ex) {702env.messages.error(HTML, tree, "dc.attr.img.border.not.number", attr);703}704}705break;706707case ROLE:708if (currTag == HtmlTag.TABLE) {709String v = getAttrValue(tree);710if (Objects.equals(v, "presentation")) {711tagStack.peek().flags.add(Flag.TABLE_IS_PRESENTATION);712}713}714break;715}716}717}718719// TODO: basic check on value720721return null;722}723724725private boolean checkAnchor(String name) {726Element e = getEnclosingPackageOrClass(env.currElement);727if (e == null)728return true;729Set<String> set = foundAnchors.get(e);730if (set == null)731foundAnchors.put(e, set = new HashSet<>());732return set.add(name);733}734735private Element getEnclosingPackageOrClass(Element e) {736while (e != null) {737switch (e.getKind()) {738case CLASS:739case ENUM:740case INTERFACE:741case PACKAGE:742return e;743default:744e = e.getEnclosingElement();745}746}747return e;748}749750// https://html.spec.whatwg.org/#the-id-attribute751private static final Pattern validId = Pattern.compile("[^\\s]+");752753private static final Pattern validNumber = Pattern.compile("-?[0-9]+");754755// pattern to remove leading {@docRoot}/?756private static final Pattern docRoot = Pattern.compile("(?i)(\\{@docRoot *\\}/?)?(.*)");757758private String getAttrValue(AttributeTree tree) {759if (tree.getValue() == null)760return null;761762StringWriter sw = new StringWriter();763try {764new DocPretty(sw).print(tree.getValue());765} catch (IOException e) {766// cannot happen767}768// ignore potential use of entities for now769return sw.toString();770}771772private void checkURI(AttributeTree tree, String uri) {773// allow URIs beginning with javascript:, which would otherwise be rejected by the URI API.774if (uri.startsWith("javascript:"))775return;776try {777URI u = new URI(uri);778} catch (URISyntaxException e) {779env.messages.error(HTML, tree, "dc.invalid.uri", uri);780}781}782// </editor-fold>783784// <editor-fold defaultstate="collapsed" desc="javadoc tags">785786@Override @DefinedBy(Api.COMPILER_TREE)787public Void visitAuthor(AuthorTree tree, Void ignore) {788warnIfEmpty(tree, tree.getName());789return super.visitAuthor(tree, ignore);790}791792@Override @DefinedBy(Api.COMPILER_TREE)793public Void visitDocRoot(DocRootTree tree, Void ignore) {794markEnclosingTag(Flag.HAS_INLINE_TAG);795return super.visitDocRoot(tree, ignore);796}797798@Override @DefinedBy(Api.COMPILER_TREE)799public Void visitIndex(IndexTree tree, Void ignore) {800markEnclosingTag(Flag.HAS_INLINE_TAG);801if (inIndex) {802env.messages.warning(HTML, tree, "dc.tag.nested.tag", "@" + tree.getTagName());803}804for (TagStackItem tsi : tagStack) {805if (tsi.tag == HtmlTag.A) {806env.messages.warning(HTML, tree, "dc.tag.a.within.a",807"{@" + tree.getTagName() + "}");808break;809}810}811boolean prevInIndex = inIndex;812try {813inIndex = true;814return super.visitIndex(tree, ignore);815} finally {816inIndex = prevInIndex;817}818}819820@Override @DefinedBy(Api.COMPILER_TREE)821public Void visitInheritDoc(InheritDocTree tree, Void ignore) {822markEnclosingTag(Flag.HAS_INLINE_TAG);823// TODO: verify on overridden method824foundInheritDoc = true;825return super.visitInheritDoc(tree, ignore);826}827828@Override @DefinedBy(Api.COMPILER_TREE)829public Void visitLink(LinkTree tree, Void ignore) {830markEnclosingTag(Flag.HAS_INLINE_TAG);831if (inLink) {832env.messages.warning(HTML, tree, "dc.tag.nested.tag", "@" + tree.getTagName());833}834boolean prevInLink = inLink;835// simulate inline context on tag stack836HtmlTag t = (tree.getKind() == DocTree.Kind.LINK)837? HtmlTag.CODE : HtmlTag.SPAN;838tagStack.push(new TagStackItem(tree, t));839try {840inLink = true;841return super.visitLink(tree, ignore);842} finally {843tagStack.pop();844inLink = prevInLink;845}846}847848@Override @DefinedBy(Api.COMPILER_TREE)849public Void visitLiteral(LiteralTree tree, Void ignore) {850markEnclosingTag(Flag.HAS_INLINE_TAG);851if (tree.getKind() == DocTree.Kind.CODE) {852for (TagStackItem tsi: tagStack) {853if (tsi.tag == HtmlTag.CODE) {854env.messages.warning(HTML, tree, "dc.tag.code.within.code");855break;856}857}858}859return super.visitLiteral(tree, ignore);860}861862@Override @DefinedBy(Api.COMPILER_TREE)863@SuppressWarnings("fallthrough")864public Void visitParam(ParamTree tree, Void ignore) {865boolean typaram = tree.isTypeParameter();866IdentifierTree nameTree = tree.getName();867Element paramElement = nameTree != null ? env.trees.getElement(new DocTreePath(getCurrentPath(), nameTree)) : null;868869if (paramElement == null) {870switch (env.currElement.getKind()) {871case CLASS: case INTERFACE: {872if (!typaram) {873env.messages.error(REFERENCE, tree, "dc.invalid.param");874break;875}876}877case METHOD: case CONSTRUCTOR: {878env.messages.error(REFERENCE, nameTree, "dc.param.name.not.found");879break;880}881882default:883env.messages.error(REFERENCE, tree, "dc.invalid.param");884break;885}886} else {887boolean unique = foundParams.add(paramElement);888889if (!unique) {890env.messages.warning(REFERENCE, tree, "dc.exists.param", nameTree);891}892}893894warnIfEmpty(tree, tree.getDescription());895return super.visitParam(tree, ignore);896}897898private void checkParamsDocumented(List<? extends Element> list) {899if (foundInheritDoc)900return;901902for (Element e: list) {903if (!foundParams.contains(e)) {904CharSequence paramName = (e.getKind() == ElementKind.TYPE_PARAMETER)905? "<" + e.getSimpleName() + ">"906: e.getSimpleName();907reportMissing("dc.missing.param", paramName);908}909}910}911912@Override @DefinedBy(Api.COMPILER_TREE)913public Void visitProvides(ProvidesTree tree, Void ignore) {914Element e = env.trees.getElement(env.currPath);915if (e.getKind() != ElementKind.MODULE) {916env.messages.error(REFERENCE, tree, "dc.invalid.provides");917}918ReferenceTree serviceType = tree.getServiceType();919Element se = env.trees.getElement(new DocTreePath(getCurrentPath(), serviceType));920if (se == null) {921env.messages.error(REFERENCE, tree, "dc.service.not.found");922}923return super.visitProvides(tree, ignore);924}925926@Override @DefinedBy(Api.COMPILER_TREE)927public Void visitReference(ReferenceTree tree, Void ignore) {928Element e = env.trees.getElement(getCurrentPath());929if (e == null)930env.messages.error(REFERENCE, tree, "dc.ref.not.found");931return super.visitReference(tree, ignore);932}933934@Override @DefinedBy(Api.COMPILER_TREE)935public Void visitReturn(ReturnTree tree, Void ignore) {936if (foundReturn) {937env.messages.warning(REFERENCE, tree, "dc.exists.return");938}939if (tree.isInline()) {940DocCommentTree dct = getCurrentPath().getDocComment();941if (tree != dct.getFirstSentence().get(0)) {942env.messages.warning(REFERENCE, tree, "dc.return.not.first");943}944}945946Element e = env.trees.getElement(env.currPath);947if (e.getKind() != ElementKind.METHOD948|| ((ExecutableElement) e).getReturnType().getKind() == TypeKind.VOID)949env.messages.error(REFERENCE, tree, "dc.invalid.return");950foundReturn = true;951warnIfEmpty(tree, tree.getDescription());952return super.visitReturn(tree, ignore);953}954955@Override @DefinedBy(Api.COMPILER_TREE)956public Void visitSerialData(SerialDataTree tree, Void ignore) {957warnIfEmpty(tree, tree.getDescription());958return super.visitSerialData(tree, ignore);959}960961@Override @DefinedBy(Api.COMPILER_TREE)962public Void visitSerialField(SerialFieldTree tree, Void ignore) {963warnIfEmpty(tree, tree.getDescription());964return super.visitSerialField(tree, ignore);965}966967@Override @DefinedBy(Api.COMPILER_TREE)968public Void visitSince(SinceTree tree, Void ignore) {969warnIfEmpty(tree, tree.getBody());970return super.visitSince(tree, ignore);971}972973@Override @DefinedBy(Api.COMPILER_TREE)974public Void visitSummary(SummaryTree tree, Void aVoid) {975markEnclosingTag(Flag.HAS_INLINE_TAG);976if (inSummary) {977env.messages.warning(HTML, tree, "dc.tag.nested.tag", "@" + tree.getTagName());978}979int idx = env.currDocComment.getFullBody().indexOf(tree);980// Warn if the node is preceded by non-whitespace characters,981// or other non-text nodes.982if ((idx == 1 && hasNonWhitespaceText) || idx > 1) {983env.messages.warning(SYNTAX, tree, "dc.invalid.summary", tree.getTagName());984}985boolean prevInSummary = inSummary;986try {987inSummary = true;988return super.visitSummary(tree, aVoid);989} finally {990inSummary = prevInSummary;991}992}993994@Override @DefinedBy(Api.COMPILER_TREE)995public Void visitSystemProperty(SystemPropertyTree tree, Void ignore) {996markEnclosingTag(Flag.HAS_INLINE_TAG);997for (TagStackItem tsi : tagStack) {998if (tsi.tag == HtmlTag.A) {999env.messages.warning(HTML, tree, "dc.tag.a.within.a",1000"{@" + tree.getTagName() + "}");1001break;1002}1003}1004return super.visitSystemProperty(tree, ignore);1005}10061007@Override @DefinedBy(Api.COMPILER_TREE)1008public Void visitThrows(ThrowsTree tree, Void ignore) {1009ReferenceTree exName = tree.getExceptionName();1010Element ex = env.trees.getElement(new DocTreePath(getCurrentPath(), exName));1011if (ex == null) {1012env.messages.error(REFERENCE, tree, "dc.ref.not.found");1013} else if (isThrowable(ex.asType())) {1014switch (env.currElement.getKind()) {1015case CONSTRUCTOR:1016case METHOD:1017if (isCheckedException(ex.asType())) {1018ExecutableElement ee = (ExecutableElement) env.currElement;1019checkThrowsDeclared(exName, ex.asType(), ee.getThrownTypes());1020}1021break;1022default:1023env.messages.error(REFERENCE, tree, "dc.invalid.throws");1024}1025} else {1026env.messages.error(REFERENCE, tree, "dc.invalid.throws");1027}1028warnIfEmpty(tree, tree.getDescription());1029return scan(tree.getDescription(), ignore);1030}10311032private boolean isThrowable(TypeMirror tm) {1033switch (tm.getKind()) {1034case DECLARED:1035case TYPEVAR:1036return env.types.isAssignable(tm, env.java_lang_Throwable);1037}1038return false;1039}10401041private void checkThrowsDeclared(ReferenceTree tree, TypeMirror t, List<? extends TypeMirror> list) {1042boolean found = false;1043for (TypeMirror tl : list) {1044if (env.types.isAssignable(t, tl)) {1045foundThrows.add(tl);1046found = true;1047}1048}1049if (!found)1050env.messages.error(REFERENCE, tree, "dc.exception.not.thrown", t);1051}10521053private void checkThrowsDocumented(List<? extends TypeMirror> list) {1054if (foundInheritDoc)1055return;10561057for (TypeMirror tl: list) {1058if (isCheckedException(tl) && !foundThrows.contains(tl))1059reportMissing("dc.missing.throws", tl);1060}1061}10621063@Override @DefinedBy(Api.COMPILER_TREE)1064public Void visitUnknownBlockTag(UnknownBlockTagTree tree, Void ignore) {1065checkUnknownTag(tree, tree.getTagName());1066return super.visitUnknownBlockTag(tree, ignore);1067}10681069@Override @DefinedBy(Api.COMPILER_TREE)1070public Void visitUnknownInlineTag(UnknownInlineTagTree tree, Void ignore) {1071markEnclosingTag(Flag.HAS_INLINE_TAG);1072checkUnknownTag(tree, tree.getTagName());1073return super.visitUnknownInlineTag(tree, ignore);1074}10751076private void checkUnknownTag(DocTree tree, String tagName) {1077if (env.customTags != null && !env.customTags.contains(tagName))1078env.messages.error(SYNTAX, tree, "dc.tag.unknown", tagName);1079}10801081@Override @DefinedBy(Api.COMPILER_TREE)1082public Void visitUses(UsesTree tree, Void ignore) {1083Element e = env.trees.getElement(env.currPath);1084if (e.getKind() != ElementKind.MODULE) {1085env.messages.error(REFERENCE, tree, "dc.invalid.uses");1086}1087ReferenceTree serviceType = tree.getServiceType();1088Element se = env.trees.getElement(new DocTreePath(getCurrentPath(), serviceType));1089if (se == null) {1090env.messages.error(REFERENCE, tree, "dc.service.not.found");1091}1092return super.visitUses(tree, ignore);1093}10941095@Override @DefinedBy(Api.COMPILER_TREE)1096public Void visitValue(ValueTree tree, Void ignore) {1097ReferenceTree ref = tree.getReference();1098if (ref == null || ref.getSignature().isEmpty()) {1099if (!isConstant(env.currElement))1100env.messages.error(REFERENCE, tree, "dc.value.not.allowed.here");1101} else {1102Element e = env.trees.getElement(new DocTreePath(getCurrentPath(), ref));1103if (!isConstant(e))1104env.messages.error(REFERENCE, tree, "dc.value.not.a.constant");1105}11061107markEnclosingTag(Flag.HAS_INLINE_TAG);1108return super.visitValue(tree, ignore);1109}11101111private boolean isConstant(Element e) {1112if (e == null)1113return false;11141115switch (e.getKind()) {1116case FIELD:1117Object value = ((VariableElement) e).getConstantValue();1118return (value != null); // can't distinguish "not a constant" from "constant is null"1119default:1120return false;1121}1122}11231124@Override @DefinedBy(Api.COMPILER_TREE)1125public Void visitVersion(VersionTree tree, Void ignore) {1126warnIfEmpty(tree, tree.getBody());1127return super.visitVersion(tree, ignore);1128}11291130@Override @DefinedBy(Api.COMPILER_TREE)1131public Void visitErroneous(ErroneousTree tree, Void ignore) {1132env.messages.error(SYNTAX, tree, null, tree.getDiagnostic().getMessage(null));1133return null;1134}1135// </editor-fold>11361137// <editor-fold defaultstate="collapsed" desc="Utility methods">11381139private boolean isCheckedException(TypeMirror t) {1140return !(env.types.isAssignable(t, env.java_lang_Error)1141|| env.types.isAssignable(t, env.java_lang_RuntimeException));1142}11431144private boolean isSynthetic() {1145switch (env.currElement.getKind()) {1146case CONSTRUCTOR:1147// A synthetic default constructor has the same pos as the1148// enclosing class1149TreePath p = env.currPath;1150return env.getPos(p) == env.getPos(p.getParentPath());1151}1152return false;1153}11541155void markEnclosingTag(Flag flag) {1156TagStackItem top = tagStack.peek();1157if (top != null)1158top.flags.add(flag);1159}11601161String toString(TreePath p) {1162StringBuilder sb = new StringBuilder("TreePath[");1163toString(p, sb);1164sb.append("]");1165return sb.toString();1166}11671168void toString(TreePath p, StringBuilder sb) {1169TreePath parent = p.getParentPath();1170if (parent != null) {1171toString(parent, sb);1172sb.append(",");1173}1174sb.append(p.getLeaf().getKind()).append(":").append(env.getPos(p)).append(":S").append(env.getStartPos(p));1175}11761177void warnIfEmpty(DocTree tree, List<? extends DocTree> list) {1178for (DocTree d: list) {1179switch (d.getKind()) {1180case TEXT:1181if (hasNonWhitespace((TextTree) d))1182return;1183break;1184default:1185return;1186}1187}1188env.messages.warning(MISSING, tree, "dc.empty", tree.getKind().tagName);1189}11901191boolean hasNonWhitespace(TextTree tree) {1192String s = tree.getBody();1193for (int i = 0; i < s.length(); i++) {1194Character c = s.charAt(i);1195if (!Character.isWhitespace(s.charAt(i)))1196return true;1197}1198return false;1199}12001201// </editor-fold>12021203}120412051206