Skip to content

Commit edce6f5

Browse files
committed
Fix the imports after formatting, as the command line does
formatSourceAndFixImports, which the Maven plugin and the Spotless step run, ordered the imports and removed the unused ones before formatting. Main formats first and fixes the imports after, because the ranges of --lines and --offset are offsets into the input. The order decides where the comment of a removed import ends up: ImportOrderer moves a comment along with its import, and RemoveUnusedImports leaves it behind. Fixing them first also dropped an extraneous semicolon between imports, where the command line fails the file (#219). formatSourceAndFixImports now fixes the imports after formatting. FormatterServiceImplTest holds the in-process service to what Main prints for whole files.
1 parent 1b71438 commit edce6f5

3 files changed

Lines changed: 147 additions & 4 deletions

File tree

‎open-java-format/src/main/java/com/palantir/javaformat/java/Formatter.java‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,8 @@ public String formatSource(String input) throws FormatterException {
296296
/**
297297
* Formats an input string (a Java compilation unit) and fixes imports.
298298
*
299-
* <p>Fixing imports includes ordering, spacing, and removal of unused import statements.
299+
* <p>Fixing imports includes ordering, spacing, and removal of unused import statements. Long strings are reflowed
300+
* too, and the result is what {@code open-java-format -} prints with no flags.
300301
*
301302
* @param input the input string
302303
* @return the output string
@@ -305,9 +306,10 @@ public String formatSource(String input) throws FormatterException {
305306
* Style Guide - 3.3.3 Import ordering and spacing</a>
306307
*/
307308
public String formatSourceAndFixImports(String input) throws FormatterException {
308-
input = ImportOrderer.reorderImports(input, options.style());
309-
input = RemoveUnusedImports.removeUnusedImports(input);
310-
String formatted = formatSource(input);
309+
// The imports are fixed after formatting, as the command line fixes them: it has to format first, because the
310+
// ranges it formats are offsets into the input. In the other order the comment of a removed import ends up
311+
// elsewhere, and an extraneous semicolon between imports is dropped where the command line fails the file.
312+
String formatted = fixImports(formatSource(input));
311313
return StringWrapper.wrap(options.maxLineLength(), formatted, this);
312314
}
313315

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package com.palantir.javaformat.java;
16+
17+
import static java.nio.charset.StandardCharsets.UTF_8;
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20+
21+
import java.io.ByteArrayInputStream;
22+
import java.io.PrintWriter;
23+
import java.io.StringWriter;
24+
import java.util.stream.Stream;
25+
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.params.ParameterizedTest;
27+
import org.junit.jupiter.params.provider.Arguments;
28+
import org.junit.jupiter.params.provider.MethodSource;
29+
30+
/**
31+
* The service that formats in the build's own JVM, for {@code formatDiff} without the native binary, the Spotless step
32+
* and the Maven plugin, against the command line, which the bootstrapping and native services run for IntelliJ IDEA
33+
* and {@code formatDiff}, and the GitHub Action runs to check.
34+
*/
35+
final class FormatterServiceImplTest {
36+
private static final FormatterService SERVICE = new FormatterServiceImpl();
37+
38+
// Two long strings, as in a file where only the line of the second one has changed.
39+
private static final String LONG_STRINGS = """
40+
class S {
41+
String outside = "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat sed.";
42+
43+
String changed = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna.";
44+
}
45+
""";
46+
47+
static Stream<Arguments> sources() {
48+
return Stream.of(
49+
Arguments.of("long strings", LONG_STRINGS),
50+
Arguments.of("joinable literals", """
51+
class J {
52+
String joined = "one " + "two";
53+
String wrapped = "Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore";
54+
}
55+
"""),
56+
// The comment stays behind when its import is removed, and the order of fixing the imports and
57+
// formatting decides where it ends up.
58+
Arguments.of("trailing comment of an unused import", """
59+
package p;
60+
61+
// group one
62+
import java.util.Map; // used
63+
import java.io.File; // unused, trailing comment
64+
/* block between */
65+
import java.util.List;
66+
67+
class C { List<String> l; Map<String,String> m; }
68+
"""),
69+
Arguments.of("comment under an unused import", """
70+
package p;
71+
72+
import java.io.File;
73+
// comment about the class
74+
class C {}
75+
"""));
76+
}
77+
78+
@ParameterizedTest(name = "{0}")
79+
@MethodSource("sources")
80+
void formatsAFileAsTheCommandLineDoes(String name, String source) throws Exception {
81+
assertThat(SERVICE.formatSourceReflowStringsAndFixImports(source)).isEqualTo(commandLine(source, "-"));
82+
}
83+
84+
@Test
85+
void failsOnAnExtraneousSemicolonAsTheCommandLineDoes() throws Exception {
86+
// Fixing the imports first used to drop the semicolon, which javac 21 rejects between imports, and format
87+
// the file that the command line refuses.
88+
String source = """
89+
package p;
90+
91+
import java.util.List;;
92+
import java.io.File;
93+
94+
class C { List<String> l; }
95+
""";
96+
StringWriter err = new StringWriter();
97+
Main main = new Main(
98+
new PrintWriter(new StringWriter(), true),
99+
new PrintWriter(err, true),
100+
new ByteArrayInputStream(source.getBytes(UTF_8)));
101+
102+
assertThat(main.format("-")).isNotZero();
103+
assertThat(err.toString()).contains("extraneous semicolon");
104+
assertThatThrownBy(() -> SERVICE.formatSourceReflowStringsAndFixImports(source))
105+
.isInstanceOf(FormatterException.class)
106+
.hasMessageContaining("extraneous semicolon");
107+
}
108+
109+
/** What {@code open-java-format} prints for the source on standard input. */
110+
private static String commandLine(String source, String... args) throws UsageException {
111+
StringWriter out = new StringWriter();
112+
StringWriter err = new StringWriter();
113+
Main main = new Main(
114+
new PrintWriter(out, true),
115+
new PrintWriter(err, true),
116+
new ByteArrayInputStream(source.getBytes(UTF_8)));
117+
assertThat(main.format(args)).as(err::toString).isZero();
118+
return out.toString();
119+
}
120+
}

‎open-java-format/src/test/java/com/palantir/javaformat/java/FormatterTest.java‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,27 @@ class T {
276276
assertThat(ImportOrderer.reorderImports(withoutBlank, Style.OJF)).isEqualTo(withoutBlank);
277277
}
278278

279+
@Test
280+
void commentUnderARemovedImportStaysOnTheClass() throws FormatterException {
281+
String input = """
282+
package p;
283+
284+
import java.io.File;
285+
// comment about the class
286+
class C {}
287+
""";
288+
String output = """
289+
package p;
290+
291+
// comment about the class
292+
class C {}
293+
""";
294+
295+
// The imports are fixed after formatting, as the command line fixes them. Ordering them first left a blank
296+
// line after the import, which stayed between the comment and the class once the import was gone.
297+
assertThat(FORMATTER.formatSourceAndFixImports(input)).isEqualTo(output);
298+
}
299+
279300
@Test
280301
void canParse_java9_private_interface_methods() {
281302
assertThatCode(() -> FORMATTER.formatSourceAndFixImports(""

0 commit comments

Comments
 (0)