Skip to content

Commit f50024a

Browse files
committed
Polish 'Detect and preserve line separators'
See gh-340
1 parent b618560 commit f50024a

File tree

13 files changed

+82
-92
lines changed

13 files changed

+82
-92
lines changed

Diff for: spring-javaformat-gradle/spring-javaformat-gradle-plugin/src/test/java/io/spring/javaformat/gradle/CheckTaskTests.java

+10-18
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@
1818

1919
import java.io.File;
2020
import java.io.IOException;
21+
import java.nio.charset.StandardCharsets;
2122
import java.nio.file.Files;
2223
import java.nio.file.Path;
23-
import java.nio.file.StandardCopyOption;
2424
import java.nio.file.StandardOpenOption;
2525
import java.util.Arrays;
26-
import java.util.List;
2726
import java.util.stream.Stream;
2827

2928
import org.gradle.testkit.runner.BuildResult;
@@ -68,12 +67,12 @@ void whenFirstInvocationSucceedsThenSecondInvocationIsUpToDate() throws IOExcept
6867

6968
@Test
7069
void whenFirstInvocationSucceedsAndSourceIsModifiedThenSecondInvocationSucceeds() throws IOException {
71-
copyFolder(new File("src/test/resources/check-ok").toPath(), this.temp.toPath());
70+
copyNormalizedFolder(new File("src/test/resources/check-ok").toPath(), this.temp.toPath());
7271
GradleBuild gradleBuild = this.gradleBuild.source(this.temp);
7372
BuildResult result = gradleBuild.build("check");
7473
assertThat(result.task(":checkFormatMain").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
75-
appendToFileNormalizingNewlines(new File(this.temp, "src/main/java/simple/Simple.java").toPath(),
76-
"// A change to the file");
74+
Files.write(new File(this.temp, "src/main/java/simple/Simple.java").toPath(),
75+
"// A change to the file\n".getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);
7776
result = gradleBuild.build("--debug", "check");
7877
assertThat(result.task(":checkFormatMain").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
7978
}
@@ -129,14 +128,17 @@ void whenFirstInvocationFailsThenSecondInvocationFails() throws IOException {
129128
assertThat(result.task(":checkFormatMain").getOutcome()).isEqualTo(TaskOutcome.FAILED);
130129
}
131130

132-
private void copyFolder(Path source, Path target) throws IOException {
131+
private void copyNormalizedFolder(Path source, Path target) throws IOException {
133132
try (Stream<Path> stream = Files.walk(source)) {
134133
stream.forEach((child) -> {
135134
try {
136135
Path relative = source.relativize(child);
137136
Path destination = target.resolve(relative);
138-
if (!destination.toFile().isDirectory()) {
139-
Files.copy(child, destination, StandardCopyOption.REPLACE_EXISTING);
137+
if (!Files.isDirectory(child)) {
138+
String content = new String(Files.readAllBytes(child), StandardCharsets.UTF_8);
139+
String normalized = content.replace("\n\r", "\n").replace('\r', '\n');
140+
Files.createDirectories(destination.getParent());
141+
Files.write(destination, normalized.getBytes(StandardCharsets.UTF_8));
140142
}
141143
}
142144
catch (Exception ex) {
@@ -146,14 +148,4 @@ private void copyFolder(Path source, Path target) throws IOException {
146148
}
147149
}
148150

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

Diff for: spring-javaformat-maven/spring-javaformat-maven-plugin/src/it/.gitattributes

-2
This file was deleted.

Diff for: spring-javaformat-maven/spring-javaformat-maven-plugin/src/test/java/io/spring/format/maven/VerifyApply.java

+7-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2021 the original author or authors.
2+
* Copyright 2017-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,16 +30,14 @@
3030
*/
3131
public class VerifyApply {
3232

33-
private static final String LF = "\n";
34-
3533
private static final String JAVA_FILE = "src/main/java/simple/Simple.java";
3634

3735
public void verify(File base) throws IOException {
38-
verify(base, LF);
36+
verify(base, null);
3937
}
4038

4139
public void verify(File base, boolean spaces) throws IOException {
42-
verify(base, LF, spaces);
40+
verify(base, null, spaces);
4341
}
4442

4543
public void verify(File base, String lineSeparator) throws IOException {
@@ -48,16 +46,14 @@ public void verify(File base, String lineSeparator) throws IOException {
4846

4947
public void verify(File base, String lineSeparator, boolean spaces) throws IOException {
5048
String formated = new String(Files.readAllBytes(base.toPath().resolve(JAVA_FILE)), StandardCharsets.UTF_8);
49+
if (lineSeparator == null) {
50+
formated = formated.replace("\r\n", "\n").replace('\r', '\n');
51+
lineSeparator = "\n";
52+
}
5153
String indent = (!spaces) ? " " : " ";
5254
assertThat(formated).contains("Simple." + lineSeparator + " *" + lineSeparator + " * @author")
5355
.contains("public class Simple {")
5456
.contains(indent + "public static void main");
5557
}
5658

57-
public static void main(String[] args) throws IOException {
58-
new VerifyApply().verify(new File(
59-
"/Users/pwebb/projects/spring-javaformat/code/spring-javaformat-maven/spring-javaformat-maven-plugin/target/it/apply-line-separator"),
60-
"\r");
61-
}
62-
6359
}

Diff for: spring-javaformat/spring-javaformat-formatter-tests/src/test/java/io/spring/javaformat/formatter/AbstractFormatterTests.java

+45-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2021 the original author or authors.
2+
* Copyright 2017-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,10 +17,11 @@
1717
package io.spring.javaformat.formatter;
1818

1919
import java.io.File;
20+
import java.io.IOException;
2021
import java.nio.charset.StandardCharsets;
2122
import java.nio.file.Files;
2223
import java.util.ArrayList;
23-
import java.util.Collection;
24+
import java.util.List;
2425

2526
import io.spring.javaformat.config.JavaBaseline;
2627
import io.spring.javaformat.config.JavaFormatConfig;
@@ -47,7 +48,7 @@ protected final String read(File file) throws Exception {
4748
}
4849

4950
protected static Item[] items(String expectedOverride) {
50-
Collection<Item> items = new ArrayList<>();
51+
List<Item> items = new ArrayList<>();
5152
File sourceDir = new File("src/test/resources/source");
5253
File expectedDir = new File("src/test/resources/expected");
5354
File configDir = new File("src/test/resources/config");
@@ -59,12 +60,52 @@ protected static Item[] items(String expectedOverride) {
5960
}
6061
File config = new File(configDir, source.getName());
6162
for (JavaBaseline javaBaseline : JavaBaseline.values()) {
62-
items.add(new Item(javaBaseline, source, expected, config));
63+
addItem(items, javaBaseline, source, expected, config);
6364
}
6465
}
6566
return items.toArray(new Item[0]);
6667
}
6768

69+
private static void addItem(List<Item> items, JavaBaseline javaBaseline, File source, File expected, File config) {
70+
if (source.getName().contains("lineendings")) {
71+
items.add(new Item(javaBaseline, copy(source, LineEnding.CR), copy(expected, LineEnding.CR), config));
72+
items.add(new Item(javaBaseline, copy(source, LineEnding.LF), copy(expected, LineEnding.LF), config));
73+
items.add(new Item(javaBaseline, copy(source, LineEnding.CRLF), copy(expected, LineEnding.CRLF), config));
74+
}
75+
else {
76+
items.add(new Item(javaBaseline, source, expected, config));
77+
}
78+
}
79+
80+
private static File copy(File file, LineEnding lineEnding) {
81+
try {
82+
String[] name = file.getName().split("\\.");
83+
File result = File.createTempFile(name[0] + "_" + lineEnding + "_", "." + name[1]);
84+
String content = Files.readString(file.toPath());
85+
content = content.replace("\r\n", "\n").replace('\r', '\n').replace("\n", lineEnding.ending());
86+
Files.writeString(result.toPath(), content);
87+
return result;
88+
}
89+
catch (IOException ex) {
90+
throw new IllegalStateException(ex);
91+
}
92+
}
93+
94+
enum LineEnding {
95+
96+
CR("\r"), LF("\n"), CRLF("\r\n");
97+
98+
private final String ending;
99+
100+
LineEnding(String ending) {
101+
this.ending = ending;
102+
}
103+
104+
String ending() {
105+
return this.ending;
106+
}
107+
};
108+
68109
static class Item {
69110

70111
private final JavaBaseline javaBaseline;

Diff for: spring-javaformat/spring-javaformat-formatter-tests/src/test/resources/.gitattributes

-4
This file was deleted.

Diff for: spring-javaformat/spring-javaformat-formatter-tests/src/test/resources/expected/correct-cr.txt

-1
This file was deleted.

Diff for: spring-javaformat/spring-javaformat-formatter-tests/src/test/resources/expected/correct-crlf.txt

-9
This file was deleted.

Diff for: spring-javaformat/spring-javaformat-formatter-tests/src/test/resources/expected/correct-lf.txt renamed to spring-javaformat/spring-javaformat-formatter-tests/src/test/resources/expected/lineendings.txt

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package correct;
33
public class CorrectLf {
44

55
public static void main(String[] args) throws Exception {
6-
// FIXME
76
}
87

98
}

Diff for: spring-javaformat/spring-javaformat-formatter-tests/src/test/resources/source/correct-cr.txt

-1
This file was deleted.

Diff for: spring-javaformat/spring-javaformat-formatter-tests/src/test/resources/source/correct-crlf.txt

-9
This file was deleted.

Diff for: spring-javaformat/spring-javaformat-formatter-tests/src/test/resources/source/correct-lf.txt

-9
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package correct;
2+
3+
public class CorrectLf {
4+
5+
public static void main(String[] args) throws Exception { }
6+
7+
}

Diff for: spring-javaformat/spring-javaformat-formatter/src/main/java/io/spring/javaformat/formatter/Formatter.java

+13-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2021 the original author or authors.
2+
* Copyright 2017-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,8 +17,6 @@
1717
package io.spring.javaformat.formatter;
1818

1919
import java.util.Map;
20-
import java.util.regex.Matcher;
21-
import java.util.regex.Pattern;
2220

2321
import org.eclipse.jface.text.IRegion;
2422
import org.eclipse.text.edits.TextEdit;
@@ -58,11 +56,6 @@ public class Formatter {
5856
*/
5957
private static final int DEFAULT_INDENTATION_LEVEL = 0;
6058

61-
/**
62-
* Pattern that matches all line separators into named-capturing group "sep".
63-
*/
64-
private static final Pattern LINE_SEPARATOR_PATTERN = Pattern.compile("(?<sep>(\r\n|\r|\n))");
65-
6659
/**
6760
* The default line separator.
6861
*/
@@ -130,9 +123,7 @@ public TextEdit format(String source, int offset, int length, String lineSeparat
130123

131124
public TextEdit format(int kind, String source, int offset, int length, int indentationLevel,
132125
String lineSeparator) {
133-
if (lineSeparator == null) {
134-
lineSeparator = detectLineSeparator(source);
135-
}
126+
lineSeparator = (lineSeparator != null) ? lineSeparator : detectLineSeparator(source);
136127
return this.delegate.format(kind, source, offset, length, indentationLevel, lineSeparator);
137128
}
138129

@@ -158,9 +149,7 @@ public TextEdit format(String source, IRegion[] regions, String lineSeparator) {
158149
}
159150

160151
public TextEdit format(int kind, String source, IRegion[] regions, int indentationLevel, String lineSeparator) {
161-
if (lineSeparator == null) {
162-
lineSeparator = detectLineSeparator(source);
163-
}
152+
lineSeparator = (lineSeparator != null) ? lineSeparator : detectLineSeparator(source);
164153
return this.delegate.format(kind, source, regions, indentationLevel, lineSeparator);
165154
}
166155

@@ -173,16 +162,17 @@ public void setOptions(Map<String, String> options) {
173162
}
174163

175164
private String detectLineSeparator(String contents) {
176-
Matcher matcher = LINE_SEPARATOR_PATTERN.matcher(contents);
177-
if (!matcher.find()) {
178-
return DEFAULT_LINE_SEPARATOR;
179-
}
180-
String firstMatch = matcher.group("sep");
181-
while (matcher.find()) {
182-
if (!matcher.group("sep").equals(firstMatch)) {
183-
return DEFAULT_LINE_SEPARATOR;
165+
int length = contents.length();
166+
for (int i = 0; i < length; i++) {
167+
char ch = contents.charAt(i);
168+
boolean isLastChar = (i + 1) == length;
169+
if (ch == '\r') {
170+
return (isLastChar || contents.charAt(i + 1) != '\n') ? "\r" : "\r\n";
171+
}
172+
if (ch == '\n') {
173+
return "\n";
184174
}
185175
}
186-
return firstMatch;
176+
return null;
187177
}
188178
}

0 commit comments

Comments
 (0)