diff --git a/lib/src/main/java/com/diffplug/spotless/FilterByContentPatternFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/FilterByContentPatternFormatterStep.java index d9a6fe4093..51935b07bd 100644 --- a/lib/src/main/java/com/diffplug/spotless/FilterByContentPatternFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/FilterByContentPatternFormatterStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * 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. @@ -24,10 +24,13 @@ final class FilterByContentPatternFormatterStep extends DelegateFormatterStep { final Pattern contentPattern; + final boolean formatIfMatches; - FilterByContentPatternFormatterStep(FormatterStep delegateStep, String contentPattern) { + FilterByContentPatternFormatterStep(FormatterStep delegateStep, String contentPattern, + boolean formatIfMatches) { super(delegateStep); this.contentPattern = Pattern.compile(Objects.requireNonNull(contentPattern)); + this.formatIfMatches = formatIfMatches; } @Override @@ -35,7 +38,8 @@ final class FilterByContentPatternFormatterStep extends DelegateFormatterStep { Objects.requireNonNull(raw, "raw"); Objects.requireNonNull(file, "file"); Matcher matcher = contentPattern.matcher(raw); - if (matcher.find()) { + boolean found = matcher.find(); + if (formatIfMatches == found) { return delegateStep.format(raw, file); } else { return raw; diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStep.java b/lib/src/main/java/com/diffplug/spotless/FormatterStep.java index ce09f68450..85fbeaf953 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStep.java @@ -54,7 +54,21 @@ public interface FormatterStep extends Serializable { * @return FormatterStep */ public default FormatterStep filterByContentPattern(String contentPattern) { - return new FilterByContentPatternFormatterStep(this, contentPattern); + return filterByContentPattern(contentPattern, true); + } + + /** + * Returns a new {@code FormatterStep} which, observing the value of {@code formatIfMatches}, + * will only apply, or not, its changes to files which pass the given filter. + * + * @param contentPattern + * java regular expression used to filter in or out files which content contain pattern + * @param formatIfMatches + * True to format only when {@code contentPattern} is found; false to format only when {@code contentPattern} is not found + * @return FormatterStep + */ + public default FormatterStep filterByContentPattern(String contentPattern, boolean formatIfMatches) { + return new FilterByContentPatternFormatterStep(this, contentPattern, formatIfMatches); } /** diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 744c6efce7..f351495462 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -4,6 +4,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] +### Added +* Add target option `targetExcludeIfContentContains` to exclude files by text they contain. ### Fixed * Correctly support the syntax ``` diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java index 4fa74bc2ea..1a2daa1cb9 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java @@ -161,6 +161,10 @@ public void encoding(String charset) { /** The files to be formatted = (target - targetExclude). */ protected FileCollection target, targetExclude; + /** The value from which files will be excluded if their content contain it. */ + @Nullable + protected String targetExcludeIfContentContains = null; + protected boolean isLicenseHeaderStep(FormatterStep formatterStep) { String formatterStepName = formatterStep.getName(); @@ -202,6 +206,11 @@ public void targetExclude(Object... targets) { this.targetExclude = parseTargetsIsExclude(targets, true); } + /** Excludes all files whose content contains {@code value}. */ + public void targetExcludeIfContentContains(String value) { + this.targetExcludeIfContentContains = value; + } + private FileCollection parseTargetsIsExclude(Object[] targets, boolean isExclude) { requireElementsNonNull(targets); if (targets.length == 0) { @@ -889,6 +898,9 @@ protected void setupTask(SpotlessTask task) { } else { steps = this.steps; } + if (targetExcludeIfContentContains != null) { + steps.replaceAll(formatterStep -> formatterStep.filterByContentPattern(targetExcludeIfContentContains, false)); + } task.setSteps(steps); task.setLineEndingsPolicy(getLineEndings().createPolicy(getProject().getProjectDir(), () -> totalTarget)); spotless.getRegisterDependenciesTask().hookSubprojectTask(task); diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/TargetExcludeIfContentContainsTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/TargetExcludeIfContentContainsTest.java new file mode 100644 index 0000000000..76ef341ab9 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/TargetExcludeIfContentContainsTest.java @@ -0,0 +1,141 @@ +/* + * Copyright 2020-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.gradle.spotless; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +class TargetExcludeIfContentContainsTest extends GradleIntegrationHarness { + @Test + void targetExcludeIfContentContainsWithOneValue() throws IOException { + setFile("build.gradle").toLines( + "plugins { id 'com.diffplug.spotless' }", + "spotless {", + " format 'toLower', {", + " target '**/*.md'", + " targetExcludeIfContentContains '// Generated by Mr. Roboto'", + " custom 'lowercase', { str -> str.toLowerCase() }", + " }", + "}"); + String content = "// Generated by Mr. Roboto, do not edit.\n" + + "A B C\n" + + "D E F\n" + + "G H I"; + setFile("test_generated.md").toContent(content); + setFile("test_manual.md").toLines( + "A B C", + "D E F", + "G H I"); + gradleRunner().withArguments("spotlessApply").build(); + // `test_generated` contains the excluding text so didn't change. + assertFile("test_generated.md").hasContent(content); + // `test_manual` does not so it changed. + assertFile("test_manual.md").hasLines( + "a b c", + "d e f", + "g h i"); + } + + @Test + void targetExcludeIfContentContainsWithMultipleSteps() throws IOException { + setFile("build.gradle").toLines( + "plugins { id 'com.diffplug.spotless' }", + "spotless {", + " format 'toLower', {", + " target '**/*.md'", + " targetExcludeIfContentContains '// Generated by Mr. Roboto'", + " custom 'lowercase', { str -> str.toLowerCase() }", + " licenseHeader('" + "// My CopyRights header" + "', '--')", + " }", + "}"); + String generatedContent = "// Generated by Mr. Roboto, do not edit.\n" + + "--\n" + + "public final class MyMessage {}\n"; + setFile("test_generated.md").toContent(generatedContent); + String manualContent = "// Typo in License\n" + + "--\n" + + "public final class MyMessage {\n" + + "}"; + setFile("test_manual.md").toContent(manualContent); + gradleRunner().withArguments("spotlessApply").build(); + + // `test_generated` contains the excluding text so didn't change, including the header. + assertFile("test_generated.md").hasContent(generatedContent); + // `test_manual` does not, it changed. + assertFile("test_manual.md").hasContent( + "// My CopyRights header\n" + + "--\n" + + "public final class mymessage {\n" + + "}"); + } + + @Test + void targetExcludeIfContentContainsWithMultipleValues() throws IOException { + setFile("build.gradle").toLines( + "plugins { id 'com.diffplug.spotless' }", + "spotless {", + " format 'toLower', {", + " target '**/*.md'", + " targetExcludeIfContentContains '// Generated by Mr. Roboto|// Generated by Mrs. Call'", + " custom 'lowercase', { str -> str.toLowerCase() }", + " }", + "}"); + String robotoContent = "A B C\n" + + "// Generated by Mr. Roboto, do not edit.\n" + + "D E F\n" + + "G H I"; + setFile("test_generated_roboto.md").toContent(robotoContent); + String callContent = "A B C\n" + + "D E F\n" + + "// Generated by Mrs. Call, do not edit.\n" + + "G H I"; + setFile("test_generated_call.md").toContent(callContent); + String collaborationContent = "A B C\n" + + "// Generated by Mr. Roboto, do not edit.\n" + + "D E F\n" + + "// Generated by Mrs. Call, do not edit.\n" + + "G H I"; + setFile("test_generated_collaboration.md").toContent(collaborationContent); + String intruderContent = "A B C\n" + + "// Generated by K2000, do not edit.\n" + + "D E F\n" + + "G H I"; + setFile("test_generated_intruder.md").toContent(intruderContent); + setFile("test_manual.md").toLines( + "A B C", + "D E F", + "G H I"); + gradleRunner().withArguments("spotlessApply").build(); + // Part of the excluding values so has not changed. + assertFile("test_generated_roboto.md").hasContent(robotoContent); + // Part of the excluding values so has not changed. + assertFile("test_generated_call.md").hasContent(callContent); + // Part of the excluding values so has not changed. + assertFile("test_generated_collaboration.md").hasContent(collaborationContent); + // Not part of the excluding values so has changed. + assertFile("test_generated_intruder.md").hasContent( + "a b c\n" + + "// generated by k2000, do not edit.\n" + + "d e f\n" + + "g h i"); + // `test_manual` does not, it changed. + assertFile("test_manual.md").hasLines( + "a b c", + "d e f", + "g h i"); + } +}