/* * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.doclint; import com.sun.source.doctree.LiteralTree; import java.util.regex.Matcher; import com.sun.source.doctree.LinkTree; import java.net.URI; import java.util.regex.Pattern; import java.io.IOException; import com.sun.tools.javac.tree.DocPretty; import java.io.StringWriter; import java.util.Deque; import java.util.EnumSet; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic.Kind; import com.sun.source.doctree.AttributeTree; import com.sun.source.doctree.AuthorTree; import com.sun.source.doctree.DocCommentTree; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.EndElementTree; import com.sun.source.doctree.EntityTree; import com.sun.source.doctree.ErroneousTree; import com.sun.source.doctree.IdentifierTree; import com.sun.source.doctree.InheritDocTree; import com.sun.source.doctree.ParamTree; import com.sun.source.doctree.ReferenceTree; import com.sun.source.doctree.ReturnTree; import com.sun.source.doctree.SerialDataTree; import com.sun.source.doctree.SerialFieldTree; import com.sun.source.doctree.SinceTree; import com.sun.source.doctree.StartElementTree; import com.sun.source.doctree.TextTree; import com.sun.source.doctree.ThrowsTree; import com.sun.source.doctree.VersionTree; import com.sun.source.util.DocTreeScanner; import com.sun.source.util.TreePath; import com.sun.tools.doclint.HtmlTag.AttrKind; import java.net.URISyntaxException; import static com.sun.tools.doclint.Messages.Group.*; /** * Validate a doc comment. * *
This is NOT part of any supported API. * If you write code that depends on this, you do so at your own * risk. This code and its internal interfaces are subject to change * or deletion without notice.
*/ public class Checker extends DocTreeScannerinside
case P: TagStackItem top = tagStack.peek(); if (top != null && top.tag == HtmlTag.PRE) env.messages.warning(HTML, tree, "dc.tag.p.in.pre"); break; } // check that only block tags and inline tags are used, // and that blocks tags are not used within inline tags switch (t.blockType) { case INLINE: break; case BLOCK: TagStackItem top = tagStack.peek(); if (top != null && top.tag != null && top.tag.blockType == HtmlTag.BlockType.INLINE) { switch (top.tree.getKind()) { case START_ELEMENT: { Name name = ((StartElementTree) top.tree).getName(); env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.element", treeName, name); break; } case LINK: case LINK_PLAIN: { String name = top.tree.getKind().tagName; env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.tag", treeName, name); break; } default: env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.other", treeName); } } break; case OTHER: env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName); break; default: throw new AssertionError(); } if (t.flags.contains(HtmlTag.Flag.NO_NEST)) { for (TagStackItem i: tagStack) { if (t == i.tag) { env.messages.warning(HTML, tree, "dc.tag.nested.not.allowed", treeName); break; } } } } // check for self closing tags, such as if (tree.isSelfClosing()) { env.messages.error(HTML, tree, "dc.tag.self.closing", treeName); } try { TagStackItem parent = tagStack.peek(); TagStackItem top = new TagStackItem(tree, t); tagStack.push(top); super.visitStartElement(tree, ignore); // handle attributes that may or may not have been found in start element if (t != null) { switch (t) { case CAPTION: if (parent != null && parent.tag == HtmlTag.TABLE) parent.flags.add(Flag.TABLE_HAS_CAPTION); break; case IMG: if (!top.attrs.contains(HtmlTag.Attr.ALT)) env.messages.error(ACCESSIBILITY, tree, "dc.no.alt.attr.for.image"); break; } } return null; } finally { if (t == null || t.endKind == HtmlTag.EndKind.NONE) tagStack.pop(); } } private void checkHeader(StartElementTree tree, HtmlTag tag) { // verify the new tag if (getHeaderLevel(tag) > getHeaderLevel(currHeaderTag) + 1) { if (currHeaderTag == null) { env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.1", tag); } else { env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.2", tag, currHeaderTag); } } currHeaderTag = tag; } private int getHeaderLevel(HtmlTag tag) { if (tag == null) return 0; switch (tag) { case H1: return 1; case H2: return 2; case H3: return 3; case H4: return 4; case H5: return 5; case H6: return 6; default: throw new IllegalArgumentException(); } } @Override public Void visitEndElement(EndElementTree tree, Void ignore) { final Name treeName = tree.getName(); final HtmlTag t = HtmlTag.get(treeName); if (t == null) { env.messages.error(HTML, tree, "dc.tag.unknown", treeName); } else if (t.endKind == HtmlTag.EndKind.NONE) { env.messages.error(HTML, tree, "dc.tag.end.not.permitted", treeName); } else { boolean done = false; while (!tagStack.isEmpty()) { TagStackItem top = tagStack.peek(); if (t == top.tag) { switch (t) { case TABLE: if (!top.attrs.contains(HtmlTag.Attr.SUMMARY) && !top.flags.contains(Flag.TABLE_HAS_CAPTION)) { env.messages.error(ACCESSIBILITY, tree, "dc.no.summary.or.caption.for.table"); } } if (t.flags.contains(HtmlTag.Flag.EXPECT_CONTENT) && !top.flags.contains(Flag.HAS_TEXT) && !top.flags.contains(Flag.HAS_ELEMENT)) { env.messages.warning(HTML, tree, "dc.tag.empty", treeName); } if (t.flags.contains(HtmlTag.Flag.NO_TEXT) && top.flags.contains(Flag.HAS_TEXT)) { env.messages.error(HTML, tree, "dc.text.not.allowed", treeName); } tagStack.pop(); done = true; break; } else if (top.tag == null || top.tag.endKind != HtmlTag.EndKind.REQUIRED) { tagStack.pop(); } else { boolean found = false; for (TagStackItem si: tagStack) { if (si.tag == t) { found = true; break; } } if (found && top.tree.getKind() == DocTree.Kind.START_ELEMENT) { env.messages.error(HTML, top.tree, "dc.tag.start.unmatched", ((StartElementTree) top.tree).getName()); tagStack.pop(); } else { env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName); done = true; break; } } } if (!done && tagStack.isEmpty()) { env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName); } } return super.visitEndElement(tree, ignore); } //