8200337: Generalize see and link tags for user-defined anchors

Reviewed-by: jjg
This commit is contained in:
Hannes Wallnöfer 2022-11-04 14:57:43 +00:00
parent 22347e46f7
commit 5622b09565
17 changed files with 570 additions and 64 deletions

View File

@ -106,16 +106,10 @@ import com.sun.tools.javac.resources.CompilerProperties.Errors;
import com.sun.tools.javac.resources.CompilerProperties.Notes;
import com.sun.tools.javac.resources.CompilerProperties.Warnings;
import com.sun.tools.javac.tree.DCTree;
import com.sun.tools.javac.tree.DCTree.DCBlockTag;
import com.sun.tools.javac.tree.DCTree.DCComment;
import com.sun.tools.javac.tree.DCTree.DCDocComment;
import com.sun.tools.javac.tree.DCTree.DCEndPosTree;
import com.sun.tools.javac.tree.DCTree.DCEntity;
import com.sun.tools.javac.tree.DCTree.DCErroneous;
import com.sun.tools.javac.tree.DCTree.DCIdentifier;
import com.sun.tools.javac.tree.DCTree.DCParam;
import com.sun.tools.javac.tree.DCTree.DCReference;
import com.sun.tools.javac.tree.DCTree.DCText;
import com.sun.tools.javac.tree.DocCommentTable;
import com.sun.tools.javac.tree.DocTreeMaker;
import com.sun.tools.javac.tree.EndPosTable;

View File

@ -410,11 +410,9 @@ public class DocCommentParser {
* Matching pairs of {@literal < >} are skipped. The text is terminated by the first
* unmatched }. It is an error if the beginning of the next tag is detected.
*/
// TODO: allowMember is currently ignored
// TODO: boolean allowMember should be enum FORBID, ALLOW, REQUIRE
// TODO: improve quality of parse to forbid bad constructions.
@SuppressWarnings("fallthrough")
protected DCReference reference(boolean allowMember) throws ParseException {
protected DCReference reference(ReferenceParser.Mode mode) throws ParseException {
int pos = bp;
int depth = 0;
@ -468,13 +466,9 @@ public class DocCommentParser {
String sig = newString(pos, bp);
try {
ReferenceParser.Reference ref = new ReferenceParser(fac).parse(sig);
return m.at(pos).newReferenceTree(sig,
ref.moduleName, ref.qualExpr,
ref.member, ref.paramTypes)
.setEndPos(bp);
ReferenceParser.Reference ref = new ReferenceParser(fac).parse(sig, mode);
return m.at(pos).newReferenceTree(sig, ref).setEndPos(bp);
} catch (ReferenceParser.ParseException pe) {
throw new ParseException(pos + pe.pos, pe.getMessage());
}
@ -1237,7 +1231,7 @@ public class DocCommentParser {
@Override
public DCTree parse(int pos) throws ParseException {
skipWhitespace();
DCReference ref = reference(false);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
List<DCTree> description = blockContent();
return m.at(pos).newExceptionTree(ref, description);
}
@ -1294,7 +1288,7 @@ public class DocCommentParser {
new TagParser(TagParser.Kind.INLINE, DCTree.Kind.LINK) {
@Override
public DCTree parse(int pos) throws ParseException {
DCReference ref = reference(true);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_OPTIONAL);
List<DCTree> label = inlineContent();
return m.at(pos).newLinkTree(ref, label);
}
@ -1304,7 +1298,7 @@ public class DocCommentParser {
new TagParser(TagParser.Kind.INLINE, DCTree.Kind.LINK_PLAIN) {
@Override
public DCTree parse(int pos) throws ParseException {
DCReference ref = reference(true);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_OPTIONAL);
List<DCTree> label = inlineContent();
return m.at(pos).newLinkPlainTree(ref, label);
}
@ -1351,7 +1345,7 @@ public class DocCommentParser {
@Override
public DCTree parse(int pos) throws ParseException {
skipWhitespace();
DCReference ref = reference(true);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
List<DCTree> description = blockContent();
return m.at(pos).newProvidesTree(ref, description);
}
@ -1411,7 +1405,7 @@ public class DocCommentParser {
default:
if (isJavaIdentifierStart(ch) || ch == '#') {
DCReference ref = reference(true);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_OPTIONAL);
List<DCTree> description = blockContent();
return m.at(pos).newSeeTree(description.prepend(ref));
}
@ -1436,7 +1430,7 @@ public class DocCommentParser {
skipWhitespace();
DCIdentifier name = identifier();
skipWhitespace();
DCReference type = reference(false);
DCReference type = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
List<DCTree> description = null;
if (isWhitespace(ch)) {
skipWhitespace();
@ -1606,7 +1600,7 @@ public class DocCommentParser {
@Override
public DCTree parse(int pos) throws ParseException {
skipWhitespace();
DCReference ref = reference(false);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
List<DCTree> description = blockContent();
return m.at(pos).newThrowsTree(ref, description);
}
@ -1617,7 +1611,7 @@ public class DocCommentParser {
@Override
public DCTree parse(int pos) throws ParseException {
skipWhitespace();
DCReference ref = reference(true);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
List<DCTree> description = blockContent();
return m.at(pos).newUsesTree(ref, description);
}
@ -1642,7 +1636,7 @@ public class DocCommentParser {
format = null;
}
}
DCReference ref = reference(true);
DCReference ref = reference(ReferenceParser.Mode.MEMBER_REQUIRED);
skipWhitespace();
if (ch == '}') {
nextChar();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 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,7 +30,6 @@ import com.sun.source.tree.Tree;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.parser.Tokens.TokenKind;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.util.DiagnosticSource;
import com.sun.tools.javac.util.JCDiagnostic;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
@ -38,8 +37,6 @@ import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Name;
import javax.tools.JavaFileObject;
import java.util.Locale;
import java.util.Queue;
/**
* A utility class to parse a string in a doc comment containing a
@ -51,6 +48,18 @@ import java.util.Queue;
* deletion without notice.</b>
*/
public class ReferenceParser {
/**
* Context dependent parsing mode which either disallows, allows or requires
* a member reference. The <code>MEMBER_OPTIONAL</code> value also allows
* arbitrary URI fragments using a double hash mark.
*/
public enum Mode {
MEMBER_DISALLOWED,
MEMBER_OPTIONAL,
MEMBER_REQUIRED
}
/**
* An object to contain the result of parsing a reference to an API element.
* Any, but not all, of the member fields may be null.
@ -98,10 +107,11 @@ public class ReferenceParser {
/**
* Parse a reference to an API element as may be found in doc comment.
* @param sig the signature to be parsed
* @param mode the parsing mode
* @return a {@code Reference} object containing the result of parsing the signature
* @throws ParseException if there is an error while parsing the signature
*/
public Reference parse(String sig) throws ParseException {
public Reference parse(String sig, Mode mode) throws ParseException {
// Break sig apart into moduleName qualifiedExpr member paramTypes.
JCTree.JCExpression moduleName;
@ -129,16 +139,28 @@ public class ReferenceParser {
qualExpr = null;
member = null;
} else if (hash == -1) {
if (lparen == -1) {
if (lparen == -1 && mode != Mode.MEMBER_REQUIRED) {
qualExpr = parseType(sig, afterSlash, sig.length(), dh);
member = null;
} else {
if (mode == Mode.MEMBER_DISALLOWED) {
throw new ParseException(hash, "dc.ref.unexpected.input");
}
qualExpr = null;
member = parseMember(sig, afterSlash, lparen, dh);
member = parseMember(sig, afterSlash, lparen > -1 ? lparen : sig.length(), dh);
}
} else {
if (mode == Mode.MEMBER_DISALLOWED) {
throw new ParseException(hash, "dc.ref.unexpected.input");
}
qualExpr = (hash == afterSlash) ? null : parseType(sig, afterSlash, hash, dh);
if (lparen == -1) {
if (sig.indexOf("#", afterHash) == afterHash) {
// A hash symbol followed by another hash indicates a literal URL fragment.
if (mode != Mode.MEMBER_OPTIONAL) {
throw new ParseException(afterHash, "dc.ref.unexpected.input");
}
member = null;
} else if (lparen == -1) {
member = parseMember(sig, afterHash, sig.length(), dh);
} else {
member = parseMember(sig, afterHash, lparen, dh);

View File

@ -872,8 +872,8 @@ public abstract class DCTree implements DocTree {
DCReference(String signature, JCTree.JCExpression moduleName, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
this.signature = signature;
this.moduleName = moduleName;
qualifierExpression = qualExpr;
memberName = member;
this.qualifierExpression = qualExpr;
this.memberName = member;
this.paramTypes = paramTypes;
}

View File

@ -350,8 +350,8 @@ public class DocTreeMaker implements DocTreeFactory {
@Override @DefinedBy(Api.COMPILER_TREE)
public DCReference newReferenceTree(String signature) {
try {
ReferenceParser.Reference ref = referenceParser.parse(signature);
DCReference tree = new DCReference(signature, ref.moduleName, ref.qualExpr, ref.member, ref.paramTypes);
ReferenceParser.Reference ref = referenceParser.parse(signature, ReferenceParser.Mode.MEMBER_OPTIONAL);
DCReference tree = newReferenceTree(signature, ref);
tree.pos = pos;
return tree;
} catch (ReferenceParser.ParseException e) {
@ -359,8 +359,8 @@ public class DocTreeMaker implements DocTreeFactory {
}
}
public DCReference newReferenceTree(String signature, JCTree.JCExpression moduleName, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
DCReference tree = new DCReference(signature, moduleName, qualExpr, member, paramTypes);
public DCReference newReferenceTree(String signature, ReferenceParser.Reference ref) {
DCReference tree = new DCReference(signature, ref.moduleName, ref.qualExpr, ref.member, ref.paramTypes);
tree.pos = pos;
return tree;
}

View File

@ -598,10 +598,21 @@ public class HtmlDocletWriter {
/**
* {@return the link to the given package}
*
* @param packageElement the package to link to.
* @param label the label for the link.
* @param packageElement the package to link to
* @param label the label for the link
*/
public Content getPackageLink(PackageElement packageElement, Content label) {
return getPackageLink(packageElement, label, null);
}
/**
* {@return the link to the given package}
*
* @param packageElement the package to link to
* @param label the label for the link
* @param fragment the link fragment
*/
public Content getPackageLink(PackageElement packageElement, Content label, String fragment) {
boolean included = packageElement != null && utils.isIncluded(packageElement);
if (!included) {
for (PackageElement p : configuration.packages) {
@ -619,7 +630,7 @@ public class HtmlDocletWriter {
}
DocLink targetLink;
if (included || packageElement == null) {
targetLink = new DocLink(pathString(packageElement, DocPaths.PACKAGE_SUMMARY));
targetLink = new DocLink(pathString(packageElement, DocPaths.PACKAGE_SUMMARY), fragment);
} else {
targetLink = getCrossPackageLink(packageElement);
}
@ -650,11 +661,23 @@ public class HtmlDocletWriter {
* @param label tag for the link
*/
public Content getModuleLink(ModuleElement mdle, Content label) {
return getModuleLink(mdle, label, null);
}
/**
* {@return a link to module}
*
* @param mdle the module being documented
* @param label tag for the link
* @param fragment the link fragment
*/
public Content getModuleLink(ModuleElement mdle, Content label, String fragment) {
Set<ElementFlag> flags = mdle != null ? utils.elementFlags(mdle)
: EnumSet.noneOf(ElementFlag.class);
boolean included = utils.isIncluded(mdle);
if (included) {
DocLink targetLink = new DocLink(pathToRoot.resolve(docPaths.moduleSummary(mdle)));
DocLink targetLink;
targetLink = new DocLink(pathToRoot.resolve(docPaths.moduleSummary(mdle)), fragment);
Content link = links.createLink(targetLink, label, "");
if (flags.contains(ElementFlag.PREVIEW) && label != contents.moduleLabel) {
link = new ContentBuilder(

View File

@ -501,24 +501,34 @@ public class TagletWriterImpl extends TagletWriter {
CommentHelper ch = utils.getCommentHelper(holder);
TypeElement refClass = ch.getReferencedClass(ref);
Element refMem = ch.getReferencedMember(ref);
String refMemName = ch.getReferencedMemberName(refSignature);
String refFragment = ch.getReferencedFragment(refSignature);
if (refMemName == null && refMem != null) {
refMemName = refMem.toString();
if (refFragment == null && refMem != null) {
refFragment = refMem.toString();
} else if (refFragment != null && refFragment.startsWith("#")) {
if (labelContent.isEmpty()) {
// A non-empty label is required for fragment links as the
// reference target does not provide a useful default label.
reportWarning.accept("doclet.link.see.no_label", null);
return invalidTagOutput(resources.getText("doclet.link.see.no_label"),
Optional.of(refSignature));
}
refFragment = refFragment.substring(1);
}
if (refClass == null) {
ModuleElement refModule = ch.getReferencedModule(ref);
if (refModule != null && utils.isIncluded(refModule)) {
return htmlWriter.getModuleLink(refModule, labelContent.isEmpty() ? text : labelContent);
return htmlWriter.getModuleLink(refModule, labelContent.isEmpty() ? text : labelContent, refFragment);
}
//@see is not referencing an included class
PackageElement refPackage = ch.getReferencedPackage(ref);
if (refPackage != null && utils.isIncluded(refPackage)) {
//@see is referencing an included package
if (labelContent.isEmpty())
if (labelContent.isEmpty()) {
labelContent = plainOrCode(isLinkPlain,
Text.of(refPackage.getQualifiedName()));
return htmlWriter.getPackageLink(refPackage, labelContent);
}
return htmlWriter.getPackageLink(refPackage, labelContent, refFragment);
} else {
// @see is not referencing an included class, module or package. Check for cross links.
String refModuleName = ch.getReferencedModuleName(refSignature);
@ -541,8 +551,8 @@ public class TagletWriterImpl extends TagletWriter {
Optional.of(labelContent.isEmpty() ? text: labelContent));
}
}
} else if (refMemName == null) {
// Must be a class reference since refClass is not null and refMemName is null.
} else if (refFragment == null) {
// Must be a class reference since refClass is not null and refFragment is null.
if (labelContent.isEmpty() && refTree != null) {
TypeMirror referencedType = ch.getReferencedType(refTree);
if (utils.isGenericType(referencedType)) {
@ -555,9 +565,11 @@ public class TagletWriterImpl extends TagletWriter {
return htmlWriter.getLink(new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.DEFAULT, refClass)
.label(labelContent));
} else if (refMem == null) {
// Must be a member reference since refClass is not null and refMemName is not null.
// However, refMem is null, so this referenced member does not exist.
return (labelContent.isEmpty() ? text: labelContent);
// This is a fragment reference since refClass and refFragment are not null but refMem is null.
return htmlWriter.getLink(new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.SEE_TAG, refClass)
.label(labelContent)
.where(refFragment)
.style(null));
} else {
// Must be a member reference since refClass is not null and refMemName is not null.
// refMem is not null, so this @see tag must be referencing a valid member.
@ -591,6 +603,7 @@ public class TagletWriterImpl extends TagletWriter {
}
}
}
String refMemName = refFragment;
if (configuration.currentTypeElement != containing) {
refMemName = (utils.isConstructor(refMem))
? refMemName

View File

@ -103,6 +103,7 @@ doclet.File_error=Error reading file: {0}
doclet.URL_error=Error fetching URL: {0}
doclet.Resource_error=Error reading resource: {0}
doclet.link.no_reference=no reference given
doclet.link.see.no_label=missing reference label
doclet.see.class_or_package_not_found=Tag {0}: reference not found: {1}
doclet.see.class_or_package_not_accessible=Tag {0}: reference not accessible: {1}
doclet.see.nested_link=Tag {0}: nested link

View File

@ -208,7 +208,7 @@ public class CommentHelper {
return (utils.isExecutableElement(e) || utils.isVariableElement(e)) ? e : null;
}
public String getReferencedMemberName(String signature) {
public String getReferencedFragment(String signature) {
if (signature == null) {
return null;
}

View File

@ -244,12 +244,13 @@ public class DocPaths {
}
/**
* The path for the output directory for module documentation files.
* Returns the path for a file within a module documentation output directory.
* @param mdle the module
* @return the path
* @param path the path to append to the module path
* @return the module documentation path
*/
public DocPath moduleDocFiles(ModuleElement mdle) {
return createModulePath(mdle, "doc-files");
public DocPath modulePath(ModuleElement mdle, String path) {
return DocPath.create(mdle.getQualifiedName().toString()).resolve(path);
}
/**

View File

@ -0,0 +1,300 @@
/*
* 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 8200337
* @summary Generalize see and link tags for user-defined anchors
* @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 TestSeeLinkAnchor
*/
import java.nio.file.Path;
import java.nio.file.Paths;
import builder.ClassBuilder;
import builder.ClassBuilder.*;
import toolbox.ModuleBuilder;
import toolbox.ToolBox;
import javadoc.tester.JavadocTester;
public class TestSeeLinkAnchor extends JavadocTester {
final ToolBox tb;
private final Path src;
public static void main(String... args) throws Exception {
TestSeeLinkAnchor tester = new TestSeeLinkAnchor();
tester.runTests(m -> new Object[]{Paths.get(m.getName())});
}
TestSeeLinkAnchor() throws Exception {
tb = new ToolBox();
src = Paths.get("src");
generateModuleSources();
generatePackageSources();
generateInvalidLinkSource();
generateMissingLabelSource();
}
@Test
public void testPackage(Path base) throws Exception {
Path out = base.resolve("out");
javadoc("-d", out.toString(),
"-sourcepath", src.toString(),
"--no-platform-links",
"p1", "p2");
checkExit(Exit.OK);
checkOrder("p1/Class1.html",
"""
Link to <a href="../p2/package-summary.html#package-p2-heading"><code>heading in package p2</code></a>""",
"""
Plain link to <a href="../p2/Class2.html#class2-sub-heading">sub heading above</a></div>""",
"""
<li><a href="../p2/Class2.html#class2main"><code>See main heading in p2.Class2</code></a></li>
<li><a href="../p2/package-summary.html#package-p2-heading"><code>See heading in p2</code></a></li>
""");
checkOrder("p2/Class2.html",
"""
Link to <a href="#class2main"><code>local anchor</code></a>""",
"""
Plain link <a href="../p1/Class1.html#main">to Class1</a>.""");
checkOrder("p2/package-summary.html",
"""
<a href="Class2.html#class2-sub-heading"><code>See sub heading in p2.Class2</code></a>""");
checkOrder("p2/doc-files/file.html",
"""
Plain link to <a href="../../p1/Class1.html#main">heading in p1.ClassA</a>.""",
"""
<a href="../Class2.html#class2main"><code>See main heading in p2.ClassB</code></a>""");
}
@Test
public void testModule(Path base) throws Exception {
Path out = base.resolve("out");
javadoc("-d", out.toString(),
"--module-source-path", src.toString(),
"--no-platform-links",
"--module", "m1,m2",
"m2/com.m2");
checkExit(Exit.OK);
checkOrder("m1/module-summary.html",
"""
<a href="../m2/com/m2/Class2.html#main-heading"><code>See main heading in Class2</code></a>""");
checkOrder("m1/com/m1/Class1.html",
"""
<a href="../../../m2/com/m2/Class2.html#sub"><code>sub heading in Class2</code></a>.""",
"""
<li><a href="../../../m2/com/m2/Class2.html#main-heading"><code>See main heading in Class2</code></a></li>
<li><a href="../../module-summary.html#module-m1-heading"><code>See heading in module m1</code></a></li>
""");
checkOrder("m2/com/m2/Class2.html",
"""
Link to <a href="../../../m1/module-summary.html#module-m1-heading"><code>heading in module m1</code></a>""",
"""
Plain link to <a href="#sub">sub heading above</a>.""");
checkOrder("m2/doc-files/file.html",
"""
Link to <a href="../com/m2/Class2.html#main-heading"><code>heading in Class2</code></a>.""",
"""
<li><a href="../../m1/module-summary.html#module-m1-heading"><code>Heading in module m1</code></a></li>""");
}
@Test
public void testMissingLabel(Path base) throws Exception {
Path out = base.resolve("out");
javadoc("-d", out.toString(),
"-sourcepath", src.toString(),
"--no-platform-links",
"nolabel");
checkExit(Exit.OK);
checkOutput(Output.OUT, true, """
warning: missing reference label
Link with missing label: {@link ##main}.
^
""",
"""
Class1.java:5: warning: missing reference label
@see ##main
^
""");
checkOutput("nolabel/Class1.html", true, """
Link with missing label:\s
<details class="invalid-tag">
<summary>missing reference label</summary>
<pre>##main</pre>
</details>
.</div>
""",
"""
<details class="invalid-tag">
<summary>missing reference label</summary>
<pre>##main</pre>
""");
}
@Test
public void testInvalidLink(Path base) throws Exception {
Path out = base.resolve("out");
javadoc("-d", out.toString(),
"-sourcepath", src.toString(),
"--no-platform-links",
"inv");
checkExit(Exit.ERROR);
checkOutput(Output.OUT, true, "error: reference not found");
checkOutput("inv/Class1.html", true, """
Invalid link to\s
<details class="invalid-tag">
<summary>invalid @link</summary>
<pre><code>main heading</code></pre>
</details>""");
}
void generatePackageSources() throws Exception {
MethodBuilder mb = MethodBuilder.parse("public String method(String s) { return s; }")
.setComments("""
@see p2.Class2##class2main See main heading in p2.Class2
@see p2##package-p2-heading See heading in p2
""");
new ClassBuilder(tb, "p1.Class1")
.setModifiers("public", "class")
.setComments("""
<h2 id="main">Class1 Main</h2>
Link to {@link p2##package-p2-heading heading in package p2}
<h3>Class1 Sub</h3>
Plain link to {@linkplain p2.Class2##class2-sub-heading sub heading above}
""")
.addMembers(mb)
.write(src);
new ClassBuilder(tb, "p2.Class2")
.setModifiers("public", "class")
.setComments("""
<h2 id="class2main">Class2 Main</h2>
Link to {@link ##class2main local anchor}
<h3>Class2 Sub</h3>
Plain link {@linkplain p1.Class1##main to Class1}.
""")
.write(src);
tb.writeFile(src.resolve("p2").resolve("package-info.java"),
"""
/**
* <h2>Package p2</h2>
*
* @see p2.Class2##class2-sub-heading See sub heading in p2.Class2
*/
package p2;
""");
Path docFiles = src.resolve("p2").resolve("doc-files");
tb.writeFile(docFiles.resolve("file.html"),
"""
<html>
<head><title>Package p2 HTML File</title></head>
<body><h1>Package p2 HTML File</h1>
Plain link to {@linkplain p1.Class1##main heading in p1.ClassA}.
@see p2.Class2##class2main See main heading in p2.ClassB
</body>
</html>
""");
}
void generateModuleSources() throws Exception {
new ModuleBuilder(tb, "m1")
.exports("com.m1")
.classes("""
package com.m1;
/**
* Link to the {@link m2/com.m2.Class2##sub sub heading in Class2}.
*
* @see m2/com.m2.Class2##main-heading See main heading in Class2
* @see m1/##module-m1-heading See heading in module m1
*/
public class Class1 {}
""")
.comment("""
<h2>Module m1</h2>
@see m2/com.m2.Class2##main-heading See main heading in Class2
""")
.write(src);
new ModuleBuilder(tb, "m2")
.exports("com.m2")
.classes("""
package com.m2;
/**
* <h2>Main</h2>
* Link to {@link m1/##module-m1-heading heading in module m1}
*
* <h3 id="sub">Sub</h3>
* Plain link to {@linkplain Class2##sub sub heading above}.
*/
public class Class2 {}
""")
.write(src);
Path docFiles = src.resolve("m2").resolve("doc-files");
tb.writeFile(docFiles.resolve("file.html"),
"""
<html>
<head><title>Module m2 HTML File</title></head>
<body><h1>Module m2 HTML File</h1>
Link to {@link com.m2.Class2##main-heading heading in Class2}.
@see m1/##module-m1-heading Heading in module m1
</body>
</html>
""");
}
void generateInvalidLinkSource() throws Exception {
new ClassBuilder(tb, "inv.Class1")
.setModifiers("public", "class")
.setComments("""
<h2 id="main">Class1 Main</h2>
Invalid link to {@link #main main heading}.
""")
.write(src);
}
void generateMissingLabelSource() throws Exception {
new ClassBuilder(tb, "nolabel.Class1")
.setModifiers("public", "class")
.setComments("""
<h2 id="main">Class1 Main</h2>
Link with missing label: {@link ##main}.
@see ##main
""")
.write(src);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 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
@ -23,7 +23,7 @@
/*
* @test
* @bug 7021614 8273244
* @bug 7021614 8273244 8200337
* @summary extend com.sun.source API to support parsing javadoc comments
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.file
@ -191,6 +191,26 @@ DocComment[DOC_COMMENT, pos:1
body: empty
block tags: empty
]
*/
/**
* abc {@linkplain java.lang.String##fragment desc} def
*/
void fragment_desc() { }
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: 3
Text[TEXT, pos:1, abc_]
Link[LINK_PLAIN, pos:5
reference:
Reference[REFERENCE, pos:17, java.lang.String##fragment]
body: 1
Text[TEXT, pos:44, desc]
]
Text[TEXT, pos:49, _def]
body: empty
block tags: empty
]
*/
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 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
@ -23,7 +23,7 @@
/*
* @test
* @bug 7021614 8273244
* @bug 7021614 8273244 8200337
* @summary extend com.sun.source API to support parsing javadoc comments
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.file
@ -191,6 +191,26 @@ DocComment[DOC_COMMENT, pos:1
body: empty
block tags: empty
]
*/
/**
* abc {@link java.lang.String##fragment desc} def
*/
void fragment_desc() { }
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: 3
Text[TEXT, pos:1, abc_]
Link[LINK, pos:5
reference:
Reference[REFERENCE, pos:12, java.lang.String##fragment]
body: 1
Text[TEXT, pos:39, desc]
]
Text[TEXT, pos:44, _def]
body: empty
block tags: empty
]
*/
}

View File

@ -23,7 +23,7 @@
/*
* @test
* @bug 7021614 8031212 8273244 8284908
* @bug 7021614 8031212 8273244 8284908 8200337
* @summary extend com.sun.source API to support parsing javadoc comments
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.file
@ -155,6 +155,25 @@ DocComment[DOC_COMMENT, pos:1
Text[TEXT, pos:51, text]
]
]
*/
/**
* abc.
* @see java.lang.String##fragment text
*/
void j_l_string_anchor() { }
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: 1
Text[TEXT, pos:1, abc.]
body: empty
block tags: 1
See[SEE, pos:7
reference: 2
Reference[REFERENCE, pos:12, java.lang.String##fragment]
Text[TEXT, pos:39, text]
]
]
*/
/**

View File

@ -72,6 +72,38 @@ DocComment[DOC_COMMENT, pos:1
Text[TEXT, pos:27, f2_is_a_String]
]
]
*/
/**
* @serialField field String#member f3 is a String
*/
String f3;
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: empty
body: empty
block tags: 1
Erroneous[ERRONEOUS, pos:1, prefPos:26
code: compiler.err.dc.ref.unexpected.input
body: @serialField_fie...ld_String#member_f3_is_a_String
]
]
*/
/**
* @serialField field String##fragment f4 is a String
*/
String f4;
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: empty
body: empty
block tags: 1
Erroneous[ERRONEOUS, pos:1, prefPos:26
code: compiler.err.dc.ref.unexpected.input
body: @serialField_fie...ld_String##fragment_f4_is_a_String
]
]
*/
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 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
@ -69,5 +69,36 @@ DocComment[DOC_COMMENT, pos:1
]
*/
/**
* @throws Exception#member text
*/
void exception_member() throws Exception { }
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: empty
body: empty
block tags: 1
Erroneous[ERRONEOUS, pos:1, prefPos:18
code: compiler.err.dc.ref.unexpected.input
body: @throws_Exception#member_text
]
]
*/
/**
* @throws Exception##fragment text
*/
void exception_fragment() throws Exception { }
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: empty
body: empty
block tags: 1
Erroneous[ERRONEOUS, pos:1, prefPos:18
code: compiler.err.dc.ref.unexpected.input
body: @throws_Exceptio...n##fragment_text
]
]
*/
}

View File

@ -177,6 +177,42 @@ DocComment[DOC_COMMENT, pos:1
body: empty
block tags: empty
]
*/
/**
* abc {@value java.awt.Color}
*/
int type_reference() { }
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: 3
Text[TEXT, pos:1, abc_]
Erroneous[ERRONEOUS, pos:5, prefPos:17
code: compiler.err.dc.ref.unexpected.input
body: {@value_java.awt.Color
]
Text[TEXT, pos:27, }]
body: empty
block tags: empty
]
*/
/**
* abc {@value java.awt.Color##fragment}
*/
int anchor_reference() { }
/*
DocComment[DOC_COMMENT, pos:1
firstSentence: 3
Text[TEXT, pos:1, abc_]
Erroneous[ERRONEOUS, pos:5, prefPos:28
code: compiler.err.dc.ref.unexpected.input
body: {@value_java.awt....Color##fragment
]
Text[TEXT, pos:37, }]
body: empty
block tags: empty
]
*/
}