8299080: Wrong default value of snippet lang attribute
Reviewed-by: jjg
This commit is contained in:
parent
871362870e
commit
6df7acbc74
@ -26,6 +26,8 @@
|
||||
package jdk.javadoc.internal.doclets.formats.html.taglets;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@ -65,41 +67,16 @@ import jdk.javadoc.internal.doclets.toolkit.Resources;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocPaths;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
|
||||
|
||||
import static jdk.javadoc.internal.doclets.formats.html.taglets.SnippetTaglet.Language.*;
|
||||
|
||||
/**
|
||||
* A taglet that represents the {@code @snippet} tag.
|
||||
*/
|
||||
public class SnippetTaglet extends BaseTaglet {
|
||||
|
||||
public enum Language {
|
||||
|
||||
JAVA("java"),
|
||||
PROPERTIES("properties");
|
||||
|
||||
private static final Map<String, Language> languages;
|
||||
|
||||
static {
|
||||
Map<String, Language> tmp = new HashMap<>();
|
||||
for (var language : values()) {
|
||||
String id = Objects.requireNonNull(language.identifier);
|
||||
if (tmp.put(id, language) != null)
|
||||
throw new IllegalStateException(); // 1-1 correspondence
|
||||
}
|
||||
languages = Map.copyOf(tmp);
|
||||
}
|
||||
|
||||
Language(String id) {
|
||||
identifier = id;
|
||||
}
|
||||
|
||||
private final String identifier;
|
||||
|
||||
public static Optional<Language> of(String identifier) {
|
||||
if (identifier == null)
|
||||
return Optional.empty();
|
||||
return Optional.ofNullable(languages.get(identifier));
|
||||
}
|
||||
|
||||
public String getIdentifier() {return identifier;}
|
||||
JAVA,
|
||||
PROPERTIES;
|
||||
}
|
||||
|
||||
SnippetTaglet(HtmlConfiguration config) {
|
||||
@ -361,18 +338,30 @@ public class SnippetTaglet extends BaseTaglet {
|
||||
}
|
||||
}
|
||||
|
||||
String lang = null;
|
||||
String lang;
|
||||
AttributeTree langAttr = attributes.get("lang");
|
||||
if (langAttr != null) {
|
||||
|
||||
if (langAttr != null) { // the lang attribute overrides everything else
|
||||
lang = stringValueOf(langAttr);
|
||||
} else if (containsClass) {
|
||||
} else if (inlineContent != null && externalContent == null) { // an inline snippet
|
||||
lang = "java";
|
||||
} else if (containsFile) {
|
||||
lang = languageFromFileName(fileObject.getName());
|
||||
} else if (externalContent != null) { // an external or a hybrid snippet
|
||||
if (containsClass) { // the class attribute means Java
|
||||
lang = "java";
|
||||
} else {
|
||||
var uri = fileObject.toUri();
|
||||
var path = uri.getPath() != null ? uri.getPath() : "";
|
||||
var fileName = path.substring(path.lastIndexOf('/') + 1);
|
||||
lang = languageFromFileName(fileName);
|
||||
}
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
Optional<Language> language = Language.of(lang);
|
||||
|
||||
var language = switch (lang) {
|
||||
case "properties" -> PROPERTIES;
|
||||
case null, default -> JAVA;
|
||||
};
|
||||
|
||||
// TODO cache parsed external snippet (WeakHashMap)
|
||||
|
||||
@ -482,7 +471,7 @@ public class SnippetTaglet extends BaseTaglet {
|
||||
""".formatted(inline, external);
|
||||
}
|
||||
|
||||
private StyledText parse(Resources resources, Diags diags, Optional<Language> language, String content) throws ParseException {
|
||||
private StyledText parse(Resources resources, Diags diags, Language language, String content) throws ParseException {
|
||||
Parser.Result result = new Parser(resources).parse(diags, language, content);
|
||||
result.actions().forEach(Action::perform);
|
||||
return result.text();
|
||||
@ -505,14 +494,16 @@ public class SnippetTaglet extends BaseTaglet {
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
// The assumption is simple: a file extension is the language
|
||||
// identifier.
|
||||
// Was about to use Path.getExtension introduced in 8057113, but then
|
||||
// learned that it was removed in 8298303.
|
||||
int lastPeriod = fileName.lastIndexOf('.');
|
||||
if (lastPeriod <= 0) {
|
||||
return null;
|
||||
}
|
||||
return (lastPeriod == fileName.length() - 1) ? null : fileName.substring(lastPeriod + 1);
|
||||
}
|
||||
|
||||
private void error(TagletWriter writer, Element holder, DocTree tag, String key, Object... args) {
|
||||
messages.error(utils.getCommentHelper(holder).getDocTreePath(tag), key, args);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2024, 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
|
||||
@ -89,9 +89,8 @@ public final class Parser {
|
||||
this.markupParser = new MarkupParser(resources);
|
||||
}
|
||||
|
||||
public Result parse(SnippetTaglet.Diags diags, Optional<SnippetTaglet.Language> language, String source) throws ParseException {
|
||||
SnippetTaglet.Language lang = language.orElse(SnippetTaglet.Language.JAVA);
|
||||
var p = switch (lang) {
|
||||
public Result parse(SnippetTaglet.Diags diags, SnippetTaglet.Language language, String source) throws ParseException {
|
||||
var p = switch (language) {
|
||||
case JAVA -> JAVA_COMMENT;
|
||||
case PROPERTIES -> PROPERTIES_COMMENT;
|
||||
};
|
||||
|
@ -121,7 +121,7 @@ public class TestMarkdownTaglets extends JavadocTester {
|
||||
<div class="snippet-container"><button class="copy snippet-copy" aria-label="Copy snippet" \
|
||||
onclick="copySnippet(this)"><span data-copied="Copied!">Copy</span>\
|
||||
<img src="../resource-files/copy.svg" alt="Copy snippet"></button>
|
||||
<pre class="snippet" id="snippet-snippet_standalone()1"><code> this is snippet_standalone
|
||||
<pre class="snippet" id="snippet-snippet_standalone()1"><code class="language-java"> this is snippet_standalone
|
||||
</code></pre>
|
||||
</div>
|
||||
|
||||
@ -131,7 +131,7 @@ public class TestMarkdownTaglets extends JavadocTester {
|
||||
<div class="block"><p>First sentence.</p>
|
||||
<p>Before.</p>
|
||||
<div class="snippet-container"><button class="copy snippet-copy" aria-label="Copy snippet" onclick="copySnippet(this)"><span data-copied="Copied!">Copy</span><img src="../resource-files/copy.svg" alt="Copy snippet"></button>
|
||||
<pre class="snippet" id="snippet-snippet_wrapped()1"><code> this is a snippet_wrapped
|
||||
<pre class="snippet" id="snippet-snippet_wrapped()1"><code class="language-java"> this is a snippet_wrapped
|
||||
</code></pre>
|
||||
</div>
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2024, 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
|
||||
@ -100,7 +100,7 @@ public class SnippetTester extends JavadocTester {
|
||||
|
||||
protected String getSnippetHtmlRepresentation(String pathToHtmlFile,
|
||||
String content) {
|
||||
return getSnippetHtmlRepresentation(pathToHtmlFile, content, Optional.empty(), Optional.empty());
|
||||
return getSnippetHtmlRepresentation(pathToHtmlFile, content, Optional.of("java"), Optional.empty());
|
||||
}
|
||||
|
||||
protected String getSnippetHtmlRepresentation(String pathToHtmlFile,
|
||||
|
@ -366,7 +366,7 @@ First line // @highlight :
|
||||
<span class="element-name">case%s</span>()</div>
|
||||
<div class="block">
|
||||
%s
|
||||
</div>""".formatted(index, getSnippetHtmlRepresentation("A.html", t.expectedOutput(), Optional.empty(), Optional.of("snippet-case" + index + "()2")));
|
||||
</div>""".formatted(index, getSnippetHtmlRepresentation("A.html", t.expectedOutput(), Optional.of("java"), Optional.of("snippet-case" + index + "()2")));
|
||||
checkOutput("A.html", true, html);
|
||||
});
|
||||
}
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8266666 8275788 8276964
|
||||
* @bug 8266666 8275788 8276964 8299080
|
||||
* @summary Implementation for snippets
|
||||
* @library /tools/lib ../../lib
|
||||
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||
@ -108,27 +108,27 @@ public class TestSnippetTag extends SnippetTester {
|
||||
{@snippet id="foo1" :
|
||||
Hello, Snippet!
|
||||
}
|
||||
""", "foo1", null),
|
||||
""", "foo1", "java"),
|
||||
new SnippetAttributes("""
|
||||
{@snippet id="foo2":
|
||||
Hello, Snippet!
|
||||
}
|
||||
""", "foo2", null),
|
||||
""", "foo2", "java"),
|
||||
new SnippetAttributes("""
|
||||
{@snippet id='foo3' :
|
||||
Hello, Snippet!
|
||||
}
|
||||
""", "foo3", null),
|
||||
""", "foo3", "java"),
|
||||
new SnippetAttributes("""
|
||||
{@snippet id='foo4':
|
||||
Hello, Snippet!
|
||||
}
|
||||
""", "foo4", null),
|
||||
""", "foo4", "java"),
|
||||
new SnippetAttributes("""
|
||||
{@snippet id=foo5 :
|
||||
Hello, Snippet!
|
||||
}
|
||||
""", "foo5", null),
|
||||
""", "foo5", "java"),
|
||||
// (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
|
||||
@ -137,28 +137,28 @@ public class TestSnippetTag extends SnippetTester {
|
||||
// {@snippet id=foo6:
|
||||
// Hello, Snippet!
|
||||
// }
|
||||
// """, "foo6", null),
|
||||
// """, "foo6", "java"),
|
||||
|
||||
new SnippetAttributes("""
|
||||
{@snippet id="" :
|
||||
Hello, Snippet!
|
||||
}
|
||||
""", null, null),
|
||||
""", null, "java"),
|
||||
new SnippetAttributes("""
|
||||
{@snippet id="":
|
||||
Hello, Snippet!
|
||||
}
|
||||
""", null, null),
|
||||
""", null, "java"),
|
||||
new SnippetAttributes("""
|
||||
{@snippet id='':
|
||||
Hello, Snippet!
|
||||
}
|
||||
""", null, null),
|
||||
""", null, "java"),
|
||||
new SnippetAttributes("""
|
||||
{@snippet id=:
|
||||
Hello, Snippet!
|
||||
}
|
||||
""", null, null),
|
||||
""", null, "java"),
|
||||
new SnippetAttributes("""
|
||||
{@snippet lang="java" :
|
||||
Hello, Snippet!
|
||||
@ -880,7 +880,7 @@ public class TestSnippetTag extends SnippetTester {
|
||||
"""
|
||||
<span class="element-name">case%s</span>()</div>
|
||||
<div class="block">
|
||||
%s""".formatted(id, getSnippetHtmlRepresentation("pkg/A.html", t.expectedOutput(), Optional.empty(),
|
||||
%s""".formatted(id, getSnippetHtmlRepresentation("pkg/A.html", t.expectedOutput(), Optional.of("java"),
|
||||
Optional.of("snippet-case" + id + "()2"))));
|
||||
});
|
||||
}
|
||||
@ -973,7 +973,7 @@ public class TestSnippetTag extends SnippetTester {
|
||||
"""
|
||||
<span class="element-name">case%s</span>()</div>
|
||||
<div class="block">
|
||||
%s""".formatted(index, getSnippetHtmlRepresentation("pkg/A.html", expectedOutput, Optional.empty(),
|
||||
%s""".formatted(index, getSnippetHtmlRepresentation("pkg/A.html", expectedOutput, Optional.of("txt"),
|
||||
Optional.of("snippet-case" + index + "()2"))));
|
||||
});
|
||||
}
|
||||
@ -1552,7 +1552,7 @@ public class TestSnippetTag extends SnippetTester {
|
||||
"""
|
||||
<span class="element-name">case%s</span>()</div>
|
||||
<div class="block">
|
||||
%s""".formatted(index, getSnippetHtmlRepresentation("pkg/A.html", t.expectedOutput(), Optional.empty(),
|
||||
%s""".formatted(index, getSnippetHtmlRepresentation("pkg/A.html", t.expectedOutput(), Optional.of("java"),
|
||||
Optional.of("snippet-case" + index + "()2"))));
|
||||
});
|
||||
}
|
||||
@ -1666,13 +1666,13 @@ public class TestSnippetTag extends SnippetTester {
|
||||
"""
|
||||
<span class="element-name">case0</span>()</div>
|
||||
<div class="block">
|
||||
""" + getSnippetHtmlRepresentation("pkg/A.html", "", Optional.empty(),
|
||||
""" + getSnippetHtmlRepresentation("pkg/A.html", "", Optional.of("txt"),
|
||||
Optional.of("snippet-case0()2")));
|
||||
checkOutput("pkg/A.html", true,
|
||||
"""
|
||||
<span class="element-name">case1</span>()</div>
|
||||
<div class="block">
|
||||
""" + getSnippetHtmlRepresentation("pkg/A.html", "", Optional.empty(),
|
||||
""" + getSnippetHtmlRepresentation("pkg/A.html", "", Optional.of("txt"),
|
||||
Optional.of("snippet-case1()2")));
|
||||
}
|
||||
|
||||
@ -1851,12 +1851,126 @@ public class TestSnippetTag extends SnippetTester {
|
||||
"""
|
||||
<span class="element-name">case%s</span>()</div>
|
||||
<div class="block">
|
||||
%s""".formatted(index, getSnippetHtmlRepresentation("pkg/A.html", t.expectedOutput(), Optional.empty(),
|
||||
%s""".formatted(index, getSnippetHtmlRepresentation("pkg/A.html", t.expectedOutput(), Optional.of("java"),
|
||||
Optional.of("snippet-case" + index + "()2"))));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPositiveExternalHybridLangAttribute(Path base) throws Exception {
|
||||
|
||||
Path srcDir = base.resolve("src");
|
||||
Path outDir = base.resolve("out");
|
||||
|
||||
record TestCase(String tag, String expectedContent, String expectedLang) { }
|
||||
|
||||
final var testCases = List.of(
|
||||
// -------------------- external snippets --------------------
|
||||
// if there's no file extension and no lang attribute, then
|
||||
// markup is that of "java" and the class="language-" attribute
|
||||
// is absent
|
||||
new TestCase("""
|
||||
{@snippet file=".file"}
|
||||
""", """
|
||||
# @highlight substring=hi:
|
||||
hi <span class="bold">there</span>
|
||||
""", null),
|
||||
new TestCase("""
|
||||
{@snippet file="file"}
|
||||
""", """
|
||||
# @highlight substring=hi:
|
||||
hi <span class="bold">there</span>
|
||||
""", null),
|
||||
// if the file extension differs from the value of the lang
|
||||
// attribute, which is set to "java", "properties" or other,
|
||||
// then the class="language-" attribute is that of lang and
|
||||
// markup is that of "properties" for lang=properties and
|
||||
// markup is that of "java" for anything else
|
||||
new TestCase("""
|
||||
{@snippet file="File.java" lang=properties}
|
||||
""", """
|
||||
<span class="bold">hi</span> there // @<span class="bold">hi</span>ghlight substring=there
|
||||
""", "properties"),
|
||||
new TestCase("""
|
||||
{@snippet file="file.properties" lang=java}
|
||||
""", """
|
||||
# @highlight substring=hi:
|
||||
hi <span class="bold">there</span>
|
||||
""", "java"),
|
||||
new TestCase("""
|
||||
{@snippet file="File.java" lang=txt}
|
||||
""", """
|
||||
# @highlight substring=hi:
|
||||
hi <span class="bold">there</span>
|
||||
""", "txt"),
|
||||
// if there's no file extension, but the lang attribute is set
|
||||
// to "java", "properties", or other, then the class="language-"
|
||||
// attribute is that of lang and markup is that of "properties"
|
||||
// for lang=properties and markup is that of "java" for
|
||||
// anything else
|
||||
new TestCase("""
|
||||
{@snippet file="file" lang=properties}
|
||||
""", """
|
||||
<span class="bold">hi</span> there // @<span class="bold">hi</span>ghlight substring=there
|
||||
""", "properties"),
|
||||
new TestCase("""
|
||||
{@snippet file="file" lang=java}
|
||||
""", """
|
||||
# @highlight substring=hi:
|
||||
hi <span class="bold">there</span>
|
||||
""", "java"),
|
||||
new TestCase("""
|
||||
{@snippet file="file" lang=txt}
|
||||
""", """
|
||||
# @highlight substring=hi:
|
||||
hi <span class="bold">there</span>
|
||||
""", "txt"),
|
||||
// --------------------- hybrid snippets ---------------------
|
||||
// the lang attribute "overrides" file extension
|
||||
new TestCase("""
|
||||
{@snippet file="File.java" lang=properties:
|
||||
# @highlight substring=hi:
|
||||
hi there // @highlight substring=there
|
||||
}
|
||||
""", """
|
||||
<span class="bold">hi</span> there // @<span class="bold">hi</span>ghlight substring=there
|
||||
""", "properties"),
|
||||
// if the lang attribute is absent, file extension determines
|
||||
// markup and the the class="language-" attribute
|
||||
new TestCase("""
|
||||
{@snippet file="file.properties":
|
||||
# @highlight substring=hi:
|
||||
hi there // @highlight substring=there
|
||||
}
|
||||
""", """
|
||||
<span class="bold">hi</span> there // @<span class="bold">hi</span>ghlight substring=there
|
||||
""", "properties")
|
||||
);
|
||||
|
||||
for (var f : List.of(".file", "file", "File.java", "file.properties"))
|
||||
addSnippetFile(srcDir, "pkg", f, """
|
||||
# @highlight substring=hi:
|
||||
hi there // @highlight substring=there
|
||||
""");
|
||||
|
||||
ClassBuilder classBuilder = new ClassBuilder(tb, "pkg.A")
|
||||
.setModifiers("public", "class");
|
||||
forEachNumbered(testCases, (s, i) -> classBuilder.addMembers(
|
||||
MethodBuilder.parse("public void case%s() { }".formatted(i)).setComments(s.tag)));
|
||||
classBuilder.write(srcDir);
|
||||
javadoc("-d", outDir.toString(),
|
||||
"-sourcepath", srcDir.toString(),
|
||||
"pkg");
|
||||
checkExit(Exit.OK);
|
||||
forEachNumbered(testCases, (t, i) -> checkOutput("pkg/A.html", true, """
|
||||
<span class="element-name">case%s</span>()</div>
|
||||
<div class="block">
|
||||
%s
|
||||
""".formatted(i, getSnippetHtmlRepresentation("pkg/A.html", t.expectedContent, Optional.ofNullable(t.expectedLang),
|
||||
Optional.of("snippet-case" + i + "()2")))));
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testNegativeHybridTag_FileNotFound(Path base) throws Exception {
|
||||
Path srcDir = base.resolve("src");
|
||||
Path outDir = base.resolve("out");
|
||||
@ -2310,7 +2424,7 @@ public class TestSnippetTag extends SnippetTester {
|
||||
"""
|
||||
<span class="element-name">case%s</span>()</div>
|
||||
<div class="block">
|
||||
%s""".formatted(index, getSnippetHtmlRepresentation("pkg/A.html", t.expectedOutput(), Optional.empty(),
|
||||
%s""".formatted(index, getSnippetHtmlRepresentation("pkg/A.html", t.expectedOutput(), Optional.of("txt"),
|
||||
Optional.of("snippet-case" + index + "()2"))));
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user