8075778: Add javadoc tag to avoid duplication of return information in simple situations.

Reviewed-by: prappo, jlahoda
This commit is contained in:
Jonathan Gibbons 2020-12-08 23:25:08 +00:00
parent 48d8650ae1
commit b29f9cd7b0
21 changed files with 667 additions and 112 deletions

View File

@ -30,13 +30,25 @@ import java.util.List;
/**
* A tree node for an {@code @return} block tag.
*
* <pre>
* &#064;return description
* </pre>
* <pre>{@code
* @return description
* {@return description}
* }</pre>
*
* @since 1.8
*/
public interface ReturnTree extends BlockTagTree {
public interface ReturnTree extends BlockTagTree, InlineTagTree {
/**
* Returns whether this instance is an inline tag.
*
* @return {@code true} if this instance is an inline tag, and {@code false} otherwise
* @implSpec this implementation returns {@code false}.
* @since 16
*/
default boolean isInline() {
return false;
}
/**
* Returns the description of the return value of a method.
* @return the description

View File

@ -265,6 +265,26 @@ public interface DocTreeFactory {
*/
ReturnTree newReturnTree(List<? extends DocTree> description);
/**
* Creates a new {@code ReturnTree} object, to represent a {@code @return} tag
* or {@code {@return}} tag.
*
* @implSpec This implementation throws {@code UnsupportedOperationException} if
* {@code isInline} is {@code true}, and calls {@link #newReturnTree(List)} otherwise.
*
* @param description the description of the return value of a method
* @return a {@code ReturnTree} object
* @throws UnsupportedOperationException if inline {@code {@return}} tags are
* not supported
* @since 16
*/
default ReturnTree newReturnTree(boolean isInline, List<? extends DocTree> description) {
if (isInline) {
throw new UnsupportedOperationException();
}
return newReturnTree(description);
}
/**
* Creates a new {@code SeeTree} object, to represent a {@code @see} tag.
* @param reference the reference

View File

@ -300,9 +300,19 @@ public class JavacTrees extends DocTrees {
return getEndPosition(file, comment, last) + correction;
}
DCBlockTag block = (DCBlockTag) tree;
int pos;
String name;
if (tree.getKind() == DocTree.Kind.RETURN) {
DCTree.DCReturn dcReturn = (DCTree.DCReturn) tree;
pos = dcReturn.pos;
name = dcReturn.getTagName();
} else {
DCBlockTag block = (DCBlockTag) tree;
pos = block.pos;
name = block.getTagName();
}
return dcComment.comment.getSourcePos(block.pos + block.getTagName().length() + 1);
return dcComment.comment.getSourcePos(pos + name.length() + 1);
}
case ENTITY: {
DCEntity endEl = (DCEntity) tree;

View File

@ -269,11 +269,10 @@ public class DocCommentParser {
List<DCTree> content = blockContent();
return m.at(p).newUnknownBlockTagTree(name, content);
} else {
switch (tp.getKind()) {
case BLOCK:
return tp.parse(p);
case INLINE:
return erroneous("dc.bad.inline.tag", p);
if (tp.allowsBlock()) {
return tp.parse(p, TagParser.Kind.BLOCK);
} else {
return erroneous("dc.bad.inline.tag", p);
}
}
}
@ -311,33 +310,29 @@ public class DocCommentParser {
int p = bp - 1;
try {
nextChar();
if (isIdentifierStart(ch)) {
Name name = readTagName();
TagParser tp = tagParsers.get(name);
if (tp == null) {
if (!isIdentifierStart(ch)) {
return erroneous("dc.no.tag.name", p);
}
Name name = readTagName();
TagParser tp = tagParsers.get(name);
if (tp == null) {
skipWhitespace();
DCTree text = inlineText(WhitespaceRetentionPolicy.REMOVE_ALL);
nextChar();
return m.at(p).newUnknownInlineTagTree(name, List.of(text)).setEndPos(bp);
} else {
if (!tp.retainWhiteSpace) {
skipWhitespace();
DCTree text = inlineText(WhitespaceRetentionPolicy.REMOVE_ALL);
if (text != null) {
nextChar();
return m.at(p).newUnknownInlineTagTree(name, List.of(text)).setEndPos(bp);
}
} else {
if (!tp.retainWhiteSpace) {
skipWhitespace();
}
if (tp.getKind() == TagParser.Kind.INLINE) {
DCEndPosTree<?> tree = (DCEndPosTree<?>) tp.parse(p);
if (tree != null) {
return tree.setEndPos(bp);
}
} else { // handle block tags (for example, @see) in inline content
inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip content
nextChar();
}
}
if (tp.allowsInline()) {
DCEndPosTree<?> tree = (DCEndPosTree<?>) tp.parse(p, TagParser.Kind.INLINE);
return tree.setEndPos(bp);
} else { // handle block tags (for example, @see) in inline content
DCTree text = inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip content
nextChar();
return m.at(p).newUnknownInlineTagTree(name, List.of(text)).setEndPos(bp);
}
}
return erroneous("dc.no.tag.name", p);
} catch (ParseException e) {
return erroneous(e.getMessage(), p);
}
@ -574,9 +569,21 @@ public class DocCommentParser {
* 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() {
private List<DCTree> inlineContent(boolean allowNestedTags) {
ListBuffer<DCTree> trees = new ListBuffer<>();
skipWhitespace();
@ -604,14 +611,23 @@ public class DocCommentParser {
newline = false;
addPendingText(trees, bp - 1);
trees.add(html());
textStart = bp;
lastNonWhite = -1;
break;
case '{':
if (textStart == -1)
textStart = bp;
newline = false;
depth++;
nextChar();
if (ch == '@' && allowNestedTags) {
addPendingText(trees, bp - 2);
trees.add(inlineTag());
textStart = bp;
lastNonWhite = -1;
} else {
depth++;
}
break;
case '}':
@ -1071,7 +1087,7 @@ public class DocCommentParser {
}
private static abstract class TagParser {
enum Kind { INLINE, BLOCK }
enum Kind { INLINE, BLOCK, EITHER }
final Kind kind;
final DCTree.Kind treeKind;
@ -1089,19 +1105,32 @@ public class DocCommentParser {
this.retainWhiteSpace = retainWhiteSpace;
}
Kind getKind() {
return kind;
boolean allowsBlock() {
return kind != Kind.INLINE;
}
boolean allowsInline() {
return kind != Kind.BLOCK;
}
DCTree.Kind getTreeKind() {
return treeKind;
}
abstract DCTree parse(int pos) throws ParseException;
DCTree parse(int pos, Kind kind) throws ParseException {
if (kind != this.kind && this.kind != Kind.EITHER) {
throw new IllegalArgumentException(kind.toString());
}
return parse(pos);
}
DCTree parse(int pos) throws ParseException {
throw new UnsupportedOperationException();
}
}
/**
* @see <a href="https://docs.oracle.com/en/java/javase/14/docs/specs/javadoc/doc-comment-spec.html">Javadoc Tags</a>
* @see <a href="https://docs.oracle.com/en/java/javase/15/docs/specs/javadoc/doc-comment-spec.html">JavaDoc Tags</a>
*/
private Map<Name, TagParser> createTagParsers() {
TagParser[] parsers = {
@ -1271,12 +1300,22 @@ public class DocCommentParser {
}
},
// @return description
new TagParser(TagParser.Kind.BLOCK, DCTree.Kind.RETURN) {
// @return description -or- {@return description}
new TagParser(TagParser.Kind.EITHER, DCTree.Kind.RETURN) {
@Override
public DCTree parse(int pos) {
List<DCTree> description = blockContent();
return m.at(pos).newReturnTree(description);
public DCTree parse(int pos, Kind kind) {
List<DCTree> description;
switch (kind) {
case BLOCK:
description = blockContent();
break;
case INLINE:
description = inlineContent(true);
break;
default:
throw new IllegalArgumentException(kind.toString());
}
return m.at(pos).newReturnTree(kind == Kind.INLINE, description);
}
},

View File

@ -677,13 +677,20 @@ public abstract class DCTree implements DocTree {
}
}
public static class DCReturn extends DCBlockTag implements ReturnTree {
public static class DCReturn extends DCEndPosTree<DCReturn> implements ReturnTree {
public final boolean inline;
public final List<DCTree> description;
DCReturn(List<DCTree> description) {
DCReturn(boolean inline, List<DCTree> description) {
this.inline = inline;
this.description = description;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public String getTagName() {
return "return";
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Kind getKind() {
return Kind.RETURN;
@ -694,6 +701,11 @@ public abstract class DCTree implements DocTree {
return v.visitReturn(this, d);
}
@Override @DefinedBy(Api.COMPILER_TREE)
public boolean isInline() {
return inline;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public List<? extends DocTree> getDescription() {
return description;

View File

@ -399,9 +399,15 @@ public class DocPretty implements DocTreeVisitor<Void,Void> {
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitReturn(ReturnTree node, Void p) {
try {
if (node.isInline()) {
print("{");
}
printTagName(node);
print(" ");
print(node.getDescription());
if (node.isInline()) {
print("}");
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}

View File

@ -386,7 +386,12 @@ public class DocTreeMaker implements DocTreeFactory {
@Override @DefinedBy(Api.COMPILER_TREE)
public DCReturn newReturnTree(List<? extends DocTree> description) {
DCReturn tree = new DCReturn(cast(description));
return newReturnTree(false, description);
}
@Override @DefinedBy(Api.COMPILER_TREE)
public DCReturn newReturnTree(boolean isInline, List<? extends DocTree> description) {
DCReturn tree = new DCReturn(isInline, cast(description));
tree.pos = pos;
return tree;
}
@ -533,6 +538,7 @@ public class DocTreeMaker implements DocTreeFactory {
continue;
}
switch (dt.getKind()) {
case RETURN:
case SUMMARY:
foundFirstSentence = true;
break;

View File

@ -174,22 +174,35 @@ public class JavadocFormatter {
if (current.matches(t)) {
if (!seenAny) {
seenAny = true;
if (result.charAt(result.length() - 1) != '\n')
result.append("\n");
result.append("\n");
result.append(escape(CODE_UNDERLINE))
.append(docSections.get(current))
.append(escape(CODE_RESET))
.append("\n");
startSection(current);
}
scan(t, null);
}
}
if (current == Sections.RETURNS && !seenAny) {
List<? extends DocTree> firstSentence = node.getFirstSentence();
if (firstSentence.size() == 1
&& firstSentence.get(0).getKind() == DocTree.Kind.RETURN) {
startSection(current);
scan(firstSentence.get(0), true);
}
}
}
return null;
}
private void startSection(Sections current) {
if (result.charAt(result.length() - 1) != '\n')
result.append("\n");
result.append("\n");
result.append(escape(CODE_UNDERLINE))
.append(docSections.get(current))
.append(escape(CODE_RESET))
.append("\n");
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Object visitText(TextTree node, Object p) {
String text = node.getBody();
@ -250,13 +263,34 @@ public class JavadocFormatter {
return scan(node.getBody(), p);
}
/**
* {@inheritDoc}
* {@code @return} is a bimodal tag and can be used as either a block tag or an inline
* tag. If the parameter {@code p} is {@code null}, the node will be formatted according to
* the value of {@link ReturnTree#isInline()}. If the parameter is not {@code null}, the node will
* be formatted as a block tag.
* @param node {@inheritDoc}
* @param p not {@code null} to force the node to be formatted as a block tag
* @return
*/
@Override @DefinedBy(Api.COMPILER_TREE)
public Object visitReturn(ReturnTree node, Object p) {
reflownTo = result.length();
try {
return super.visitReturn(node, p);
} finally {
reflow(result, reflownTo, 0, limit);
if (node.isInline() && p == null) {
String MARKER = "{0}";
int p0 = inlineReturns.indexOf(MARKER);
result.append(inlineReturns, 0, p0);
try {
return super.visitReturn(node, p);
} finally {
result.append(inlineReturns.substring(p0 + MARKER.length()));
}
} else {
reflownTo = result.length();
try {
return super.visitReturn(node, p);
} finally {
reflow(result, reflownTo, 0, limit);
}
}
}
@ -569,6 +603,7 @@ public class JavadocFormatter {
}
private static final Map<Sections, String> docSections = new LinkedHashMap<>();
private static final String inlineReturns;
static {
ResourceBundle bundle =
@ -577,6 +612,7 @@ public class JavadocFormatter {
docSections.put(Sections.PARAMS, bundle.getString("CAP_Parameters"));
docSections.put(Sections.RETURNS, bundle.getString("CAP_Returns"));
docSections.put(Sections.THROWS, bundle.getString("CAP_Thrown_Exceptions"));
inlineReturns = bundle.getString("Inline_Returns");
}
private static String indentString(int indent) {

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2016, 2020, 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
@ -27,3 +27,4 @@ CAP_TypeParameters=Type Parameters:
CAP_Parameters=Parameters:
CAP_Returns=Returns:
CAP_Thrown_Exceptions=Thrown Exceptions:
Inline_Returns=Returns {0}.

View File

@ -1384,9 +1384,8 @@ public class HtmlDocletWriter {
StartElementTree st = (StartElementTree)tag;
Name name = st.getName();
if (name != null) {
jdk.javadoc.internal.doclint.HtmlTag htag =
jdk.javadoc.internal.doclint.HtmlTag.get(name);
return htag != null && htag.equals(jdk.javadoc.internal.doclint.HtmlTag.A);
HtmlTag htag = HtmlTag.get(name);
return htag != null && htag.equals(HtmlTag.A);
}
}
return false;

View File

@ -210,12 +210,13 @@ public class TagletWriterImpl extends TagletWriter {
}
@Override
public Content returnTagOutput(Element element, ReturnTree returnTag) {
public Content returnTagOutput(Element element, ReturnTree returnTag, boolean inline) {
CommentHelper ch = utils.getCommentHelper(element);
return new ContentBuilder(
HtmlTree.DT(contents.returns),
HtmlTree.DD(htmlWriter.commentTagsToContent(
returnTag, element, ch.getDescription(returnTag), false, inSummary)));
List<? extends DocTree> desc = ch.getDescription(returnTag);
Content content = htmlWriter.commentTagsToContent(returnTag, element, desc , false, inSummary);
return inline
? new ContentBuilder(contents.getContent("doclet.Returns_0", content))
: new ContentBuilder(HtmlTree.DT(contents.returns), HtmlTree.DD(content));
}
@Override

View File

@ -100,6 +100,7 @@ doclet.TypeParameters_dup_warn=Type parameter "{0}" is documented more than once
doclet.RecordComponents_warn=@param argument "{0}" is not the name of a record component.
doclet.RecordComponents_dup_warn=Record component "{0}" is documented more than once.
doclet.Returns=Returns:
doclet.Returns_0=Returns {0}.
doclet.Return_tag_on_void_method=@return tag cannot be used in method with void return type.
doclet.See_Also=See Also:
doclet.SerialData=Serial Data:

View File

@ -44,7 +44,7 @@ import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Input;
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
/**
* A taglet that represents the @return tag.
* A taglet that represents the {@code @return} and {@code {@return }} tags.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
@ -54,47 +54,77 @@ import jdk.javadoc.internal.doclets.toolkit.util.Utils;
public class ReturnTaglet extends BaseTaglet implements InheritableTaglet {
public ReturnTaglet() {
super(DocTree.Kind.RETURN, false, EnumSet.of(Location.METHOD));
super(DocTree.Kind.RETURN, true, EnumSet.of(Location.METHOD));
}
@Override
public boolean isBlockTag() {
return true;
}
@Override
public void inherit(DocFinder.Input input, DocFinder.Output output) {
List<? extends ReturnTree> tags = input.utils.getReturnTrees(input.element);
CommentHelper ch = input.utils.getCommentHelper(input.element);
Utils utils = input.utils;
CommentHelper ch = utils.getCommentHelper(input.element);
ReturnTree tag = null;
List<? extends ReturnTree> tags = utils.getReturnTrees(input.element);
if (!tags.isEmpty()) {
tag = tags.get(0);
} else {
List<? extends DocTree> firstSentence = utils.getFirstSentenceTrees(input.element);
if (firstSentence.size() == 1 && firstSentence.get(0).getKind() == DocTree.Kind.RETURN) {
tag = (ReturnTree) firstSentence.get(0);
}
}
if (tag != null) {
output.holder = input.element;
output.holderTag = tags.get(0);
output.holderTag = tag;
output.inlineTags = input.isFirstSentence
? ch.getFirstSentenceTrees(output.holderTag)
: ch.getDescription(output.holderTag);
}
}
@Override
public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter writer) {
return writer.returnTagOutput(element, (ReturnTree) tag, true);
}
@Override
public Content getAllBlockTagOutput(Element holder, TagletWriter writer) {
Messages messages = writer.configuration().getMessages();
Utils utils = writer.configuration().utils;
TypeMirror returnType = utils.getReturnType(writer.getCurrentPageElement(), (ExecutableElement)holder);
List<? extends ReturnTree> tags = utils.getReturnTrees(holder);
//Make sure we are not using @return tag on method with void return type.
// Make sure we are not using @return tag on method with void return type.
TypeMirror returnType = utils.getReturnType(writer.getCurrentPageElement(), (ExecutableElement)holder);
if (returnType != null && utils.isVoid(returnType)) {
if (!tags.isEmpty()) {
messages.warning(holder, "doclet.Return_tag_on_void_method");
}
return null;
}
if (!tags.isEmpty())
return writer.returnTagOutput(holder, tags.get(0));
//Inherit @return tag if necessary.
List<DocTree> ntags = new ArrayList<>();
if (!tags.isEmpty()) {
return writer.returnTagOutput(holder, tags.get(0), false);
}
// Check for inline tag in first sentence.
List<? extends DocTree> firstSentence = utils.getFirstSentenceTrees(holder);
if (firstSentence.size() == 1 && firstSentence.get(0).getKind() == DocTree.Kind.RETURN) {
return writer.returnTagOutput(holder, (ReturnTree) firstSentence.get(0), false);
}
// Inherit @return tag if necessary.
Input input = new DocFinder.Input(utils, holder, this);
DocFinder.Output inheritedDoc = DocFinder.search(writer.configuration(), input);
if (inheritedDoc.holderTag != null) {
CommentHelper ch = utils.getCommentHelper(input.element);
ch.setOverrideElement(inheritedDoc.holder);
ntags.add(inheritedDoc.holderTag);
return writer.returnTagOutput(holder, (ReturnTree) inheritedDoc.holderTag, false);
}
return !ntags.isEmpty() ? writer.returnTagOutput(holder, (ReturnTree) ntags.get(0)) : null;
return null;
}
}

View File

@ -733,7 +733,8 @@ public class TagletManager {
taglets.addAll(allTaglets.values());
for (Taglet t : taglets) {
String name = t.isInlineTag() ? "{@" + t.getName() + "}" : "@" + t.getName();
// give preference to simpler block form if a tag can be either
String name = t.isBlockTag() ? "@" + t.getName() : "{@" + t.getName() + "}";
out.println(String.format("%20s", name) + ": "
+ format(t.isBlockTag(), "block")+ " "
+ format(t.inOverview(), "overview") + " "

View File

@ -145,12 +145,13 @@ public abstract class TagletWriter {
/**
* Returns the output for a {@code @return} tag.
*
* @param element The element that owns the doc comment
* @param element the element that owns the doc comment
* @param returnTag the return tag to document
* @param inline whether this should be written as an inline instance or block instance
*
* @return the output
*/
protected abstract Content returnTagOutput(Element element, ReturnTree returnTag);
protected abstract Content returnTagOutput(Element element, ReturnTree returnTag, boolean inline);
/**
* Returns the output for {@code @see} tags.

View File

@ -939,6 +939,12 @@ public class Checker extends DocTreePathScanner<Void, Void> {
if (foundReturn) {
env.messages.warning(REFERENCE, tree, "dc.exists.return");
}
if (tree.isInline()) {
DocCommentTree dct = getCurrentPath().getDocComment();
if (tree != dct.getFirstSentence().get(0)) {
env.messages.warning(REFERENCE, tree, "dc.return.not.first");
}
}
Element e = env.trees.getElement(env.currPath);
if (e.getKind() != ElementKind.METHOD

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2012, 2020, 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
@ -57,8 +57,9 @@ dc.no.alt.attr.for.image = no "alt" attribute for image
dc.no.summary.or.caption.for.table=no summary or caption for table
dc.param.name.not.found = @param name not found
dc.ref.not.found = reference not found
dc.return.not.first = '{@return} not at beginning of description
dc.service.not.found = service-type not found
dc.tag.code.within.code = '{@code'} within <code>
dc.tag.code.within.code = '{@code} within <code>
dc.tag.empty = empty <{0}> tag
dc.tag.a.within.a = {0} tag, which expands to <a>, within <a>
dc.tag.end.not.permitted = invalid end tag: </{0}>
@ -81,7 +82,7 @@ dc.tag.unknown = unknown tag: {0}
dc.tag.not.supported = tag not supported in the generated HTML version: {0}
dc.text.not.allowed = text not allowed in <{0}> element
dc.unexpected.comment=documentation comment not expected here
dc.value.not.allowed.here='{@value}' not allowed here
dc.value.not.allowed.here='{@value} not allowed here
dc.value.not.a.constant=value does not refer to a constant
dc.main.ioerror=IO error: {0}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2020, 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
@ -137,7 +137,7 @@ public class JavadocFormatterTest {
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 1234\n" +
"1234 1234 1234 1234 1234 1234 1234 1234 1234\n";
"1234 1234 1234 1234 1234 1234 1234 1234 1234 \n";
if (!Objects.equals(actual, expected)) {
throw new AssertionError("Incorrect output: " + actual);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2020, 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
@ -23,39 +23,409 @@
/*
* @test
* @bug 4490068
* @summary Warn when a return tag is used on a method without a return type.
* @library ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
* @build javadoc.tester.*
* @bug 4490068 8075778
* @summary General tests for inline or block at-return tag
* @library /tools/lib ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
* @build toolbox.ToolBox javadoc.tester.*
* @run main TestReturnTag
*/
import java.io.IOException;
import java.nio.file.Path;
import javadoc.tester.JavadocTester;
import toolbox.ToolBox;
public class TestReturnTag extends JavadocTester {
/**
* Trigger warning message when return tag is used on a void method.
*
* @return I really don't return anything.
*/
public void method() {}
public static void main(String... args) throws Exception {
TestReturnTag tester = new TestReturnTag();
tester.runTests();
tester.runTests(m -> new Object[] { Path.of(m.getName()) });
}
@Test
public void tests() {
ToolBox tb = new ToolBox();
@Test // 4490068
public void testInvalidReturn(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src,
"""
/** Comment. */
public class C {
/**
* Trigger warning message when return tag is used on a void method.
*
* @return I really don't return anything.
*/
public void method() {}
}
""");
javadoc("-Xdoclint:none",
"-d", "out",
"-sourcepath", testSrc,
testSrc("TestReturnTag.java"));
"-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
src.resolve("C.java").toString());
checkExit(Exit.OK);
checkOutput(Output.OUT, true,
"warning - @return tag cannot be used in method with void return type.");
}
@Test
public void testBlock(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src,
"""
/** Comment. */
public class C {
/**
* First sentence. Second sentence.
* @return the result
*/
public int m() { return 0; }
}
""");
javadoc("-Xdoclint:none",
"-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
src.resolve("C.java").toString());
checkExit(Exit.OK);
checkOutput("C.html", true,
"""
<div class="block">First sentence. Second sentence.</div>
<dl class="notes">
<dt>Returns:</dt>
<dd>the result</dd>
</dl>
""");
}
@Test
public void testInlineShort(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src,
"""
/** Comment. */
public class C {
/**
* {@return the result}
*/
public int m() { return 0; }
}
""");
javadoc("-Xdoclint:none",
"-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
src.resolve("C.java").toString());
checkExit(Exit.OK);
checkOutput("C.html", true,
"""
<div class="block">Returns the result.</div>
<dl class="notes">
<dt>Returns:</dt>
<dd>the result</dd>
</dl>
""");
}
@Test
public void testInlineLong(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src,
"""
/** Comment. */
public class C {
/**
* {@return the result} More text.
*/
public int m() { return 0; }
}
""");
javadoc("-Xdoclint:none",
"-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
src.resolve("C.java").toString());
checkExit(Exit.OK);
checkOutput("C.html", true,
"""
<div class="block">Returns the result. More text.</div>
<dl class="notes">
<dt>Returns:</dt>
<dd>the result</dd>
</dl>
""");
}
@Test
public void testInlineMarkup(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src,
"""
/** Comment. */
public class C {
/**
* {@return abc {@code def} <b>ghi</b> jkl}
*/
public int m() { return 0; }
}
""");
javadoc("-Xdoclint:none",
"-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
src.resolve("C.java").toString());
checkExit(Exit.OK);
checkOutput("C.html", true,
"""
<div class="block">Returns abc <code>def</code> <b>ghi</b> jkl.</div>
<dl class="notes">
<dt>Returns:</dt>
<dd>abc <code>def</code> <b>ghi</b> jkl</dd>
</dl>
""");
}
@Test
public void testBlockMarkup(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src,
"""
/** Comment. */
public class C {
/**
* @return abc {@code def} <b>ghi</b> jkl
*/
public int m() { return 0; }
}
""");
javadoc("-Xdoclint:none",
"-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
src.resolve("C.java").toString());
checkExit(Exit.OK);
checkOutput("C.html", true,
"""
<dl class="notes">
<dt>Returns:</dt>
<dd>abc <code>def</code> <b>ghi</b> jkl</dd>
</dl>
""");
}
@Test
public void testEmptyInline(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src,
"""
/** Comment. */
public class C {
/**
* {@return}
*/
public int m() { return 0; }
}
""");
javadoc("-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
src.resolve("C.java").toString());
checkExit(Exit.OK);
checkOutput(Output.OUT, true,
"C.java:4: warning: no description for @return");
checkOutput("C.html", true,
"""
<div class="block">Returns .</div>
<dl class="notes">
<dt>Returns:</dt>
</dl>
""");
}
@Test
public void testInlineNotFirst(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src,
"""
/** Comment. */
public class C {
/**
* Some text. {@return the result} More text.
*/
public int m() { return 0; }
}
""");
javadoc("-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
src.resolve("C.java").toString());
checkExit(Exit.OK);
checkOutput(Output.OUT, true,
"C.java:4: warning: {@return} not at beginning of description");
checkOutput("C.html", true,
"""
<div class="block">Some text. Returns the result. More text.</div>
</section>
""");
}
@Test
public void testDuplicate(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src,
"""
/** Comment. */
public class C {
/**
* {@return the result} More text.
* @return again
*/
public int m() { return 0; }
}
""");
javadoc( "-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
src.resolve("C.java").toString());
checkExit(Exit.OK);
checkOutput(Output.OUT, true,
"C.java:5: warning: @return has already been specified");
checkOutput("C.html", true,
"""
<div class="block">Returns the result. More text.</div>
<dl class="notes">
<dt>Returns:</dt>
<dd>again</dd>
""");
}
@Test
public void testSimpleInheritBlock(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src,
"""
/** Comment. */
public class Super {
/**
* @return the result
*/
public int m() { return 0; }
}
""",
"""
/** Comment. */
public class C extends Super {
@Override
public int m() { return 1; }
}
""");
javadoc( "-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
src.resolve("C.java").toString());
checkExit(Exit.OK);
checkOutput("C.html", true,
"""
<dl class="notes">
<dt>Overrides:</dt>
<dd><code>m</code>&nbsp;in class&nbsp;<code>Super</code></dd>
<dt>Returns:</dt>
<dd>the result</dd>
</dl>
""");
}
@Test
public void testSimpleInheritInline(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src,
"""
/** Comment. */
public class Super {
/**
* {@return the result}
*/
public int m() { return 0; }
}
""",
"""
/** Comment. */
public class C extends Super {
@Override
public int m() { return 1; }
}
""");
javadoc( "-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
src.resolve("C.java").toString());
checkExit(Exit.OK);
checkOutput("C.html", true,
"""
<div class="block"><span class="descfrm-type-label">Description copied from class:&nbsp;<code>Super</code></span></div>
<div class="block">Returns the result.</div>
<dl class="notes">
<dt>Overrides:</dt>
<dd><code>m</code>&nbsp;in class&nbsp;<code>Super</code></dd>
<dt>Returns:</dt>
<dd>the result</dd>""");
}
@Test
public void testPreferInlineOverInherit(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src,
"""
/** Comment. */
public class Super {
/**
* {@return the result}
*/
public int m() { return 0; }
}
""",
"""
/** Comment. */
public class C extends Super {
/**
* {@return the overriding result}
*/
@Override
public int m() { return 1; }
}
""");
javadoc( "-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
src.resolve("C.java").toString());
checkExit(Exit.OK);
checkOutput("C.html", true,
"""
<div class="block">Returns the overriding result.</div>
<dl class="notes">
<dt>Overrides:</dt>
<dd><code>m</code>&nbsp;in class&nbsp;<code>Super</code></dd>
<dt>Returns:</dt>
<dd>the overriding result</dd>
</dl>
""");
}
}

View File

@ -14,7 +14,7 @@
@param: block ........ ...... ....... type constructor method ..... ...... ........
@propertyDescription: block ........ ...... ....... .... ........... method field ...... ........
@provides: block ........ module ....... .... ........... ...... ..... ...... ........
@return: block ........ ...... ....... .... ........... method ..... ...... ........
@return: block ........ ...... ....... .... ........... method ..... inline ........
@see: block overview module package type constructor method field ...... ........
@serial: block ........ ...... package type ........... ...... field ...... ........
@serialData: block ........ ...... ....... .... ........... ...... ..... ...... ........

View File

@ -102,6 +102,9 @@ public class EmptyHtmlTest extends TestRunner {
case "LiteralTree" ->
test(d, type, "{@literal abc}");
case "ReturnTree" ->
test(d, type, "{@return abc}");
case "SummaryTree" ->
test(d, type, "{@summary First sentence.}");