Skip to content

Commit b7680e0

Browse files
eamonnmcmanusgoogle-java-format Team
authored andcommitted
Add support for Markdown tables.
This is very basic, and the main intent is just to avoid mangling tables. We essentially preserve the formatting of any table we find, without attempting to adjust it in any way. PiperOrigin-RevId: 905301629
1 parent 7657be4 commit b7680e0

7 files changed

Lines changed: 121 additions & 19 deletions

File tree

core/pom.xml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,6 @@
3939
<groupId>com.google.guava</groupId>
4040
<artifactId>guava</artifactId>
4141
</dependency>
42-
<dependency>
43-
<groupId>org.commonmark</groupId>
44-
<artifactId>commonmark</artifactId>
45-
<version>0.28.0</version>
46-
</dependency>
4742

4843
<!-- Compile-time dependencies -->
4944
<dependency>
@@ -66,6 +61,17 @@
6661
<artifactId>auto-service-annotations</artifactId>
6762
<optional>true</optional>
6863
</dependency>
64+
<dependency>
65+
<groupId>org.commonmark</groupId>
66+
<artifactId>commonmark</artifactId>
67+
<version>0.28.0</version>
68+
</dependency>
69+
<dependency>
70+
<groupId>org.commonmark</groupId>
71+
<artifactId>commonmark-ext-gfm-tables</artifactId>
72+
<version>0.28.0</version>
73+
<scope>compile</scope>
74+
</dependency>
6975

7076
<!-- Test dependencies -->
7177
<dependency>

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import com.google.googlejavaformat.java.javadoc.Token.MarkdownCodeSpanStart;
4545
import com.google.googlejavaformat.java.javadoc.Token.MarkdownFencedCodeBlock;
4646
import com.google.googlejavaformat.java.javadoc.Token.MarkdownHardLineBreak;
47+
import com.google.googlejavaformat.java.javadoc.Token.MarkdownTable;
4748
import com.google.googlejavaformat.java.javadoc.Token.MoeBeginStripComment;
4849
import com.google.googlejavaformat.java.javadoc.Token.MoeEndStripComment;
4950
import com.google.googlejavaformat.java.javadoc.Token.OptionalLineBreak;
@@ -137,6 +138,7 @@ private static String render(List<Token> input, int blockIndent, boolean classic
137138
case MarkdownHardLineBreak unused -> output.writeMarkdownHardLineBreak();
138139
case Literal t -> output.writeLiteral(t);
139140
case MarkdownFencedCodeBlock t -> output.writeMarkdownFencedCodeBlock(t);
141+
case MarkdownTable t -> output.writeMarkdownTable(t);
140142
case ListItemCloseTag unused -> {}
141143
case OptionalLineBreak unused -> {}
142144
case ParagraphCloseTag unused -> {}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import com.google.googlejavaformat.java.javadoc.Token.ListOpenTag;
3636
import com.google.googlejavaformat.java.javadoc.Token.Literal;
3737
import com.google.googlejavaformat.java.javadoc.Token.MarkdownFencedCodeBlock;
38+
import com.google.googlejavaformat.java.javadoc.Token.MarkdownTable;
3839
import com.google.googlejavaformat.java.javadoc.Token.MoeBeginStripComment;
3940
import com.google.googlejavaformat.java.javadoc.Token.MoeEndStripComment;
4041
import com.google.googlejavaformat.java.javadoc.Token.PreCloseTag;
@@ -44,6 +45,7 @@
4445
import com.google.googlejavaformat.java.javadoc.Token.StartOfLineToken;
4546
import com.google.googlejavaformat.java.javadoc.Token.TableCloseTag;
4647
import com.google.googlejavaformat.java.javadoc.Token.TableOpenTag;
48+
import java.util.List;
4749

4850
/**
4951
* Stateful object that accepts "requests" and "writes," producing formatted Javadoc.
@@ -335,6 +337,9 @@ void writeLiteral(Literal token) {
335337
}
336338

337339
void writeMarkdownFencedCodeBlock(MarkdownFencedCodeBlock token) {
340+
if (!token.precededByNonWhitespace() && wroteAnythingSignificant) {
341+
requestBlankLine();
342+
}
338343
flushWhitespace();
339344
output.append(token.start());
340345
token
@@ -350,6 +355,20 @@ void writeMarkdownFencedCodeBlock(MarkdownFencedCodeBlock token) {
350355
requestBlankLine();
351356
}
352357

358+
void writeMarkdownTable(MarkdownTable token) {
359+
if (!token.precededByNonWhitespace() && wroteAnythingSignificant) {
360+
requestBlankLine();
361+
}
362+
flushWhitespace();
363+
List<String> lines = token.value().lines().toList();
364+
output.append(lines.get(0));
365+
for (String line : lines.subList(1, lines.size())) {
366+
writeNewline(AutoIndent.NO_AUTO_INDENT);
367+
output.append(line);
368+
}
369+
requestBlankLine();
370+
}
371+
353372
@Override
354373
public String toString() {
355374
return output.toString();

core/src/main/java/com/google/googlejavaformat/java/javadoc/MarkdownPositions.java

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,14 @@
2828
import com.google.googlejavaformat.java.javadoc.Token.MarkdownCodeSpanEnd;
2929
import com.google.googlejavaformat.java.javadoc.Token.MarkdownCodeSpanStart;
3030
import com.google.googlejavaformat.java.javadoc.Token.MarkdownFencedCodeBlock;
31+
import com.google.googlejavaformat.java.javadoc.Token.MarkdownTable;
3132
import com.google.googlejavaformat.java.javadoc.Token.ParagraphCloseTag;
3233
import com.google.googlejavaformat.java.javadoc.Token.ParagraphOpenTag;
34+
import java.util.Objects;
3335
import java.util.regex.Matcher;
3436
import java.util.regex.Pattern;
37+
import org.commonmark.ext.gfm.tables.TableBlock;
38+
import org.commonmark.ext.gfm.tables.TablesExtension;
3539
import org.commonmark.node.BulletList;
3640
import org.commonmark.node.Code;
3741
import org.commonmark.node.FencedCodeBlock;
@@ -92,6 +96,10 @@ void visit(Node node) {
9296
case OrderedList orderedList -> addSpan(orderedList, LIST_OPEN_TOKEN, LIST_CLOSE_TOKEN);
9397
case ListItem listItem -> alreadyVisitedChildren = visitListItem(listItem);
9498
case FencedCodeBlock fencedCodeBlock -> visitFencedCodeBlock(fencedCodeBlock);
99+
case TableBlock tableBlock -> {
100+
visitTableBlock(tableBlock);
101+
alreadyVisitedChildren = true;
102+
}
95103
case Code code -> visitCodeSpan(code);
96104
// TODO: others
97105
default -> {}
@@ -128,16 +136,30 @@ private void visitFencedCodeBlock(FencedCodeBlock fencedCodeBlock) {
128136
// indentation gets subtracted from FencedCodeBlock.getLiteral(), which is the actual text
129137
// represented by the code block.
130138
int start = startPosition(fencedCodeBlock) + fencedCodeBlock.getFenceIndent();
139+
boolean precededByNonWhitespace = precededByNonWhitespace(start);
140+
int closingLength =
141+
Objects.requireNonNullElse(
142+
fencedCodeBlock.getClosingFenceLength(), fencedCodeBlock.getOpeningFenceLength());
131143
MarkdownFencedCodeBlock token =
132144
new MarkdownFencedCodeBlock(
133145
input.substring(start, endPosition(fencedCodeBlock)),
134146
fencedCodeBlock.getFenceCharacter().repeat(fencedCodeBlock.getOpeningFenceLength())
135147
+ fencedCodeBlock.getInfo(),
136-
fencedCodeBlock.getFenceCharacter().repeat(fencedCodeBlock.getClosingFenceLength()),
137-
fencedCodeBlock.getLiteral());
148+
fencedCodeBlock.getFenceCharacter().repeat(closingLength),
149+
fencedCodeBlock.getLiteral(),
150+
precededByNonWhitespace);
138151
positionToToken.get(start).addLast(token);
139152
}
140153

154+
private void visitTableBlock(TableBlock tableBlock) {
155+
int start = startPosition(tableBlock);
156+
boolean precededByNonWhitespace = precededByNonWhitespace(start);
157+
int end = endPosition(tableBlock);
158+
positionToToken
159+
.get(start)
160+
.addLast(new MarkdownTable(input.substring(start, end), precededByNonWhitespace));
161+
}
162+
141163
private void visitCodeSpan(Code code) {
142164
int start = startPosition(code);
143165
int end = endPosition(code);
@@ -164,6 +186,15 @@ private void visitNodeList(Node node) {
164186
}
165187
}
166188

189+
private boolean precededByNonWhitespace(int position) {
190+
for (int i = position - 1; i >= 0 && input.charAt(i) != '\n'; i--) {
191+
if (!Character.isWhitespace(input.charAt(i))) {
192+
return true;
193+
}
194+
}
195+
return false;
196+
}
197+
167198
/**
168199
* Adds tokens for the given node, {@code startToken} at the point where the node starts in the
169200
* input, and {@code endToken} at the point where it ends. The {@code startToken} goes after any
@@ -195,7 +226,10 @@ public String toString() {
195226
}
196227

197228
private static final Parser PARSER =
198-
Parser.builder().includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES).build();
229+
Parser.builder()
230+
.includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES)
231+
.extensions(ImmutableList.of(TablesExtension.create()))
232+
.build();
199233

200234
private static final HeaderOpenTag HEADER_OPEN_TOKEN = new HeaderOpenTag("");
201235
private static final HeaderCloseTag HEADER_CLOSE_TOKEN = new HeaderCloseTag("");

core/src/main/java/com/google/googlejavaformat/java/javadoc/Token.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,29 @@ record MarkdownCodeSpanEnd(String value) implements Token {}
135135
* @param end the end fence.
136136
* @param literal the text that the code block represents. This does not include the start and end
137137
* fences, nor any indentation that precedes these fences and every intervening line.
138+
* @param precededByNonWhitespace whether the start of the code block is preceded by at least one
139+
* non-whitespace character on the same line, for example {@code - ```}.
138140
*/
139-
record MarkdownFencedCodeBlock(String value, String start, String end, String literal)
141+
record MarkdownFencedCodeBlock(
142+
String value, String start, String end, String literal, boolean precededByNonWhitespace)
140143
implements Token {}
141144

145+
/**
146+
* A Markdown table, like:
147+
*
148+
* {@snippet :
149+
* | foo | bar |
150+
* | --- | --- |
151+
* | baz | qux |
152+
* }
153+
*
154+
* @param value the full text of the table as it appeared in the input, including the delimiters
155+
* and the literal content.
156+
* @param precededByNonWhitespace whether the start of the table is preceded by at least one
157+
* non-whitespace character on the same line, for example {@code - |foo|bar|}.
158+
*/
159+
record MarkdownTable(String value, boolean precededByNonWhitespace) implements Token {}
160+
142161
/**
143162
* Whitespace that is not in a {@code <pre>} or {@code <table>} section. Whitespace includes
144163
* leading newlines, asterisks, and tabs and spaces. In the output, it is translated to newlines

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

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1738,6 +1738,12 @@ public void markdownFencedCodeBlocks() {
17381738
/// in a list
17391739
/// ```
17401740
///
1741+
/// - flibbertigibbet
1742+
///
1743+
/// ```
1744+
/// code block in a list after text
1745+
/// ```
1746+
///
17411747
/// ~~~java
17421748
/// code block
17431749
/// with tildes and an info string ("java")
@@ -1761,6 +1767,12 @@ class Test {}
17611767
/// in a list
17621768
/// ```
17631769
///
1770+
/// - flibbertigibbet
1771+
///
1772+
/// ```
1773+
/// code block in a list after text
1774+
/// ```
1775+
///
17641776
/// ~~~java
17651777
/// code block
17661778
/// with tildes and an info string ("java")
@@ -1993,22 +2005,25 @@ public void markdownTables() {
19932005
assume().that(MARKDOWN_JAVADOC_SUPPORTED).isTrue();
19942006
String input =
19952007
"""
2008+
/// Table McTableface
2009+
///
19962010
/// | foo | bar |
19972011
/// | --- | --- |
19982012
/// | baz | qux |
19992013
///
20002014
/// - |foo|bar|
20012015
/// |--:|:--|
20022016
/// |baz|qux|
2017+
///
2018+
/// - Another list.
2019+
///
2020+
/// | which | contains |
2021+
/// | ----- | -------- |
2022+
/// | a | table |
20032023
class Test {}
20042024
""";
2005-
// TODO: unmangle the tables
2006-
String expected =
2007-
"""
2008-
/// | foo | bar | | --- | --- | | baz | qux |
2009-
/// - |foo|bar| |--:|:--| |baz|qux|
2010-
class Test {}
2011-
""";
2025+
// We don't currently try to align the column markers in the rows of the last table.
2026+
String expected = input;
20122027
doFormatTest(input, expected);
20132028
}
20142029

core/src/test/java/com/google/googlejavaformat/java/javadoc/MarkdownPositionsTest.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,19 @@ public void codeBlock() {
129129
int secondCodeEnd = text.indexOf("~~~", secondCodeStart + 3) + 3;
130130
int thirdCodeStart = text.indexOf("````", secondCodeEnd);
131131
int thirdCodeEnd = text.indexOf("````", thirdCodeStart + 4) + 4;
132+
boolean precededByNonWhitespace = true;
132133
ImmutableListMultimap<Integer, Token> expected =
133134
ImmutableListMultimap.<Integer, Token>builder()
134135
.put(bullet, new ListOpenTag(""))
135136
.put(bullet, new ListItemOpenTag("- "))
136137
.put(
137138
firstCodeStart,
138139
new MarkdownFencedCodeBlock(
139-
text.substring(firstCodeStart, firstCodeEnd), "```", "```", "foo\nbar\n"))
140+
text.substring(firstCodeStart, firstCodeEnd),
141+
"```",
142+
"```",
143+
"foo\nbar\n",
144+
/* precededByNonWhitespace= */ true))
140145
.put(firstCodeEnd, new ListItemCloseTag(""))
141146
.put(firstCodeEnd, new ListCloseTag(""))
142147
.put(
@@ -145,14 +150,16 @@ public void codeBlock() {
145150
text.substring(secondCodeStart, secondCodeEnd),
146151
"~~~java",
147152
"~~~",
148-
"code\nwith tildes\n"))
153+
"code\nwith tildes\n",
154+
/* precededByNonWhitespace= */ false))
149155
.put(
150156
thirdCodeStart,
151157
new MarkdownFencedCodeBlock(
152158
text.substring(thirdCodeStart, thirdCodeEnd),
153159
"````",
154160
"````",
155-
"indented code\nwith more than three backticks\n"))
161+
"indented code\nwith more than three backticks\n",
162+
/* precededByNonWhitespace= */ false))
156163
.build();
157164
assertThat(map).isEqualTo(expected);
158165
}

0 commit comments

Comments
 (0)