Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.google.googlejavaformat.java.javadoc.JavadocLexer.LexException;
import com.google.googlejavaformat.java.javadoc.Token.BeginJavadoc;
import com.google.googlejavaformat.java.javadoc.Token.BlockQuoteCloseTag;
import com.google.googlejavaformat.java.javadoc.Token.BlockQuoteMarker;
import com.google.googlejavaformat.java.javadoc.Token.BlockQuoteOpenTag;
import com.google.googlejavaformat.java.javadoc.Token.BrTag;
import com.google.googlejavaformat.java.javadoc.Token.CodeCloseTag;
Expand All @@ -40,6 +41,8 @@
import com.google.googlejavaformat.java.javadoc.Token.ListItemOpenTag;
import com.google.googlejavaformat.java.javadoc.Token.ListOpenTag;
import com.google.googlejavaformat.java.javadoc.Token.Literal;
import com.google.googlejavaformat.java.javadoc.Token.MarkdownBlockQuoteClose;
import com.google.googlejavaformat.java.javadoc.Token.MarkdownBlockQuoteOpen;
import com.google.googlejavaformat.java.javadoc.Token.MarkdownCodeSpanEnd;
import com.google.googlejavaformat.java.javadoc.Token.MarkdownCodeSpanStart;
import com.google.googlejavaformat.java.javadoc.Token.MarkdownFencedCodeBlock;
Expand Down Expand Up @@ -121,6 +124,8 @@ private static String render(List<Token> input, int blockIndent, boolean classic
case ParagraphOpenTag t -> output.writeParagraphOpen(standardizePToken(t));
case BlockQuoteOpenTag t -> output.writeBlockQuoteOpen(t);
case BlockQuoteCloseTag t -> output.writeBlockQuoteClose(t);
case MarkdownBlockQuoteOpen t -> output.writeMarkdownBlockQuoteOpen(t);
case MarkdownBlockQuoteClose t -> output.writeMarkdownBlockQuoteClose();
case PreOpenTag t -> output.writePreOpen(t);
case PreCloseTag t -> output.writePreClose(t);
case CodeOpenTag t -> output.writeCodeOpen(t);
Expand All @@ -142,6 +147,7 @@ private static String render(List<Token> input, int blockIndent, boolean classic
case ParagraphCloseTag unused -> {}
case MarkdownCodeSpanStart unused -> {}
case MarkdownCodeSpanEnd unused -> {}
case BlockQuoteMarker unused -> {}
}
}
throw new AssertionError();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.google.common.collect.PeekingIterator;
import com.google.googlejavaformat.java.javadoc.Token.BeginJavadoc;
import com.google.googlejavaformat.java.javadoc.Token.BlockQuoteCloseTag;
import com.google.googlejavaformat.java.javadoc.Token.BlockQuoteMarker;
import com.google.googlejavaformat.java.javadoc.Token.BlockQuoteOpenTag;
import com.google.googlejavaformat.java.javadoc.Token.BrTag;
import com.google.googlejavaformat.java.javadoc.Token.CodeCloseTag;
Expand All @@ -44,6 +45,8 @@
import com.google.googlejavaformat.java.javadoc.Token.ListItemOpenTag;
import com.google.googlejavaformat.java.javadoc.Token.ListOpenTag;
import com.google.googlejavaformat.java.javadoc.Token.Literal;
import com.google.googlejavaformat.java.javadoc.Token.MarkdownBlockQuoteClose;
import com.google.googlejavaformat.java.javadoc.Token.MarkdownBlockQuoteOpen;
import com.google.googlejavaformat.java.javadoc.Token.MarkdownCodeSpanEnd;
import com.google.googlejavaformat.java.javadoc.Token.MarkdownCodeSpanStart;
import com.google.googlejavaformat.java.javadoc.Token.MarkdownHardLineBreak;
Expand Down Expand Up @@ -139,7 +142,10 @@ enum NestingContext {
* An inline tag such as {@code {@link ...}} or {@code {@code ...}}, but not {@code {@snippet
* ...}}.
*/
INLINE_TAG_CONTEXT
INLINE_TAG_CONTEXT,

/** Markdown {@code >}. */
BLOCKQUOTE
}

private final CharStream input;
Expand Down Expand Up @@ -171,21 +177,7 @@ private ImmutableList<Token> generateTokens() throws LexException {
// assumed that there are no other tokens (markdown or otherwise) in a non-empty text span
// covered by a markdown token.
for (Token markdownToken : markdownPositions.tokensAt(input.position())) {
// For `...`, we switch to MARKDOWN_CODE_CONTEXT for the duration of the span, and we
// change the start or end token to a Literal so it will get joined to adjacent Literal
// tokens. That prevents line breaks adjacent to the backticks in "foo`bar`baz", but still
// allows them at the spaces in "foo `bar` baz" or "foo` bar `baz".
switch (markdownToken) {
case MarkdownCodeSpanStart unused -> {
contextStack.push(NestingContext.MARKDOWN_CODE_CONTEXT);
markdownToken = new Literal(markdownToken.value());
}
case MarkdownCodeSpanEnd unused -> {
contextStack.popUntil(NestingContext.MARKDOWN_CODE_CONTEXT);
markdownToken = new Literal(markdownToken.value());
}
default -> {}
}
markdownToken = processMarkdownToken(markdownToken);
tokens.add(markdownToken);
if (!markdownToken.value().isEmpty()) {
boolean consumed = input.tryConsume(markdownToken.value());
Expand All @@ -202,6 +194,11 @@ private ImmutableList<Token> generateTokens() throws LexException {
tokens.add(token);
}

for (Token markdownToken : markdownPositions.tokensAt(input.position())) {
markdownToken = processMarkdownToken(markdownToken);
tokens.add(markdownToken);
}

checkMatchingTags();

token = new EndJavadoc(classicJavadoc ? "*/" : "");
Expand All @@ -217,6 +214,31 @@ private ImmutableList<Token> generateTokens() throws LexException {
return result;
}

private Token processMarkdownToken(Token markdownToken) {
// For `...`, we switch to MARKDOWN_CODE_CONTEXT for the duration of the span, and we change the
// start or end token to a Literal so it will get joined to adjacent Literal tokens. That
// prevents line breaks adjacent to the backticks in "foo`bar`baz", but still allows them at the
// spaces in "foo `bar` baz" or "foo` bar `baz".
switch (markdownToken) {
case MarkdownCodeSpanStart unused -> {
contextStack.push(NestingContext.MARKDOWN_CODE_CONTEXT);
return new Literal(markdownToken.value());
}
case MarkdownCodeSpanEnd unused -> {
contextStack.popUntil(NestingContext.MARKDOWN_CODE_CONTEXT);
return new Literal(markdownToken.value());
}
case MarkdownBlockQuoteOpen unused -> {
contextStack.push(NestingContext.BLOCKQUOTE);
}
case MarkdownBlockQuoteClose unused -> {
contextStack.popUntil(NestingContext.BLOCKQUOTE);
}
default -> {}
}
return markdownToken;
}

private Token readToken() throws LexException {
Function<String, Token> tokenFactory = consumeToken();
String value = input.readAndResetRecorded();
Expand All @@ -230,7 +252,16 @@ private Function<String, Token> consumeToken() throws LexException {
if (input.tryConsumeRegex(newlinePattern)) {
somethingSinceNewline = false;
return preserveExistingFormatting ? ForcedNewline::new : Whitespace::new;
} else if (input.tryConsume(" ") || input.tryConsume("\t")) {
}

if (!classicJavadoc
&& !somethingSinceNewline
&& contextStack.contains(NestingContext.BLOCKQUOTE)
&& input.tryConsumeRegex(BLOCKQUOTE_MARKER_PATTERN)) {
return BlockQuoteMarker::new;
}

if (input.tryConsume(" ") || input.tryConsume("\t")) {
// TODO(cpovirk): How about weird whitespace chars? Ideally we'd distinguish breaking vs. not.
// Returning Literal here prevents us from breaking a <pre> line. For more info, see Literal.
return preserveExistingFormatting ? Literal::new : Whitespace::new;
Expand Down Expand Up @@ -663,6 +694,7 @@ static boolean hasMultipleNewlines(String s) {
* initial whitespace have been removed at the point where this pattern is applied.
*/
private static final Pattern MARKDOWN_NEWLINE_PATTERN = compile("[ \t]*\n");
private static final Pattern BLOCKQUOTE_MARKER_PATTERN = compile("> ?");

// We ensure elsewhere that we match this only at the beginning of a line.
// Only match tags that start with a lowercase letter, to avoid false matches on unescaped
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.google.googlejavaformat.java.javadoc.Token.ListItemOpenTag;
import com.google.googlejavaformat.java.javadoc.Token.ListOpenTag;
import com.google.googlejavaformat.java.javadoc.Token.Literal;
import com.google.googlejavaformat.java.javadoc.Token.MarkdownBlockQuoteOpen;
import com.google.googlejavaformat.java.javadoc.Token.MarkdownFencedCodeBlock;
import com.google.googlejavaformat.java.javadoc.Token.MarkdownTable;
import com.google.googlejavaformat.java.javadoc.Token.MoeBeginStripComment;
Expand Down Expand Up @@ -73,14 +74,13 @@ final class JavadocWriter {
private boolean continuingListItemOfInnermostList;

private boolean continuingFooterTag;
private final NestingStack.Int continuingListItemStack = new NestingStack.Int();
private final NestingStack.Int continuingListStack = new NestingStack.Int();
private final NestingStack<Indent> indentStack = new NestingStack<>();
private final NestingStack.Int postWriteModifiedContinuingListStack = new NestingStack.Int();
private int remainingOnLine;
private boolean atStartOfLine;
private RequestedWhitespace requestedWhitespace = NONE;
private Token requestedMoeBeginStripComment;
private int indentForMoeEndStripComment;
private String indentForMoeEndStripComment = "";
private boolean wroteAnythingSignificant;

JavadocWriter(int blockIndent, boolean classicJavadoc) {
Expand Down Expand Up @@ -155,8 +155,7 @@ void writeFooterJavadocTagStart(FooterJavadocTagStart token) {
* currently know which of those tags are open.
*/
continuingListItemOfInnermostList = false;
continuingListItemStack.reset();
continuingListStack.reset();
indentStack.reset();
/*
* There's probably no need for this, since its only effect is to disable blank lines in some
* cases -- and we're doing that already in the footer.
Expand Down Expand Up @@ -215,7 +214,7 @@ void writeListOpen(ListOpenTag token) {
writeToken(token);
continuingListItemOfInnermostList = false;
int indent = token.value().isEmpty() ? 0 : 2; // No indent for Markdown since no explicit open
continuingListStack.push(indent);
indentStack.push(new ListIndent(indent));
postWriteModifiedContinuingListStack.push();

requestNewline();
Expand All @@ -226,8 +225,7 @@ void writeListClose(ListCloseTag token) {
requestNewline();
}

continuingListItemStack.popIfNotEmpty();
continuingListStack.popIfNotEmpty();
indentStack.popUntil(ListIndent.class);
writeToken(token);
postWriteModifiedContinuingListStack.popIfNotEmpty();

Expand All @@ -240,13 +238,14 @@ void writeListItemOpen(ListItemOpenTag token) {
requestNewline();

if (continuingListItemOfInnermostList) {
// TODO(cpovirk): consider whether we can handle this only with operations on the indent stack
continuingListItemOfInnermostList = false;
continuingListItemStack.popIfNotEmpty();
indentStack.popUntil(ListItemIndent.class);
}
writeToken(token);
continuingListItemOfInnermostList = true;
int indent = token.value().length();
continuingListItemStack.push(indent);
indentStack.push(new ListItemIndent(indent));
}

void writeHeaderOpen(HeaderOpenTag token) {
Expand Down Expand Up @@ -277,20 +276,28 @@ void writeParagraphOpen(Token token) {

void writeBlockQuoteOpen(BlockQuoteOpenTag token) {
requestBlankLine();

writeToken(token);

requestNewline();
}

void writeBlockQuoteClose(BlockQuoteCloseTag token) {
requestNewline();

writeToken(token);

requestBlankLine();
}

void writeMarkdownBlockQuoteOpen(MarkdownBlockQuoteOpen token) {
if (!atStartOfLine) {
requestNewline();
}
writeToken(new MarkdownBlockQuoteOpen("> "));
indentStack.push(new BlockQuoteIndent());
}

void writeMarkdownBlockQuoteClose() {
indentStack.popUntil(BlockQuoteIndent.class);
}

void writePreOpen(PreOpenTag token) {
requestBlankLine();

Expand Down Expand Up @@ -325,7 +332,7 @@ void writeTableClose(TableCloseTag token) {

void writeMoeEndStripComment(MoeEndStripComment token) {
writeLineBreakNoAutoIndent();
appendSpaces(indentForMoeEndStripComment);
output.append(indentForMoeEndStripComment);

// Or maybe just "output.append(token.getValue())?" I'm kind of surprised this is so easy.
writeToken(token);
Expand Down Expand Up @@ -477,7 +484,7 @@ private void writeToken(Token token) {
if (requestedMoeBeginStripComment != null) {
output.append(requestedMoeBeginStripComment.value());
requestedMoeBeginStripComment = null;
indentForMoeEndStripComment = innerIndent();
indentForMoeEndStripComment = innerIndentString();
wroteAnythingSignificant = true;
requestNewline();
writeToken(token);
Expand Down Expand Up @@ -513,6 +520,14 @@ private void writeNewlineStart() {

private void writeBlankLine() {
writeNewlineStart();
String indent = innerIndentString();
if (!indent.isBlank()) {
// The motivation here is that we may have a blank line that is inside a Markdown block quote.
// Then we still want the `>` marker or markers at the start of the line, but we don't want
// any trailing whitespace.
output.append(" ");
output.append(indent.stripTrailing());
}
writeNewline();
}

Expand All @@ -525,8 +540,9 @@ private void writeNewline(AutoIndent autoIndent) {
appendSpaces(1);
remainingOnLine = JavadocFormatter.MAX_LINE_LENGTH - blockIndent - (classicJavadoc ? 3 : 4);
if (autoIndent == AUTO_INDENT) {
appendSpaces(innerIndent());
remainingOnLine -= innerIndent();
String indent = innerIndentString();
output.append(indent);
remainingOnLine -= indent.length();
}
atStartOfLine = true;
}
Expand All @@ -536,12 +552,40 @@ enum AutoIndent {
NO_AUTO_INDENT
}

private int innerIndent() {
int innerIndent = continuingListItemStack.total() + continuingListStack.total();
private String innerIndentString() {
StringBuilder sb = new StringBuilder();
if (continuingFooterTag) {
innerIndent += classicJavadoc ? 4 : 2;
sb.repeat(' ', classicJavadoc ? 4 : 2);
}
for (Indent indent : indentStack.bottomToTop()) {
indent.render(sb);
}
return sb.toString();
}

private sealed interface Indent {
void render(StringBuilder sb);
}

private record ListIndent(int width) implements Indent {
@Override
public void render(StringBuilder sb) {
sb.repeat(' ', width);
}
}

private record ListItemIndent(int width) implements Indent {
@Override
public void render(StringBuilder sb) {
sb.repeat(' ', width);
}
}

private record BlockQuoteIndent() implements Indent {
@Override
public void render(StringBuilder sb) {
sb.append("> ");
}
return innerIndent;
}

private void appendSpaces(int count) {
Expand Down
Loading