Skip to content

Commit

Permalink
Detect and preserve line separators
Browse files Browse the repository at this point in the history
Update `Formatter` so that line endings are now detected based on
the contents of the file. This mirrors the behavior of the Eclipse
plugin when used in the IDE, specifically the `nextDelimiterInfo`
method in `org.eclipse.jface.text.DefaultLineTracker`.

See gh-340
  • Loading branch information
ParkerM authored and philwebb committed Feb 16, 2023
1 parent b543266 commit b618560
Show file tree
Hide file tree
Showing 12 changed files with 86 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ void applyToProjectWithFileMergesToDotSettings() throws Exception {
}).given(projectFile).setContents((InputStream) any(), anyInt(), any());
files.applyToProject(project, monitor);
verify(projectFile).setContents((InputStream) any(), eq(1), eq(monitor));
assertThat(out.toString(StandardCharsets.UTF_8)).isEqualTo("a=b\ny=z\n");
assertThat(out.toString(StandardCharsets.UTF_8))
.isEqualToNormalizingNewlines("a=b\ny=z\n");
}

private ProjectSettingsFile createPrefsFile() throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;

import org.gradle.testkit.runner.BuildResult;
Expand Down Expand Up @@ -72,8 +72,8 @@ void whenFirstInvocationSucceedsAndSourceIsModifiedThenSecondInvocationSucceeds(
GradleBuild gradleBuild = this.gradleBuild.source(this.temp);
BuildResult result = gradleBuild.build("check");
assertThat(result.task(":checkFormatMain").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
Files.write(new File(this.temp, "src/main/java/simple/Simple.java").toPath(),
Collections.singletonList("// A change to the file"), StandardOpenOption.APPEND);
appendToFileNormalizingNewlines(new File(this.temp, "src/main/java/simple/Simple.java").toPath(),
"// A change to the file");
result = gradleBuild.build("--debug", "check");
assertThat(result.task(":checkFormatMain").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
}
Expand Down Expand Up @@ -146,4 +146,14 @@ private void copyFolder(Path source, Path target) throws IOException {
}
}

/**
* Uses a read/modify/truncate approach to append a line to a file.
* This avoids issues where the standard append option results in mixed line-endings.
*/
private void appendToFileNormalizingNewlines(Path sourceFilePath, String lineToAppend) throws IOException {
List<String> lines = Files.readAllLines(sourceFilePath);
lines.add(lineToAppend);
Files.write(sourceFilePath, lines, StandardOpenOption.TRUNCATE_EXISTING);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Test resources that need a predictable eol
apply*/src/main/java/simple/Simple.java eol=lf
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
*/
public class VerifyApply {

private static final String LF = System.lineSeparator();
private static final String LF = "\n";

private static final String JAVA_FILE = "src/main/java/simple/Simple.java";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Test resources that need specific eol
**/correct-crlf.txt eol=crlf
**/correct-cr.txt eol=cr
**/correct-lf.txt eol=lf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package correct;public class CorrectCr { public static void main(String[] args) throws Exception { // FIXME }}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package correct;

public class CorrectCrlf {

public static void main(String[] args) throws Exception {
// FIXME
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package correct;

public class CorrectLf {

public static void main(String[] args) throws Exception {
// FIXME
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package correct;public class CorrectCr { public static void main(String[] args) throws Exception { // FIXME }}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package correct;

public class CorrectCrlf {

public static void main(String[] args) throws Exception {
// FIXME
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package correct;

public class CorrectLf {

public static void main(String[] args) throws Exception {
// FIXME
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package io.spring.javaformat.formatter;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jface.text.IRegion;
import org.eclipse.text.edits.TextEdit;
Expand Down Expand Up @@ -56,6 +58,11 @@ public class Formatter {
*/
private static final int DEFAULT_INDENTATION_LEVEL = 0;

/**
* Pattern that matches all line separators into named-capturing group "sep".
*/
private static final Pattern LINE_SEPARATOR_PATTERN = Pattern.compile("(?<sep>(\r\n|\r|\n))");

/**
* The default line separator.
*/
Expand Down Expand Up @@ -123,6 +130,9 @@ public TextEdit format(String source, int offset, int length, String lineSeparat

public TextEdit format(int kind, String source, int offset, int length, int indentationLevel,
String lineSeparator) {
if (lineSeparator == null) {
lineSeparator = detectLineSeparator(source);
}
return this.delegate.format(kind, source, offset, length, indentationLevel, lineSeparator);
}

Expand All @@ -148,6 +158,9 @@ public TextEdit format(String source, IRegion[] regions, String lineSeparator) {
}

public TextEdit format(int kind, String source, IRegion[] regions, int indentationLevel, String lineSeparator) {
if (lineSeparator == null) {
lineSeparator = detectLineSeparator(source);
}
return this.delegate.format(kind, source, regions, indentationLevel, lineSeparator);
}

Expand All @@ -159,4 +172,17 @@ public void setOptions(Map<String, String> options) {
this.delegate.setOptions(options);
}

private String detectLineSeparator(String contents) {
Matcher matcher = LINE_SEPARATOR_PATTERN.matcher(contents);
if (!matcher.find()) {
return DEFAULT_LINE_SEPARATOR;
}
String firstMatch = matcher.group("sep");
while (matcher.find()) {
if (!matcher.group("sep").equals(firstMatch)) {
return DEFAULT_LINE_SEPARATOR;
}
}
return firstMatch;
}
}

0 comments on commit b618560

Please sign in to comment.