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 @@ -55,12 +55,14 @@
import static java.util.regex.Pattern.compile;

import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.PeekingIterator;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** Lexer for the Javadoc formatter. */
Expand All @@ -74,9 +76,52 @@ static ImmutableList<Token> lex(String input) throws LexException {
*/
input = stripJavadocBeginAndEnd(input);
input = normalizeLineEndings(input);
input = stripMargins(input);
return new JavadocLexer(new CharStream(input)).generateTokens();
}

/**
* Strips the margin from every continuation line: the {@code *} prefix (with the space after it) from lines that
* have one, and the comment's own indentation from lines that do not. What remains of a bare line's indentation is
* relative to the comment, as on {@code *} lines after their {@code * }, so a {@code <pre>{@code} block written
* without the {@code *} margin keeps its shape for {@link #deindentPreCodeBlocks}.
*
* <p>The indentation stripped from bare lines is the smaller of the shortest {@code *} prefix and the shortest
* indentation of a bare line, so a bare line is never cut into and lines of both kinds stay aligned with each
* other.
*/
private static String stripMargins(String input) {
List<String> lines = Splitter.on('\n').splitToList(input);
List<String> continuations = lines.subList(1, lines.size());
int starPrefixLength = continuations.stream()
.map(STAR_PREFIX_PATTERN::matcher)
.filter(Matcher::find)
.mapToInt(Matcher::end)
.min()
.orElse(0);
int bareIndentation = continuations.stream()
.filter(line -> !STAR_PREFIX_PATTERN.matcher(line).find())
.filter(NOT_SPACE_OR_TAB::matchesAnyOf)
.mapToInt(NOT_SPACE_OR_TAB::indexIn)
.min()
.orElse(0);
int bareStrip = Math.min(starPrefixLength, bareIndentation);
StringBuilder result = new StringBuilder(lines.get(0));
for (String line : continuations) {
result.append('\n');
Matcher star = STAR_PREFIX_PATTERN.matcher(line);
if (star.find()) {
result.append(line, star.end(), line.length());
} else if (NOT_SPACE_OR_TAB.matchesAnyOf(line)) {
result.append(line, bareStrip, line.length());
}
}
return result.toString();
}

private static final Pattern STAR_PREFIX_PATTERN = compile("^[ \t]*[*][ \t]?");
private static final CharMatcher NOT_SPACE_OR_TAB = CharMatcher.noneOf(" \t");

/** The lexer crashes on windows line endings, so for now just normalize to `\n`. */
// TODO(cushon): use the platform line separator for output
private static String normalizeLineEndings(String input) {
Expand Down Expand Up @@ -528,8 +573,11 @@ private static boolean hasMultipleNewlines(String s) {
*
* We'd remove the trailing whitespace later on (in JavaCommentsHelper.rewrite), but I feel safer
* stripping it now: It otherwise might confuse our line-length count, which we use for wrapping.
*
* The margin of the next line (its `*` and the comment's indentation) is gone already: see
* stripMargins().
*/
private static final Pattern NEWLINE_PATTERN = compile("^[ \t]*\n[ \t]*[*]?[ \t]?");
private static final Pattern NEWLINE_PATTERN = compile("^[ \t]*\n");

// 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 @@ -23,11 +23,13 @@
import static com.palantir.javaformat.java.javadoc.JavadocWriter.RequestedWhitespace.NONE;
import static com.palantir.javaformat.java.javadoc.JavadocWriter.RequestedWhitespace.WHITESPACE;
import static com.palantir.javaformat.java.javadoc.Token.Type.HEADER_OPEN_TAG;
import static com.palantir.javaformat.java.javadoc.Token.Type.HTML_COMMENT;
import static com.palantir.javaformat.java.javadoc.Token.Type.LIST_ITEM_OPEN_TAG;
import static com.palantir.javaformat.java.javadoc.Token.Type.PARAGRAPH_OPEN_TAG;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
import java.util.List;
import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -263,7 +265,14 @@ void writeMoeEndStripComment(Token token) {
void writeHtmlComment(Token token) {
requestNewline();

writeToken(token);
// The lexer has stripped the margin from every line, so each one gets the margin back here.
List<String> lines = token.getValue().lines().toList();
writeToken(new Token(HTML_COMMENT, lines.get(0)));
for (String line : lines.subList(1, lines.size())) {
writeNewline(NO_AUTO_INDENT);
output.append(line);
remainingOnLine -= line.length();
}

requestNewline();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ void commentMostlyUntouched() {
"/**", " * Foo.", " *", " * <!--", "*abc", " * def ", " * </tr>", " *-->bar", " */", "class Test {}",
};
String[] expected = {
"/**", " * Foo.", " * <!--", " *abc", " * def", " * </tr>", " *-->", " * bar", " */", "class Test {}",
"/**", " * Foo.", " * <!--", " * abc", " * def", " * </tr>", " * -->", " * bar", " */", "class Test {}",
};
doFormatTest(input, expected);
}
Expand Down Expand Up @@ -1496,4 +1496,104 @@ void snippetKeepsCommentsAndIndentation() {
};
doFormatTest(input, input);
}

@Test
void preCodeWithoutLeadingStarPreservesIndent() {
String[] input = {
"/**", //
" * Example:",
" * <pre>{@code",
"class Demo {",
" // Comment",
" static final int X = 1;",
"",
" public static void example() {",
" int y = 2;",
" }",
"}",
" * }</pre>",
" */",
"class Test {}",
};
String[] expected = {
"/**", //
" * Example:",
" *",
" * <pre>{@code",
" * class Demo {",
" * // Comment",
" * static final int X = 1;",
" *",
" * public static void example() {",
" * int y = 2;",
" * }",
" * }",
" * }</pre>",
" */",
"class Test {}",
};
doFormatTest(input, expected);
}

@Test
void preCodeWithoutLeadingStarInIndentedComment() {
String[] input = {
"class Outer {", //
" /**",
" * Usage:",
" * <pre>{@code",
" Demo demo = new Demo();",
" if (demo.ready()) {",
" demo.run();",
" }",
" * }</pre>",
" */",
" void m() {}",
"}",
};
String[] expected = {
"class Outer {", //
" /**",
" * Usage:",
" *",
" * <pre>{@code",
" * Demo demo = new Demo();",
" * if (demo.ready()) {",
" * demo.run();",
" * }",
" * }</pre>",
" */",
" void m() {}",
"}",
};
doFormatTest(input, expected);
}

@Test
void preCodeMixedStarAndBareLinesKeepRelativeIndent() {
String[] input = {
"/**", //
" * Example:",
" * <pre>{@code",
" * class Demo {",
" int x;",
" * }",
" * }</pre>",
" */",
"class Test {}",
};
String[] expected = {
"/**", //
" * Example:",
" *",
" * <pre>{@code",
" * class Demo {",
" * int x;",
" * }",
" * }</pre>",
" */",
"class Test {}",
};
doFormatTest(input, expected);
}
}
Loading