From 0b8c9f6d2397dcb480dc5ae109607d86f2b15619 Mon Sep 17 00:00:00 2001 From: Jonathan Gibbons Date: Tue, 24 Sep 2024 20:09:40 +0000 Subject: [PATCH] 8338525: Leading and trailing code blocks by indentation Reviewed-by: hannesw, prappo --- .../tools/javac/parser/DocCommentParser.java | 20 +++- .../sun/tools/javac/tree/DocTreeMaker.java | 49 +++++---- .../testMarkdown/TestMarkdownCodeBlocks.java | 103 ++++++++++++++++++ .../tools/javac/doctree/DocCommentTester.java | 3 +- .../tools/javac/doctree/MarkdownTest.java | 46 ++++++++ 5 files changed, 197 insertions(+), 24 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java index acd79c69bd4..be311bb3ab8 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java @@ -286,16 +286,17 @@ public class DocCommentParser { int depth = 1; // only used when phase is INLINE int pos = bp; // only used when phase is INLINE + if (textKind == DocTree.Kind.MARKDOWN) { + initMarkdownLine(); + } + loop: while (bp < buflen) { switch (ch) { case '\n', '\r' -> { nextChar(); if (textKind == DocTree.Kind.MARKDOWN) { - markdown.update(); - if (markdown.isIndentedCodeBlock()) { - markdown.skipLine(); - } + initMarkdownLine(); } } @@ -488,6 +489,17 @@ public class DocCommentParser { 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) { return new IllegalStateException(textKind.toString()); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java index 77f6eefc073..9e0767b6e21 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java @@ -592,27 +592,33 @@ public class DocTreeMaker implements DocTreeFactory { case TEXT, MARKDOWN -> { var peekedNext = iter.hasNext() ? alist.get(iter.nextIndex()) : null; var content = getContent(dt); - int breakOffset = getSentenceBreak(dt.getKind(), content, peekedNext); - 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); + if (isFirst && dt.getKind() == Kind.MARKDOWN && isIndented(content)) { + // begins with an indented code block (unusual), so no first sentence + body.add(dt); foundFirstSentence = true; } else { - // no sentence break found; keep scanning - fs.add(dt); + int breakOffset = getSentenceBreak(dt.getKind(), content, peekedNext); + 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) { return switch (kind) { case TEXT -> m.at(pos).newTextTree(text); diff --git a/test/langtools/jdk/javadoc/doclet/testMarkdown/TestMarkdownCodeBlocks.java b/test/langtools/jdk/javadoc/doclet/testMarkdown/TestMarkdownCodeBlocks.java index 8f1c76b9e47..dda8a76868e 100644 --- a/test/langtools/jdk/javadoc/doclet/testMarkdown/TestMarkdownCodeBlocks.java +++ b/test/langtools/jdk/javadoc/doclet/testMarkdown/TestMarkdownCodeBlocks.java @@ -486,4 +486,107 @@ public class TestMarkdownCodeBlocks extends JavadocTester {
NullPointerException - if other is null
"""); } + + @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, + """ +
C
+
 
"""); + + checkOutput("p/C.html", true, + """ +
Leading code block
+                    
+

Lorum ipsum.

"""); + + } + + @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, + """ +

Lorum ipsum.

+
Trailing code block
+                    
+
"""); + } + + // 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, + """ +
void
+
m()
+
 
""", + """ +
public \ + void m()
+
@Override
+                    void m() {}
+                    
+

Plain text

+
@Override
+                    void m() {}
+                    
+
"""); + + } } diff --git a/test/langtools/tools/javac/doctree/DocCommentTester.java b/test/langtools/tools/javac/doctree/DocCommentTester.java index f14dde2e567..d418005745a 100644 --- a/test/langtools/tools/javac/doctree/DocCommentTester.java +++ b/test/langtools/tools/javac/doctree/DocCommentTester.java @@ -1043,8 +1043,9 @@ public class DocCommentTester { .replaceAll("(\\{@value\\s+[^}]+)\\s+(})", "$1$2"); } + // See comment in MarkdownTest for explanation of dummy and Override 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) { diff --git a/test/langtools/tools/javac/doctree/MarkdownTest.java b/test/langtools/tools/javac/doctree/MarkdownTest.java index 8da9e6ed44c..3ec23076db9 100644 --- a/test/langtools/tools/javac/doctree/MarkdownTest.java +++ b/test/langtools/tools/javac/doctree/MarkdownTest.java @@ -40,6 +40,7 @@ * 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. * 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 * 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. @@ -409,6 +410,32 @@ DocComment[DOC_COMMENT, pos:0 RawText[MARKDOWN, pos:85, .] 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. @@ -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 +] +*/ + }