8286101: Support formatting in @value tag

Reviewed-by: prappo
This commit is contained in:
Jonathan Gibbons 2022-06-13 18:13:21 +00:00
parent 8f400b9aab
commit 53a0acee06
18 changed files with 334 additions and 12 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2022, 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
@ -30,6 +30,7 @@ package com.sun.source.doctree;
*
* <pre>
* {&#064;value reference}
* {&#064;value format reference}
* </pre>
*
* @since 1.8
@ -40,4 +41,16 @@ public interface ValueTree extends InlineTagTree {
* @return the reference
*/
ReferenceTree getReference();
/**
* Returns the format string, or {@code null} if none was provided.
*
* @return the format string
*
* @implSpec This implementation returns {@code null}.
* @since 19
*/
default TextTree getFormat() {
return null;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2022, 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
@ -413,6 +413,19 @@ public interface DocTreeFactory {
*/
ValueTree newValueTree(ReferenceTree ref);
/**
* Creates a new {@code ValueTree} object, to represent a {@code {@value }} tag.
* @param format a format string for the value
* @param ref a reference to the value
* @return a {@code ValueTree} object
*
* @implSpec This implementation calls {@link #newValueTree(ReferenceTree) newValueTree(ref)}.
* @since 19
*/
default ValueTree newValueTree(TextTree format, ReferenceTree ref) {
return newValueTree(ref);
}
/**
* Creates a new {@code VersionTree} object, to represent a {@code {@version }} tag.
* @param text the content of the tag

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2022, 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
@ -638,7 +638,9 @@ public class DocTreeScanner<R,P> implements DocTreeVisitor<R,P> {
*/
@Override
public R visitValue(ValueTree node, P p) {
return scan(node.getReference(), p);
R r = scan(node.getFormat(), p);
r = scanAndReduce(node.getReference(), p, r);
return r;
}
/**

View File

@ -1579,15 +1579,32 @@ public class DocCommentParser {
}
},
// {@value package.class#field}
// {@value [format-string] package.class#field}
new TagParser(TagParser.Kind.INLINE, DCTree.Kind.VALUE) {
@Override
public DCTree parse(int pos) throws ParseException {
skipWhitespace();
DCText format;
switch (ch) {
case '%' -> {
format = inlineWord();
skipWhitespace();
}
case '"' -> {
format = quotedString();
skipWhitespace();
}
default -> {
format = null;
}
}
DCReference ref = reference(true);
skipWhitespace();
if (ch == '}') {
nextChar();
return m.at(pos).newValueTree(ref);
return format == null
? m.at(pos).newValueTree(ref)
: m.at(pos).newValueTree(format, ref);
}
nextChar();
throw new ParseException("dc.unexpected.content");

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2022, 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
@ -1310,9 +1310,11 @@ public abstract class DCTree implements DocTree {
}
public static class DCValue extends DCInlineTag implements ValueTree {
public final DCText format;
public final DCReference ref;
DCValue(DCReference ref) {
DCValue(DCText format, DCReference ref) {
this.format = format;
this.ref = ref;
}
@ -1326,6 +1328,11 @@ public abstract class DCTree implements DocTree {
return v.visitValue(this, d);
}
@Override @DefinedBy(Api.COMPILER_TREE)
public TextTree getFormat() {
return format;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public ReferenceTree getReference() {
return ref;

View File

@ -632,6 +632,10 @@ public class DocPretty implements DocTreeVisitor<Void,Void> {
try {
print("{");
printTagName(node);
if (node.getFormat() != null) {
print(" ");
print(node.getFormat());
}
if (node.getReference() != null) {
print(" ");
print(node.getReference());

View File

@ -480,8 +480,13 @@ public class DocTreeMaker implements DocTreeFactory {
@Override @DefinedBy(Api.COMPILER_TREE)
public DCValue newValueTree(ReferenceTree ref) {
return newValueTree(null, ref);
}
@Override @DefinedBy(Api.COMPILER_TREE)
public DCValue newValueTree(TextTree format, ReferenceTree ref) {
// TODO: verify the reference is to a constant value
DCValue tree = new DCValue((DCReference) ref);
DCValue tree = new DCValue((DCText) format, (DCReference) ref);
tree.pos = pos;
return tree;
}

View File

@ -123,6 +123,17 @@ public class Messages {
report(ERROR, fo, start, pos, end, resources.getText(key, args));
}
/**
* Reports an error message to the doclet's reporter.
*
* @param e an element identifying the position to be included with the message
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message
*/
public void error(Element e, String key, Object... args) {
report(ERROR, e, resources.getText(key, args));
}
// ***** Warnings *****
/**

View File

@ -203,6 +203,7 @@ doclet.Groupname_already_used=In -group option, groupname already used: {0}
doclet.value_tag_invalid_reference={0} (referenced by @value tag) is an unknown reference.
doclet.value_tag_invalid_constant=@value tag (which references {0}) can only be used in constants.
doclet.value_tag_invalid_use=@value tag cannot be used here.
doclet.value_tag_invalid_format=invalid format: {0}
doclet.dest_dir_create=Creating destination directory: "{0}"
doclet.in={0} in {1}
doclet.Fields=Fields

View File

@ -26,10 +26,14 @@
package jdk.javadoc.internal.doclets.toolkit.taglets;
import java.util.EnumSet;
import java.util.IllegalFormatException;
import java.util.Optional;
import javax.lang.model.element.Element;
import javax.lang.model.element.VariableElement;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.TextTree;
import com.sun.source.doctree.ValueTree;
import jdk.javadoc.doclet.Taglet.Location;
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
import jdk.javadoc.internal.doclets.toolkit.Content;
@ -95,8 +99,27 @@ public class ValueTaglet extends BaseTaglet {
"doclet.value_tag_invalid_reference", tag.toString());
}
} else if (field.getConstantValue() != null) {
TextTree format = ((ValueTree) tag).getFormat();
String text;
if (format != null) {
String f = format.getBody();
if (f.startsWith("\"")) {
f = f.substring(1, f.length() - 1);
}
try {
text = String.format(configuration.getLocale(), f, field.getConstantValue());
} catch (IllegalFormatException e) {
messages.error(holder,
"doclet.value_tag_invalid_format", format);
return writer.invalidTagOutput(
messages.getResources().getText("doclet.value_tag_invalid_format", format),
Optional.empty());
}
} else {
text = utils.constantValueExpression(field);
}
return writer.valueTagOutput(field,
utils.constantValueExpression(field),
text,
// TODO: investigate and cleanup
// in the j.l.m world, equals will not be accurate
// !field.equals(tag.holder())

View File

@ -1115,6 +1115,16 @@ public class Checker extends DocTreePathScanner<Void, Void> {
if (!isConstant(e))
env.messages.error(REFERENCE, tree, "dc.value.not.a.constant");
}
TextTree format = tree.getFormat();
if (format != null) {
String f = format.getBody().toString();
long count = format.getBody().toString().chars()
.filter(ch -> ch == '%')
.count();
if (count != 1) {
env.messages.error(REFERENCE, format, "dc.value.bad.format", f);
}
}
markEnclosingTag(Flag.HAS_INLINE_TAG);
return super.visitValue(tree, ignore);

View File

@ -88,6 +88,7 @@ dc.tag.unknown = unknown tag: {0}
dc.tag.not.supported.html5 = tag not supported in HTML5: {0}
dc.text.not.allowed = text not allowed in <{0}> element
dc.unexpected.comment=documentation comment not expected here
dc.value.bad.format=invalid format: {0}
dc.value.not.allowed.here='{@value} not allowed here
dc.value.not.a.constant=value does not refer to a constant

View File

@ -0,0 +1,119 @@
/*
* Copyright (c) 2022, 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 8286101
* @summary Support formatting in at-value tag
* @library /tools/lib ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
* jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* @build javadoc.tester.*
* @run main TestValueFormats
*/
import javadoc.tester.JavadocTester;
import toolbox.ModuleBuilder;
import toolbox.ToolBox;
import java.nio.file.Path;
import java.nio.file.Paths;
public class TestValueFormats extends JavadocTester {
final ToolBox tb;
public static void main(String... args) throws Exception {
TestValueFormats tester = new TestValueFormats();
tester.runTests(m -> new Object[]{Paths.get(m.getName())});
}
TestValueFormats() {
tb = new ToolBox();
}
@Test
public void testValid(Path base) throws Exception {
Path srcDir = base.resolve("src");
tb.writeJavaFiles(srcDir,
"""
package p;
/**
* Comment.
*/
public class C {
/** The value {@value} is {@value %4x} or {@value "0x%04x"}. */
public static final int i65535 = 65535;
/** The value {@value} is {@value %5.2f}. */
public static final double pi = 3.1415926525;
}""");
Path outDir = base.resolve("out");
javadoc("-d", outDir.toString(),
"-sourcepath", srcDir.toString(),
"p");
checkExit(Exit.OK);
checkOutput("p/C.html", true,
"""
<h3>i65535</h3>
<div class="member-signature"><span class="modifiers">public static final</span>&nbsp;<span clas\
s="return-type">int</span>&nbsp;<span class="element-name">i65535</span></div>
<div class="block">The value 65535 is ffff or 0xffff.</div>""",
"""
<h3>pi</h3>
<div class="member-signature"><span class="modifiers">public static final</span>&nbsp;<span class="return-type">double</span>&nbsp;<span class="element-name">pi</span></div>
<div class="block">The value 3.1415926525 is 3.14.</div>""");
}
@Test
public void testBadFormat(Path base) throws Exception {
Path srcDir = base.resolve("src");
tb.writeJavaFiles(srcDir,
"""
package p;
/**
* Comment.
*/
public class C {
/** The value {@value} is {@value %a}. */
public static final int i65535 = 65535;
}""");
Path outDir = base.resolve("out");
javadoc("-d", outDir.toString(),
"-sourcepath", srcDir.toString(),
"p");
checkExit(Exit.ERROR);
checkOutput("p/C.html", true,
"""
<h3>i65535</h3>
<div class="member-signature"><span class="modifiers">public static final</span>&nbsp;<span class="return-type">int</span>&nbsp;<span class="element-name">i65535</span></div>
<div class="block">The value 65535 is <span class="invalid-tag">invalid format: %a</span>.</div>""");
}
}

View File

@ -65,4 +65,16 @@ public class ValueTest {
/** invalid enum constant: {@value Thread.State#NEW} */
public int badEnum;
/** valid: {@value %04x} */
public static final int maxShort = 65535;
/** valid: {@value "%5.2f"} */
public static final double pi = 3.14159265358979323846;
/** invalid format: {@value %%04x} */
public static final int f3 = 0;
/** invalid format: {@value "04x"} */
public static final int f4 = 0;
}

View File

@ -19,4 +19,10 @@ ValueTest.java:63: error: value does not refer to a constant
ValueTest.java:66: error: value does not refer to a constant
/** invalid enum constant: {@value Thread.State#NEW} */
^
7 errors
ValueTest.java:75: error: invalid format: %%04x
/** invalid format: {@value %%04x} */
^
ValueTest.java:78: error: invalid format: "04x"
/** invalid format: {@value "04x"} */
^
9 errors

View File

@ -699,6 +699,7 @@ public class DocCommentTester {
public Void visitValue(ValueTree node, Void p) {
header(node);
indent(+1);
print("format", node.getFormat());
print("reference", node.getReference());
indent(-1);
indent();

View File

@ -43,6 +43,7 @@ DocComment[DOC_COMMENT, pos:1
firstSentence: 2
Text[TEXT, pos:1, abc_]
Value[VALUE, pos:5
format: null
reference: null
]
body: empty
@ -59,6 +60,7 @@ DocComment[DOC_COMMENT, pos:1
firstSentence: 2
Text[TEXT, pos:1, abc_]
Value[VALUE, pos:5
format: null
reference:
Reference[REFERENCE, pos:13, java.awt.Color#RED]
]
@ -76,6 +78,7 @@ DocComment[DOC_COMMENT, pos:1
firstSentence: 2
Text[TEXT, pos:1, abc_]
Value[VALUE, pos:5
format: null
reference:
Reference[REFERENCE, pos:13, java.awt.Color#RED]
]
@ -102,6 +105,79 @@ DocComment[DOC_COMMENT, pos:1
]
*/
/**
* abc {@value %d java.awt.Color#RED}
*/
int format_plain() { }
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: 2
Text[TEXT, pos:1, abc_]
Value[VALUE, pos:5
format:
Text[TEXT, pos:13, %d]
reference:
Reference[REFERENCE, pos:16, java.awt.Color#RED]
]
body: empty
block tags: empty
]
*/
/**
* abc {@value "%d" java.awt.Color#RED}
*/
int format_quoted() { }
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: 2
Text[TEXT, pos:1, abc_]
Value[VALUE, pos:5
format:
Text[TEXT, pos:13, "%d"]
reference:
Reference[REFERENCE, pos:18, java.awt.Color#RED]
]
body: empty
block tags: empty
]
*/
/**
* abc {@value 0x%x4 java.awt.Color#RED}
*/
int format_invalid() { }
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: 3
Text[TEXT, pos:1, abc_]
Erroneous[ERRONEOUS, pos:5, prefPos:13
code: compiler.err.dc.ref.unexpected.input
body: {@value_0x%x4
]
Text[TEXT, pos:18, _java.awt.Color#RED}]
body: empty
block tags: empty
]
*/
/**
* abc {@value "%d" java.awt.Color#RED junk}
*/
int format_trailing_junk() { }
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: 3
Text[TEXT, pos:1, abc_]
Erroneous[ERRONEOUS, pos:5, prefPos:37
code: compiler.err.dc.unexpected.content
body: {@value_"%d"_jav...a.awt.Color#RED_j
]
Text[TEXT, pos:38, unk}]
body: empty
block tags: empty
]
*/
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2022, 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
@ -1186,6 +1186,7 @@ public class DPrinter {
}
public Void visitValue(ValueTree node, Void p) {
printDocTree("format", node.getFormat());
printDocTree("value", node.getReference());
return visitInlineTag(node, null);
}