diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 908ab1048a..9c760b6f49 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -3,6 +3,7 @@ ### Version 3.17.0-SNAPSHOT - TBD ([javadoc](https://diffplug.github.io/spotless/javadoc/snapshot/), [snapshot](https://oss.sonatype.org/content/repositories/snapshots/com/diffplug/spotless/spotless-plugin-gradle/)) * Updated default eclipse-jdt from 4.7.3a to 4.9.0 ([#316](https://github.com/diffplug/spotless/pull/316)). New version addresses enum-tab formatting bug in 4.8 ([#314](https://github.com/diffplug/spotless/issues/314)). +* Added `-spotlessFiles` switch to allow targeting specific files ([#322](https://github.com/diffplug/spotless/pull/322)) ### Version 3.16.0 - October 30th 2018 ([javadoc](https://diffplug.github.io/spotless/javadoc/spotless-plugin-gradle/3.16.0/), [jcenter](https://bintray.com/diffplug/opensource/spotless-plugin-gradle/3.16.0)) diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index c7626d075f..b5cab576df 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -659,6 +659,16 @@ Note that `enforceCheck` is a global property which affects all formats (outside +## Can I apply Spotless to specific files? + +You can target specific files by setting the `spotlessFiles` project property to a comma-separated list of file patterns: + +``` +cmd> gradlew spotlessApply -PspotlessFiles=my/file/pattern.java,more/generic/.*-pattern.java +``` + +The patterns are matched using `String#matches(String)` against the absolute file path. + ## Example configurations (from real-world projects) Spotless is hosted on jcenter and at plugins.gradle.org. [Go here](https://plugins.gradle.org/plugin/com.diffplug.gradle.spotless) if you're not sure how to import the plugin. diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPlugin.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPlugin.java index c4f00ef547..31cede4d52 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPlugin.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPlugin.java @@ -37,6 +37,7 @@ public class SpotlessPlugin implements Plugin { private static final String TASK_GROUP = "Verification"; private static final String CHECK_DESCRIPTION = "Checks that sourcecode satisfies formatting steps."; private static final String APPLY_DESCRIPTION = "Applies code formatting steps to sourcecode in-place."; + private static final String FILES_PROPERTY = "spotlessFiles"; @Override public void apply(Project project) { @@ -63,12 +64,20 @@ void createTasks(Project project) { Task rootApplyTask = project.task(EXTENSION + APPLY); rootApplyTask.setGroup(TASK_GROUP); rootApplyTask.setDescription(APPLY_DESCRIPTION); + String filePatterns; + if (project.hasProperty(FILES_PROPERTY) && project.property(FILES_PROPERTY) instanceof String) { + filePatterns = (String) project.property(FILES_PROPERTY); + } else { + // needs to be non-null since it is an @Input property of the task + filePatterns = ""; + } spotlessExtension.formats.forEach((key, value) -> { // create the task that does the work String taskName = EXTENSION + capitalize(key); SpotlessTask spotlessTask = project.getTasks().create(taskName, SpotlessTask.class); value.setupTask(spotlessTask); + spotlessTask.setFilePatterns(filePatterns); // create the check and apply control tasks Task checkTask = project.getTasks().create(taskName + CHECK); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java index 7201af4a6f..0fe5c5a4d0 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java @@ -23,10 +23,14 @@ import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; @@ -82,6 +86,17 @@ public void setPaddedCell(boolean paddedCell) { this.paddedCell = paddedCell; } + protected String filePatterns = ""; + + @Input + public String getFilePatterns() { + return filePatterns; + } + + public void setFilePatterns(String filePatterns) { + this.filePatterns = Objects.requireNonNull(filePatterns); + } + protected FormatExceptionPolicy exceptionPolicy = new FormatExceptionPolicyStrict(); public void setExceptionPolicy(FormatExceptionPolicy exceptionPolicy) { @@ -160,6 +175,46 @@ public void performAction(IncrementalTaskInputs inputs) throws Exception { throw new GradleException("Don't call " + getName() + " directly, call " + getName() + SpotlessPlugin.CHECK + " or " + getName() + SpotlessPlugin.APPLY); } + Predicate shouldInclude; + if (this.filePatterns.isEmpty()) { + shouldInclude = file -> true; + } else { + // a list of files has been passed in via project property + final String[] includePatterns = this.filePatterns.split(","); + final List compiledIncludePatterns = Arrays.stream(includePatterns) + .map(Pattern::compile) + .collect(Collectors.toList()); + shouldInclude = file -> compiledIncludePatterns + .stream() + .anyMatch(filePattern -> filePattern.matcher(file.getAbsolutePath()) + .matches()); + } + // find the outOfDate files + List outOfDate = new ArrayList<>(); + inputs.outOfDate(inputDetails -> { + File file = inputDetails.getFile(); + if (shouldInclude.test(file) && file.isFile() && !file.equals(getCacheFile())) { + outOfDate.add(file); + } + }); + // load the files that were changed by the last run + // because it's possible the user changed them back to their + // unformatted form, so we need to treat them as dirty + // (see bug #144) + if (getCacheFile().exists()) { + LastApply lastApply = SerializableMisc.fromFile(LastApply.class, getCacheFile()); + for (File file : lastApply.changedFiles) { + if (shouldInclude.test(file) && !outOfDate.contains(file) && file.exists() && Iterables.contains(target, file)) { + outOfDate.add(file); + } + } + } + + if (outOfDate.isEmpty()) { + // no work to do + return; + } + // create the formatter try (Formatter formatter = Formatter.builder() .lineEndingsPolicy(lineEndingsPolicy) @@ -168,26 +223,6 @@ public void performAction(IncrementalTaskInputs inputs) throws Exception { .steps(steps) .exceptionPolicy(exceptionPolicy) .build()) { - // find the outOfDate files - List outOfDate = new ArrayList<>(); - inputs.outOfDate(inputDetails -> { - File file = inputDetails.getFile(); - if (file.isFile() && !file.equals(getCacheFile())) { - outOfDate.add(file); - } - }); - // load the files that were changed by the last run - // because it's possible the user changed them back to their - // unformatted form, so we need to treat them as dirty - // (see bug #144) - if (getCacheFile().exists()) { - LastApply lastApply = SerializableMisc.fromFile(LastApply.class, getCacheFile()); - for (File file : lastApply.changedFiles) { - if (!outOfDate.contains(file) && file.exists() && Iterables.contains(target, file)) { - outOfDate.add(file); - } - } - } if (apply) { List changedFiles = applyAnyChanged(formatter, outOfDate); diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SpecificFilesTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SpecificFilesTest.java new file mode 100644 index 0000000000..9357802a6e --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SpecificFilesTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2016 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.Test; + +public class SpecificFilesTest extends GradleIntegrationTest { + private String testFile(int number, boolean absolute) throws IOException { + String rel = "src/main/java/test" + number + ".java"; + if (absolute) { + return rootFolder() + "/" + rel; + } else { + return rel; + } + } + + private String testFile(int number) throws IOException { + return testFile(number, false); + } + + private String fixture(boolean formatted) { + return "java/googlejavaformat/JavaCode" + (formatted ? "F" : "Unf") + "ormatted.test"; + } + + private void integration(String patterns, boolean firstFormatted, boolean secondFormatted, boolean thirdFormatted) + throws IOException { + + setFile("build.gradle").toLines( + "buildscript { repositories { mavenCentral() } }", + "plugins {", + " id 'com.diffplug.gradle.spotless'", + "}", + "apply plugin: 'java'", + "spotless {", + " java {", + " googleJavaFormat('1.2')", + " }", + "}"); + + setFile(testFile(1)).toResource(fixture(false)); + setFile(testFile(2)).toResource(fixture(false)); + setFile(testFile(3)).toResource(fixture(false)); + + gradleRunner() + .withArguments("spotlessApply", "-PspotlessFiles=" + patterns) + .build(); + + assertFile(testFile(1)).sameAsResource(fixture(firstFormatted)); + assertFile(testFile(2)).sameAsResource(fixture(secondFormatted)); + assertFile(testFile(3)).sameAsResource(fixture(thirdFormatted)); + } + + @Test + public void singleFile() throws IOException { + integration(testFile(2, true), false, true, false); + } + + @Test + public void multiFile() throws IOException { + integration(testFile(1, true) + "," + testFile(3, true), true, false, true); + } + + @Test + public void regexp() throws IOException { + integration(".*/src/main/java/test(1|3).java", true, false, true); + } +}