mirror of
https://github.com/openjdk/jdk.git
synced 2026-04-29 00:02:34 +00:00
8352389: Remove incidental whitespace in pre/code content
Reviewed-by: liach
This commit is contained in:
parent
257f817c7f
commit
24ff96afe4
@ -34,8 +34,12 @@ import java.util.regex.Pattern;
|
||||
import com.sun.source.doctree.AttributeTree.ValueKind;
|
||||
import com.sun.source.doctree.DocTree;
|
||||
import com.sun.source.doctree.ErroneousTree;
|
||||
import com.sun.source.doctree.LiteralTree;
|
||||
import com.sun.source.doctree.StartElementTree;
|
||||
import com.sun.source.doctree.TextTree;
|
||||
import com.sun.source.doctree.UnknownBlockTagTree;
|
||||
import com.sun.source.doctree.UnknownInlineTagTree;
|
||||
import com.sun.source.util.SimpleDocTreeVisitor;
|
||||
import com.sun.tools.javac.parser.Tokens.Comment;
|
||||
import com.sun.tools.javac.tree.DCTree;
|
||||
import com.sun.tools.javac.tree.DCTree.DCAttribute;
|
||||
@ -126,6 +130,9 @@ public class DocCommentParser {
|
||||
private int lastNonWhite = -1;
|
||||
private boolean newline = true;
|
||||
|
||||
/** Used for whitespace normalization in pre/code/literal tags. */
|
||||
private boolean inPre = false;
|
||||
|
||||
private final Map<Name, TagParser> tagParsers;
|
||||
|
||||
/**
|
||||
@ -281,6 +288,7 @@ public class DocCommentParser {
|
||||
*/
|
||||
protected List<DCTree> content(Phase phase) {
|
||||
ListBuffer<DCTree> trees = new ListBuffer<>();
|
||||
ListBuffer<DCTree> mainTrees = null;
|
||||
textStart = -1;
|
||||
|
||||
int depth = 1; // only used when phase is INLINE
|
||||
@ -349,6 +357,18 @@ public class DocCommentParser {
|
||||
addPendingText(trees, bp - 1);
|
||||
trees.add(html());
|
||||
|
||||
// Create new list for <pre> content which is merged into main list when element is closed.
|
||||
if (inPre) {
|
||||
if (mainTrees == null) {
|
||||
mainTrees = trees;
|
||||
trees = new ListBuffer<>();
|
||||
}
|
||||
} else if (mainTrees != null) {
|
||||
mainTrees.addAll(normalizePreContent(trees));
|
||||
trees = mainTrees;
|
||||
mainTrees = null;
|
||||
}
|
||||
|
||||
if (phase == Phase.PREAMBLE || phase == Phase.POSTAMBLE) {
|
||||
break; // Ignore newlines after html tags, in the meta content
|
||||
}
|
||||
@ -476,6 +496,13 @@ public class DocCommentParser {
|
||||
if (lastNonWhite != -1)
|
||||
addPendingText(trees, lastNonWhite);
|
||||
|
||||
// Happens with unclosed <pre> element. Add content without normalizing.
|
||||
if (mainTrees != null) {
|
||||
mainTrees.addAll(trees);
|
||||
trees = mainTrees;
|
||||
mainTrees = null;
|
||||
}
|
||||
|
||||
return (phase == Phase.INLINE)
|
||||
? List.of(erroneous("dc.unterminated.inline.tag", pos))
|
||||
: trees.toList();
|
||||
@ -1054,6 +1081,9 @@ public class DocCommentParser {
|
||||
}
|
||||
if (ch == '>') {
|
||||
nextChar();
|
||||
if ("pre".equalsIgnoreCase(name.toString())) {
|
||||
inPre = true;
|
||||
}
|
||||
return m.at(p).newStartElementTree(name, attrs, selfClosing).setEndPos(bp);
|
||||
}
|
||||
}
|
||||
@ -1064,6 +1094,9 @@ public class DocCommentParser {
|
||||
skipWhitespace();
|
||||
if (ch == '>') {
|
||||
nextChar();
|
||||
if ("pre".equalsIgnoreCase(name.toString())) {
|
||||
inPre = false;
|
||||
}
|
||||
return m.at(p).newEndElementTree(name).setEndPos(bp);
|
||||
}
|
||||
}
|
||||
@ -1186,6 +1219,102 @@ public class DocCommentParser {
|
||||
return attrs.toList();
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes a newline character following a <code>, {@code or {@literal tag at the
|
||||
* beginning of <pre> element content, as well as any space/tabs between the pre
|
||||
* and code tags. The operation is only performed on traditional doc comments.
|
||||
* If conditions are not met the list is returned unchanged.
|
||||
*/
|
||||
ListBuffer<DCTree> normalizePreContent(ListBuffer<DCTree> trees) {
|
||||
// Do nothing if comment is not eligible for whitespace normalization.
|
||||
if (textKind == DocTree.Kind.MARKDOWN || isHtmlFile) {
|
||||
return trees;
|
||||
}
|
||||
|
||||
enum State {
|
||||
BEFORE_CODE, // at beginning of <pre> content, or after leading horizontal whitespace
|
||||
AFTER_CODE, // after <code> start tag (not used for {@code} tag)
|
||||
SUCCEEDED, // normalization succeeded, add remaining trees
|
||||
FAILED; // normalization failed, return original trees
|
||||
}
|
||||
|
||||
class Context {
|
||||
State state = State.BEFORE_CODE;
|
||||
|
||||
// Called when an unexpected tree is encountered. Set state to
|
||||
// FAILED unless normalization already terminated successfully.
|
||||
void unexpectedTree() {
|
||||
if (state != State.SUCCEEDED) {
|
||||
state = State.FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var visitor = new SimpleDocTreeVisitor<DCTree, Context>() {
|
||||
@Override
|
||||
public DCTree visitText(TextTree text, Context cx) {
|
||||
if (cx.state == State.BEFORE_CODE && text.getBody().matches("[ \t]+")) {
|
||||
// <pre> ...
|
||||
return null;
|
||||
} else if (cx.state == State.AFTER_CODE && text.getBody().startsWith("\n")) {
|
||||
// <pre><code>\n...
|
||||
cx.state = State.SUCCEEDED;
|
||||
return m.at(((DCText) text).pos + 1).newTextTree(text.getBody().substring(1));
|
||||
}
|
||||
cx.unexpectedTree();
|
||||
return (DCTree) text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DCTree visitLiteral(LiteralTree literal, Context cx) {
|
||||
if (cx.state == State.BEFORE_CODE && literal.getBody().getBody().startsWith("\n")) {
|
||||
// <pre>{@code\n...
|
||||
cx.state = State.SUCCEEDED;
|
||||
DCText oldBody = (DCText) literal.getBody();
|
||||
DCText newBody = m.at(oldBody.pos + 1).newTextTree(oldBody.getBody().substring(1));
|
||||
m.at(((DCTree) literal).pos);
|
||||
return literal.getKind() == DocTree.Kind.CODE
|
||||
? m.newCodeTree(newBody)
|
||||
: m.newLiteralTree(newBody);
|
||||
}
|
||||
cx.unexpectedTree();
|
||||
return (DCTree) literal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DCTree visitStartElement(StartElementTree node, Context cx) {
|
||||
if (cx.state == State.BEFORE_CODE && node.getName().toString().equalsIgnoreCase("code")) {
|
||||
cx.state = State.AFTER_CODE;
|
||||
} else {
|
||||
cx.unexpectedTree();
|
||||
}
|
||||
return (DCTree) node;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DCTree defaultAction(DocTree node, Context cx) {
|
||||
cx.unexpectedTree();
|
||||
return (DCTree) node;
|
||||
}
|
||||
};
|
||||
|
||||
Context cx = new Context();
|
||||
var normalized = new ListBuffer<DCTree>();
|
||||
|
||||
for (var tree : trees) {
|
||||
var visited = visitor.visit(tree, cx);
|
||||
// null return value means the tree should be dropped
|
||||
if (visited != null) {
|
||||
normalized.add(visited);
|
||||
}
|
||||
if (cx.state == State.FAILED) {
|
||||
return trees;
|
||||
}
|
||||
}
|
||||
|
||||
return cx.state == State.SUCCEEDED ? normalized : trees;
|
||||
}
|
||||
|
||||
protected void attrValueChar(ListBuffer<DCTree> list) {
|
||||
switch (ch) {
|
||||
case '&' -> entity(list);
|
||||
|
||||
@ -133,7 +133,7 @@ pre {
|
||||
line-height: var(--code-line-height);
|
||||
background-color: var(--pre-background-color);
|
||||
color: var(--pre-text-color);
|
||||
padding: 8px;
|
||||
padding: 10px;
|
||||
overflow-x:auto;
|
||||
}
|
||||
h1 {
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8002387 8014636 8078320 8175200 8186332 8352249
|
||||
* @bug 8002387 8014636 8078320 8175200 8186332 8352249 8352389
|
||||
* @summary Improve rendered HTML formatting for {@code}
|
||||
* @library ../../lib
|
||||
* @modules jdk.javadoc/jdk.javadoc.internal.tool
|
||||
@ -70,8 +70,7 @@ public class TestLiteralCodeInPre extends JavadocTester {
|
||||
"""
|
||||
typical_usage_code</span>()</div>
|
||||
<div class="block">Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Example: <pre><code>
|
||||
line 0 @Override
|
||||
Example: <pre><code> line 0 @Override
|
||||
line 1 <T> void m(T t) {
|
||||
line 2 // do something with T
|
||||
line 3 }
|
||||
@ -80,8 +79,7 @@ public class TestLiteralCodeInPre extends JavadocTester {
|
||||
"""
|
||||
typical_usage_literal</span>()</div>
|
||||
<div class="block">Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Example: <pre>
|
||||
line 0 @Override
|
||||
Example: <pre> line 0 @Override
|
||||
line 1 <T> void m(T t) {
|
||||
line 2 // do something with T
|
||||
line 3 }
|
||||
@ -90,8 +88,7 @@ public class TestLiteralCodeInPre extends JavadocTester {
|
||||
"""
|
||||
recommended_usage_literal</span>()</div>
|
||||
<div class="block">Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Example: <pre>
|
||||
line 0 @Override
|
||||
Example: <pre> line 0 @Override
|
||||
line 1 <T> void m(T t) {
|
||||
line 2 // do something with T
|
||||
line 3 } </pre>
|
||||
|
||||
165
test/langtools/jdk/javadoc/doclet/testPreCode/TestPreCode.java
Normal file
165
test/langtools/jdk/javadoc/doclet/testPreCode/TestPreCode.java
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (c) 2025, 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 8352389
|
||||
* @summary Remove incidental whitespace in pre/code content
|
||||
* @library /tools/lib ../../lib
|
||||
* @modules jdk.javadoc/jdk.javadoc.internal.tool
|
||||
* @build javadoc.tester.* toolbox.ToolBox builder.ClassBuilder
|
||||
* @run main TestPreCode
|
||||
*/
|
||||
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
import builder.AbstractBuilder;
|
||||
import builder.ClassBuilder;
|
||||
import toolbox.ToolBox;
|
||||
|
||||
import javadoc.tester.JavadocTester;
|
||||
|
||||
public class TestPreCode extends JavadocTester {
|
||||
|
||||
final ToolBox tb;
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
var tester = new TestPreCode();
|
||||
tester.runTests();
|
||||
}
|
||||
|
||||
TestPreCode() {
|
||||
tb = new ToolBox();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWhitespace(Path base) throws Exception {
|
||||
Path srcDir = base.resolve("src");
|
||||
Path outDir = base.resolve("out");
|
||||
|
||||
new ClassBuilder(tb, "pkg.A")
|
||||
.setComments("""
|
||||
Class A.
|
||||
<pre> \t<code>
|
||||
first line
|
||||
second line
|
||||
</code></pre>""")
|
||||
.setModifiers("public", "class")
|
||||
.addMembers(ClassBuilder.MethodBuilder.parse("public void m0() {}")
|
||||
.setComments("""
|
||||
Method m0.
|
||||
<pre> {@code
|
||||
first line
|
||||
second line
|
||||
}</pre>"""),
|
||||
ClassBuilder.MethodBuilder.parse("public void m1() {}")
|
||||
.setComments("""
|
||||
Method m1.
|
||||
<pre> <code> first line
|
||||
second line
|
||||
</code></pre>"""),
|
||||
ClassBuilder.MethodBuilder.parse("public void m2() {}")
|
||||
.setComments("""
|
||||
Method m2.
|
||||
<pre> {@code\s
|
||||
first line
|
||||
second line
|
||||
}</pre>"""),
|
||||
ClassBuilder.MethodBuilder.parse("public void m3() {}")
|
||||
.setComments("""
|
||||
Method m3.
|
||||
<pre> .<code>
|
||||
second line
|
||||
</code></pre>"""))
|
||||
.write(srcDir);
|
||||
|
||||
javadoc("-d", outDir.toString(),
|
||||
"-sourcepath", srcDir.toString(),
|
||||
"pkg");
|
||||
|
||||
checkExit(Exit.OK);
|
||||
|
||||
checkOrder("pkg/A.html",
|
||||
"""
|
||||
Class A.
|
||||
<pre><code> first line
|
||||
second line
|
||||
</code></pre>""",
|
||||
"""
|
||||
Method m0.
|
||||
<pre><code> first line
|
||||
second line
|
||||
</code></pre>""",
|
||||
"""
|
||||
Method m1.
|
||||
<pre> <code> first line
|
||||
second line
|
||||
</code></pre>""",
|
||||
"""
|
||||
Method m2.
|
||||
<pre><code> first line
|
||||
second line
|
||||
</code></pre>""",
|
||||
"""
|
||||
Method m3.
|
||||
<pre> .<code>
|
||||
second line
|
||||
</code></pre>""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnclosed(Path base) throws Exception {
|
||||
Path srcDir = base.resolve("src");
|
||||
Path outDir = base.resolve("out");
|
||||
|
||||
new ClassBuilder(tb, "pkg.A")
|
||||
.setComments("""
|
||||
Class A.
|
||||
<pre><code>
|
||||
first line
|
||||
second line
|
||||
</code>""")
|
||||
.setModifiers("public", "class")
|
||||
.write(srcDir);
|
||||
|
||||
javadoc("-d", outDir.toString(),
|
||||
"-sourcepath", srcDir.toString(),
|
||||
"pkg");
|
||||
|
||||
checkExit(Exit.ERROR);
|
||||
|
||||
// No whitespace normalization for unclosed <pre> element
|
||||
checkOrder("pkg/A.html",
|
||||
"""
|
||||
Class A.
|
||||
<pre><code>
|
||||
first line
|
||||
second line
|
||||
</code></div>""");
|
||||
}
|
||||
|
||||
}
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 7021614 8241780 8273244 8284908 8352249
|
||||
* @bug 7021614 8241780 8273244 8284908 8352249 8352389
|
||||
* @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
|
||||
@ -128,7 +128,7 @@ DocComment[DOC_COMMENT, pos:0
|
||||
name:pre
|
||||
attributes: empty
|
||||
]
|
||||
Literal[CODE, pos:5, |____@Override|____void_m()_{_}|]
|
||||
Literal[CODE, pos:5, ____@Override|____void_m()_{_}|]
|
||||
body: 1
|
||||
EndElement[END_ELEMENT, pos:44, pre]
|
||||
block tags: empty
|
||||
|
||||
@ -1023,7 +1023,9 @@ public class DocCommentTester {
|
||||
.replaceFirst("\\.\\s*\\n *@(?![@*])", ".\n@") // Between block tags
|
||||
.replaceAll("\n[ \t]+@(?!([@*]|(dummy|Override)))", "\n@")
|
||||
.replaceAll("(?i)\\{@([a-z][a-z0-9.:-]*)\\s+}", "{@$1}")
|
||||
.replaceAll("(\\{@value\\s+[^}]+)\\s+(})", "$1$2");
|
||||
.replaceAll("(\\{@value\\s+[^}]+)\\s+(})", "$1$2")
|
||||
.replaceAll("<pre> *\\{@code\\n", "<pre>{@code ")
|
||||
.replaceAll("<pre> *<code>\\n", "<pre><code>");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8078320 8273244 8284908 8352249
|
||||
* @bug 8078320 8273244 8284908 8352249 8352389
|
||||
* @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
|
||||
@ -163,6 +163,50 @@ DocComment[DOC_COMMENT, pos:1
|
||||
EndElement[END_ELEMENT, pos:26, pre]
|
||||
block tags: empty
|
||||
]
|
||||
*/
|
||||
/**
|
||||
* <pre> {@code
|
||||
* abc }
|
||||
* def</pre>
|
||||
*/
|
||||
public void in_pre_with_space_at_code_nl() { }
|
||||
/*
|
||||
DocComment[DOC_COMMENT, pos:0
|
||||
firstSentence: 3
|
||||
StartElement[START_ELEMENT, pos:0
|
||||
name:pre
|
||||
attributes: empty
|
||||
]
|
||||
Literal[CODE, pos:6, abc__]
|
||||
Text[TEXT, pos:19, |def]
|
||||
body: 1
|
||||
EndElement[END_ELEMENT, pos:23, pre]
|
||||
block tags: empty
|
||||
]
|
||||
*/
|
||||
/**
|
||||
* <pre> <code>
|
||||
* abc
|
||||
* </code></pre>
|
||||
*/
|
||||
public void in_pre_with_space_code_nl() { }
|
||||
/*
|
||||
DocComment[DOC_COMMENT, pos:0
|
||||
firstSentence: 4
|
||||
StartElement[START_ELEMENT, pos:0
|
||||
name:pre
|
||||
attributes: empty
|
||||
]
|
||||
StartElement[START_ELEMENT, pos:6
|
||||
name:code
|
||||
attributes: empty
|
||||
]
|
||||
Text[TEXT, pos:13, __abc|]
|
||||
EndElement[END_ELEMENT, pos:19, code]
|
||||
body: 1
|
||||
EndElement[END_ELEMENT, pos:26, pre]
|
||||
block tags: empty
|
||||
]
|
||||
*/
|
||||
/**
|
||||
* abc {@code
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user