diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index 9700cdcf1c..1338d42ddb 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -4,6 +4,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added +* Add `-DspotlessIdeHook` that provides the ability to apply Spotless exclusively to a specified file. It accepts the absolute path of the file. ([#1782](https://github.com/diffplug/spotless/pull/1782)) + * BETA, subject to change until we have proven compatibility with some IDE plugins. * Added support for `google-java-format`'s `skip-javadoc-formatting` option ([#1793](https://github.com/diffplug/spotless/pull/1793)) * Added support for biome. The Rome project [was renamed to Biome](https://biomejs.dev/blog/annoucing-biome/). The configuration is still the same, but you should switch to the new `` tag and adjust diff --git a/plugin-maven/build.gradle b/plugin-maven/build.gradle index 3f46e6eba7..dcada0b9c9 100644 --- a/plugin-maven/build.gradle +++ b/plugin-maven/build.gradle @@ -39,6 +39,7 @@ dependencies { compileOnly "org.eclipse.aether:aether-api:${VER_ECLIPSE_AETHER}" implementation "com.diffplug.durian:durian-core:${VER_DURIAN}" + implementation "com.diffplug.durian:durian-io:${VER_DURIAN}" implementation "com.diffplug.durian:durian-collect:${VER_DURIAN}" implementation("org.codehaus.plexus:plexus-resources:${VER_PLEXUS_RESOURCES}") implementation "org.eclipse.jgit:org.eclipse.jgit:${VER_JGIT}" diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/IdeHook.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/IdeHook.java new file mode 100644 index 0000000000..0001046871 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/IdeHook.java @@ -0,0 +1,83 @@ +/* + * Copyright 2016-2023 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; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import com.diffplug.common.base.Errors; +import com.diffplug.common.io.ByteStreams; +import com.diffplug.spotless.Formatter; +import com.diffplug.spotless.PaddedCell; + +class IdeHook { + + private static void dumpIsClean() { + System.err.println("IS CLEAN"); + } + + //No need to check ratchet (using isClean()) as it is performed in Gradle's IDE hook, since we have already gathered the available git files from ratchet. + static void performHook(Iterable projectFiles, Formatter formatter, String path, boolean spotlessIdeHookUseStdIn, boolean spotlessIdeHookUseStdOut) { + File file = new File(path); + if (!file.isAbsolute()) { + System.err.println("Argument passed to spotlessIdeHook must be an absolute path"); + return; + } + + if (!projectContainsFile(projectFiles, file)) { + return; + } + + try { + byte[] bytes; + if (spotlessIdeHookUseStdIn) { + bytes = ByteStreams.toByteArray(System.in); + } else { + bytes = Files.readAllBytes(file.toPath()); + } + PaddedCell.DirtyState dirty = PaddedCell.calculateDirtyState(formatter, file, bytes); + if (dirty.isClean()) { + dumpIsClean(); + } else if (dirty.didNotConverge()) { + System.err.println("DID NOT CONVERGE"); + System.err.println("See details https://github.com/diffplug/spotless/blob/main/PADDEDCELL.md"); + } else { + System.err.println("IS DIRTY"); + if (spotlessIdeHookUseStdOut) { + dirty.writeCanonicalTo(System.out); + } else { + dirty.writeCanonicalTo(file); + } + } + } catch (IOException e) { + e.printStackTrace(System.err); + throw Errors.asRuntime(e); + } finally { + System.err.close(); + System.out.close(); + } + } + + private static boolean projectContainsFile(Iterable projectFiles, File file) { + for (File projectFile : projectFiles) { + if (projectFile.equals(file)) { + return true; + } + } + return false; + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java index 5ebe2885c9..07cc2cbc85 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java @@ -20,6 +20,7 @@ import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; import com.diffplug.spotless.Formatter; import com.diffplug.spotless.PaddedCell; @@ -31,8 +32,22 @@ @Mojo(name = AbstractSpotlessMojo.GOAL_APPLY, threadSafe = true) public class SpotlessApplyMojo extends AbstractSpotlessMojo { + @Parameter(property = "spotlessIdeHook") + private String spotlessIdeHook; + + @Parameter(property = "spotlessIdeHookUseStdIn") + private boolean spotlessIdeHookUseStdIn; + + @Parameter(property = "spotlessIdeHookUseStdOut") + private boolean spotlessIdeHookUseStdOut; + @Override protected void process(Iterable files, Formatter formatter, UpToDateChecker upToDateChecker) throws MojoExecutionException { + if (isIdeHook()) { + IdeHook.performHook(files, formatter, spotlessIdeHook, spotlessIdeHookUseStdIn, spotlessIdeHookUseStdOut); + return; + } + ImpactedFilesTracker counter = new ImpactedFilesTracker(); for (File file : files) { @@ -69,4 +84,8 @@ protected void process(Iterable files, Formatter formatter, UpToDateChecke getLog().debug(String.format("Spotless.%s has no target files. Examine your ``: https://github.com/diffplug/spotless/tree/main/plugin-maven#quickstart", formatter.getName())); } } + + private boolean isIdeHook() { + return !(spotlessIdeHook == null || spotlessIdeHook.isEmpty()); + } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/IdeHookTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/IdeHookTest.java new file mode 100644 index 0000000000..3c9a1a17d6 --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/IdeHookTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2016-2023 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; + +import java.io.File; +import java.io.IOException; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.ProcessRunner; + +class IdeHookTest extends MavenIntegrationHarness { + private String output, error; + private File dirty, clean, diverge, outofbounds; + + @BeforeEach + void before() throws IOException { + writePomWithFormatSteps("\n" + + " DIRTY.md\n" + + " CLEAN.md\n" + + " \n" + + " \n" + + " Greetings to Mars\n" + + " World\n" + + " Mars\n" + + " "); + + dirty = setFile("DIRTY.md").toContent("World"); + clean = setFile("CLEAN.md").toContent("Mars"); + outofbounds = setFile("OUTOFBOUNDS.md").toContent("Mars"); + ; + } + + private void runWith(String... arguments) throws IOException, InterruptedException { + ProcessRunner.Result result = mavenRunner() + .withArguments(arguments) + .runNoError(); + + this.output = result.stdOutUtf8(); + this.error = result.stdErrUtf8(); + } + + @Test + void dirty() throws IOException, InterruptedException { + runWith("spotless:apply", "--quiet", "-DspotlessIdeHook=\"" + dirty.getAbsolutePath() + "\"", "-DspotlessIdeHookUseStdOut=true"); + Assertions.assertThat(output).isEqualTo("Mars"); + Assertions.assertThat(error).startsWith("IS DIRTY"); + } + + @Test + void clean() throws IOException, InterruptedException { + runWith("spotless:apply", "--quiet", "-DspotlessIdeHook=" + clean.getAbsolutePath(), "-DspotlessIdeHookUseStdOut=true"); + Assertions.assertThat(output).isEmpty(); + Assertions.assertThat(error).startsWith("IS CLEAN"); + } + + @Test + void outofbounds() throws IOException, InterruptedException { + runWith("spotless:apply", "--quiet", "-DspotlessIdeHook=" + outofbounds.getAbsolutePath(), "-DspotlessIdeHookUseStdOut=true"); + Assertions.assertThat(output).isEmpty(); + Assertions.assertThat(error).isEmpty(); + } + + @Test + void notAbsolute() throws IOException, InterruptedException { + runWith("spotless:apply", "--quiet", "-DspotlessIdeHook=\"pom.xml\"", "-DspotlessIdeHookUseStdOut=true"); + Assertions.assertThat(output).isEmpty(); + Assertions.assertThat(error).contains("Argument passed to spotlessIdeHook must be an absolute path"); + } +}