diff --git a/core/src/main/java/com/google/googlejavaformat/Doc.java b/core/src/main/java/com/google/googlejavaformat/Doc.java index e9db73a12..8bfdc5d8d 100644 --- a/core/src/main/java/com/google/googlejavaformat/Doc.java +++ b/core/src/main/java/com/google/googlejavaformat/Doc.java @@ -14,9 +14,12 @@ package com.google.googlejavaformat; +import static com.google.common.collect.Iterables.getLast; + import com.google.common.base.MoreObjects; import com.google.common.base.Optional; import com.google.common.collect.DiscreteDomain; +import com.google.common.collect.Iterators; import com.google.common.collect.Range; import com.google.googlejavaformat.Output.BreakTag; import java.util.ArrayList; @@ -274,7 +277,7 @@ private static void splitByBreaks(List docs, List> splits, List()); } else { - splits.get(splits.size() - 1).add(doc); + getLast(splits).add(doc); } } } @@ -714,16 +717,16 @@ public void add(DocBuilder builder) { @Override float computeWidth() { + int idx = Newlines.firstBreak(tok.getOriginalText()); // only count the first line of multi-line block comments if (tok.isComment()) { - int idx = tok.getOriginalText().indexOf('\n'); if (idx > 0) { return idx; } else { return tok.length(); } } - return tok.getOriginalText().contains("\n") ? Float.POSITIVE_INFINITY : (float) tok.length(); + return idx != -1 ? Float.POSITIVE_INFINITY : (float) tok.length(); } @Override @@ -740,19 +743,8 @@ Range computeRange() { @Override public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { - int column = state.column; - int lines = 0; - text = commentsHelper.rewrite(tok, maxWidth, column); - // TODO(lowasser): use lastIndexOf('\n') - for (char c : text.toCharArray()) { - if (c == '\n') { - column = 0; - lines++; - } else { - column++; - } - } - return state.withColumn(column); + text = commentsHelper.rewrite(tok, maxWidth, state.column); + return state.withColumn(text.length() - Iterators.getLast(Newlines.lineOffsetIterator(text))); } @Override diff --git a/core/src/main/java/com/google/googlejavaformat/InputOutput.java b/core/src/main/java/com/google/googlejavaformat/InputOutput.java index f856f5f08..ea5831610 100644 --- a/core/src/main/java/com/google/googlejavaformat/InputOutput.java +++ b/core/src/main/java/com/google/googlejavaformat/InputOutput.java @@ -14,7 +14,6 @@ package com.google.googlejavaformat; -import com.google.common.base.CharMatcher; import com.google.common.collect.DiscreteDomain; import com.google.common.collect.ImmutableList; import com.google.common.collect.Range; @@ -28,7 +27,6 @@ public abstract class InputOutput { private ImmutableList lines = ImmutableList.of(); protected static final Range EMPTY_RANGE = Range.closedOpen(-1, -1); - private static final CharMatcher NEWLINE_MATCHER = CharMatcher.is('\n'); private static final DiscreteDomain INTEGERS = DiscreteDomain.integers(); /** Set the lines. */ @@ -77,7 +75,7 @@ protected final void computeRanges(List toks) { for (Input.Tok tok : toks) { String txt = tok.getOriginalText(); int lineI0 = lineI; - lineI += NEWLINE_MATCHER.countIn(txt); + lineI += Newlines.count(txt); int k = tok.getIndex(); if (k >= 0) { addToRanges(range0s, lineI0, k); diff --git a/core/src/main/java/com/google/googlejavaformat/Newlines.java b/core/src/main/java/com/google/googlejavaformat/Newlines.java new file mode 100644 index 000000000..aba20610d --- /dev/null +++ b/core/src/main/java/com/google/googlejavaformat/Newlines.java @@ -0,0 +1,167 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.googlejavaformat; + +import com.google.common.base.CharMatcher; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** Platform-independent newline handling. */ +public class Newlines { + + /** Returns the number of line breaks in the input. */ + public static int count(String input) { + return Iterators.size(lineOffsetIterator(input)) - 1; + } + + /** Returns the index of the first break in the input, or {@code -1}. */ + public static int firstBreak(String input) { + Iterator it = lineOffsetIterator(input); + it.next(); + return it.hasNext() ? it.next() : -1; + } + + private static final ImmutableSet BREAKS = ImmutableSet.of("\r\n", "\n", "\r"); + + /** Returns true if the entire input string is a recognized line break. */ + public static boolean isNewline(String input) { + return BREAKS.contains(input); + } + + /** + * Returns the terminating line break in the input, or {@code null} if the input does not end in a + * break. + */ + public static String getLineEnding(String input) { + for (String b : BREAKS) { + if (input.endsWith(b)) { + return b; + } + } + return null; + } + + /** Returns true if the input contains any line breaks. */ + public static boolean containsBreaks(String text) { + return CharMatcher.anyOf("\n\r").matchesAnyOf(text); + } + + /** Returns an iterator over the start offsets of lines in the input. */ + public static Iterator lineOffsetIterator(String input) { + return new LineOffsetIterator(input); + } + + /** Returns an iterator over lines in the input, including trailing whitespace. */ + public static Iterator lineIterator(String input) { + return new LineIterator(input); + } + + private static class LineOffsetIterator implements Iterator { + + private int curr = 0; + private int idx = 0; + private final String input; + + private LineOffsetIterator(String input) { + this.input = input; + } + + @Override + public boolean hasNext() { + return curr != -1; + } + + @Override + public Integer next() { + if (curr == -1) { + throw new NoSuchElementException(); + } + int result = curr; + advance(); + return result; + } + + private void advance() { + for (; idx < input.length(); idx++) { + char c = input.charAt(idx); + switch (c) { + case '\r': + if (idx + 1 < input.length() && input.charAt(idx + 1) == '\n') { + idx++; + } + // falls through + case '\n': + idx++; + curr = idx; + return; + default: + break; + } + } + curr = -1; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); + } + } + + private static class LineIterator implements Iterator { + + int idx; + String curr; + + private final String input; + private final Iterator indices; + + private LineIterator(String input) { + this.input = input; + this.indices = lineOffsetIterator(input); + idx = indices.next(); // read leading 0 + } + + private void advance() { + int last = idx; + if (indices.hasNext()) { + idx = indices.next(); + } else if (hasNext()) { + // no terminal line break + idx = input.length(); + } else { + throw new NoSuchElementException(); + } + curr = input.substring(last, idx); + } + + @Override + public boolean hasNext() { + return idx < input.length(); + } + + @Override + public String next() { + advance(); + return curr; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); + } + } +} diff --git a/core/src/main/java/com/google/googlejavaformat/java/Formatter.java b/core/src/main/java/com/google/googlejavaformat/java/Formatter.java index 996d9eb40..fec8ed504 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/Formatter.java +++ b/core/src/main/java/com/google/googlejavaformat/java/Formatter.java @@ -16,10 +16,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.common.base.CharMatcher; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; import com.google.common.collect.Range; import com.google.common.collect.RangeSet; import com.google.common.collect.TreeRangeSet; @@ -29,6 +29,7 @@ import com.google.googlejavaformat.Doc; import com.google.googlejavaformat.DocBuilder; import com.google.googlejavaformat.FormattingError; +import com.google.googlejavaformat.Newlines; import com.google.googlejavaformat.Op; import com.google.googlejavaformat.OpsBuilder; import com.sun.tools.javac.file.JavacFileManager; @@ -242,20 +243,13 @@ public ImmutableList getFormatReplacements( return javaOutput.getFormatReplacements(tokenRangeSet); } - static final CharMatcher NEWLINE = CharMatcher.is('\n'); - /** * Converts zero-indexed, [closed, open) line ranges in the given source file to character ranges. */ public static RangeSet lineRangesToCharRanges( String input, RangeSet lineRanges) { List lines = new ArrayList<>(); - lines.add(0); - int idx = NEWLINE.indexIn(input); - while (idx >= 0) { - lines.add(idx + 1); - idx = NEWLINE.indexIn(input, idx + 1); - } + Iterators.addAll(lines, Newlines.lineOffsetIterator(input)); lines.add(input.length() + 1); final RangeSet characterRanges = TreeRangeSet.create(); diff --git a/core/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java b/core/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java index 5029c3ac5..538520709 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java +++ b/core/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java @@ -13,6 +13,7 @@ */ package com.google.googlejavaformat.java; +import static com.google.common.collect.Iterables.getLast; import static org.eclipse.jdt.core.compiler.ITerminalSymbols.TokenNameclass; import static org.eclipse.jdt.core.compiler.ITerminalSymbols.TokenNameenum; import static org.eclipse.jdt.core.compiler.ITerminalSymbols.TokenNameinterface; @@ -124,7 +125,7 @@ private String reorderImports() throws FormatterException { if (toks.isEmpty()) { tail = ""; } else { - Tok lastTok = toks.get(toks.size() - 1); + Tok lastTok = getLast(toks); int tailStart = lastTok.getPosition() + lastTok.length(); tail = text.substring(tailStart); } @@ -336,8 +337,7 @@ private boolean isSpaceToken(int i) { if (s.isEmpty()) { return false; } else { - // TODO(b/26984991): if the formatter starts understanding \r\n then \r should be removed here - return " \t\f\r".indexOf(s.codePointAt(0)) >= 0; + return " \t\f".indexOf(s.codePointAt(0)) >= 0; } } diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaCommentsHelper.java b/core/src/main/java/com/google/googlejavaformat/java/JavaCommentsHelper.java index 538a91017..d444205e1 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaCommentsHelper.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaCommentsHelper.java @@ -15,10 +15,10 @@ package com.google.googlejavaformat.java; import com.google.common.base.CharMatcher; -import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.googlejavaformat.CommentsHelper; import com.google.googlejavaformat.Input.Tok; +import com.google.googlejavaformat.Newlines; import com.google.googlejavaformat.java.javadoc.JavadocFormatter; import java.util.ArrayList; import java.util.Iterator; @@ -27,8 +27,6 @@ /** {@code JavaCommentsHelper} extends {@link CommentsHelper} to rewrite Java comments. */ public final class JavaCommentsHelper implements CommentsHelper { - private static final Splitter NEWLINE_SPLITTER = Splitter.on('\n'); - private final JavaFormatterOptions options; public JavaCommentsHelper(JavaFormatterOptions options) { @@ -45,8 +43,9 @@ public String rewrite(Tok tok, int maxWidth, int column0) { text = JavadocFormatter.formatJavadoc(text, column0, options); } List lines = new ArrayList<>(); - for (String line : NEWLINE_SPLITTER.split(text)) { - lines.add(CharMatcher.whitespace().trimTrailingFrom(line)); + Iterator it = Newlines.lineIterator(text); + while (it.hasNext()) { + lines.add(CharMatcher.whitespace().trimTrailingFrom(it.next())); } if (tok.isSlashSlashComment()) { return indentLineComments(lines, column0); diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaInput.java b/core/src/main/java/com/google/googlejavaformat/java/JavaInput.java index e40cb809e..fbd99314b 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaInput.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaInput.java @@ -18,7 +18,6 @@ import static com.google.common.collect.Iterables.getLast; import com.google.common.base.MoreObjects; -import com.google.common.base.Splitter; import com.google.common.base.Verify; import com.google.common.collect.DiscreteDomain; import com.google.common.collect.ImmutableCollection; @@ -26,13 +25,16 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableRangeMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; import com.google.common.collect.Range; import com.google.common.collect.RangeSet; import com.google.common.collect.TreeRangeSet; import com.google.googlejavaformat.Input; +import com.google.googlejavaformat.Newlines; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.jdt.core.ToolFactory; @@ -128,7 +130,7 @@ boolean isToken() { @Override public boolean isNewline() { - return "\n".equals(text); + return Newlines.isNewline(text); } @Override @@ -236,8 +238,6 @@ public String toString() { } } - private static final Splitter NEWLINE_SPLITTER = Splitter.on('\n'); - private final String text; // The input. private int kN; // The number of numbered toks (tokens or comments), excluding the EOF. private Map> kToI = null; // Map from token indices to line numbers. @@ -265,8 +265,7 @@ public String toString() { */ public JavaInput(String text) throws FormatterException { this.text = checkNotNull(text); - List lines = NEWLINE_SPLITTER.splitToList(text); - setLines(ImmutableList.copyOf(lines)); + setLines(ImmutableList.copyOf(Newlines.lineIterator(text))); ImmutableList toks = buildToks(text); positionToColumnMap = makePositionToColumnMap(toks); tokens = buildTokens(toks); @@ -369,20 +368,24 @@ static ImmutableList buildToks(String text, ImmutableSet stopIds) char tokText0 = tokText.charAt(0); // The token's first character. final boolean isToken; // Is this tok a token? final boolean isNumbered; // Is this tok numbered? (tokens and comments) - boolean extraNewline = false; // Extra newline at end? + String extraNewline = null; // Extra newline at end? List strings = new ArrayList<>(); if (Character.isWhitespace(tokText0)) { isToken = false; isNumbered = false; - boolean first = true; - for (String spaces : NEWLINE_SPLITTER.split(originalTokText)) { - if (!first) { - strings.add("\n"); - } - if (!spaces.isEmpty()) { - strings.add(spaces); + Iterator it = Newlines.lineIterator(originalTokText); + while (it.hasNext()) { + String line = it.next(); + String newline = Newlines.getLineEnding(line); + if (newline != null) { + String spaces = line.substring(0, line.length() - newline.length()); + if (!spaces.isEmpty()) { + strings.add(spaces); + } + strings.add(newline); + } else if (!line.isEmpty()) { + strings.add(line); } - first = false; } } else if (tokText.startsWith("'") || tokText.startsWith("\"")) { isToken = true; @@ -390,10 +393,12 @@ static ImmutableList buildToks(String text, ImmutableSet stopIds) strings.add(originalTokText); } else if (tokText.startsWith("//") || tokText.startsWith("/*")) { // For compatibility with an earlier lexer, the newline after a // comment is its own tok. - if (tokText.startsWith("//") && originalTokText.endsWith("\n")) { - originalTokText = originalTokText.substring(0, originalTokText.length() - 1); - tokText = tokText.substring(0, tokText.length() - 1); - extraNewline = true; + if (tokText.startsWith("//") + && (originalTokText.endsWith("\n") || originalTokText.endsWith("\r"))) { + extraNewline = Newlines.getLineEnding(originalTokText); + tokText = tokText.substring(0, tokText.length() - extraNewline.length()); + originalTokText = + originalTokText.substring(0, originalTokText.length() - extraNewline.length()); } isToken = false; isNumbered = true; @@ -425,14 +430,9 @@ static ImmutableList buildToks(String text, ImmutableSet stopIds) columnI, isToken, tokenId)); - for (char c : originalTokText.toCharArray()) { - if (c == '\n') { - columnI = 0; - } else { - ++columnI; - } - ++charI; - } + charI += originalTokText.length(); + columnI = updateColumn(columnI, originalTokText); + } else { if (strings.size() != 1 && !tokText.equals(originalTokText)) { throw new FormatterException( @@ -440,26 +440,30 @@ static ImmutableList buildToks(String text, ImmutableSet stopIds) } for (String str : strings) { toks.add(new Tok(isNumbered ? kN++ : -1, str, str, charI, columnI, isToken, tokenId)); - for (char c : str.toCharArray()) { - if (c == '\n') { - columnI = 0; - } else { - ++columnI; - } - ++charI; - } + charI += str.length(); + columnI = updateColumn(columnI, originalTokText); } } - if (extraNewline) { - toks.add(new Tok(-1, "\n", "\n", charI, columnI, false, tokenId)); + if (extraNewline != null) { + toks.add(new Tok(-1, extraNewline, extraNewline, charI, columnI, false, tokenId)); columnI = 0; - ++charI; + charI += extraNewline.length(); } } toks.add(new Tok(kN, "", "", charI, columnI, true, ITerminalSymbols.TokenNameEOF)); // EOF tok. return ImmutableList.copyOf(toks); } + private static int updateColumn(int columnI, String originalTokText) { + Integer last = Iterators.getLast(Newlines.lineOffsetIterator(originalTokText)); + if (last > 0) { + columnI = originalTokText.length() - last; + } else { + columnI += originalTokText.length(); + } + return columnI; + } + private static ImmutableList buildTokens(List toks) { ImmutableList.Builder tokens = ImmutableList.builder(); int k = 0; @@ -505,7 +509,7 @@ private static ImmutableList buildTokens(List toks) { } Tok nonTokenAfter = toks.get(k++); toksAfter.add(nonTokenAfter); - if (nonTokenAfter.getText().contains("\n")) { + if (Newlines.containsBreaks(nonTokenAfter.getText())) { break; } } diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java b/core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java index bf2ff5fe3..d0081707c 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java @@ -26,6 +26,7 @@ import com.google.googlejavaformat.CommentsHelper; import com.google.googlejavaformat.Input; import com.google.googlejavaformat.Input.Token; +import com.google.googlejavaformat.Newlines; import com.google.googlejavaformat.OpsBuilder.BlankLineWanted; import com.google.googlejavaformat.Output; import java.util.ArrayList; @@ -118,7 +119,7 @@ public void append(String text, Range range) { ++newlinesPending; } } - if (text.equals("\n")) { + if (Newlines.isNewline(text)) { /* * Don't update range information, and swallow extra newlines. The case below for '\n' is for * block comments. @@ -137,6 +138,11 @@ public void append(String text, Range range) { case ' ': ++spacesPending; break; + case '\r': + if (i + 1 < text.length() && text.charAt(i + 1) == '\n') { + i++; + } + // falls through case '\n': spacesPending = 0; ++newlinesPending; diff --git a/core/src/test/java/com/google/googlejavaformat/NewlinesTest.java b/core/src/test/java/com/google/googlejavaformat/NewlinesTest.java new file mode 100644 index 000000000..4b7dab78d --- /dev/null +++ b/core/src/test/java/com/google/googlejavaformat/NewlinesTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.googlejavaformat; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableList; +import java.util.Iterator; +import java.util.NoSuchElementException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** {@link Newlines}Test */ +@RunWith(JUnit4.class) +public class NewlinesTest { + @Test + public void offsets() { + assertThat(ImmutableList.copyOf(Newlines.lineOffsetIterator("foo\nbar\n"))) + .containsExactly(0, 4, 8); + assertThat(ImmutableList.copyOf(Newlines.lineOffsetIterator("foo\nbar"))).containsExactly(0, 4); + + assertThat(ImmutableList.copyOf(Newlines.lineOffsetIterator("foo\rbar\r"))) + .containsExactly(0, 4, 8); + assertThat(ImmutableList.copyOf(Newlines.lineOffsetIterator("foo\rbar"))).containsExactly(0, 4); + + assertThat(ImmutableList.copyOf(Newlines.lineOffsetIterator("foo\r\nbar\r\n"))) + .containsExactly(0, 5, 10); + assertThat(ImmutableList.copyOf(Newlines.lineOffsetIterator("foo\r\nbar"))) + .containsExactly(0, 5); + } + + @Test + public void lines() { + assertThat(ImmutableList.copyOf(Newlines.lineIterator("foo\nbar\n"))) + .containsExactly("foo\n", "bar\n"); + assertThat(ImmutableList.copyOf(Newlines.lineIterator("foo\nbar"))) + .containsExactly("foo\n", "bar"); + + assertThat(ImmutableList.copyOf(Newlines.lineIterator("foo\rbar\r"))) + .containsExactly("foo\r", "bar\r"); + assertThat(ImmutableList.copyOf(Newlines.lineIterator("foo\rbar"))) + .containsExactly("foo\r", "bar"); + + assertThat(ImmutableList.copyOf(Newlines.lineIterator("foo\r\nbar\r\n"))) + .containsExactly("foo\r\n", "bar\r\n"); + assertThat(ImmutableList.copyOf(Newlines.lineIterator("foo\r\nbar"))) + .containsExactly("foo\r\n", "bar"); + } + + @Test + public void terminalOffset() { + Iterator it = Newlines.lineOffsetIterator("foo\nbar\n"); + it.next(); + it.next(); + it.next(); + try { + it.next(); + fail(); + } catch (NoSuchElementException e) { + // expected + } + + it = Newlines.lineOffsetIterator("foo\nbar"); + it.next(); + it.next(); + try { + it.next(); + fail(); + } catch (NoSuchElementException e) { + // expected + } + } + + @Test + public void terminalLine() { + Iterator it = Newlines.lineIterator("foo\nbar\n"); + it.next(); + it.next(); + try { + it.next(); + fail(); + } catch (NoSuchElementException e) { + // expected + } + + it = Newlines.lineIterator("foo\nbar"); + it.next(); + it.next(); + try { + it.next(); + fail(); + } catch (NoSuchElementException e) { + // expected + } + } +} diff --git a/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java b/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java index d78a5f96e..96433a877 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java @@ -84,9 +84,9 @@ public static Iterable data() throws IOException { return testInputs; } - private String name; - private String input; - private String expected; + private final String name; + private final String input; + private final String expected; public FormatterIntegrationTest(String name, String input, String expected) { this.name = name; @@ -113,4 +113,24 @@ public void idempotent() { fail(String.format("Formatter crashed on %s: %s", name, e.getMessage())); } } + + @Test + public void cr() throws IOException { + try { + String output = new Formatter().formatSource(expected.replace('\n', '\r')); + assertEquals("bad output for " + name, expected, output); + } catch (FormatterException e) { + fail(String.format("Formatter crashed on %s: %s", name, e.getMessage())); + } + } + + @Test + public void crlf() { + try { + String output = new Formatter().formatSource(expected.replace("\n", "\r\n")); + assertEquals("bad output for " + name, expected, output); + } catch (FormatterException e) { + fail(String.format("Formatter crashed on %s: %s", name, e.getMessage())); + } + } }