Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
8257925: enable more support for nested inline tags
Reviewed-by: prappo, hannesw
  • Loading branch information
jonathan-gibbons committed Feb 19, 2021
1 parent 433096a commit c4f17a3
Show file tree
Hide file tree
Showing 15 changed files with 710 additions and 92 deletions.
Expand Up @@ -569,21 +569,9 @@ protected DCText inlineWord() {
* Read general text content of an inline tag, including HTML entities and elements.
* Matching pairs of { } are skipped; the text is terminated by the first
* unmatched }. It is an error if the beginning of the next tag is detected.
* Nested tags are not permitted.
*/
private List<DCTree> inlineContent() {
return inlineContent(false);
}

/**
* Read general text content of an inline tag, including HTML entities and elements.
* Matching pairs of { } are skipped; the text is terminated by the first
* unmatched }. It is an error if the beginning of the next tag is detected.
*
* @param allowNestedTags whether or not to allow nested tags
*/
@SuppressWarnings("fallthrough")
private List<DCTree> inlineContent(boolean allowNestedTags) {
private List<DCTree> inlineContent() {
ListBuffer<DCTree> trees = new ListBuffer<>();

skipWhitespace();
Expand Down Expand Up @@ -620,7 +608,7 @@ private List<DCTree> inlineContent(boolean allowNestedTags) {
textStart = bp;
newline = false;
nextChar();
if (ch == '@' && allowNestedTags) {
if (ch == '@') {
addPendingText(trees, bp - 2);
trees.add(inlineTag());
textStart = bp;
Expand Down Expand Up @@ -1311,7 +1299,7 @@ public DCTree parse(int pos, Kind kind) {
description = blockContent();
break;
case INLINE:
description = inlineContent(true);
description = inlineContent();
break;
default:
throw new IllegalArgumentException(kind.toString());
Expand Down

Large diffs are not rendered by default.

Expand Up @@ -25,7 +25,9 @@

package jdk.javadoc.internal.doclets.formats.html;

import java.util.EnumSet;
import java.util.List;
import java.util.Set;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
Expand Down Expand Up @@ -77,14 +79,67 @@
*/

public class TagletWriterImpl extends TagletWriter {
/**
* A class that provides the information about the enclosing context for
* a series of {@code DocTree} nodes.
* This context may be used to determine the content that should be generated from the tree nodes.
*/
static class Context {
/**
* Whether or not the trees are appearing in a context of just the first sentence,
* such as in the summary table of the enclosing element.
*/
final boolean isFirstSentence;
/**
* Whether or not the trees are appearing in the "summary" section of the
* page for a declaration.
*/
final boolean inSummary;
/**
* The set of enclosing kinds of tags.
*/
final Set<DocTree.Kind> inTags;

/**
* Creates an outermost context, with no enclosing tags.
*
* @param isFirstSentence {@code true} if the trees are appearing in a context of just the
* first sentence and {@code false} otherwise
* @param inSummary {@code true} if the trees are appearing in the "summary" section
* of the page for a declaration and {@code false} otherwise
*/
Context(boolean isFirstSentence, boolean inSummary) {
this(isFirstSentence, inSummary, EnumSet.noneOf(DocTree.Kind.class));
}

private Context(boolean isFirstSentence, boolean inSummary, Set<DocTree.Kind> inTags) {
this.isFirstSentence = isFirstSentence;
this.inSummary = inSummary;
this.inTags = inTags;
}

/**
* Creates a new {@code Context} that includes an extra tag kind in the set of enclosing
* kinds of tags.
*
* @param tree the enclosing tree
*
* @return the new {@code Context}
*/
Context within(DocTree tree) {
var newInTags = EnumSet.copyOf(inTags);
newInTags.add(tree.getKind());
return new Context(isFirstSentence, inSummary, newInTags);
}
}

private final HtmlDocletWriter htmlWriter;
private final HtmlConfiguration configuration;
private final HtmlOptions options;
private final Utils utils;
private final boolean inSummary;
private final Resources resources;
private final Contents contents;
private final Context context;

/**
* Creates a taglet writer.
Expand All @@ -107,9 +162,19 @@ public TagletWriterImpl(HtmlDocletWriter htmlWriter, boolean isFirstSentence) {
* of a {@code {@summary ...}} tag, and {@code false} otherwise
*/
public TagletWriterImpl(HtmlDocletWriter htmlWriter, boolean isFirstSentence, boolean inSummary) {
super(isFirstSentence);
this(htmlWriter, new Context(isFirstSentence, inSummary));
}

/**
* Creates a taglet writer.
*
* @param htmlWriter the {@code HtmlDocletWriter} for the page
* @param context the enclosing context for any tags
*/
public TagletWriterImpl(HtmlDocletWriter htmlWriter, Context context) {
super(context.isFirstSentence);
this.htmlWriter = htmlWriter;
this.inSummary = inSummary;
this.context = context;
configuration = htmlWriter.configuration;
options = configuration.getOptions();
utils = configuration.utils;
Expand Down Expand Up @@ -139,9 +204,17 @@ protected Content indexTagOutput(Element element, IndexTree tag) {
tagText = tagText.substring(1, tagText.length() - 1)
.replaceAll("\\s+", " ");
}
String desc = ch.getText(tag.getDescription());

return createAnchorAndSearchIndex(element, tagText, desc, tag);
Content desc = htmlWriter.commentTagsToContent(tag, element, tag.getDescription(), context.within(tag));
String descText = extractText(desc);

return createAnchorAndSearchIndex(element, tagText, descText, tag);
}

// ugly but simple;
// alternatives would be to walk the Content tree, or to add new functionality to Content
private String extractText(Content c) {
return c.toString().replaceAll("<[^>]+>", "");
}

@Override
Expand Down Expand Up @@ -222,15 +295,15 @@ public Content paramTagOutput(Element element, ParamTree paramTag, String paramN
body.add(HtmlTree.CODE(defineID ? HtmlTree.SPAN_ID(HtmlIds.forParam(paramName), nameTree) : nameTree));
body.add(" - ");
List<? extends DocTree> description = ch.getDescription(paramTag);
body.add(htmlWriter.commentTagsToContent(paramTag, element, description, false, inSummary));
body.add(htmlWriter.commentTagsToContent(paramTag, element, description, context.within(paramTag)));
return HtmlTree.DD(body);
}

@Override
public Content returnTagOutput(Element element, ReturnTree returnTag, boolean inline) {
CommentHelper ch = utils.getCommentHelper(element);
List<? extends DocTree> desc = ch.getDescription(returnTag);
Content content = htmlWriter.commentTagsToContent(returnTag, element, desc , false, inSummary);
Content content = htmlWriter.commentTagsToContent(returnTag, element, desc , context.within(returnTag));
return inline
? new ContentBuilder(contents.getContent("doclet.Returns_0", content))
: new ContentBuilder(HtmlTree.DT(contents.returns), HtmlTree.DD(content));
Expand All @@ -241,7 +314,7 @@ public Content seeTagOutput(Element holder, List<? extends SeeTree> seeTags) {
ContentBuilder body = new ContentBuilder();
for (DocTree dt : seeTags) {
appendSeparatorIfNotEmpty(body);
body.add(htmlWriter.seeTagToContent(holder, dt));
body.add(htmlWriter.seeTagToContent(holder, dt, context.within(dt)));
}
if (utils.isVariableElement(holder) && ((VariableElement)holder).getConstantValue() != null &&
htmlWriter instanceof ClassWriterImpl) {
Expand Down Expand Up @@ -292,7 +365,7 @@ public Content simpleBlockTagOutput(Element element, List<? extends DocTree> sim
body.add(", ");
}
List<? extends DocTree> bodyTags = ch.getBody(simpleTag);
body.add(htmlWriter.commentTagsToContent(simpleTag, element, bodyTags, false, inSummary));
body.add(htmlWriter.commentTagsToContent(simpleTag, element, bodyTags, context.within(simpleTag)));
many = true;
}
return new ContentBuilder(
Expand Down Expand Up @@ -333,7 +406,7 @@ public Content throwsTagOutput(Element element, ThrowsTree throwsTag, TypeMirror
}
body.add(HtmlTree.CODE(excName));
List<? extends DocTree> description = ch.getDescription(throwsTag);
Content desc = htmlWriter.commentTagsToContent(throwsTag, element, description, false, inSummary);
Content desc = htmlWriter.commentTagsToContent(throwsTag, element, description, context.within(throwsTag));
if (desc != null && !desc.isEmpty()) {
body.add(" - ");
body.add(desc);
Expand Down Expand Up @@ -373,7 +446,7 @@ public Content commentTagsToOutput(Element holder,
boolean isFirstSentence)
{
return htmlWriter.commentTagsToContent(holderTag, holder,
tags, isFirstSentence, inSummary);
tags, holderTag == null ? context : context.within(holderTag));
}

@Override
Expand All @@ -389,7 +462,7 @@ protected TypeElement getCurrentPageElement() {
@SuppressWarnings("preview")
private Content createAnchorAndSearchIndex(Element element, String tagText, String desc, DocTree tree) {
Content result = null;
if (isFirstSentence && inSummary) {
if (context.isFirstSentence && context.inSummary || context.inTags.contains(DocTree.Kind.INDEX)) {
result = new StringContent(tagText);
} else {
HtmlId id = HtmlIds.forText(tagText, htmlWriter.indexAnchorTable);
Expand Down
Expand Up @@ -94,6 +94,7 @@ doclet.URL_error=Error fetching URL: {0}
doclet.Resource_error=Error reading resource: {0}
doclet.see.class_or_package_not_found=Tag {0}: reference not found: {1}
doclet.see.class_or_package_not_accessible=Tag {0}: reference not accessible: {1}
doclet.see.nested_link=Tag {0}: nested link
doclet.tag.invalid_usage=invalid usage of tag {0}
doclet.Deprecated_API=Deprecated API
doclet.Preview_API=Preview API
Expand Down
Expand Up @@ -50,6 +50,7 @@ public SummaryTaglet() {

@Override
public Content getInlineTagOutput(Element holder, DocTree tag, TagletWriter writer) {
return writer.commentTagsToOutput(holder, ((SummaryTree)tag).getSummary());
return writer.commentTagsToOutput(holder, tag, ((SummaryTree)tag).getSummary(),
writer.isFirstSentence);
}
}
Expand Up @@ -144,6 +144,9 @@ public String toString() {
private HtmlTag currHeadingTag;

private int implicitHeadingRank;
private boolean inIndex;
private boolean inLink;
private boolean inSummary;

// <editor-fold defaultstate="collapsed" desc="Top level">

Expand Down Expand Up @@ -783,14 +786,23 @@ public Void visitDocRoot(DocRootTree tree, Void ignore) {
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitIndex(IndexTree tree, Void ignore) {
markEnclosingTag(Flag.HAS_INLINE_TAG);
if (inIndex) {
env.messages.warning(HTML, tree, "dc.tag.nested.tag", "@" + tree.getTagName());
}
for (TagStackItem tsi : tagStack) {
if (tsi.tag == HtmlTag.A) {
env.messages.warning(HTML, tree, "dc.tag.a.within.a",
"{@" + tree.getTagName() + "}");
break;
}
}
return super.visitIndex(tree, ignore);
boolean prevInIndex = inIndex;
try {
inIndex = true;
return super.visitIndex(tree, ignore);
} finally {
inIndex = prevInIndex;
}
}

@Override @DefinedBy(Api.COMPILER_TREE)
Expand All @@ -804,14 +816,20 @@ public Void visitInheritDoc(InheritDocTree tree, Void ignore) {
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitLink(LinkTree tree, Void ignore) {
markEnclosingTag(Flag.HAS_INLINE_TAG);
if (inLink) {
env.messages.warning(HTML, tree, "dc.tag.nested.tag", "@" + tree.getTagName());
}
boolean prevInLink = inLink;
// simulate inline context on tag stack
HtmlTag t = (tree.getKind() == DocTree.Kind.LINK)
? HtmlTag.CODE : HtmlTag.SPAN;
tagStack.push(new TagStackItem(tree, t));
try {
inLink = true;
return super.visitLink(tree, ignore);
} finally {
tagStack.pop();
inLink = prevInLink;
}
}

Expand Down Expand Up @@ -941,15 +959,24 @@ public Void visitSince(SinceTree tree, Void ignore) {
}

@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitSummary(SummaryTree node, Void aVoid) {
public Void visitSummary(SummaryTree tree, Void aVoid) {
markEnclosingTag(Flag.HAS_INLINE_TAG);
int idx = env.currDocComment.getFullBody().indexOf(node);
if (inSummary) {
env.messages.warning(HTML, tree, "dc.tag.nested.tag", "@" + tree.getTagName());
}
int idx = env.currDocComment.getFullBody().indexOf(tree);
// Warn if the node is preceded by non-whitespace characters,
// or other non-text nodes.
if ((idx == 1 && hasNonWhitespaceText) || idx > 1) {
env.messages.warning(SYNTAX, node, "dc.invalid.summary", node.getTagName());
env.messages.warning(SYNTAX, tree, "dc.invalid.summary", tree.getTagName());
}
boolean prevInSummary = inSummary;
try {
inSummary = true;
return super.visitSummary(tree, aVoid);
} finally {
inSummary = prevInSummary;
}
return super.visitSummary(node, aVoid);
}

@Override @DefinedBy(Api.COMPILER_TREE)
Expand Down
Expand Up @@ -69,6 +69,7 @@ dc.tag.heading.sequence.1 = heading used out of sequence: <{0}>, compared to imp
dc.tag.heading.sequence.2 = heading used out of sequence: <{0}>, compared to previous heading: <{1}>
dc.tag.heading.sequence.3 = unexpected heading used: <{0}>, compared to implicit preceding heading: <H{1}>
dc.tag.nested.not.allowed=nested tag not allowed: <{0}>
dc.tag.nested.tag=nested tag: {0}
dc.tag.not.allowed.here = tag not allowed here: <{0}>
dc.tag.not.allowed = element not allowed in documentation comments: <{0}>
dc.tag.not.allowed.inline.element = block element not allowed within inline element <{1}>: {0}
Expand Down
Expand Up @@ -125,15 +125,15 @@ public void testInvalidLinks() {
"-package", "pkg2");
checkExit(Exit.ERROR);
checkOutput("pkg2/B.html", true,
"<div class=\"block\"><code>java.util.Foo<String></code>\n"
+ " Baz<Object>\n"
+ " <code>#b(List<Integer>)</code></div>",
"<div class=\"block\"><code>java.util.Foo&lt;String&gt;</code>\n"
+ " Baz&lt;Object&gt;\n"
+ " <code>#b(List&lt;Integer&gt;)</code></div>",

"<dl class=\"notes\">\n"
+ "<dt>See Also:</dt>\n"
+ "<dd><code>java.util.List<Bar></code>, \n"
+ "<code>Baz<Object, String></code>, \n"
+ "<code>B#b(List<Baz>)</code></dd>\n</dl>");
+ "<dd><code>java.util.List&lt;Bar&gt;</code>, \n"
+ "<code>Baz&lt;Object, String&gt;</code>, \n"
+ "<code>B#b(List&lt;Baz&gt;)</code></dd>\n</dl>");
}
}

1 comment on commit c4f17a3

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.