Skip to content

Commit

Permalink
Avoid mangling {@snippet ...}.
Browse files Browse the repository at this point in the history
This CL also implements some minimal formatting behavior _around_ `{@snippet ...}` (namely, a blank line before and after). It does not make any effort to change formatting _inside_ `{@snippet ...}`, only to stop reflowing it blindly.

PiperOrigin-RevId: 665540906
  • Loading branch information
cpovirk authored and google-java-format Team committed Aug 20, 2024
1 parent b3c7c11 commit 762c614
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ private static String render(List<Token> input, int blockIndent) {
case FOOTER_JAVADOC_TAG_START:
output.writeFooterJavadocTagStart(token);
break;
case SNIPPET_BEGIN:
output.writeSnippetBegin(token);
break;
case SNIPPET_END:
output.writeSnippetEnd(token);
break;
case LIST_OPEN_TAG:
output.writeListOpen(token);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import static com.google.googlejavaformat.java.javadoc.Token.Type.PARAGRAPH_OPEN_TAG;
import static com.google.googlejavaformat.java.javadoc.Token.Type.PRE_CLOSE_TAG;
import static com.google.googlejavaformat.java.javadoc.Token.Type.PRE_OPEN_TAG;
import static com.google.googlejavaformat.java.javadoc.Token.Type.SNIPPET_BEGIN;
import static com.google.googlejavaformat.java.javadoc.Token.Type.SNIPPET_END;
import static com.google.googlejavaformat.java.javadoc.Token.Type.TABLE_CLOSE_TAG;
import static com.google.googlejavaformat.java.javadoc.Token.Type.TABLE_OPEN_TAG;
import static com.google.googlejavaformat.java.javadoc.Token.Type.WHITESPACE;
Expand Down Expand Up @@ -97,6 +99,7 @@ private static String stripJavadocBeginAndEnd(String input) {
private final NestingCounter preDepth = new NestingCounter();
private final NestingCounter codeDepth = new NestingCounter();
private final NestingCounter tableDepth = new NestingCounter();
private boolean outerInlineTagIsSnippet;
private boolean somethingSinceNewline;

private JavadocLexer(CharStream input) {
Expand Down Expand Up @@ -158,13 +161,26 @@ private Type consumeToken() throws LexException {
}
somethingSinceNewline = true;

if (input.tryConsumeRegex(INLINE_TAG_OPEN_PATTERN)) {
if (input.tryConsumeRegex(SNIPPET_TAG_OPEN_PATTERN)) {
if (braceDepth.value() == 0) {
braceDepth.increment();
outerInlineTagIsSnippet = true;
return SNIPPET_BEGIN;
}
braceDepth.increment();
return LITERAL;
} else if (input.tryConsumeRegex(INLINE_TAG_OPEN_PATTERN)) {
braceDepth.increment();
return LITERAL;
} else if (input.tryConsume("{")) {
braceDepth.incrementIfPositive();
return LITERAL;
} else if (input.tryConsume("}")) {
if (outerInlineTagIsSnippet && braceDepth.value() == 1) {
braceDepth.decrementIfPositive();
outerInlineTagIsSnippet = false;
return SNIPPET_END;
}
braceDepth.decrementIfPositive();
return LITERAL;
}
Expand Down Expand Up @@ -239,7 +255,10 @@ private Type consumeToken() throws LexException {
}

private boolean preserveExistingFormatting() {
return preDepth.isPositive() || tableDepth.isPositive() || codeDepth.isPositive();
return preDepth.isPositive()
|| tableDepth.isPositive()
|| codeDepth.isPositive()
|| outerInlineTagIsSnippet;
}

private void checkMatchingTags() throws LexException {
Expand Down Expand Up @@ -400,6 +419,7 @@ private static ImmutableList<Token> optionalizeSpacesAfterLinks(List<Token> inpu
* <p>Also trim leading and trailing blank lines, and move the trailing `}` to its own line.
*/
private static ImmutableList<Token> deindentPreCodeBlocks(List<Token> input) {
// TODO: b/323389829 - De-indent {@snippet ...} blocks, too.
ImmutableList.Builder<Token> output = ImmutableList.builder();
for (PeekingIterator<Token> tokens = peekingIterator(input.iterator()); tokens.hasNext(); ) {
if (tokens.peek().getType() != PRE_OPEN_TAG) {
Expand Down Expand Up @@ -528,6 +548,7 @@ private static boolean hasMultipleNewlines(String s) {
private static final Pattern BLOCKQUOTE_OPEN_PATTERN = openTagPattern("blockquote");
private static final Pattern BLOCKQUOTE_CLOSE_PATTERN = closeTagPattern("blockquote");
private static final Pattern BR_PATTERN = openTagPattern("br");
private static final Pattern SNIPPET_TAG_OPEN_PATTERN = compile("^[{]@snippet\\b");
private static final Pattern INLINE_TAG_OPEN_PATTERN = compile("^[{]@\\w*");
/*
* We exclude < so that we don't swallow following HTML tags. This lets us fix up "foo<p>" (~400
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,38 @@ void writeFooterJavadocTagStart(Token token) {
continuingFooterTag = true;
}

void writeSnippetBegin(Token token) {
requestBlankLine();
writeToken(token);
/*
* We don't request a newline here because we should have at least a colon following on this
* line, and we may have attributes after that.
*
* (If we find it convenient, we could instead consume the entire rest of the line as part of
* the same token as `{@snippet` itself. But we already would never split the rest of the line
* across lines (because we preserve whitespace), so that might not accomplish anything. Plus,
* we'd probably want to be careful not to swallow an expectedly early closing `}`.)
*/
}

void writeSnippetEnd(Token token) {
/*
* We don't request a newline here because we have preserved all newlines that existed in the
* input. TODO: b/323389829 - Improve upon that. Specifically:
*
* - If there is not yet a newline, we should add one.
*
* - If there are multiple newlines, we should probably collapse them.
*
* - If the closing brace isn't indented as we'd want (as in the link below, in which the whole
* @apiNote isn't indented), we should indent it.
*
* https://github.com/openjdk/jdk/blob/1ebf2cf639300728ffc024784f5dc1704317b0b3/src/java.base/share/classes/java/util/Collections.java#L5993-L6006
*/
writeToken(token);
requestBlankLine();
}

void writeListOpen(Token token) {
requestBlankLine();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ enum Type {
END_JAVADOC,
/** The {@code @foo} that begins a block Javadoc tag like {@code @throws}. */
FOOTER_JAVADOC_TAG_START,
/** The opening {@code {@snippet} of a code snippet. */
SNIPPET_BEGIN,
/** The closing {@code }} of a code snippet. */
SNIPPET_END,
LIST_OPEN_TAG,
LIST_CLOSE_TAG,
LIST_ITEM_OPEN_TAG,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,48 @@ public void unicodeCharacterCountArguableBug() {
doFormatTest(input, expected);
}

@Test
public void blankLinesAroundSnippetAndNoMangling() {
String[] input = {
"/**", //
" * hello world",
" * {@snippet :",
" * public class Foo {",
" * private String s;",
" * }",
" * }",
" * hello again",
" */",
"class Test {}",
};
String[] expected = {
"/**", //
" * hello world",
" *",
" * {@snippet :",
" * public class Foo {",
" * private String s;",
" * }",
" * }",
" *",
" * hello again",
" */",
"class Test {}",
};
doFormatTest(input, expected);
}

@Test
public void notASnippetUnlessOuterTag() {
String[] input = {
"/** I would like to tell you about the {@code {@snippet ...}} tag. */", "class Test {}",
};
String[] expected = {
"/** I would like to tell you about the {@code {@snippet ...}} tag. */", "class Test {}",
};
doFormatTest(input, expected);
}

@Test
public void blankLineBeforeParams() {
String[] input = {
Expand Down

0 comments on commit 762c614

Please sign in to comment.