diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java index 4b86fd28ebc..b5992daea7e 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java @@ -381,24 +381,22 @@ public class TagletWriterImpl extends TagletWriter { } @Override - protected Content snippetTagOutput(Element element, SnippetTree tag, StyledText content) { - String copyText = resources.getText("doclet.Copy_snippet_to_clipboard"); - String copiedText = resources.getText("doclet.Copied_snippet_to_clipboard"); - HtmlTree copy = HtmlTree.DIV(HtmlStyle.snippetContainer, - HtmlTree.A("#", new HtmlTree(TagName.IMG) - .put(HtmlAttr.SRC, htmlWriter.pathToRoot.resolve(DocPaths.CLIPBOARD_SVG).getPath()) - .put(HtmlAttr.ALT, copyText)) - .addStyle(HtmlStyle.snippetCopy) - .put(HtmlAttr.ONCLICK, "copySnippet(this)") - .put(HtmlAttr.ARIA_LABEL, copyText) - .put(HtmlAttr.DATA_COPIED, copiedText)); - HtmlTree pre = new HtmlTree(TagName.PRE) - .setStyle(HtmlStyle.snippet); - pre.add(Text.of(utils.normalizeNewlines("\n"))); + protected Content snippetTagOutput(Element element, SnippetTree tag, StyledText content, + String id, String lang) { + HtmlTree pre = new HtmlTree(TagName.PRE).setStyle(HtmlStyle.snippet); + if (id != null && !id.isBlank()) { + pre.put(HtmlAttr.ID, id); + } + HtmlTree code = new HtmlTree(TagName.CODE) + .add(HtmlTree.EMPTY); // Make sure the element is always rendered + if (lang != null && !lang.isBlank()) { + code.addStyle("language-" + lang); + } + content.consumeBy((styles, sequence) -> { CharSequence text = utils.normalizeNewlines(sequence); if (styles.isEmpty()) { - pre.add(text); + code.add(text); } else { Element e = null; String t = null; @@ -443,10 +441,20 @@ public class TagletWriterImpl extends TagletWriter { c = HtmlTree.SPAN(Text.of(sequence)); classes.forEach(((HtmlTree) c)::addStyle); } - pre.add(c); + code.add(c); } }); - return copy.add(pre); + String copyText = resources.getText("doclet.Copy_snippet_to_clipboard"); + String copiedText = resources.getText("doclet.Copied_snippet_to_clipboard"); + HtmlTree snippetContainer = HtmlTree.DIV(HtmlStyle.snippetContainer, + HtmlTree.A("#", new HtmlTree(TagName.IMG) + .put(HtmlAttr.SRC, htmlWriter.pathToRoot.resolve(DocPaths.CLIPBOARD_SVG).getPath()) + .put(HtmlAttr.ALT, copyText)) + .addStyle(HtmlStyle.snippetCopy) + .put(HtmlAttr.ONCLICK, "copySnippet(this)") + .put(HtmlAttr.ARIA_LABEL, copyText) + .put(HtmlAttr.DATA_COPIED, copiedText)); + return snippetContainer.add(pre.add(code)); } /* diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SnippetTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SnippetTaglet.java index 9fa3a1b0939..9219a78814a 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SnippetTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SnippetTaglet.java @@ -261,7 +261,21 @@ public class SnippetTaglet extends BaseTaglet { assert inlineSnippet != null || externalSnippet != null; StyledText text = inlineSnippet != null ? inlineSnippet : externalSnippet; - return writer.snippetTagOutput(holder, snippetTag, text); + String lang = null; + AttributeTree langAttr = attributes.get("lang"); + if (langAttr != null && langAttr.getValueKind() != AttributeTree.ValueKind.EMPTY) { + lang = stringOf(langAttr.getValue()); + } else if (containsClass) { + lang = "java"; + } else if (containsFile) { + lang = languageFromFileName(fileObject.getName()); + } + AttributeTree idAttr = attributes.get("id"); + String id = idAttr == null || idAttr.getValueKind() == AttributeTree.ValueKind.EMPTY + ? null + : stringOf(idAttr.getValue()); + + return writer.snippetTagOutput(holder, snippetTag, text, id, lang); } /* @@ -298,6 +312,16 @@ public class SnippetTaglet extends BaseTaglet { .collect(Collectors.joining()); } + private String languageFromFileName(String fileName) { + // TODO: find a way to extend/customize the list of recognized file name extensions + if (fileName.endsWith(".java")) { + return "java"; + } else if (fileName.endsWith(".properties")) { + return "properties"; + } + return null; + } + private void error(TagletWriter writer, Element holder, DocTree tag, String key, Object... args) { writer.configuration().getMessages().error( writer.configuration().utils.getCommentHelper(holder).getDocTreePath(tag), key, args); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/TagletWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/TagletWriter.java index ca4ad2064c1..a12c7b42ffd 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/TagletWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/TagletWriter.java @@ -181,10 +181,13 @@ public abstract class TagletWriter { * * @param element The element that owns the doc comment * @param snippetTag the snippet tag + * @param id the value of the id attribute, or null if not defined + * @param lang the value of the lang attribute, or null if not defined * * @return the output */ - protected abstract Content snippetTagOutput(Element element, SnippetTree snippetTag, StyledText text); + protected abstract Content snippetTagOutput(Element element, SnippetTree snippetTag, StyledText text, + String id, String lang); /** * Returns the output for a {@code {@systemProperty...}} tag. diff --git a/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java b/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java index 7aab8ff19b1..19d36d41e7e 100644 --- a/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java +++ b/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8266666 + * @bug 8266666 8275788 * @summary Implementation for snippets * @library /tools/lib ../../lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -84,144 +84,237 @@ public class TestSnippetTag extends JavadocTester { } /* - * While the "id" and "lang" attributes are advertised in JEP 413, they are - * currently unused by the implementation. The goal of this test is to make - * sure that specifying these attributes causes no errors and exhibits no - * unexpected behavior. + * Make sure the "id" and "lang" attributes defined in JEP 413 are rendered + * properly as recommended by the HTML5 specification. */ @Test public void testIdAndLangAttributes(Path base) throws IOException { Path srcDir = base.resolve("src"); Path outDir = base.resolve("out"); + + // A record of a snippet content and matching expected attribute values + record SnippetAttributes(String content, String id, String lang) { + public String idAttribute() { + return id == null ? "" : " id=\"" + id + "\""; + } + public String langAttribute() { + return lang == null ? "" : " class=\"language-" + lang + "\""; + } + } + final var snippets = List.of( - """ - {@snippet id="foo" : - Hello, Snippet! - } - """, - """ - {@snippet id="foo": - Hello, Snippet! - } - """, - """ - {@snippet id='foo' : - Hello, Snippet! - } - """, - """ - {@snippet id='foo': - Hello, Snippet! - } - """, - """ - {@snippet id=foo : - Hello, Snippet! - } - """, + new SnippetAttributes(""" + {@snippet id="foo1" : + Hello, Snippet! + } + """, "foo1", null), + new SnippetAttributes(""" + {@snippet id="foo2": + Hello, Snippet! + } + """, "foo2", null), + new SnippetAttributes(""" + {@snippet id='foo3' : + Hello, Snippet! + } + """, "foo3", null), + new SnippetAttributes(""" + {@snippet id='foo4': + Hello, Snippet! + } + """, "foo4", null), + new SnippetAttributes(""" + {@snippet id=foo5 : + Hello, Snippet! + } + """, "foo5", null), // (1) Haven't yet decided on this one. It's a consistency issue. On the one // hand, `:` is considered a part of a javadoc tag's name (e.g. JDK-4750173); // on the other hand, snippet markup treats `:` (next-line modifier) as a value // terminator. -// """ -// {@snippet id=foo: -// Hello, Snippet! -// } -// """, - """ - {@snippet id="" : - Hello, Snippet! - } - """, - """ - {@snippet id="": - Hello, Snippet! - } - """, - """ - {@snippet id='': - Hello, Snippet! - } - """, - """ - {@snippet id=: - Hello, Snippet! - } - """, - """ - {@snippet lang="java" : - Hello, Snippet! - } - """, - """ - {@snippet lang="java": - Hello, Snippet! - } - """, - """ - {@snippet lang='java' : - Hello, Snippet! - } - """, - """ - {@snippet lang='java': - Hello, Snippet! - } - """, - """ - {@snippet lang=java : - Hello, Snippet! - } - """, - """ - {@snippet lang="properties" : - Hello, Snippet! - } - """, - """ - {@snippet lang="text" : - Hello, Snippet! - } - """, - """ - {@snippet lang="" : - Hello, Snippet! - } - """, - """ - {@snippet lang="foo" id="bar" : - Hello, Snippet! - } - """ +// new SnippetAttributes(""" +// {@snippet id=foo6: +// Hello, Snippet! +// } +// """, "foo6", null), + + new SnippetAttributes(""" + {@snippet id="" : + Hello, Snippet! + } + """, null, null), + new SnippetAttributes(""" + {@snippet id="": + Hello, Snippet! + } + """, null, null), + new SnippetAttributes(""" + {@snippet id='': + Hello, Snippet! + } + """, null, null), + new SnippetAttributes(""" + {@snippet id=: + Hello, Snippet! + } + """, null, null), + new SnippetAttributes(""" + {@snippet lang="java" : + Hello, Snippet! + } + """, null, "java"), + new SnippetAttributes(""" + {@snippet lang="java": + Hello, Snippet! + } + """, null, "java"), + new SnippetAttributes(""" + {@snippet lang='java' : + Hello, Snippet! + } + """, null, "java"), + new SnippetAttributes(""" + {@snippet lang='java': + Hello, Snippet! + } + """, null, "java"), + new SnippetAttributes(""" + {@snippet lang=java : + Hello, Snippet! + } + """, null, "java"), + new SnippetAttributes(""" + {@snippet lang="properties" : + Hello, Snippet! + } + """, null, "properties"), + new SnippetAttributes(""" + {@snippet lang="text" : + Hello, Snippet! + } + """, null, "text"), + new SnippetAttributes(""" + {@snippet lang="" : + Hello, Snippet! + } + """, null, null), + new SnippetAttributes(""" + {@snippet lang="foo" id="bar" : + Hello, Snippet! + } + """, "bar", "foo") ); ClassBuilder classBuilder = new ClassBuilder(tb, "pkg.A") .setModifiers("public", "class"); forEachNumbered(snippets, (s, i) -> { classBuilder.addMembers( MethodBuilder.parse("public void case%s() { }".formatted(i)) - .setComments(s)); + .setComments("A method.", s.content())); }); classBuilder.write(srcDir); javadoc("-d", outDir.toString(), "-sourcepath", srcDir.toString(), "pkg"); checkExit(Exit.OK); + checkLinks(); for (int j = 0; j < snippets.size(); j++) { + SnippetAttributes snippet = snippets.get(j); checkOutput("pkg/A.html", true, """ case%s() -
+ System.out.println(msg);
+ """,
+ """
+
+ System.out.println(msg);
+ """,
+ """
+
+ System.out.println(msg);
+ """,
+ """
+
+ System.out.println(msg);
+ """,
+ """
+
+ System.out.println(msg);
+ """,
+ """
+
+ System.out.println(msg);
+ """,
+ """
+ user=jane
+ home=/home/jane
+ """,
+ """
+ user=jane
+ home=/home/jane
+ """,
+ """
+ user=jane
+ home=/home/jane
+ """);
+ }
+
/*
* This is a convenience method to iterate through a list.
* Unlike List.forEach, this method provides the consumer not only with an
@@ -858,8 +951,7 @@ public class TestSnippetTag extends JavadocTester {
""".formatted(id, t.expectedOutput()));
});
}
@@ -955,8 +1047,7 @@ public class TestSnippetTag extends JavadocTester {
""".formatted(index, expectedOutput));
});
}
@@ -1517,8 +1608,7 @@ public class TestSnippetTag extends JavadocTester {
""".formatted(index, t.expectedOutput()));
});
}
@@ -1635,8 +1725,7 @@ public class TestSnippetTag extends JavadocTester {
""");
checkOutput("pkg/A.html", true,
"""
@@ -1645,8 +1734,7 @@ public class TestSnippetTag extends JavadocTester {
""");
}
@@ -1747,8 +1835,7 @@ public class TestSnippetTag extends JavadocTester {
""".formatted(j));
}
@@ -1832,8 +1919,7 @@ public class TestSnippetTag extends JavadocTester {
""".formatted(index, t.expectedOutput()));
});
}
@@ -2165,8 +2251,7 @@ public class TestSnippetTag extends JavadocTester {
""".formatted(index, t.expectedOutput()));
});
}