Skip to content

Commit c26116a

Browse files
committed
feat: add remove-unused-import
1 parent f931d3b commit c26116a

File tree

7 files changed

+225
-19
lines changed

7 files changed

+225
-19
lines changed

app/src/main/java/com/diffplug/spotless/cli/SpotlessCLI.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@
101101
GoogleJavaFormat.class,
102102
LicenseHeader.class,
103103
PalantirJavaFormat.class,
104-
Prettier.class
104+
Prettier.class,
105+
RemoveUnusedImports.class
105106
})
106107
public class SpotlessCLI implements SpotlessAction, SpotlessCommand, SpotlessActionContextProvider {
107108

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2025 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.cli.steps;
17+
18+
import com.diffplug.spotless.FormatterStep;
19+
import com.diffplug.spotless.cli.core.SpotlessActionContext;
20+
import com.diffplug.spotless.cli.help.AdditionalInfoLinks;
21+
import com.diffplug.spotless.cli.help.OptionConstants;
22+
import com.diffplug.spotless.cli.help.SupportedFileTypes;
23+
import com.diffplug.spotless.java.CleanthatJavaStep;
24+
import com.diffplug.spotless.java.RemoveUnusedImportsStep;
25+
import org.jetbrains.annotations.NotNull;
26+
import picocli.CommandLine;
27+
28+
import java.util.ArrayList;
29+
import java.util.Collections;
30+
import java.util.List;
31+
import java.util.Objects;
32+
33+
@CommandLine.Command(name = "remove-unused-imports", description = "Removes unused imports from Java files.")
34+
@SupportedFileTypes("Java")
35+
@AdditionalInfoLinks("https://github.com/diffplug/spotless/tree/main/plugin-gradle#removeunusedimports")
36+
public class RemoveUnusedImports extends SpotlessFormatterStep {
37+
38+
@CommandLine.Option(
39+
names = {"--engine", "-e"},
40+
defaultValue = "GOOGLE_JAVA_FORMAT",
41+
description =
42+
"The backing engine to use for detecting and removing unused imports." + OptionConstants.VALID_AND_DEFAULT_VALUES_SUFFIX)
43+
Engine engine;
44+
45+
public enum Engine {
46+
GOOGLE_JAVA_FORMAT {
47+
@Override
48+
String formatterName() {
49+
return RemoveUnusedImportsStep.defaultFormatter();
50+
51+
}
52+
},
53+
CLEAN_THAT {
54+
@Override
55+
String formatterName() {
56+
return "cleanthat-javaparser-unnecessaryimport";
57+
}
58+
};
59+
60+
61+
abstract String formatterName();
62+
}
63+
64+
@Override
65+
public @NotNull List<FormatterStep> prepareFormatterSteps(SpotlessActionContext context) {
66+
return Collections.singletonList(RemoveUnusedImportsStep.create(engine.formatterName(), context.provisioner()));
67+
}
68+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.diffplug.spotless.cli.steps;
2+
3+
import com.diffplug.spotless.cli.CLIIntegrationHarness;
4+
import com.diffplug.spotless.tag.CliNativeTest;
5+
import com.diffplug.spotless.tag.CliProcessTest;
6+
import org.junit.jupiter.api.Test;
7+
8+
import static org.junit.jupiter.api.Assertions.*;
9+
10+
@CliProcessTest
11+
@CliNativeTest
12+
class RemoveUnusedImportsTest extends CLIIntegrationHarness {
13+
14+
@Test
15+
void itRemovesUnusedImportsWithDefaultEngine() {
16+
setFile("Java.java").toResource("java/removeunusedimports/JavaCodeWithLicensePackageUnformatted.test");
17+
18+
cliRunner()
19+
.withTargets("Java.java")
20+
.withStep(RemoveUnusedImports.class)
21+
.run();
22+
23+
assertFile("Java.java")
24+
.notSameSasResource("java/removeunusedimports/JavaCodeWithLicensePackageUnformatted.test")
25+
.hasNotContent("Unused");
26+
27+
28+
selfie().expectResource("Java.java").toMatchDisk();
29+
}
30+
31+
@Test
32+
void itRemovesWithExplicitDefaultEngine() {
33+
setFile("Java.java").toResource("java/removeunusedimports/JavaCodeWithLicensePackageUnformatted.test");
34+
35+
cliRunner()
36+
.withTargets("Java.java")
37+
.withStep(RemoveUnusedImports.class)
38+
.withOption("--engine", "GOOGLE_JAVA_FORMAT")
39+
.run();
40+
41+
assertFile("Java.java")
42+
.notSameSasResource("java/removeunusedimports/JavaCodeWithLicensePackageUnformatted.test")
43+
.hasNotContent("Unused");
44+
45+
selfie().expectResource("Java.java").toMatchDisk();
46+
}
47+
48+
@Test
49+
void itRemovesWithExplicitCleanThatEngine() {
50+
setFile("Java.java").toResource("java/removeunusedimports/JavaCodeWithLicensePackageUnformatted.test");
51+
52+
cliRunner()
53+
.withTargets("Java.java")
54+
.withStep(RemoveUnusedImports.class)
55+
.withOption("--engine", "CLEAN_THAT")
56+
.run();
57+
58+
assertFile("Java.java")
59+
.notSameSasResource("java/removeunusedimports/JavaCodeWithLicensePackageUnformatted.test")
60+
.hasNotContent("Unused");
61+
62+
selfie().expectResource("Java.java").toMatchDisk();
63+
}
64+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
╔═ itRemovesUnusedImportsWithDefaultEngine ═╗
2+
/*
3+
* Some license stuff.
4+
* Very official.
5+
*/
6+
package hello.world;
7+
8+
import mylib.UsedB;
9+
import mylib.UsedA;
10+
11+
public class Java {
12+
public static void main(String[] args) {
13+
System.out.println("hello");
14+
UsedB.someMethod();
15+
UsedA.someMethod();
16+
}
17+
}
18+
╔═ itRemovesWithExplicitCleanThatEngine ═╗
19+
/*
20+
* Some license stuff.
21+
* Very official.
22+
*/
23+
package hello.world;
24+
25+
import mylib.UsedB;
26+
import mylib.UsedA;
27+
28+
public class Java {
29+
public static void main(String[] args) {
30+
System.out.println("hello");
31+
UsedB.someMethod();
32+
UsedA.someMethod();
33+
}
34+
}
35+
╔═ itRemovesWithExplicitDefaultEngine ═╗
36+
/*
37+
* Some license stuff.
38+
* Very official.
39+
*/
40+
package hello.world;
41+
42+
import mylib.UsedB;
43+
import mylib.UsedA;
44+
45+
public class Java {
46+
public static void main(String[] args) {
47+
System.out.println("hello");
48+
UsedB.someMethod();
49+
UsedA.someMethod();
50+
}
51+
}
52+
╔═ [end of file] ═╗

build-logic/src/main/groovy/com/diffplug/spotless/cli/picocli/usage/DocumentedUsages.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ enum DocumentedUsages {
99
GOOGLE_JAVA_FORMAT(),
1010
LICENSE_HEADER(),
1111
PALANTIR_JAVA_FORMAT(),
12-
PRETTIER()
12+
PRETTIER(),
13+
REMOVE_UNUSED_IMPORTS(),
1314

1415
private final String fileName
1516

testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -201,47 +201,50 @@ private ReadAsserter(File file) {
201201
this.file = file;
202202
}
203203

204-
public void hasContent(String expected) {
205-
hasContent(expected, StandardCharsets.UTF_8);
204+
public ReadAsserter hasContent(String expected) {
205+
return hasContent(expected, StandardCharsets.UTF_8);
206206
}
207207

208-
public void hasNotContent(String notExpected) {
209-
notHasContent(notExpected, StandardCharsets.UTF_8);
208+
public ReadAsserter hasNotContent(String notExpected) {
209+
return notHasContent(notExpected, StandardCharsets.UTF_8);
210210
}
211211

212-
public void hasContent(String expected, Charset charset) {
212+
public ReadAsserter hasContent(String expected, Charset charset) {
213213
assertThat(file).usingCharset(charset).hasContent(expected);
214+
return this;
214215
}
215216

216-
public void notHasContent(String notExpected, Charset charset) {
217+
public ReadAsserter notHasContent(String notExpected, Charset charset) {
217218
assertThat(file).usingCharset(charset).content().isNotEqualTo(notExpected);
219+
return this;
218220
}
219221

220-
public void hasLines(String... lines) {
221-
hasContent(String.join("\n", Arrays.asList(lines)));
222+
public ReadAsserter hasLines(String... lines) {
223+
return hasContent(String.join("\n", Arrays.asList(lines)));
222224
}
223225

224-
public void sameAsResource(String resource) {
225-
hasContent(getTestResource(resource));
226+
public ReadAsserter sameAsResource(String resource) {
227+
return hasContent(getTestResource(resource));
226228
}
227229

228-
public void notSameSasResource(String resource) {
229-
hasNotContent(getTestResource(resource));
230+
public ReadAsserter notSameSasResource(String resource) {
231+
return hasNotContent(getTestResource(resource));
230232
}
231233

232-
public void matches(Consumer<AbstractCharSequenceAssert<?, String>> conditions) throws IOException {
234+
public ReadAsserter matches(Consumer<AbstractCharSequenceAssert<?, String>> conditions) throws IOException {
233235
String content = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
234236
conditions.accept(assertThat(content));
237+
return this;
235238
}
236239

237-
public void sameAsFile(File otherFile) throws IOException {
240+
public ReadAsserter sameAsFile(File otherFile) throws IOException {
238241
String otherFileContent = Files.readString(otherFile.toPath());
239-
hasContent(otherFileContent, StandardCharsets.UTF_8);
242+
return hasContent(otherFileContent, StandardCharsets.UTF_8);
240243
}
241244

242-
public void notSameAsFile(File otherFile) throws IOException {
245+
public ReadAsserter notSameAsFile(File otherFile) throws IOException {
243246
String otherFileContent = Files.readString(otherFile.toPath());
244-
notHasContent(otherFileContent, StandardCharsets.UTF_8);
247+
return notHasContent(otherFileContent, StandardCharsets.UTF_8);
245248
}
246249
}
247250

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Some license stuff.
3+
* Very official.
4+
*/
5+
package hello.world;
6+
7+
import mylib.Unused;
8+
import mylib.UsedB;
9+
import mylib.UsedA;
10+
11+
public class Java {
12+
public static void main(String[] args) {
13+
System.out.println("hello");
14+
UsedB.someMethod();
15+
UsedA.someMethod();
16+
}
17+
}

0 commit comments

Comments
 (0)