Skip to content

Commit 14e9039

Browse files
eamonnmcmanusgoogle-java-format Team
authored andcommitted
Handle blank lines before and within Markdown lists more accurately.
PiperOrigin-RevId: 956203301
1 parent 7d9649f commit 14e9039

4 files changed

Lines changed: 42 additions & 36 deletions

File tree

‎core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocFormatter.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ private static String render(List<Token> input, int blockIndent, boolean classic
131131
case MoeEndStripComment t -> output.writeMoeEndStripComment(t);
132132
case HtmlComment t -> output.writeHtmlComment(t);
133133
case BrTag t -> output.writeBr(standardizeBrToken(t));
134-
case Whitespace unused -> output.requestWhitespace();
134+
case Whitespace t -> output.requestWhitespaceOrBlankLine(t);
135135
case ForcedNewline unused -> output.writeLineBreakNoAutoIndent();
136136
case MarkdownHardLineBreak unused -> output.writeMarkdownHardLineBreak();
137137
case Literal t -> output.writeLiteral(t);

‎core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocLexer.java‎

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -388,9 +388,9 @@ private void checkMatchingTags() throws LexException {
388388
* ["<b>foo</b>"]}. See {@link #literalPattern()} for discussion of why those tokens are separate
389389
* to begin with.
390390
*
391-
* <p>Whitespace tokens are treated analogously. We don't really "want" to join whitespace tokens,
392-
* but in the course of joining literals, we incidentally join whitespace, too. We do take
393-
* advantage of the joining later on: It simplifies {@link #inferParagraphTags}.
391+
* <p>Whitespace tokens are treated analogously. The joining of whitespace tokens allows our
392+
* Markdown output to detect "loose lists" and our Traditional output to {@linkplain
393+
* #inferParagraphTags infer where to place paragraph tags}.
394394
*
395395
* <p>Note that we do <i>not</i> merge a literal token and a whitespace token together.
396396
*/
@@ -419,14 +419,15 @@ private static ImmutableList<Token> joinAdjacentLiteralsAndAdjacentWhitespace(Li
419419
*/
420420

421421
if (accumulated.isEmpty()) {
422-
output.add(tokens.next());
422+
if (tokens.peek() instanceof Whitespace) {
423+
output.add(new Whitespace(consumeAdjacentWhitespace(tokens)));
424+
} else {
425+
output.add(tokens.next());
426+
}
423427
continue;
424428
}
425429

426-
StringBuilder seenWhitespace = new StringBuilder();
427-
while (tokens.peek() instanceof Whitespace) {
428-
seenWhitespace.append(tokens.next().value());
429-
}
430+
String seenWhitespace = consumeAdjacentWhitespace(tokens);
430431

431432
if (tokens.peek() instanceof Literal literal && literal.value().startsWith("@")) {
432433
// OK, we're in the case described above.
@@ -439,10 +440,10 @@ private static ImmutableList<Token> joinAdjacentLiteralsAndAdjacentWhitespace(Li
439440
accumulated.setLength(0);
440441

441442
if (!seenWhitespace.isEmpty()) {
442-
output.add(new Whitespace(seenWhitespace.toString()));
443+
output.add(new Whitespace(seenWhitespace));
443444
}
444445

445-
// We have another token coming, possibly of type OTHER. Leave it for the next iteration.
446+
// We have another token coming. Leave it for the next iteration.
446447
}
447448

448449
/*
@@ -452,6 +453,14 @@ private static ImmutableList<Token> joinAdjacentLiteralsAndAdjacentWhitespace(Li
452453
return output.build();
453454
}
454455

456+
private static String consumeAdjacentWhitespace(PeekingIterator<Token> tokens) {
457+
StringBuilder seenWhitespace = new StringBuilder();
458+
while (tokens.peek() instanceof Whitespace) {
459+
seenWhitespace.append(tokens.next().value());
460+
}
461+
return seenWhitespace.toString();
462+
}
463+
455464
/**
456465
* Where the input has two consecutive line breaks between literals, insert a {@code <p>} tag
457466
* between the literals.
@@ -635,7 +644,7 @@ private static void deindentPreCodeBlock(
635644

636645
private static final CharMatcher NEWLINE = CharMatcher.is('\n');
637646

638-
private static boolean hasMultipleNewlines(String s) {
647+
static boolean hasMultipleNewlines(String s) {
639648
return NEWLINE.countIn(s) > 1;
640649
}
641650

‎core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocWriter.java‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import com.google.googlejavaformat.java.javadoc.Token.StartOfLineToken;
4848
import com.google.googlejavaformat.java.javadoc.Token.TableCloseTag;
4949
import com.google.googlejavaformat.java.javadoc.Token.TableOpenTag;
50+
import com.google.googlejavaformat.java.javadoc.Token.Whitespace;
5051
import java.util.List;
5152

5253
/**
@@ -100,6 +101,23 @@ private void requestWhitespace(RequestedWhitespace requestedWhitespace) {
100101
this.requestedWhitespace = max(requestedWhitespace, this.requestedWhitespace);
101102
}
102103

104+
/**
105+
* Requests whitespace or a blank line depending on the whitespace token.
106+
*
107+
* <p>In Markdown Javadoc, if the whitespace token contains multiple newlines, it represents a
108+
* blank line in the input (e.g., between a paragraph and a list, or between loose list items). We
109+
* want to preserve these blank lines, so we request a blank line. Otherwise, or in classic
110+
* Javadoc (where blank lines are handled via inferred {@code <p>} tags), we just request standard
111+
* whitespace.
112+
*/
113+
void requestWhitespaceOrBlankLine(Whitespace token) {
114+
if (!classicJavadoc && JavadocLexer.hasMultipleNewlines(token.value())) {
115+
requestBlankLine();
116+
} else {
117+
requestWhitespace();
118+
}
119+
}
120+
103121
void requestMoeBeginStripComment(MoeBeginStripComment token) {
104122
// We queue this up so that we can put it after any requested whitespace.
105123
requestedMoeBeginStripComment = checkNotNull(token);

‎core/src/test/java/com/google/googlejavaformat/java/JavadocFormattingTest.java‎

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1745,6 +1745,7 @@ class Test {}
17451745
/// 1. nested thing 1 on more than one line
17461746
/// 2. nested thing 2 on only one line but which is long enough that it is going to need to be
17471747
/// wrapped
1748+
///
17481749
/// 3. nested thing 3 after a blank line
17491750
///
17501751
/// A following paragraph.
@@ -2081,13 +2082,7 @@ public void markdownLooseLists() {
20812082
/// - item 2
20822083
class Test {}
20832084
""";
2084-
// TODO: the line break between items should be preserved.
2085-
String expected =
2086-
"""
2087-
/// - item 1
2088-
/// - item 2
2089-
class Test {}
2090-
""";
2085+
String expected = input;
20912086
doFormatTest(input, expected);
20922087
}
20932088

@@ -2104,16 +2099,7 @@ public void markdownListPrecedingBlankLine() {
21042099
/// - Item 2.
21052100
class Test {}
21062101
""";
2107-
// TODO(b/534219145): The blank line before the list should be preserved.
2108-
String expected =
2109-
"""
2110-
/// Title.
2111-
///
2112-
/// Some paragraph.
2113-
/// - Item 1.
2114-
/// - Item 2.
2115-
class Test {}
2116-
""";
2102+
String expected = input;
21172103
doFormatTest(input, expected);
21182104
}
21192105

@@ -2433,13 +2419,6 @@ class Test {}
24332419
// [foo]: /url "title"
24342420
// https://spec.commonmark.org/0.31.2/#link-reference-definitions
24352421
//
2436-
// - Loose lists
2437-
// "A list is loose if any of its constituent list items are separated by blank lines, or if any
2438-
// of its constituent list items directly contain two block-level elements with a blank line
2439-
// between them."
2440-
// We should test that we do not remove blank lines from a loose list, which would make it a
2441-
// tight one. https://spec.commonmark.org/0.31.2/#loose
2442-
//
24432422
// - Block quotes
24442423
// > foo
24452424
// > bar

0 commit comments

Comments
 (0)