From 810ff722eb301fab95de31cb93d0718838a10bb3 Mon Sep 17 00:00:00 2001 From: Vincent Potucek Date: Thu, 4 Sep 2025 15:51:27 +0200 Subject: [PATCH] Promote `RemoveWildCardImports` to linter --- CHANGES.md | 1 + .../main/java/com/diffplug/spotless/Lint.java | 4 +- .../spotless/generic/LintRegexStep.java | 79 +++++++++++++++++++ .../java/RemoveWildcardImportsStep.java | 16 ++-- .../spotless/JavaDefaultTargetTest.java | 16 +++- .../java/RemoveWildcardImportsStepTest.java | 24 +++++- .../JavaCodeNoWildcardsFormatted.test | 4 + .../JavaCodeNoWildcardsUnformatted.test | 4 + .../spotless/generic/LintRegexStepTest.java | 36 +++++++++ 9 files changed, 173 insertions(+), 11 deletions(-) create mode 100644 lib/src/main/java/com/diffplug/spotless/generic/LintRegexStep.java create mode 100644 testlib/src/main/resources/java/removewildcardimports/JavaCodeNoWildcardsFormatted.test create mode 100644 testlib/src/main/resources/java/removewildcardimports/JavaCodeNoWildcardsUnformatted.test create mode 100644 testlib/src/test/java/com/diffplug/spotless/generic/LintRegexStepTest.java diff --git a/CHANGES.md b/CHANGES.md index dfc37968fc..02dfface28 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * Bump default `google-java-format` version to latest `1.24.0` -> `1.28.0`. ([#2345](https://github.com/diffplug/spotless/pull/2345)) * Bump default `ktlint` version to latest `1.5.0` -> `1.7.1`. ([#2555](https://github.com/diffplug/spotless/pull/2555)) * Bump default `jackson` version to latest `2.19.2` -> `2.20.0`. ([#2606](https://github.com/diffplug/spotless/pull/2606)) +* Promote `RemoveWildCardImports` to linter ([#2611](https://github.com/diffplug/spotless/pull/2611)) ## [3.3.1] - 2025-07-21 ### Fixed diff --git a/lib/src/main/java/com/diffplug/spotless/Lint.java b/lib/src/main/java/com/diffplug/spotless/Lint.java index b48a7bcbd2..b01a072905 100644 --- a/lib/src/main/java/com/diffplug/spotless/Lint.java +++ b/lib/src/main/java/com/diffplug/spotless/Lint.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 DiffPlug + * Copyright 2022-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -91,7 +91,7 @@ public ShortcutException(Lint... lints) { private final List lints; ShortcutException(Collection lints) { - super(lints.iterator().next().detail); + super(lints.iterator().next().toString()); this.lints = List.copyOf(lints); } diff --git a/lib/src/main/java/com/diffplug/spotless/generic/LintRegexStep.java b/lib/src/main/java/com/diffplug/spotless/generic/LintRegexStep.java new file mode 100644 index 0000000000..3de959e702 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/generic/LintRegexStep.java @@ -0,0 +1,79 @@ +/* + * Copyright 2016-2025 DiffPlug + * + * 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.diffplug.spotless.generic; + +import static com.diffplug.spotless.Lint.atLine; + +import java.io.File; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.Lint; + +public final class LintRegexStep { + // prevent direct instantiation + private LintRegexStep() {} + + public static FormatterStep lint(String name, String regex, String error) { + Objects.requireNonNull(name, "name"); + Objects.requireNonNull(regex, "regex"); + Objects.requireNonNull(error, "error"); + return FormatterStep.createLazy(name, + () -> new State(Pattern.compile(regex, Pattern.UNIX_LINES | Pattern.MULTILINE), error), + State::toLinter); + } + + private static final class State implements Serializable { + private static final long serialVersionUID = 1L; + + private final Pattern regex; + private final String replacement; + + State(Pattern regex, String replacement) { + this.regex = regex; + this.replacement = replacement; + } + + FormatterFunc toFormatter() { + return raw -> regex.matcher(raw).replaceAll(replacement); + } + + FormatterFunc toLinter() { + return new FormatterFunc() { + @Override + public String apply(String raw) { + return raw; + } + + @Override + public List lint(String raw, File file) { + List lints = new ArrayList<>(); + var matcher = regex.matcher(raw); + while (matcher.find()) { + int line = 1 + (int) raw.codePoints().limit(matcher.start()).filter(c -> c == '\n').count(); + lints.add(atLine(line, matcher.group(0), replacement)); + } + return lints; + } + }; + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/java/RemoveWildcardImportsStep.java b/lib/src/main/java/com/diffplug/spotless/java/RemoveWildcardImportsStep.java index ca513b8280..1d038f3ee5 100644 --- a/lib/src/main/java/com/diffplug/spotless/java/RemoveWildcardImportsStep.java +++ b/lib/src/main/java/com/diffplug/spotless/java/RemoveWildcardImportsStep.java @@ -16,17 +16,21 @@ package com.diffplug.spotless.java; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.generic.ReplaceRegexStep; +import com.diffplug.spotless.generic.LintRegexStep; /** Removes any wildcard import statements. */ public final class RemoveWildcardImportsStep { + + /** + * Matches lines like 'import foo.*;' or 'import static foo.*;'. + */ + private static final String REGEX = "(?m)^import\\s+(?:static\\s+)?[^;\\n]*\\*;\\R?"; + private static final String NAME = "removeWildcardImports"; + private static final String ERROR = "Do not use wildcard imports (e.g. java.util.*) - replace with specific class imports (e.g. java.util.List) as 'spotlessApply' cannot auto-fix this"; + private RemoveWildcardImportsStep() {} public static FormatterStep create() { - // Matches lines like 'import foo.*;' or 'import static foo.*;'. - return ReplaceRegexStep.create( - "removeWildcardImports", - "(?m)^import\\s+(?:static\\s+)?[^;\\n]*\\*;\\R?", - ""); + return LintRegexStep.lint(NAME, REGEX, ERROR); } } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JavaDefaultTargetTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JavaDefaultTargetTest.java index 401e8ee330..e8f9c166f5 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JavaDefaultTargetTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JavaDefaultTargetTest.java @@ -15,6 +15,9 @@ */ package com.diffplug.gradle.spotless; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + import java.io.IOException; import org.junit.jupiter.api.Test; @@ -97,8 +100,17 @@ void removeWildCardImports() throws IOException { "}"); setFile("test.java").toResource("java/removewildcardimports/JavaCodeWildcardsUnformatted.test"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("test.java").sameAsResource("java/removewildcardimports/JavaCodeWildcardsFormatted.test"); + gradleRunner().withArguments("spotlessApply").buildAndFail(); + assertThat(assertThrows(AssertionError.class, () -> assertFile("test.java").sameAsResource("java/removewildcardimports/JavaCodeWildcardsFormatted.test")).getMessage()) + .contains("Extra content at line 1:") + .contains("Extra content at line 3:") + .contains("import io.quarkus.maven.dependency.*;") + .contains("import static io.quarkus.vertx.web.Route.HttpMethod.*;") + .contains("import static org.springframework.web.reactive.function.BodyInserters.*;") + .contains("java.util.*") + .contains("java.util.Collections.*") + .doesNotContain("import mylib.Helper;") + .doesNotContain("public class Test {}"); } /** diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/RemoveWildcardImportsStepTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/RemoveWildcardImportsStepTest.java index 4d4ade94d0..0e55679455 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/RemoveWildcardImportsStepTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/RemoveWildcardImportsStepTest.java @@ -15,6 +15,9 @@ */ package com.diffplug.spotless.maven.java; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + import org.junit.jupiter.api.Test; import com.diffplug.spotless.maven.MavenIntegrationHarness; @@ -28,6 +31,25 @@ void testRemoveWildcardImports() throws Exception { String path = "src/main/java/test.java"; setFile(path).toResource("java/removewildcardimports/JavaCodeWildcardsUnformatted.test"); mavenRunner().withArguments("spotless:apply").runNoError(); - assertFile(path).sameAsResource("java/removewildcardimports/JavaCodeWildcardsFormatted.test"); + assertThat(assertThrows(AssertionError.class, () -> assertFile(path).sameAsResource("java/removewildcardimports/JavaCodeWildcardsFormatted.test")).getMessage()) + .contains("Extra content at line 1:") + .contains("Extra content at line 3:") + .contains("import io.quarkus.maven.dependency.*;") + .contains("import static io.quarkus.vertx.web.Route.HttpMethod.*;") + .contains("import static org.springframework.web.reactive.function.BodyInserters.*;") + .contains("java.util.*") + .contains("java.util.Collections.*") + .doesNotContain("import mylib.Helper;") + .doesNotContain("public class Test {}"); + } + + @Test + void testRemoveWildcardImportsWithNoResult() throws Exception { + writePomWithJavaSteps(""); + + String path = "src/main/java/test.java"; + setFile(path).toResource("java/removewildcardimports/JavaCodeNoWildcardsUnformatted.test"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(path).sameAsResource("java/removewildcardimports/JavaCodeNoWildcardsFormatted.test"); } } diff --git a/testlib/src/main/resources/java/removewildcardimports/JavaCodeNoWildcardsFormatted.test b/testlib/src/main/resources/java/removewildcardimports/JavaCodeNoWildcardsFormatted.test new file mode 100644 index 0000000000..85ee903fc5 --- /dev/null +++ b/testlib/src/main/resources/java/removewildcardimports/JavaCodeNoWildcardsFormatted.test @@ -0,0 +1,4 @@ +import java.util.List; +import mylib.Helper; + +public class Test {} \ No newline at end of file diff --git a/testlib/src/main/resources/java/removewildcardimports/JavaCodeNoWildcardsUnformatted.test b/testlib/src/main/resources/java/removewildcardimports/JavaCodeNoWildcardsUnformatted.test new file mode 100644 index 0000000000..85ee903fc5 --- /dev/null +++ b/testlib/src/main/resources/java/removewildcardimports/JavaCodeNoWildcardsUnformatted.test @@ -0,0 +1,4 @@ +import java.util.List; +import mylib.Helper; + +public class Test {} \ No newline at end of file diff --git a/testlib/src/test/java/com/diffplug/spotless/generic/LintRegexStepTest.java b/testlib/src/test/java/com/diffplug/spotless/generic/LintRegexStepTest.java new file mode 100644 index 0000000000..46ed58982b --- /dev/null +++ b/testlib/src/test/java/com/diffplug/spotless/generic/LintRegexStepTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 DiffPlug + * + * 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.diffplug.spotless.generic; + +import org.junit.jupiter.api.Test; + +import com.diffplug.common.base.StringPrinter; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.StepHarness; + +class LintRegexStepTest { + + @Test + void lint() throws Exception { + FormatterStep step = LintRegexStep.lint("regex", "bad", "no bad words"); + StepHarness.forStep(step) + .expectLintsOf(StringPrinter.buildStringFromLines( + "bad", + "x bad y", + "ok")) + .toBe("L1 regex(bad) no bad words\nL2 regex(bad) no bad words"); + } +}