diff --git a/CHANGES.md b/CHANGES.md index b0d80fd7ff..039282acee 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added * `FileSignature.Promised` and `JarState.Promised` to facilitate round-trip serialization for the Gradle configuration cache. ([#1945](https://github.com/diffplug/spotless/pull/1945)) +* Support for `idea` ([#2020](https://github.com/diffplug/spotless/pull/2020)) * Respect `.editorconfig` settings for formatting shell via `shfmt` ([#2031](https://github.com/diffplug/spotless/pull/2031)) ### Removed diff --git a/gradle/special-tests.gradle b/gradle/special-tests.gradle index 8721f1e22d..8b74975403 100644 --- a/gradle/special-tests.gradle +++ b/gradle/special-tests.gradle @@ -6,6 +6,7 @@ def special = [ 'buf', 'clang', 'gofmt', + 'idea', 'npm', 'shfmt' ] diff --git a/lib/src/main/java/com/diffplug/spotless/java/IdeaStep.java b/lib/src/main/java/com/diffplug/spotless/java/IdeaStep.java new file mode 100644 index 0000000000..cfe8c5f349 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/java/IdeaStep.java @@ -0,0 +1,137 @@ +/* + * Copyright 2024 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.java; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.diffplug.spotless.ForeignExe; +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.ProcessRunner; + +public final class IdeaStep { + + private static final Logger LOGGER = LoggerFactory.getLogger(IdeaStep.class); + + private IdeaStep() {} + + public static FormatterStep create() { + return create(true); + } + + public static FormatterStep create(boolean withDefaults) { + return create(withDefaults, null); + } + + public static FormatterStep create(boolean withDefaults, + @Nullable String binaryPath) { + return create(withDefaults, binaryPath, null); + } + + public static FormatterStep create(boolean withDefaults, + @Nullable String binaryPath, @Nullable String configPath) { + return FormatterStep.createLazy("IDEA", + () -> createState(withDefaults, binaryPath, configPath), + state -> state); + } + + private static State createState(boolean withDefaults, + @Nullable String binaryPath, @Nullable String configPath) { + return new State(withDefaults, binaryPath, configPath); + } + + private static class State + implements FormatterFunc.NeedsFile, Serializable { + + private static final long serialVersionUID = -1825662355363926318L; + private static final String DEFAULT_IDEA = "idea"; + + private String binaryPath; + @Nullable + private String configPath; + private boolean withDefaults; + + private State(boolean withDefaults, @Nullable String binaryPath, + @Nullable String configPath) { + this.withDefaults = withDefaults; + this.configPath = configPath; + this.binaryPath = Objects.requireNonNullElse(binaryPath, DEFAULT_IDEA); + resolveFullBinaryPathAndCheckVersion(); + } + + private void resolveFullBinaryPathAndCheckVersion() { + var exe = ForeignExe + .nameAndVersion(this.binaryPath, "IntelliJ IDEA") + .versionRegex(Pattern.compile("(IntelliJ IDEA) .*")) + .fixCantFind( + "IDEA executable cannot be found on your machine, " + + "please install it and put idea binary to PATH; or report the problem") + .fixWrongVersion("Provided binary is not IDEA, " + + "please check it and fix the problem; or report the problem"); + try { + this.binaryPath = exe.confirmVersionAndGetAbsolutePath(); + } catch (IOException e) { + throw new IllegalArgumentException("binary cannot be found", e); + } catch (InterruptedException e) { + throw new IllegalArgumentException( + "binary cannot be found, process was interrupted", e); + } + } + + @Override + public String applyWithFile(String unix, File file) throws Exception { + List params = getParams(file); + + try (ProcessRunner runner = new ProcessRunner()) { + var result = runner.exec(params); + + LOGGER.debug("command finished with stdout: {}", + result.assertExitZero(StandardCharsets.UTF_8)); + + return Files.readString(file.toPath()); + } + } + + private List getParams(File file) { + var builder = Stream. builder(); + builder.add(binaryPath); + builder.add("format"); + if (withDefaults) { + builder.add("-allowDefaults"); + } + if (configPath != null) { + builder.add("-s"); + builder.add(configPath); + } + builder.add(file.toString()); + return builder.build().collect(Collectors.toList()); + } + } +} diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 7ce10e1edc..014dad2657 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -3,6 +3,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`). ## [Unreleased] +### Added +* Support for `idea` ([#2020](https://github.com/diffplug/spotless/pull/2020)) ### Fixed * Ignore system git config when running tests ([#1990](https://github.com/diffplug/spotless/issues/1990)) diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java index db869dc4e6..5acd080d16 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java @@ -39,6 +39,7 @@ import com.diffplug.spotless.java.CleanthatJavaStep; import com.diffplug.spotless.java.FormatAnnotationsStep; import com.diffplug.spotless.java.GoogleJavaFormatStep; +import com.diffplug.spotless.java.IdeaStep; import com.diffplug.spotless.java.ImportOrderStep; import com.diffplug.spotless.java.PalantirJavaFormatStep; import com.diffplug.spotless.java.RemoveUnusedImportsStep; @@ -313,6 +314,45 @@ public EclipseConfig withP2Mirrors(Map mirrors) { } + public IdeaConfig idea() { + return new IdeaConfig(); + } + + public class IdeaConfig { + private String binaryPath; + private String configPath; + private boolean withDefaults = false; + + IdeaConfig() { + addStep(createStep()); + } + + private FormatterStep createStep() { + return IdeaStep.create(withDefaults, binaryPath, configPath); + } + + public IdeaConfig binaryPath(String binaryPath) { + Objects.requireNonNull(binaryPath); + this.binaryPath = binaryPath; + replaceStep(createStep()); + return this; + } + + public IdeaConfig configPath(String configPath) { + Objects.requireNonNull(configPath); + this.configPath = configPath; + replaceStep(createStep()); + return this; + } + + public IdeaConfig withDefaults(Boolean withDefaults) { + Objects.requireNonNull(withDefaults); + this.withDefaults = withDefaults; + replaceStep(createStep()); + return this; + } + } + /** Removes newlines between type annotations and types. */ public FormatAnnotationsConfig formatAnnotations() { return new FormatAnnotationsConfig(); @@ -400,7 +440,7 @@ public CleanthatJavaConfig clearMutators() { } // An id of a mutator (see IMutator.getIds()) or - // tThe fully qualified name of a class implementing eu.solven.cleanthat.engine.java.refactorer.meta.IMutator + // The fully qualified name of a class implementing eu.solven.cleanthat.engine.java.refactorer.meta.IMutator public CleanthatJavaConfig addMutator(String mutator) { this.mutators.add(mutator); replaceStep(createStep()); diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JavaIdeaTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JavaIdeaTest.java new file mode 100644 index 0000000000..b154c2d851 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JavaIdeaTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020-2024 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.gradle.spotless; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.tag.IdeaTest; + +@IdeaTest +class JavaIdeaTest extends GradleIntegrationHarness { + @Test + void idea() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "spotless {", + " java {", + " target file('test.java')", + " idea().binaryPath('idea').withDefaults(true)", + " }", + "}"); + + setFile("test.java").toResource("java/idea/full.dirty.java"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("test.java").notSameAsResource("java/idea/full.dirty.java"); + } +} diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index 3e3cc975b3..ff65729278 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -3,11 +3,11 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`). ## [Unreleased] -### Fixed -* Ignore system git config when running tests ([#1990](https://github.com/diffplug/spotless/issues/1990)) - ### Added +* Support for `idea` ([#2020](https://github.com/diffplug/spotless/pull/2020)) * Respect `.editorconfig` settings for formatting shell via `shfmt` ([#2031](https://github.com/diffplug/spotless/pull/2031)) +### Fixed +* Ignore system git config when running tests ([#1990](https://github.com/diffplug/spotless/issues/1990)) ## [2.43.0] - 2024-01-23 ### Added diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Idea.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Idea.java new file mode 100644 index 0000000000..8981a9dc9b --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Idea.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016-2024 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.maven.java; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.java.IdeaStep; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; + +public class Idea implements FormatterStepFactory { + + @Parameter + private String binaryPath; + + @Parameter + private String configPath; + + @Parameter + private Boolean withDefaults = false; + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig config) { + return IdeaStep.create(withDefaults, binaryPath, configPath); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java index b0692446b7..20a2db96f7 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,6 +68,10 @@ public void addImportOrder(ImportOrder importOrder) { addStepFactory(importOrder); } + public void addIdea(Idea idea) { + addStepFactory(idea); + } + public void addPalantirJavaFormat(PalantirJavaFormat palantirJavaFormat) { addStepFactory(palantirJavaFormat); } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/IdeaTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/IdeaTest.java new file mode 100644 index 0000000000..c11b2a64d1 --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/IdeaTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016-2024 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.maven.java; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +@com.diffplug.spotless.tag.IdeaTest +class IdeaTest extends MavenIntegrationHarness { + @Test + void idea() throws Exception { + setFile("test.java").toResource("java/cleanthat/MultipleMutators.dirty.test"); + writePomWithJavaSteps( + "", + " idea", + " true", + ""); + + mavenRunner().withArguments("spotless:apply").runNoError(); + + assertFile("test.java").notSameAsResource("java/cleanthat/MultipleMutators.dirty.test"); + } +} diff --git a/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java b/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java index 0304889e1f..b704d5c981 100644 --- a/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java +++ b/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -151,10 +151,18 @@ public void hasContent(String expected) { hasContent(expected, StandardCharsets.UTF_8); } + public void hasDifferentContent(String expected) { + hasDifferentContent(expected, StandardCharsets.UTF_8); + } + public void hasContent(String expected, Charset charset) { assertThat(file).usingCharset(charset).hasContent(expected); } + public void hasDifferentContent(String expected, Charset charset) { + assertThat(file).usingCharset(charset).isNotEqualTo(expected); + } + public void hasLines(String... lines) { hasContent(String.join("\n", Arrays.asList(lines))); } @@ -163,6 +171,10 @@ public void sameAsResource(String resource) throws IOException { hasContent(getTestResource(resource)); } + public void notSameAsResource(String resource) throws IOException { + hasDifferentContent(getTestResource(resource)); + } + public void matches(Consumer> conditions) throws IOException { String content = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); conditions.accept(assertThat(content)); diff --git a/testlib/src/main/java/com/diffplug/spotless/tag/IdeaTest.java b/testlib/src/main/java/com/diffplug/spotless/tag/IdeaTest.java new file mode 100644 index 0000000000..79813d91d9 --- /dev/null +++ b/testlib/src/main/java/com/diffplug/spotless/tag/IdeaTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2024 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.tag; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Tag; + +@Target({TYPE, METHOD}) +@Retention(RUNTIME) +@Tag("idea") +public @interface IdeaTest {} diff --git a/testlib/src/main/resources/java/idea/full.clean.java b/testlib/src/main/resources/java/idea/full.clean.java new file mode 100644 index 0000000000..84d76d4650 --- /dev/null +++ b/testlib/src/main/resources/java/idea/full.clean.java @@ -0,0 +1,14 @@ +package com.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} + diff --git a/testlib/src/main/resources/java/idea/full.dirty.java b/testlib/src/main/resources/java/idea/full.dirty.java new file mode 100644 index 0000000000..83923130e6 --- /dev/null +++ b/testlib/src/main/resources/java/idea/full.dirty.java @@ -0,0 +1,22 @@ +package com.example; + +import org.springframework.boot.SpringApplication; + + + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@ + +SpringBootApplication +public + + +class Application{ + + public static void main( String[] args) { + SpringApplication. run(Application.class, args); + } + +} + diff --git a/testlib/src/test/java/com/diffplug/spotless/java/IdeaStepTest.java b/testlib/src/test/java/com/diffplug/spotless/java/IdeaStepTest.java new file mode 100644 index 0000000000..98de28627a --- /dev/null +++ b/testlib/src/test/java/com/diffplug/spotless/java/IdeaStepTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 2024 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.java; + +import java.io.File; +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.diffplug.common.io.Files; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.ResourceHarness; +import com.diffplug.spotless.tag.IdeaTest; + +@IdeaTest +class IdeaStepTest extends ResourceHarness { + + @Test + void name() throws Exception { + FormatterStep step = IdeaStep.create(true, "idea"); + + String name = step.getName(); + + Assertions.assertEquals("IDEA", name); + } + + @Test + void notFormattings() throws Exception { + File cleanFile = newFile("clean.java"); + String cleanJava = ResourceHarness.getTestResource("java/idea/full.clean.java"); + Files.write(cleanJava, cleanFile, StandardCharsets.UTF_8); + FormatterStep step = IdeaStep.create(true, "idea"); + + var result = step.format(cleanJava, cleanFile); + + Assertions.assertEquals(cleanJava, result, + "formatting was applied to clean file"); + } + + @Test + void formattings() throws Exception { + File dirtyFile = newFile("dirty.java"); + String dirtyJava = ResourceHarness.getTestResource("java/idea/full.dirty.java"); + Files.write(dirtyJava, dirtyFile, StandardCharsets.UTF_8); + FormatterStep step = IdeaStep.create(true, "idea"); + + var result = step.format(dirtyJava, dirtyFile); + + Assertions.assertNotEquals(dirtyJava, result, + "files were not changed after reformat"); + } + + @Test + void formattingsWorkWithDefaultParameters() throws Exception { + File dirtyFile = newFile("dirty.java"); + String dirtyJava = ResourceHarness.getTestResource("java/idea/full.dirty.java"); + Files.write(dirtyJava, dirtyFile, StandardCharsets.UTF_8); + FormatterStep step = IdeaStep.create(); + + var result = step.format(dirtyJava, dirtyFile); + + Assertions.assertNotEquals(dirtyJava, result, + "files were not changed after reformat"); + } + + @Test + void formattingsWithOutDefaultDoesNothing() throws Exception { + File dirtyFile = newFile("dirty.java"); + String dirtyJava = ResourceHarness.getTestResource("java/idea/full.dirty.java"); + Files.write(dirtyJava, dirtyFile, StandardCharsets.UTF_8); + FormatterStep step = IdeaStep.create(false, "idea"); + + var result = step.format(dirtyJava, dirtyFile); + + Assertions.assertEquals(dirtyJava, result, + "files were changed after reformat"); + } + + @Test + void configureFile() throws Exception { + File cleanFile = newFile("clean.java"); + String cleanJava = ResourceHarness.getTestResource("java/idea/full.clean.java"); + Files.write(cleanJava, cleanFile, StandardCharsets.UTF_8); + FormatterStep step = IdeaStep.create(true, "idea"); + + var result = step.format(cleanJava, cleanFile); + + Assertions.assertEquals(cleanJava, result, + "formatting was applied to clean file"); + } +}