8276124: Provide snippet support for properties files
Co-authored-by: Jonathan Gibbons <jjg@openjdk.org> Co-authored-by: Hannes Wallnöfer <hannesw@openjdk.org> Reviewed-by: jjg
This commit is contained in:
parent
96fe1d0d4d
commit
e785f69961
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets
test/langtools/jdk/javadoc/doclet/testSnippetTag
@ -30,6 +30,7 @@ import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
@ -63,6 +64,38 @@ import jdk.javadoc.internal.doclets.toolkit.util.Utils;
|
||||
*/
|
||||
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;}
|
||||
}
|
||||
|
||||
public SnippetTaglet() {
|
||||
super(DocTree.Kind.SNIPPET, true, EnumSet.allOf(Taglet.Location.class));
|
||||
}
|
||||
@ -217,6 +250,19 @@ public class SnippetTaglet extends BaseTaglet {
|
||||
}
|
||||
}
|
||||
|
||||
String lang = null;
|
||||
AttributeTree langAttr = attributes.get("lang");
|
||||
if (langAttr != null) {
|
||||
lang = stringValueOf(langAttr);
|
||||
} else if (containsClass) {
|
||||
lang = "java";
|
||||
} else if (containsFile) {
|
||||
lang = languageFromFileName(fileObject.getName());
|
||||
}
|
||||
|
||||
Optional<Language> language = Language.of(lang);
|
||||
|
||||
|
||||
// TODO cache parsed external snippet (WeakHashMap)
|
||||
|
||||
StyledText inlineSnippet = null;
|
||||
@ -224,7 +270,7 @@ public class SnippetTaglet extends BaseTaglet {
|
||||
|
||||
try {
|
||||
if (inlineContent != null) {
|
||||
inlineSnippet = parse(writer.configuration().getDocResources(), inlineContent);
|
||||
inlineSnippet = parse(writer.configuration().getDocResources(), language, inlineContent);
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
var path = writer.configuration().utils.getCommentHelper(holder)
|
||||
@ -239,7 +285,7 @@ public class SnippetTaglet extends BaseTaglet {
|
||||
|
||||
try {
|
||||
if (externalContent != null) {
|
||||
externalSnippet = parse(writer.configuration().getDocResources(), externalContent);
|
||||
externalSnippet = parse(writer.configuration().getDocResources(), language, externalContent);
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
assert fileObject != null;
|
||||
@ -289,15 +335,6 @@ public class SnippetTaglet extends BaseTaglet {
|
||||
assert inlineSnippet != null || externalSnippet != null;
|
||||
StyledText text = inlineSnippet != null ? inlineSnippet : externalSnippet;
|
||||
|
||||
String lang = null;
|
||||
AttributeTree langAttr = attributes.get("lang");
|
||||
if (langAttr != null) {
|
||||
lang = stringValueOf(langAttr);
|
||||
} else if (containsClass) {
|
||||
lang = "java";
|
||||
} else if (containsFile) {
|
||||
lang = languageFromFileName(fileObject.getName());
|
||||
}
|
||||
AttributeTree idAttr = attributes.get("id");
|
||||
String id = idAttr == null
|
||||
? null
|
||||
@ -326,8 +363,8 @@ public class SnippetTaglet extends BaseTaglet {
|
||||
""".formatted(inline, external);
|
||||
}
|
||||
|
||||
private StyledText parse(Resources resources, String content) throws ParseException {
|
||||
Parser.Result result = new Parser(resources).parse(content);
|
||||
private StyledText parse(Resources resources, Optional<Language> language, String content) throws ParseException {
|
||||
Parser.Result result = new Parser(resources).parse(language, content);
|
||||
result.actions().forEach(Action::perform);
|
||||
return result.text();
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import jdk.javadoc.internal.doclets.toolkit.Resources;
|
||||
import jdk.javadoc.internal.doclets.toolkit.taglets.SnippetTaglet;
|
||||
|
||||
/*
|
||||
* Semantics of a EOL comment; plus
|
||||
@ -76,10 +77,10 @@ import jdk.javadoc.internal.doclets.toolkit.Resources;
|
||||
*/
|
||||
public final class Parser {
|
||||
|
||||
// next-line tag behaves as if it were specified on the next line
|
||||
|
||||
private String eolMarker;
|
||||
private Matcher markedUpLine;
|
||||
private static final Pattern JAVA_COMMENT = Pattern.compile(
|
||||
"^(?<payload>.*)//(?<markup>\\s*@\\s*\\w+.+?)$");
|
||||
private static final Pattern PROPERTIES_COMMENT = Pattern.compile(
|
||||
"^(?<payload>[ \t]*([#!].*)?)[#!](?<markup>\\s*@\\s*\\w+.+?)$");
|
||||
|
||||
private final Resources resources;
|
||||
private final MarkupParser markupParser;
|
||||
@ -93,32 +94,23 @@ public final class Parser {
|
||||
this.markupParser = new MarkupParser(resources);
|
||||
}
|
||||
|
||||
public Result parse(String source) throws ParseException {
|
||||
return parse("//", source);
|
||||
public Result parse(Optional<SnippetTaglet.Language> language, String source) throws ParseException {
|
||||
SnippetTaglet.Language lang = language.orElse(SnippetTaglet.Language.JAVA);
|
||||
var p = switch (lang) {
|
||||
case JAVA -> JAVA_COMMENT;
|
||||
case PROPERTIES -> PROPERTIES_COMMENT;
|
||||
};
|
||||
return parse(p, source);
|
||||
}
|
||||
|
||||
/*
|
||||
* Newline characters in the returned text are of the \n form.
|
||||
*/
|
||||
public Result parse(String eolMarker, String source) throws ParseException {
|
||||
Objects.requireNonNull(eolMarker);
|
||||
private Result parse(Pattern commentPattern, String source) throws ParseException {
|
||||
Objects.requireNonNull(commentPattern);
|
||||
Objects.requireNonNull(source);
|
||||
if (!Objects.equals(eolMarker, this.eolMarker)) {
|
||||
if (eolMarker.length() < 1) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
for (int i = 0; i < eolMarker.length(); i++) {
|
||||
switch (eolMarker.charAt(i)) {
|
||||
case '\f', '\n', '\r' -> throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
this.eolMarker = eolMarker;
|
||||
// capture the rightmost eolMarker (e.g. "//")
|
||||
// The below Pattern.compile should never throw PatternSyntaxException
|
||||
Pattern pattern = Pattern.compile("^(.*)(" + Pattern.quote(eolMarker)
|
||||
+ "(\\s*@\\s*\\w+.+?))$");
|
||||
this.markedUpLine = pattern.matcher(""); // reusable matcher
|
||||
}
|
||||
|
||||
Matcher markedUpLine = commentPattern.matcher(""); // reusable matcher
|
||||
|
||||
tags.clear();
|
||||
regions.clear();
|
||||
@ -151,17 +143,17 @@ public final class Parser {
|
||||
if (!markedUpLine.matches()) { // (1)
|
||||
line = rawLine + (addLineTerminator ? "\n" : "");
|
||||
} else {
|
||||
String maybeMarkup = markedUpLine.group(3);
|
||||
String maybeMarkup = rawLine.substring(markedUpLine.start("markup"));
|
||||
List<Tag> parsedTags;
|
||||
try {
|
||||
parsedTags = markupParser.parse(maybeMarkup);
|
||||
} catch (ParseException e) {
|
||||
// translate error position from markup to file line
|
||||
throw new ParseException(e::getMessage, markedUpLine.start(3) + e.getPosition());
|
||||
throw new ParseException(e::getMessage, markedUpLine.start("markup") + e.getPosition());
|
||||
}
|
||||
for (Tag t : parsedTags) {
|
||||
t.lineSourceOffset = next.offset();
|
||||
t.markupLineOffset = markedUpLine.start(3);
|
||||
t.markupLineOffset = markedUpLine.start("markup");
|
||||
}
|
||||
thisLineTags.addAll(parsedTags);
|
||||
for (var tagIterator = thisLineTags.iterator(); tagIterator.hasNext(); ) {
|
||||
@ -176,7 +168,7 @@ public final class Parser {
|
||||
// TODO: log this with NOTICE;
|
||||
line = rawLine + (addLineTerminator ? "\n" : "");
|
||||
} else { // (3)
|
||||
String payload = markedUpLine.group(1);
|
||||
String payload = rawLine.substring(0, markedUpLine.end("payload"));
|
||||
line = payload + (addLineTerminator ? "\n" : "");
|
||||
}
|
||||
}
|
||||
|
@ -101,6 +101,12 @@ public class SnippetTester extends JavadocTester {
|
||||
return getSnippetHtmlRepresentation(pathToHtmlFile, content, Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
protected String getSnippetHtmlRepresentation(String pathToHtmlFile,
|
||||
String content,
|
||||
Optional<String> lang) {
|
||||
return getSnippetHtmlRepresentation(pathToHtmlFile, content, lang, Optional.empty());
|
||||
}
|
||||
|
||||
protected String getSnippetHtmlRepresentation(String pathToHtmlFile,
|
||||
String content,
|
||||
Optional<String> lang,
|
||||
|
@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8266666
|
||||
* @summary Implementation for snippets
|
||||
* @library /tools/lib ../../lib
|
||||
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||
* jdk.compiler/com.sun.tools.javac.main
|
||||
* jdk.javadoc/jdk.javadoc.internal.tool
|
||||
* @build javadoc.tester.* toolbox.ToolBox toolbox.ModuleBuilder builder.ClassBuilder
|
||||
* @run main TestLangProperties
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class TestLangProperties extends SnippetTester {
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
new TestLangProperties().runTests(m -> new Object[]{Paths.get(m.getName())});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPositiveOuterMarkup(Path base) throws Exception {
|
||||
var testCases = new ArrayList<TestSnippetMarkup.TestCase>();
|
||||
for (String whitespace1 : List.of("", " ", "\t"))
|
||||
for (String commentIndicator1 : List.of("#", "!"))
|
||||
for (String whitespace2 : List.of("", " ", "\t")) {
|
||||
String markup = whitespace1 + commentIndicator1
|
||||
+ whitespace2 + "@highlight :";
|
||||
var t = new TestSnippetMarkup.TestCase(
|
||||
"""
|
||||
%s
|
||||
coffee=espresso
|
||||
tea=black
|
||||
""".formatted(markup),
|
||||
"""
|
||||
|
||||
<span class="bold">coffee=espresso
|
||||
</span>tea=black
|
||||
""");
|
||||
testCases.add(t);
|
||||
}
|
||||
testPositive(base, testCases);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPositiveInnerMarkup(Path base) throws Exception {
|
||||
var testCases = new ArrayList<TestSnippetMarkup.TestCase>();
|
||||
for (String whitespace1 : List.of("", " ", "\t"))
|
||||
for (String commentIndicator1 : List.of("#", "!"))
|
||||
for (String whitespace2 : List.of("", " ", "\t"))
|
||||
for (String unrelatedComment : List.of("a comment"))
|
||||
for (String whitespace3 : List.of("", " "))
|
||||
for (String commentIndicator2 : List.of("#", "!")) {
|
||||
String payload = whitespace1 + commentIndicator1 + whitespace2 + unrelatedComment;
|
||||
String markup = payload + whitespace3 + commentIndicator2 + "@highlight :";
|
||||
var t = new TestSnippetMarkup.TestCase(
|
||||
"""
|
||||
%s
|
||||
coffee=espresso
|
||||
tea=black
|
||||
""".formatted(markup),
|
||||
"""
|
||||
%s
|
||||
<span class="bold">coffee=espresso
|
||||
</span>tea=black
|
||||
""".formatted(payload));
|
||||
testCases.add(t);
|
||||
}
|
||||
testPositive(base, testCases);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPositiveIneffectiveOuterMarkup(Path base) throws Exception {
|
||||
var testCases = new ArrayList<TestSnippetMarkup.TestCase>();
|
||||
for (String whitespace1 : List.of("", " ", "\t"))
|
||||
for (String commentIndicator1 : List.of("#", "!"))
|
||||
for (String whitespace2 : List.of("", " ", "\t")) {
|
||||
String ineffectiveMarkup = whitespace1
|
||||
+ commentIndicator1 + whitespace2
|
||||
+ "@highlight :";
|
||||
var t = new TestSnippetMarkup.TestCase(
|
||||
"""
|
||||
coffee=espresso%s
|
||||
tea=black
|
||||
""".formatted(ineffectiveMarkup),
|
||||
"""
|
||||
coffee=espresso%s
|
||||
tea=black
|
||||
""".formatted(ineffectiveMarkup));
|
||||
testCases.add(t);
|
||||
}
|
||||
testPositive(base, testCases);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPositiveIneffectiveInnerMarkup(Path base) throws Exception {
|
||||
var testCases = new ArrayList<TestSnippetMarkup.TestCase>();
|
||||
for (String whitespace1 : List.of("", " ", "\t"))
|
||||
for (String commentIndicator1 : List.of("#", "!"))
|
||||
for (String whitespace2 : List.of("", " ", "\t"))
|
||||
for (String unrelatedComment : List.of("a comment"))
|
||||
for (String whitespace3 : List.of("", " "))
|
||||
for (String commentIndicator2 : List.of("#", "!")) {
|
||||
String ineffectiveMarkup = whitespace1
|
||||
+ commentIndicator1 + whitespace2
|
||||
+ unrelatedComment + whitespace3
|
||||
+ commentIndicator2 + "@highlight :";
|
||||
var t = new TestSnippetMarkup.TestCase(
|
||||
"""
|
||||
coffee=espresso%s
|
||||
tea=black
|
||||
""".formatted(ineffectiveMarkup),
|
||||
"""
|
||||
coffee=espresso%s
|
||||
tea=black
|
||||
""".formatted(ineffectiveMarkup));
|
||||
testCases.add(t);
|
||||
}
|
||||
testPositive(base, testCases);
|
||||
}
|
||||
|
||||
private void testPositive(Path base, List<TestSnippetMarkup.TestCase> testCases)
|
||||
throws IOException {
|
||||
StringBuilder methods = new StringBuilder();
|
||||
forEachNumbered(testCases, (i, n) -> {
|
||||
String r = i.region().isBlank() ? "" : "region=" + i.region();
|
||||
var methodDef = """
|
||||
|
||||
/**
|
||||
{@snippet lang="properties" %s:
|
||||
%s}*/
|
||||
public void case%s() {}
|
||||
""".formatted(r, i.input(), n);
|
||||
methods.append(methodDef);
|
||||
});
|
||||
var classDef = """
|
||||
public class A {
|
||||
%s
|
||||
}
|
||||
""".formatted(methods.toString());
|
||||
Path src = Files.createDirectories(base.resolve("src"));
|
||||
tb.writeJavaFiles(src, classDef);
|
||||
javadoc("-d", base.resolve("out").toString(),
|
||||
"-sourcepath", src.toString(),
|
||||
src.resolve("A.java").toString());
|
||||
checkExit(Exit.OK);
|
||||
checkNoCrashes();
|
||||
forEachNumbered(testCases, (t, index) -> {
|
||||
String html = """
|
||||
<span class="element-name">case%s</span>()</div>
|
||||
<div class="block">
|
||||
%s
|
||||
</div>""".formatted(index, getSnippetHtmlRepresentation("A.html",
|
||||
t.expectedOutput(), Optional.of("properties")));
|
||||
checkOutput("A.html", true, html);
|
||||
});
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user