8320207: doclet incorrectly chooses code font for a See Also link
Reviewed-by: hannesw
This commit is contained in:
parent
1629a9059b
commit
407cdd4cac
@ -117,7 +117,7 @@ public class LinkTaglet extends BaseTaglet {
|
||||
* @param refTree the tree node containing the information, or {@code null} if not available
|
||||
* @param refSignature the normalized signature of the target of the reference
|
||||
* @param ref the target of the reference
|
||||
* @param isLinkPlain {@code true} if the link should be presented in "plain" font,
|
||||
* @param isPlain {@code true} if the link should be presented in "plain" font,
|
||||
* or {@code false} for "code" font
|
||||
* @param label the label for the link,
|
||||
* or an empty item to use a default label derived from the signature
|
||||
@ -130,17 +130,17 @@ public class LinkTaglet extends BaseTaglet {
|
||||
DocTree refTree,
|
||||
String refSignature,
|
||||
Element ref,
|
||||
boolean isLinkPlain,
|
||||
boolean isPlain,
|
||||
Content label,
|
||||
BiConsumer<String, Object[]> reportWarning,
|
||||
TagletWriter tagletWriter) {
|
||||
var config = tagletWriter.configuration;
|
||||
var htmlWriter = tagletWriter.htmlWriter;
|
||||
|
||||
Content labelContent = plainOrCode(isLinkPlain, label);
|
||||
Content labelContent = plainOrCode(isPlain, label);
|
||||
|
||||
// The signature from the @see tag. We will output this text when a label is not specified.
|
||||
Content text = plainOrCode(isLinkPlain,
|
||||
Content text = plainOrCode(isPlain,
|
||||
Text.of(Objects.requireNonNullElse(refSignature, "")));
|
||||
|
||||
CommentHelper ch = utils.getCommentHelper(holder);
|
||||
@ -170,7 +170,7 @@ public class LinkTaglet extends BaseTaglet {
|
||||
if (refPackage != null && utils.isIncluded(refPackage)) {
|
||||
//@see is referencing an included package
|
||||
if (labelContent.isEmpty()) {
|
||||
labelContent = plainOrCode(isLinkPlain,
|
||||
labelContent = plainOrCode(isPlain,
|
||||
Text.of(refPackage.getQualifiedName()));
|
||||
}
|
||||
return htmlWriter.getPackageLink(refPackage, labelContent, refFragment);
|
||||
@ -202,10 +202,10 @@ public class LinkTaglet extends BaseTaglet {
|
||||
TypeMirror referencedType = ch.getReferencedType(refTree);
|
||||
if (utils.isGenericType(referencedType)) {
|
||||
// This is a generic type link, use the TypeMirror representation.
|
||||
return plainOrCode(isLinkPlain, htmlWriter.getLink(
|
||||
return plainOrCode(isPlain, htmlWriter.getLink(
|
||||
new HtmlLinkInfo(config, HtmlLinkInfo.Kind.LINK_TYPE_PARAMS_AND_BOUNDS, referencedType)));
|
||||
}
|
||||
labelContent = plainOrCode(isLinkPlain, Text.of(utils.getSimpleName(refClass)));
|
||||
labelContent = plainOrCode(isPlain, Text.of(utils.getSimpleName(refClass)));
|
||||
}
|
||||
return htmlWriter.getLink(new HtmlLinkInfo(config, HtmlLinkInfo.Kind.PLAIN, refClass)
|
||||
.label(labelContent));
|
||||
@ -267,7 +267,7 @@ public class LinkTaglet extends BaseTaglet {
|
||||
|
||||
return htmlWriter.getDocLink(HtmlLinkInfo.Kind.SHOW_PREVIEW, containing,
|
||||
refMem, (labelContent.isEmpty()
|
||||
? plainOrCode(isLinkPlain, Text.of(refMemName))
|
||||
? plainOrCode(isPlain, Text.of(refMemName))
|
||||
: labelContent), null, false);
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import javax.lang.model.element.VariableElement;
|
||||
|
||||
import com.sun.source.doctree.DocTree;
|
||||
import com.sun.source.doctree.SeeTree;
|
||||
import com.sun.source.doctree.TextTree;
|
||||
|
||||
import jdk.javadoc.doclet.Taglet;
|
||||
import jdk.javadoc.internal.doclets.formats.html.ClassWriter;
|
||||
@ -174,7 +175,7 @@ public class SeeTaglet extends BaseTaglet implements InheritableTaglet {
|
||||
seeTag,
|
||||
refSignature,
|
||||
ch.getReferencedElement(seeTag),
|
||||
false,
|
||||
isPlain(refSignature, label),
|
||||
htmlWriter.commentTagsToContent(element, label, tagletWriter.getContext().within(seeTag)),
|
||||
(key, args) -> messages.warning(ch.getDocTreePath(seeTag), key, args),
|
||||
tagletWriter
|
||||
@ -189,7 +190,124 @@ public class SeeTaglet extends BaseTaglet implements InheritableTaglet {
|
||||
|
||||
default -> throw new IllegalStateException(ref0.getKind().toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return {@code true} if the label should be rendered in plain font}
|
||||
*
|
||||
* The method uses a heuristic, to see if the string form of the label
|
||||
* is a substring of the reference. Thus, for example:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@code @see MyClass.MY_CONSTANT MY_CONSTANT} returns {@code true}
|
||||
* <li>{@code @see MyClass.MY_CONSTANT a constant} returns {@code false}
|
||||
* </ul>
|
||||
*
|
||||
* The result will be {@code true} (meaning, displayed in plain font) if
|
||||
* any of the following are true about the label:
|
||||
*
|
||||
* <ul>
|
||||
* <li>There is more than a single item in the list of nodes,
|
||||
* suggesting there may be formatting nodes.
|
||||
* <li>There is whitespace outside any parentheses,
|
||||
* suggesting the label is a phrase
|
||||
* <li>There are nested parentheses, or content after the parentheses,
|
||||
* which cannot occur in a standalone signature
|
||||
* <li>The simple name inferred from the reference does not match
|
||||
* any simple name inferred from the label
|
||||
* </ul>
|
||||
*
|
||||
* @param refSignature the signature of the target of the reference
|
||||
* @param label the label
|
||||
*/
|
||||
private boolean isPlain(String refSignature, List<? extends DocTree> label) {
|
||||
if (label.isEmpty()) {
|
||||
return false;
|
||||
} else if (label.size() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var l0 = label.get(0);
|
||||
String s;
|
||||
if (l0 instanceof TextTree t) {
|
||||
s = t.getBody().trim();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
// look for whitespace outside any parens, nested parens, or characters after parens:
|
||||
// all of which will not be found in a simple signature
|
||||
var inParens = false;
|
||||
var ids = new ArrayList<String>();
|
||||
var sb = new StringBuilder();
|
||||
for (var i = 0; i < s.length(); i++) {
|
||||
var ch = s.charAt(i);
|
||||
if (!sb.isEmpty() && !Character.isJavaIdentifierPart(ch)) {
|
||||
ids.add(sb.toString());
|
||||
sb.setLength(0);
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
case '(' -> {
|
||||
if (inParens) {
|
||||
return true;
|
||||
} else {
|
||||
inParens = true;
|
||||
}
|
||||
}
|
||||
case ')' -> {
|
||||
if (inParens && i < s.length() - 1) {
|
||||
return true;
|
||||
} else {
|
||||
inParens = false;
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
if (!inParens) {
|
||||
if (Character.isJavaIdentifierStart(ch)
|
||||
|| (!sb.isEmpty() && Character.isJavaIdentifierPart(ch))) {
|
||||
sb.append(ch);
|
||||
} else if (Character.isWhitespace(ch)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sb.isEmpty()) {
|
||||
ids.add(sb.toString());
|
||||
}
|
||||
|
||||
if (ids.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// final check: does the simple name inferred from the label
|
||||
// match the simple name inferred from the reference
|
||||
var labelSimpleName = ids.get(ids.size() - 1);
|
||||
var refSimpleName = getSimpleName(refSignature);
|
||||
return !labelSimpleName.equals((refSimpleName));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the simple name from a signature}
|
||||
*
|
||||
* If there is a member part in the signature, the simple name is the
|
||||
* identifier after the {@code #} character.
|
||||
* Otherwise, the simple name is the last identifier in the signature.
|
||||
*
|
||||
* @param sig the signature
|
||||
*/
|
||||
private String getSimpleName(String sig) {
|
||||
int hash = sig.indexOf('#');
|
||||
if (hash == -1 ) {
|
||||
int lastDot = sig.lastIndexOf(".");
|
||||
return lastDot == -1 ? sig : sig.substring(lastDot + 1);
|
||||
} else {
|
||||
int parens = sig.indexOf("(", hash);
|
||||
return parens == -1 ? sig.substring(hash + 1) : sig.substring(hash + 1, parens);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -112,8 +112,8 @@ public class TestGenericTypeLink extends JavadocTester {
|
||||
" class="external-link">String</a>,<wbr><a href="A.SomeException.html" title="class\
|
||||
in pkg1">A.SomeException</a>></code></li>
|
||||
<li><a href="http://example.com/docs/api/java.base/java/util/List.html" title="clas\
|
||||
s or interface in java.util" class="external-link"><code>Link to generic type with \
|
||||
label</code></a></li>
|
||||
s or interface in java.util" class="external-link">Link to generic type with label<\
|
||||
/a></li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>"""
|
||||
|
@ -79,8 +79,8 @@ public class TestSeeLinkAnchor extends JavadocTester {
|
||||
"""
|
||||
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>
|
||||
<li><a href="../p2/Class2.html#class2main">See main heading in p2.Class2</a></li>
|
||||
<li><a href="../p2/package-summary.html#package-p2-heading">See heading in p2</a></li>
|
||||
""");
|
||||
checkOrder("p2/Class2.html",
|
||||
"""
|
||||
@ -89,13 +89,13 @@ public class TestSeeLinkAnchor extends JavadocTester {
|
||||
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>""");
|
||||
<a href="Class2.html#class2-sub-heading">See sub heading in p2.Class2</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>""");
|
||||
<a href="../Class2.html#class2main">See main heading in p2.ClassB</a>""");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -111,13 +111,13 @@ public class TestSeeLinkAnchor extends JavadocTester {
|
||||
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>""");
|
||||
<a href="../m2/com/m2/Class2.html#main-heading">See main heading in Class2</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>
|
||||
<li><a href="../../../m2/com/m2/Class2.html#main-heading">See main heading in Class2</a></li>
|
||||
<li><a href="../../module-summary.html#module-m1-heading">See heading in module m1</a></li>
|
||||
""");
|
||||
checkOrder("m2/com/m2/Class2.html",
|
||||
"""
|
||||
@ -128,7 +128,7 @@ public class TestSeeLinkAnchor extends JavadocTester {
|
||||
"""
|
||||
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>""");
|
||||
<li><a href="../../m1/module-summary.html#module-m1-heading">Heading in module m1</a></li>""");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -62,7 +62,7 @@ public class TestSeeTag extends JavadocTester {
|
||||
<li><a href="Test.InnerOne.html#foo()"><code>Test.InnerOne.foo()</code></a></li>
|
||||
<li><a href="Test.InnerOne.html#bar(java.lang.Object)"><code>Test.InnerOne.bar(Object)</code></a></li>
|
||||
<li><a href="http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#see">Javadoc</a></li>
|
||||
<li><a href="Test.InnerOne.html#baz(float)"><code>something</code></a></li>
|
||||
<li><a href="Test.InnerOne.html#baz(float)">something</a></li>
|
||||
<li><a href="Test.InnerOne.html#format(java.lang.String,java.lang.Object...)"><code>\
|
||||
Test.InnerOne.format(java.lang.String, java.lang.Object...)</code></a></li>
|
||||
</ul>
|
||||
@ -80,7 +80,7 @@ public class TestSeeTag extends JavadocTester {
|
||||
<dd>
|
||||
<ul class="tag-list-long">
|
||||
<li><code>Serializable</code></li>
|
||||
<li><a href="Test.html" title="class in pkg"><code>See tag with very long label text</code></a></li>
|
||||
<li><a href="Test.html" title="class in pkg">See tag with very long label text</a></li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>""");
|
||||
@ -213,17 +213,17 @@ public class TestSeeTag extends JavadocTester {
|
||||
"<section class=\"detail\" id=\"noComma()\">",
|
||||
"""
|
||||
<ul class="tag-list">
|
||||
<li><a href="#noArgs()"><code>no args</code></a></li>
|
||||
<li><a href="#oneArg(int)"><code>one arg</code></a></li>
|
||||
<li><a href="#twoArgs(int,int)"><code>two args</code></a></li>
|
||||
<li><a href="#noArgs()">no args</a></li>
|
||||
<li><a href="#oneArg(int)">one arg</a></li>
|
||||
<li><a href="#twoArgs(int,int)">two args</a></li>
|
||||
</ul>""",
|
||||
|
||||
"<section class=\"detail\" id=\"commaInDescription()\">",
|
||||
"""
|
||||
<ul class="tag-list-long">
|
||||
<li><a href="#noArgs()"><code>no args</code></a></li>
|
||||
<li><a href="#oneArg(int)"><code>one arg</code></a></li>
|
||||
<li><a href="#twoArgs(int,int)"><code>two args with a comma , in the description</code></a></li>
|
||||
<li><a href="#noArgs()">no args</a></li>
|
||||
<li><a href="#oneArg(int)">one arg</a></li>
|
||||
<li><a href="#twoArgs(int,int)">two args with a comma , in the description</a></li>
|
||||
</ul>""",
|
||||
|
||||
"<section class=\"detail\" id=\"commaInDefaultDescription()\">",
|
||||
|
145
test/langtools/jdk/javadoc/doclet/testSeeTag/TestSeeTagFont.java
Normal file
145
test/langtools/jdk/javadoc/doclet/testSeeTag/TestSeeTagFont.java
Normal file
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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 8320207
|
||||
* @summary doclet incorrectly chooses code font for a See Also link
|
||||
* @library /tools/lib ../../lib
|
||||
* @modules jdk.javadoc/jdk.javadoc.internal.tool
|
||||
* @build toolbox.ToolBox javadoc.tester.*
|
||||
* @run main TestSeeTagFont
|
||||
*/
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import javadoc.tester.JavadocTester;
|
||||
import toolbox.ToolBox;
|
||||
|
||||
public class TestSeeTagFont extends JavadocTester {
|
||||
public static void main(String... args) throws Exception {
|
||||
var tester = new TestSeeTagFont();
|
||||
tester.runTests();
|
||||
}
|
||||
|
||||
private final ToolBox tb = new ToolBox();
|
||||
|
||||
@Test
|
||||
public void testPlain(Path base) throws Exception {
|
||||
Path src = base.resolve("src");
|
||||
tb.writeJavaFiles(src,
|
||||
"""
|
||||
package p;
|
||||
import p2.Other;
|
||||
/**
|
||||
* Description.
|
||||
* @see Other multi-word phrase
|
||||
* @see Other <em>Other</em>
|
||||
* @see Other Other() with trailing text
|
||||
* @see Other simpleNameMismatch
|
||||
*
|
||||
* @see Other#Other() multi-word phrase
|
||||
* @see Other#Other() Other#Other() with trailing text
|
||||
* @see Other#Other() simpleNameMismatch
|
||||
*
|
||||
* @see Other#m() <code>Other.m</code> with formatting and trailing text
|
||||
*/
|
||||
public class C { }
|
||||
""",
|
||||
"""
|
||||
package p2;
|
||||
/** Lorem ipsum. */
|
||||
public class Other {
|
||||
/** Lorem ipsum. */
|
||||
public void m() { }
|
||||
}
|
||||
""");
|
||||
|
||||
javadoc("-d", base.resolve("api").toString(),
|
||||
"-Xdoclint:none",
|
||||
"-sourcepath", src.toString(),
|
||||
"p", "p2");
|
||||
checkExit(Exit.OK);
|
||||
|
||||
// none of the following should contain <code>...</code>
|
||||
checkOutput("p/C.html", true,
|
||||
"""
|
||||
<ul class="tag-list-long">
|
||||
<li><a href="../p2/Other.html" title="class in p2">multi-word phrase</a></li>
|
||||
<li><a href="../p2/Other.html" title="class in p2"><em>Other</em></a></li>
|
||||
<li><a href="../p2/Other.html" title="class in p2">Other() with trailing text</a></li>
|
||||
<li><a href="../p2/Other.html" title="class in p2">simpleNameMismatch</a></li>
|
||||
<li><a href="../p2/Other.html#%3Cinit%3E()">multi-word phrase</a></li>
|
||||
<li><a href="../p2/Other.html#%3Cinit%3E()">Other#Other() with trailing text</a></li>
|
||||
<li><a href="../p2/Other.html#%3Cinit%3E()">simpleNameMismatch</a></li>
|
||||
<li><a href="../p2/Other.html#m()"><code>Other.m</code> with formatting and trailing text</a></li>
|
||||
</ul>
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCode(Path base) throws Exception {
|
||||
Path src = base.resolve("src");
|
||||
tb.writeJavaFiles(src,
|
||||
"""
|
||||
package p;
|
||||
import p2.Other;
|
||||
/**
|
||||
* Description.
|
||||
* @see Other
|
||||
* @see p2.Other Other
|
||||
*
|
||||
* @see Other#Other() Other
|
||||
* @see Other#m() m
|
||||
* @see Other#m() Other.m
|
||||
*/
|
||||
public class C { }
|
||||
""",
|
||||
"""
|
||||
package p2;
|
||||
/** Lorem ipsum. */
|
||||
public class Other {
|
||||
/** Lorem ipsum. */
|
||||
public void m() { }
|
||||
}
|
||||
""");
|
||||
|
||||
javadoc("-d", base.resolve("api").toString(),
|
||||
"-Xdoclint:none",
|
||||
"-sourcepath", src.toString(),
|
||||
"p", "p2");
|
||||
checkExit(Exit.OK);
|
||||
|
||||
// all of the following should contain <code>...</code>
|
||||
checkOutput("p/C.html", true,
|
||||
"""
|
||||
<ul class="tag-list">
|
||||
<li><a href="../p2/Other.html" title="class in p2"><code>Other</code></a></li>
|
||||
<li><a href="../p2/Other.html" title="class in p2"><code>Other</code></a></li>
|
||||
<li><a href="../p2/Other.html#%3Cinit%3E()"><code>Other</code></a></li>
|
||||
<li><a href="../p2/Other.html#m()"><code>m</code></a></li>
|
||||
<li><a href="../p2/Other.html#m()"><code>Other.m</code></a></li>
|
||||
</ul>
|
||||
""");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user