8281969: Bad result for the snippet @link tag if substring/regex consists of whitespace

Reviewed-by: jjg
This commit is contained in:
Pavel Rappo 2022-07-14 22:27:53 +00:00
parent c8e0315114
commit 15d3329edd
2 changed files with 48 additions and 28 deletions
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html
test/langtools/jdk/javadoc/doclet/testSnippetTag

@ -425,24 +425,13 @@ public class TagletWriterImpl extends TagletWriter {
return; return;
} else if (linkEncountered) { } else if (linkEncountered) {
assert e != null; assert e != null;
String line = sequence.toString();
String strippedLine = line.strip();
int idx = line.indexOf(strippedLine);
assert idx >= 0; // because the stripped line is a substring of the line being stripped
Text whitespace = Text.of(utils.normalizeNewlines(line.substring(0, idx)));
//disable preview tagging inside the snippets: //disable preview tagging inside the snippets:
PreviewFlagProvider prevPreviewProvider = utils.setPreviewFlagProvider(el -> false); PreviewFlagProvider prevPreviewProvider = utils.setPreviewFlagProvider(el -> false);
try { try {
// If the leading whitespace is not excluded from the link, c = new ContentBuilder(htmlWriter.linkToContent(element, e, t, sequence.toString()));
// browsers might exhibit unwanted behavior. For example, a
// browser might display hand-click cursor while user hovers
// over that whitespace portion of the line; or use
// underline decoration.
c = new ContentBuilder(whitespace, htmlWriter.linkToContent(element, e, t, strippedLine));
} finally { } finally {
utils.setPreviewFlagProvider(prevPreviewProvider); utils.setPreviewFlagProvider(prevPreviewProvider);
} }
// We don't care about trailing whitespace.
} else { } else {
c = HtmlTree.SPAN(Text.of(text)); c = HtmlTree.SPAN(Text.of(text));
classes.forEach(((HtmlTree) c)::addStyle); classes.forEach(((HtmlTree) c)::addStyle);

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -23,7 +23,7 @@
/* /*
* @test * @test
* @bug 8266666 * @bug 8266666 8281969
* @summary Implementation for snippets * @summary Implementation for snippets
* @library /tools/lib ../../lib * @library /tools/lib ../../lib
* @modules jdk.compiler/com.sun.tools.javac.api * @modules jdk.compiler/com.sun.tools.javac.api
@ -180,7 +180,28 @@ public class TestSnippetMarkup extends SnippetTester {
link(First) link(line) link(First) link(line)
Second line Second line
""", "link\\((.+?)\\)", r -> link(true, "java.lang.Object#Object", r.group(1))) """, "link\\((.+?)\\)", r -> link(true, "java.lang.Object#Object", r.group(1)))
)); ),
new TestCase(
"""
First line
Second line // @link substring=" " target="java.lang.System#out"
""",
replace("""
First line
link( )Secondlink( )line
""", "link\\((.+?)\\)", r -> link(true, "java.lang.System#out", r.group(1)))
),
new TestCase(
"""
First line
Second line // @link regex=" " target="java.lang.System#in"
""",
replace("""
First line
link( )Secondlink( )line
""", "link\\((.+?)\\)", r -> link(true, "java.lang.System#in", r.group(1)))
)
);
testPositive(base, testCases); testPositive(base, testCases);
} }
@ -575,7 +596,7 @@ First line // @highlight :
""", """,
replace(""" replace("""
First line First line
link(Third line) link( Third line)
""", "link\\((.+?)\\)", r -> link(true, "java.lang.Object#equals(Object)", r.group(1))) """, "link\\((.+?)\\)", r -> link(true, "java.lang.Object#equals(Object)", r.group(1)))
), ),
new TestCase(""" new TestCase("""
@ -717,19 +738,29 @@ First line // @highlight :
String content) String content)
throws UncheckedIOException { throws UncheckedIOException {
// The HTML <a> tag generated from the @link snippet markup tag is the // The HTML A element generated for the @link snippet markup tag is
// same as that of the {@link} Standard doclet tag. This is specified // the same as that for the similar Standard doclet {@link} tag.
// and can be used for comparison and testing. // This fact can be used for comparison and testing.
// generate documentation for {@link} to grab its HTML <a> tag; // Generate documentation for {@link} to grab its HTML A element.
// generate documentation at low cost and do not interfere with the // Generate documentation cheaply and do not interfere with the
// calling test state; for that, do not create file trees, do not write // calling test state; for that: do not create file trees, do not write
// to std out/err, and generally try to keep everything in memory // to std out/err, and generally try to keep everything in memory.
String source = """ // Caveat: a label used in snippet's @link tag can start, end, or both,
// with whitespace. In this regard, snippet's @link differs from
// {@link} and {@linkplain} Standard doclet tags, which trim whitespace
// from labels. In particular, {@link} and {@linkplain} treat
// whitespace after the reference as an absent label, whereas
// snippet's @link does not. To avoid whitespace problems,
// LABEL_PLACEHOLDER is used. It is later substituted with "content",
// which might be an empty or blank string.
var LABEL_PLACEHOLDER = "label";
var source = """
/** {@link %s %s} */ /** {@link %s %s} */
public interface A { } public interface A { }
""".formatted(targetReference, content); """.formatted(targetReference, LABEL_PLACEHOLDER);
JavaFileObject src = new JavaFileObject() { JavaFileObject src = new JavaFileObject() {
@Override @Override
@ -850,12 +881,12 @@ First line // @highlight :
} }
String output = fileManager.getFileString(DOCUMENTATION_OUTPUT, "A.html"); String output = fileManager.getFileString(DOCUMENTATION_OUTPUT, "A.html");
// use the [^<>] regex to select HTML elements that immediately enclose "content" // use the [^<>] regex to select HTML elements that immediately enclose "content"
Matcher m = Pattern.compile("(?is)<a href=\"[^<>]*\" title=\"[^<>]*\" class=\"[^<>]*\"><code>" Matcher m = Pattern.compile("(?is)(<a href=\"[^<>]*\" title=\"[^<>]*\" class=\"[^<>]*\"><code>)"
+ content + "</code></a>").matcher(output); + LABEL_PLACEHOLDER + "(</code></a>)").matcher(output);
if (!m.find()) { if (!m.find()) {
throw new IOException(output); throw new IOException(output);
} }
return m.group(0); return m.group(1) + content + m.group(2);
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} }