/* * 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 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 if (tagStack.isEmpty()) {
env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName);
} else {
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();
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);
break;
}
}
}
}
return super.visitEndElement(tree, ignore);
}
//