Skip to content

Commit

Permalink
Preserve existing line breaks in output
Browse files Browse the repository at this point in the history
MOE_MIGRATED_REVID=136643288
  • Loading branch information
cushon committed Oct 19, 2016
1 parent 98a2101 commit e345bce
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 46 deletions.
22 changes: 22 additions & 0 deletions core/src/main/java/com/google/googlejavaformat/Newlines.java
Expand Up @@ -55,6 +55,28 @@ public static String getLineEnding(String input) {
return null; return null;
} }


/**
* Returns the first line separator in the text, or {@code "\n"} if the text does not contain a
* single line separator.
*/
public static String guessLineSeparator(String text) {
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
switch (c) {
case '\r':
if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
return "\r\n";
}
return "\r";
case '\n':
return "\n";
default:
break;
}
}
return "\n";
}

/** Returns true if the input contains any line breaks. */ /** Returns true if the input contains any line breaks. */
public static boolean containsBreaks(String text) { public static boolean containsBreaks(String text) {
return CharMatcher.anyOf("\n\r").matchesAnyOf(text); return CharMatcher.anyOf("\n\r").matchesAnyOf(text);
Expand Down
Expand Up @@ -232,8 +232,10 @@ public ImmutableList<Replacement> getFormatReplacements(
// 'de-linting' changes (e.g. import ordering). // 'de-linting' changes (e.g. import ordering).
input = ModifierOrderer.reorderModifiers(input, characterRanges); input = ModifierOrderer.reorderModifiers(input, characterRanges);


String lineSeparator = Newlines.guessLineSeparator(input);
JavaInput javaInput = new JavaInput(input); JavaInput javaInput = new JavaInput(input);
JavaOutput javaOutput = new JavaOutput(javaInput, new JavaCommentsHelper(options)); JavaOutput javaOutput =
new JavaOutput(lineSeparator, javaInput, new JavaCommentsHelper(lineSeparator, options));
try { try {
format(javaInput, javaOutput, options); format(javaInput, javaOutput, options);
} catch (FormattingError e) { } catch (FormattingError e) {
Expand Down
Expand Up @@ -22,6 +22,7 @@
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.ImmutableSortedSet;
import com.google.googlejavaformat.Newlines;
import com.google.googlejavaformat.java.JavaInput.Tok; import com.google.googlejavaformat.java.JavaInput.Tok;
import org.eclipse.jdt.core.compiler.InvalidInputException; import org.eclipse.jdt.core.compiler.InvalidInputException;


Expand Down Expand Up @@ -61,10 +62,12 @@ public static String reorderImports(String text) throws FormatterException {


private final String text; private final String text;
private final ImmutableList<Tok> toks; private final ImmutableList<Tok> toks;
private final String lineSeparator;


private ImportOrderer(String text, ImmutableList<Tok> toks) throws FormatterException { private ImportOrderer(String text, ImmutableList<Tok> toks) throws FormatterException {
this.text = text; this.text = text;
this.toks = toks; this.toks = toks;
this.lineSeparator = Newlines.guessLineSeparator(text);
} }


/** An import statement. */ /** An import statement. */
Expand Down Expand Up @@ -242,7 +245,7 @@ private String reorderedImportsString(ImmutableSortedSet<Import> imports) {
for (Import thisImport : imports) { for (Import thisImport : imports) {
if (lastWasStatic && !thisImport.isStatic) { if (lastWasStatic && !thisImport.isStatic) {
// Blank line between static and non-static imports. // Blank line between static and non-static imports.
sb.append('\n'); sb.append(lineSeparator);
} }
lastWasStatic = thisImport.isStatic; lastWasStatic = thisImport.isStatic;
sb.append(thisImport); sb.append(thisImport);
Expand Down
Expand Up @@ -28,8 +28,10 @@
public final class JavaCommentsHelper implements CommentsHelper { public final class JavaCommentsHelper implements CommentsHelper {


private final JavaFormatterOptions options; private final JavaFormatterOptions options;
private final String lineSeparator;


public JavaCommentsHelper(JavaFormatterOptions options) { public JavaCommentsHelper(String lineSeparator, JavaFormatterOptions options) {
this.lineSeparator = lineSeparator;
this.options = options; this.options = options;
} }


Expand Down Expand Up @@ -58,7 +60,7 @@ public String rewrite(Tok tok, int maxWidth, int column0) {


// For non-javadoc-shaped block comments, shift the entire block to the correct // For non-javadoc-shaped block comments, shift the entire block to the correct
// column, but do not adjust relative indentation. // column, but do not adjust relative indentation.
private static String preserveIndentation(List<String> lines, int column0) { private String preserveIndentation(List<String> lines, int column0) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();


// find the leftmost non-whitespace character in all trailing lines // find the leftmost non-whitespace character in all trailing lines
Expand All @@ -75,7 +77,7 @@ private static String preserveIndentation(List<String> lines, int column0) {


// output all trailing lines with plausible indentation // output all trailing lines with plausible indentation
for (int i = 1; i < lines.size(); ++i) { for (int i = 1; i < lines.size(); ++i) {
builder.append("\n").append(Strings.repeat(" ", column0)); builder.append(lineSeparator).append(Strings.repeat(" ", column0));
// check that startCol is valid index, e.g. for blank lines // check that startCol is valid index, e.g. for blank lines
if (lines.get(i).length() >= startCol) { if (lines.get(i).length() >= startCol) {
builder.append(lines.get(i).substring(startCol)); builder.append(lines.get(i).substring(startCol));
Expand All @@ -87,25 +89,25 @@ private static String preserveIndentation(List<String> lines, int column0) {
} }


// Remove leading and trailing whitespace, and re-indent each line. // Remove leading and trailing whitespace, and re-indent each line.
private static String indentLineComments(List<String> lines, int column0) { private String indentLineComments(List<String> lines, int column0) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append(lines.get(0).trim()); builder.append(lines.get(0).trim());
String indentString = Strings.repeat(" ", column0); String indentString = Strings.repeat(" ", column0);
for (int i = 1; i < lines.size(); ++i) { for (int i = 1; i < lines.size(); ++i) {
builder.append("\n").append(indentString).append(lines.get(i).trim()); builder.append(lineSeparator).append(indentString).append(lines.get(i).trim());
} }
return builder.toString(); return builder.toString();
} }


// Remove leading and trailing whitespace, and re-indent each line. // Remove leading and trailing whitespace, and re-indent each line.
// Add a +1 indent before '*', and add the '*' if necessary. // Add a +1 indent before '*', and add the '*' if necessary.
private static String indentJavadoc(List<String> lines, int column0) { private String indentJavadoc(List<String> lines, int column0) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append(lines.get(0).trim()); builder.append(lines.get(0).trim());
int indent = column0 + 1; int indent = column0 + 1;
String indentString = Strings.repeat(" ", indent); String indentString = Strings.repeat(" ", indent);
for (int i = 1; i < lines.size(); ++i) { for (int i = 1; i < lines.size(); ++i) {
builder.append("\n").append(indentString); builder.append(lineSeparator).append(indentString);
String line = lines.get(i).trim(); String line = lines.get(i).trim();
if (!line.startsWith("*")) { if (!line.startsWith("*")) {
builder.append("* "); builder.append("* ");
Expand Down
31 changes: 21 additions & 10 deletions core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java
Expand Up @@ -47,7 +47,7 @@
* methods to emit the output document. * methods to emit the output document.
*/ */
public final class JavaOutput extends Output { public final class JavaOutput extends Output {

private final String lineSeparator;
private final JavaInput javaInput; // Used to follow along while emitting the output. private final JavaInput javaInput; // Used to follow along while emitting the output.
private final CommentsHelper commentsHelper; // Used to re-flow comments. private final CommentsHelper commentsHelper; // Used to re-flow comments.
private final Map<Integer, BlankLineWanted> blankLines = new HashMap<>(); // Info on blank lines. private final Map<Integer, BlankLineWanted> blankLines = new HashMap<>(); // Info on blank lines.
Expand All @@ -67,7 +67,8 @@ public final class JavaOutput extends Output {
* @param javaInput the {@link JavaInput}, used to match up blank lines in the output * @param javaInput the {@link JavaInput}, used to match up blank lines in the output
* @param commentsHelper the {@link CommentsHelper}, used to rewrite comments * @param commentsHelper the {@link CommentsHelper}, used to rewrite comments
*/ */
public JavaOutput(JavaInput javaInput, CommentsHelper commentsHelper) { public JavaOutput(String lineSeparator, JavaInput javaInput, CommentsHelper commentsHelper) {
this.lineSeparator = lineSeparator;
this.javaInput = javaInput; this.javaInput = javaInput;
this.commentsHelper = commentsHelper; this.commentsHelper = commentsHelper;
kN = javaInput.getkN(); kN = javaInput.getkN();
Expand Down Expand Up @@ -256,7 +257,7 @@ public ImmutableList<Replacement> getFormatReplacements(RangeSet<Integer> iRange
int replaceFrom = startTok.getPosition(); int replaceFrom = startTok.getPosition();
while (replaceFrom > 0) { while (replaceFrom > 0) {
char previous = javaInput.getText().charAt(replaceFrom - 1); char previous = javaInput.getText().charAt(replaceFrom - 1);
if (previous == '\n') { if (previous == '\n' || previous == '\r') {
break; break;
} }
if (CharMatcher.whitespace().matches(previous)) { if (CharMatcher.whitespace().matches(previous)) {
Expand All @@ -268,7 +269,7 @@ public ImmutableList<Replacement> getFormatReplacements(RangeSet<Integer> iRange
} }


if (needsBreakBefore) { if (needsBreakBefore) {
replacement.append('\n'); replacement.append(lineSeparator);
} }


boolean first = true; boolean first = true;
Expand All @@ -282,12 +283,12 @@ public ImmutableList<Replacement> getFormatReplacements(RangeSet<Integer> iRange
if (first) { if (first) {
first = false; first = false;
} else { } else {
replacement.append('\n'); replacement.append(lineSeparator);
} }
replacement.append(getLine(i)); replacement.append(getLine(i));
} }
} }
replacement.append('\n'); replacement.append(lineSeparator);


String trailingLine = i < getLineCount() ? getLine(i) : null; String trailingLine = i < getLineCount() ? getLine(i) : null;


Expand All @@ -305,12 +306,22 @@ public ImmutableList<Replacement> getFormatReplacements(RangeSet<Integer> iRange
// the next line after the reformatted range). However, if the partial // the next line after the reformatted range). However, if the partial
// formatting range doesn't end in a newline, then break and re-indent. // formatting range doesn't end in a newline, then break and re-indent.
boolean reIndent = true; boolean reIndent = true;
OUTER:
while (replaceTo < javaInput.getText().length()) { while (replaceTo < javaInput.getText().length()) {
char endChar = javaInput.getText().charAt(replaceTo); char endChar = javaInput.getText().charAt(replaceTo);
if (endChar == '\n') { switch (endChar) {
reIndent = false; case '\r':
replaceTo++; if (replaceTo + 1 < javaInput.getText().length()
break; && javaInput.getText().charAt(replaceTo + 1) == '\n') {
replaceTo++;
}
// falls through
case '\n':
replaceTo++;
reIndent = false;
break OUTER;
default:
break;
} }
if (CharMatcher.whitespace().matches(endChar)) { if (CharMatcher.whitespace().matches(endChar)) {
replaceTo++; replaceTo++;
Expand Down
Expand Up @@ -24,6 +24,7 @@
import com.google.common.io.CharStreams; import com.google.common.io.CharStreams;
import com.google.common.reflect.ClassPath; import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ResourceInfo; import com.google.common.reflect.ClassPath.ResourceInfo;
import com.google.googlejavaformat.Newlines;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
Expand Down Expand Up @@ -87,11 +88,13 @@ public static Iterable<Object[]> data() throws IOException {
private final String name; private final String name;
private final String input; private final String input;
private final String expected; private final String expected;
private final String separator;


public FormatterIntegrationTest(String name, String input, String expected) { public FormatterIntegrationTest(String name, String input, String expected) {
this.name = name; this.name = name;
this.input = input; this.input = input;
this.expected = expected; this.expected = expected;
this.separator = Newlines.getLineEnding(expected);
} }


@Test @Test
Expand All @@ -105,30 +108,33 @@ public void format() {
} }


@Test @Test
public void idempotent() { public void idempotentLF() {
try { try {
String output = new Formatter().formatSource(expected); String mangled = expected.replace(separator, "\n");
assertEquals("bad output for " + name, expected, output); String output = new Formatter().formatSource(mangled);
assertEquals("bad output for " + name, mangled, output);
} catch (FormatterException e) { } catch (FormatterException e) {
fail(String.format("Formatter crashed on %s: %s", name, e.getMessage())); fail(String.format("Formatter crashed on %s: %s", name, e.getMessage()));
} }
} }


@Test @Test
public void cr() throws IOException { public void idempotentCR() throws IOException {
try { try {
String output = new Formatter().formatSource(expected.replace('\n', '\r')); String mangled = expected.replace(separator, "\r");
assertEquals("bad output for " + name, expected, output); String output = new Formatter().formatSource(mangled);
assertEquals("bad output for " + name, mangled, output);
} catch (FormatterException e) { } catch (FormatterException e) {
fail(String.format("Formatter crashed on %s: %s", name, e.getMessage())); fail(String.format("Formatter crashed on %s: %s", name, e.getMessage()));
} }
} }


@Test @Test
public void crlf() { public void idempotentCRLF() {
try { try {
String output = new Formatter().formatSource(expected.replace("\n", "\r\n")); String mangled = expected.replace(separator, "\r\n");
assertEquals("bad output for " + name, expected, output); String output = new Formatter().formatSource(mangled);
assertEquals("bad output for " + name, mangled, output);
} catch (FormatterException e) { } catch (FormatterException e) {
fail(String.format("Formatter crashed on %s: %s", name, e.getMessage())); fail(String.format("Formatter crashed on %s: %s", name, e.getMessage()));
} }
Expand Down
Expand Up @@ -1279,7 +1279,7 @@ public void windowsLineSeparator() throws FormatterException {
}; };
for (String separator : Arrays.asList("\r", "\r\n")) { for (String separator : Arrays.asList("\r", "\r\n")) {
String actual = formatter.formatSource(Joiner.on(separator).join(input)); String actual = formatter.formatSource(Joiner.on(separator).join(input));
assertThat(actual).isEqualTo(Joiner.on('\n').join(input) + "\n"); assertThat(actual).isEqualTo(Joiner.on(separator).join(input) + separator);
} }
} }
} }

0 comments on commit e345bce

Please sign in to comment.