8338525: Leading and trailing code blocks by indentation
Reviewed-by: hannesw, prappo
This commit is contained in:
parent
b639661e79
commit
0b8c9f6d23
@ -286,16 +286,17 @@ public class DocCommentParser {
|
|||||||
int depth = 1; // only used when phase is INLINE
|
int depth = 1; // only used when phase is INLINE
|
||||||
int pos = bp; // only used when phase is INLINE
|
int pos = bp; // only used when phase is INLINE
|
||||||
|
|
||||||
|
if (textKind == DocTree.Kind.MARKDOWN) {
|
||||||
|
initMarkdownLine();
|
||||||
|
}
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
while (bp < buflen) {
|
while (bp < buflen) {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case '\n', '\r' -> {
|
case '\n', '\r' -> {
|
||||||
nextChar();
|
nextChar();
|
||||||
if (textKind == DocTree.Kind.MARKDOWN) {
|
if (textKind == DocTree.Kind.MARKDOWN) {
|
||||||
markdown.update();
|
initMarkdownLine();
|
||||||
if (markdown.isIndentedCodeBlock()) {
|
|
||||||
markdown.skipLine();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,6 +489,17 @@ public class DocCommentParser {
|
|||||||
nextChar();
|
nextChar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void initMarkdownLine() {
|
||||||
|
if (textStart == -1) {
|
||||||
|
textStart = bp;
|
||||||
|
}
|
||||||
|
markdown.update();
|
||||||
|
if (markdown.isIndentedCodeBlock()) {
|
||||||
|
markdown.skipLine();
|
||||||
|
lastNonWhite = bp - 1; // do not include newline or EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private IllegalStateException unknownTextKind(DocTree.Kind textKind) {
|
private IllegalStateException unknownTextKind(DocTree.Kind textKind) {
|
||||||
return new IllegalStateException(textKind.toString());
|
return new IllegalStateException(textKind.toString());
|
||||||
}
|
}
|
||||||
|
@ -592,27 +592,33 @@ public class DocTreeMaker implements DocTreeFactory {
|
|||||||
case TEXT, MARKDOWN -> {
|
case TEXT, MARKDOWN -> {
|
||||||
var peekedNext = iter.hasNext() ? alist.get(iter.nextIndex()) : null;
|
var peekedNext = iter.hasNext() ? alist.get(iter.nextIndex()) : null;
|
||||||
var content = getContent(dt);
|
var content = getContent(dt);
|
||||||
int breakOffset = getSentenceBreak(dt.getKind(), content, peekedNext);
|
if (isFirst && dt.getKind() == Kind.MARKDOWN && isIndented(content)) {
|
||||||
if (breakOffset > 0) {
|
// begins with an indented code block (unusual), so no first sentence
|
||||||
// the end of sentence is within the current node;
|
body.add(dt);
|
||||||
// split it, skipping whitespace in between the two parts
|
|
||||||
var fsPart = newNode(dt.getKind(), dt.pos, content.substring(0, breakOffset).stripTrailing());
|
|
||||||
fs.add(fsPart);
|
|
||||||
int wsOffset = skipWhiteSpace(content, breakOffset);
|
|
||||||
if (wsOffset > 0) {
|
|
||||||
var bodyPart = newNode(dt.getKind(), dt.pos + wsOffset, content.substring(wsOffset));
|
|
||||||
body.add(bodyPart);
|
|
||||||
}
|
|
||||||
foundFirstSentence = true;
|
|
||||||
} else if (peekedNext != null && isSentenceBreak(peekedNext, false)) {
|
|
||||||
// the next node is a sentence break, so this is the end of the first sentence;
|
|
||||||
// remove trailing spaces
|
|
||||||
var fsPart = newNode(dt.getKind(), dt.pos, content.stripTrailing());
|
|
||||||
fs.add(fsPart);
|
|
||||||
foundFirstSentence = true;
|
foundFirstSentence = true;
|
||||||
} else {
|
} else {
|
||||||
// no sentence break found; keep scanning
|
int breakOffset = getSentenceBreak(dt.getKind(), content, peekedNext);
|
||||||
fs.add(dt);
|
if (breakOffset > 0) {
|
||||||
|
// the end of sentence is within the current node;
|
||||||
|
// split it, skipping whitespace in between the two parts
|
||||||
|
var fsPart = newNode(dt.getKind(), dt.pos, content.substring(0, breakOffset).stripTrailing());
|
||||||
|
fs.add(fsPart);
|
||||||
|
int wsOffset = skipWhiteSpace(content, breakOffset);
|
||||||
|
if (wsOffset > 0) {
|
||||||
|
var bodyPart = newNode(dt.getKind(), dt.pos + wsOffset, content.substring(wsOffset));
|
||||||
|
body.add(bodyPart);
|
||||||
|
}
|
||||||
|
foundFirstSentence = true;
|
||||||
|
} else if (peekedNext != null && isSentenceBreak(peekedNext, false)) {
|
||||||
|
// the next node is a sentence break, so this is the end of the first sentence;
|
||||||
|
// remove trailing spaces
|
||||||
|
var fsPart = newNode(dt.getKind(), dt.pos, content.stripTrailing());
|
||||||
|
fs.add(fsPart);
|
||||||
|
foundFirstSentence = true;
|
||||||
|
} else {
|
||||||
|
// no sentence break found; keep scanning
|
||||||
|
fs.add(dt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,6 +657,11 @@ public class DocTreeMaker implements DocTreeFactory {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Pattern INDENT = Pattern.compile(" {4}| {0,3}\t");
|
||||||
|
private boolean isIndented(String s) {
|
||||||
|
return INDENT.matcher(s).lookingAt();
|
||||||
|
}
|
||||||
|
|
||||||
private DCTree newNode(DocTree.Kind kind, int pos, String text) {
|
private DCTree newNode(DocTree.Kind kind, int pos, String text) {
|
||||||
return switch (kind) {
|
return switch (kind) {
|
||||||
case TEXT -> m.at(pos).newTextTree(text);
|
case TEXT -> m.at(pos).newTextTree(text);
|
||||||
|
@ -486,4 +486,107 @@ public class TestMarkdownCodeBlocks extends JavadocTester {
|
|||||||
<dd><code><a href="NullPointerException.html" title="class in p">NullPointerException</a></code> - if other is <code>null</code></dd>
|
<dd><code><a href="NullPointerException.html" title="class in p">NullPointerException</a></code> - if other is <code>null</code></dd>
|
||||||
</dl>""");
|
</dl>""");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLeadingCodeBlock(Path base) throws Exception {
|
||||||
|
Path src = base.resolve("src");
|
||||||
|
tb.writeJavaFiles(src,
|
||||||
|
"""
|
||||||
|
package p;
|
||||||
|
/// Leading code block
|
||||||
|
/// Lorum ipsum.
|
||||||
|
public class C { }
|
||||||
|
""");
|
||||||
|
|
||||||
|
javadoc("-d", base.resolve("api").toString(),
|
||||||
|
"--no-platform-links",
|
||||||
|
"--source-path", src.toString(),
|
||||||
|
"p");
|
||||||
|
checkExit(Exit.OK);
|
||||||
|
|
||||||
|
// check first sentence is empty in package summary file
|
||||||
|
checkOutput("p/package-summary.html", true,
|
||||||
|
"""
|
||||||
|
<div class="col-first even-row-color class-summary class-summary-tab2"><a href="C.html" title="class in p">C</a></div>
|
||||||
|
<div class="col-last even-row-color class-summary class-summary-tab2"> </div>""");
|
||||||
|
|
||||||
|
checkOutput("p/C.html", true,
|
||||||
|
"""
|
||||||
|
<div class="block"><pre><code>Leading code block
|
||||||
|
</code></pre>
|
||||||
|
<p>Lorum ipsum.</p>""");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTrailingCodeBlock(Path base) throws Exception {
|
||||||
|
Path src = base.resolve("src");
|
||||||
|
tb.writeJavaFiles(src,
|
||||||
|
"""
|
||||||
|
package p;
|
||||||
|
/// Lorum ipsum.
|
||||||
|
///
|
||||||
|
/// Trailing code block
|
||||||
|
public class C { }
|
||||||
|
""");
|
||||||
|
|
||||||
|
javadoc("-d", base.resolve("api").toString(),
|
||||||
|
"--no-platform-links",
|
||||||
|
"--source-path", src.toString(),
|
||||||
|
"p");
|
||||||
|
checkExit(Exit.OK);
|
||||||
|
|
||||||
|
checkOutput("p/C.html", true,
|
||||||
|
"""
|
||||||
|
<div class="block"><p>Lorum ipsum.</p>
|
||||||
|
<pre><code>Trailing code block
|
||||||
|
</code></pre>
|
||||||
|
</div>""");
|
||||||
|
}
|
||||||
|
|
||||||
|
// this example is derived from the test case in JDK-8338525
|
||||||
|
@Test
|
||||||
|
public void testLeadingTrailingCodeBlockWithAnnotations(Path base) throws Exception {
|
||||||
|
Path src = base.resolve("src");
|
||||||
|
tb.writeJavaFiles(src,
|
||||||
|
"""
|
||||||
|
package p;
|
||||||
|
public class C {
|
||||||
|
/// @Override
|
||||||
|
/// void m() {}
|
||||||
|
///
|
||||||
|
/// Plain text
|
||||||
|
///
|
||||||
|
/// @Override
|
||||||
|
/// void m() {}
|
||||||
|
public void m() {}
|
||||||
|
}""");
|
||||||
|
|
||||||
|
javadoc("-d", base.resolve("api").toString(),
|
||||||
|
"--no-platform-links",
|
||||||
|
"--source-path", src.toString(),
|
||||||
|
"p");
|
||||||
|
checkExit(Exit.OK);
|
||||||
|
|
||||||
|
checkOutput("p/C.html", true,
|
||||||
|
"""
|
||||||
|
<div class="col-first even-row-color method-summary-table method-summary-table-tab2 \
|
||||||
|
method-summary-table-tab4"><code>void</code></div>
|
||||||
|
<div class="col-second even-row-color method-summary-table method-summary-table-tab2 \
|
||||||
|
method-summary-table-tab4"><code><a href="#m()" class="member-name-link">m</a>()</code></div>
|
||||||
|
<div class="col-last even-row-color method-summary-table method-summary-table-tab2 \
|
||||||
|
method-summary-table-tab4"> </div>""",
|
||||||
|
"""
|
||||||
|
<div class="member-signature"><span class="modifiers">public</span> \
|
||||||
|
<span class="return-type">void</span> <span class="element-name">m</span>()</div>
|
||||||
|
<div class="block"><pre><code>@Override
|
||||||
|
void m() {}
|
||||||
|
</code></pre>
|
||||||
|
<p>Plain text</p>
|
||||||
|
<pre><code>@Override
|
||||||
|
void m() {}
|
||||||
|
</code></pre>
|
||||||
|
</div>""");
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1043,8 +1043,9 @@ public class DocCommentTester {
|
|||||||
.replaceAll("(\\{@value\\s+[^}]+)\\s+(})", "$1$2");
|
.replaceAll("(\\{@value\\s+[^}]+)\\s+(})", "$1$2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See comment in MarkdownTest for explanation of dummy and Override
|
||||||
String normalizeFragment(String s) {
|
String normalizeFragment(String s) {
|
||||||
return s.replaceAll("\n[ \t]+@(?!([@*]|dummy))", "\n@");
|
return s.replaceAll("\n[ \t]+@(?!([@*]|(dummy|Override)))", "\n@");
|
||||||
}
|
}
|
||||||
|
|
||||||
int copyLiteral(String s, int start, StringBuilder sb) {
|
int copyLiteral(String s, int start, StringBuilder sb) {
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
* In the tests for code spans and code blocks, "@dummy" is used as a dummy inline
|
* In the tests for code spans and code blocks, "@dummy" is used as a dummy inline
|
||||||
* or block tag to verify that it is skipped as part of the code span or code block.
|
* or block tag to verify that it is skipped as part of the code span or code block.
|
||||||
* In other words, "@dummy" should appear as a literal part of the Markdown content.
|
* In other words, "@dummy" should appear as a literal part of the Markdown content.
|
||||||
|
* ("@Override" is also treated the same way, as a commonly found annotation.)
|
||||||
* Conversely, standard tags are used to verify that a fragment of text is not being
|
* Conversely, standard tags are used to verify that a fragment of text is not being
|
||||||
* skipped as a code span or code block. In other words, they should be recognized as tags
|
* skipped as a code span or code block. In other words, they should be recognized as tags
|
||||||
* and not skipped as part of any Markdown content.
|
* and not skipped as part of any Markdown content.
|
||||||
@ -409,6 +410,32 @@ DocComment[DOC_COMMENT, pos:0
|
|||||||
RawText[MARKDOWN, pos:85, .]
|
RawText[MARKDOWN, pos:85, .]
|
||||||
block tags: empty
|
block tags: empty
|
||||||
]
|
]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Indented Code Block
|
||||||
|
/// Lorum ipsum.
|
||||||
|
void indentedCodeBlock_leading() { }
|
||||||
|
/*
|
||||||
|
DocComment[DOC_COMMENT, pos:0
|
||||||
|
firstSentence: empty
|
||||||
|
body: 1
|
||||||
|
RawText[MARKDOWN, pos:0, ____Indented_Code_Block|Lorum_ipsum.]
|
||||||
|
block tags: empty
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Lorum ipsum.
|
||||||
|
///
|
||||||
|
/// Indented Code Block
|
||||||
|
void indentedCodeBlock_trailing() { }
|
||||||
|
/*
|
||||||
|
DocComment[DOC_COMMENT, pos:0
|
||||||
|
firstSentence: 1
|
||||||
|
RawText[MARKDOWN, pos:0, Lorum_ipsum.]
|
||||||
|
body: 1
|
||||||
|
RawText[MARKDOWN, pos:18, Indented_Code_Block]
|
||||||
|
block tags: empty
|
||||||
|
]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
///123.
|
///123.
|
||||||
@ -613,5 +640,24 @@ DocComment[DOC_COMMENT, pos:0
|
|||||||
]
|
]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// The following test case is derived from the test case in JDK-8338525.
|
||||||
|
|
||||||
|
/// @Override
|
||||||
|
/// void m() { }
|
||||||
|
///
|
||||||
|
/// Plain text
|
||||||
|
///
|
||||||
|
/// @Override
|
||||||
|
/// void m() { }
|
||||||
|
void leadingTrailingCodeBlocksWithAnnos() { }
|
||||||
|
/*
|
||||||
|
DocComment[DOC_COMMENT, pos:0
|
||||||
|
firstSentence: empty
|
||||||
|
body: 1
|
||||||
|
RawText[MARKDOWN, pos:0, ____@Override|____void_m()_{_}||...||____@Override|____void_m()_{_}]
|
||||||
|
block tags: empty
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user