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);
+ }
+}