diff --git a/CHANGES.md b/CHANGES.md index 6e8688375f..4fd4322b2e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Changed +* Added `named` option to `licenseHeader` to support alternate license header within same format (like java) ([872](https://github.com/diffplug/spotless/issues/872)). +* Added `onlyIfContentMatches` option to `licenseHeader` to skip license header application based on source file content pattern ([#650](https://github.com/diffplug/spotless/issues/650)). * Bump jgit version ([#992](https://github.com/diffplug/spotless/pull/992)). * jgit `5.10.0.202012080955-r` -> `5.13.0.202109080827-r` diff --git a/lib/src/main/java/com/diffplug/spotless/FilterByContentPatternFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/FilterByContentPatternFormatterStep.java new file mode 100644 index 0000000000..9b39361719 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/FilterByContentPatternFormatterStep.java @@ -0,0 +1,72 @@ +/* + * Copyright 2016-2021 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; + +import java.io.File; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.Nullable; + +final class FilterByContentPatternFormatterStep implements FormatterStep { + private final FormatterStep delegateStep; + final Pattern contentPattern; + + FilterByContentPatternFormatterStep(FormatterStep delegateStep, String contentPattern) { + this.delegateStep = Objects.requireNonNull(delegateStep); + this.contentPattern = Pattern.compile(Objects.requireNonNull(contentPattern)); + } + + @Override + public String getName() { + return delegateStep.getName(); + } + + @Override + public @Nullable String format(String raw, File file) throws Exception { + Objects.requireNonNull(raw, "raw"); + Objects.requireNonNull(file, "file"); + + Matcher matcher = contentPattern.matcher(raw); + + if (matcher.find()) { + return delegateStep.format(raw, file); + } else { + return raw; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FilterByContentPatternFormatterStep that = (FilterByContentPatternFormatterStep) o; + return Objects.equals(delegateStep, that.delegateStep) && + Objects.equals(contentPattern.pattern(), that.contentPattern.pattern()); + } + + @Override + public int hashCode() { + return Objects.hash(delegateStep, contentPattern.pattern()); + } + + private static final long serialVersionUID = 1L; +} diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStep.java b/lib/src/main/java/com/diffplug/spotless/FormatterStep.java index 6675d09128..5729f676b2 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStep.java @@ -45,6 +45,18 @@ public interface FormatterStep extends Serializable { */ public @Nullable String format(String rawUnix, File file) throws Exception; + /** + * Returns a new FormatterStep which will only apply its changes + * to files which pass the given filter. + * + * @param contentPattern + * java regular expression used to filter out files which content doesn't contain pattern + * @return FormatterStep + */ + public default FormatterStep filterByContentPattern(String contentPattern) { + return new FilterByContentPatternFormatterStep(this, contentPattern); + } + /** * Returns a new FormatterStep which will only apply its changes * to files which pass the given filter. diff --git a/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java b/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java index 1e93acce31..01b1d85c78 100644 --- a/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java +++ b/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java @@ -51,35 +51,51 @@ public static LicenseHeaderStep headerDelimiter(String header, String delimiter) } public static LicenseHeaderStep headerDelimiter(ThrowingEx.Supplier headerLazy, String delimiter) { - return new LicenseHeaderStep(headerLazy, delimiter, DEFAULT_YEAR_DELIMITER, () -> YearMode.PRESERVE); + return new LicenseHeaderStep(null, null, headerLazy, delimiter, DEFAULT_YEAR_DELIMITER, () -> YearMode.PRESERVE); } + final String name; + final @Nullable String contentPattern; final ThrowingEx.Supplier headerLazy; final String delimiter; final String yearSeparator; final Supplier yearMode; - private LicenseHeaderStep(ThrowingEx.Supplier headerLazy, String delimiter, String yearSeparator, Supplier yearMode) { + private LicenseHeaderStep(String name, String contentPattern, ThrowingEx.Supplier headerLazy, String delimiter, String yearSeparator, Supplier yearMode) { + this.name = sanitizeName(name); + this.contentPattern = sanitizeContentPattern(contentPattern); this.headerLazy = Objects.requireNonNull(headerLazy); this.delimiter = Objects.requireNonNull(delimiter); this.yearSeparator = Objects.requireNonNull(yearSeparator); this.yearMode = Objects.requireNonNull(yearMode); } + public String getName() { + return name; + } + + public LicenseHeaderStep withName(String name) { + return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode); + } + + public LicenseHeaderStep withContentPattern(String contentPattern) { + return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode); + } + public LicenseHeaderStep withHeaderString(String header) { return withHeaderLazy(() -> header); } public LicenseHeaderStep withHeaderLazy(ThrowingEx.Supplier headerLazy) { - return new LicenseHeaderStep(headerLazy, delimiter, yearSeparator, yearMode); + return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode); } public LicenseHeaderStep withDelimiter(String delimiter) { - return new LicenseHeaderStep(headerLazy, delimiter, yearSeparator, yearMode); + return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode); } public LicenseHeaderStep withYearSeparator(String yearSeparator) { - return new LicenseHeaderStep(headerLazy, delimiter, yearSeparator, yearMode); + return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode); } public LicenseHeaderStep withYearMode(YearMode yearMode) { @@ -87,18 +103,20 @@ public LicenseHeaderStep withYearMode(YearMode yearMode) { } public LicenseHeaderStep withYearModeLazy(Supplier yearMode) { - return new LicenseHeaderStep(headerLazy, delimiter, yearSeparator, yearMode); + return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode); } public FormatterStep build() { + FormatterStep formatterStep = null; + if (yearMode.get() == YearMode.SET_FROM_GIT) { - return FormatterStep.createNeverUpToDateLazy(LicenseHeaderStep.name(), () -> { + formatterStep = FormatterStep.createNeverUpToDateLazy(name, () -> { boolean updateYear = false; // doesn't matter Runtime runtime = new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear); return FormatterFunc.needsFile(runtime::setLicenseHeaderYearsFromGitHistory); }); } else { - return FormatterStep.createLazy(LicenseHeaderStep.name(), () -> { + formatterStep = FormatterStep.createLazy(name, () -> { // by default, we should update the year if the user is using ratchetFrom boolean updateYear; switch (yearMode.get()) { @@ -115,19 +133,50 @@ public FormatterStep build() { return new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear); }, step -> step::format); } + + if (contentPattern == null) { + return formatterStep; + } + + return formatterStep.filterByContentPattern(contentPattern); + } + + private String sanitizeName(String name) { + if (name == null) { + return DEFAULT_NAME_PREFIX; + } + + name = name.trim(); + + if (Objects.equals(DEFAULT_NAME_PREFIX, name) || name.startsWith(DEFAULT_NAME_PREFIX)) { + return name; + } + + return DEFAULT_NAME_PREFIX + "-" + name; } - private static final String NAME = "licenseHeader"; + @Nullable + private String sanitizeContentPattern(String contentPattern) { + if (contentPattern == null) { + return contentPattern; + } + + contentPattern = contentPattern.trim(); + + if (contentPattern.isEmpty()) { + return null; + } + + return contentPattern; + } + + private static final String DEFAULT_NAME_PREFIX = LicenseHeaderStep.class.getName(); private static final String DEFAULT_YEAR_DELIMITER = "-"; private static final List YEAR_TOKENS = Arrays.asList("$YEAR", "$today.year"); private static final SerializableFileFilter UNSUPPORTED_JVM_FILES_FILTER = SerializableFileFilter.skipFilesNamed( "package-info.java", "package-info.groovy", "module-info.java"); - public static String name() { - return NAME; - } - public static String defaultYearDelimiter() { return DEFAULT_YEAR_DELIMITER; } diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 1bd4f13881..2d18683b10 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -5,6 +5,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Changed +* Added `named` option to `licenseHeader` to support alternate license header within same format (like java) ([872](https://github.com/diffplug/spotless/issues/872)). +* Added `onlyIfContentMatches` option to `licenseHeader` to skip license header application based on source file content pattern ([#650](https://github.com/diffplug/spotless/issues/650)). * Bump jgit version ([#992](https://github.com/diffplug/spotless/pull/992)). * jgit `5.10.0.202012080955-r` -> `5.13.0.202109080827-r` 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 c340a5551a..d59400bbd0 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 @@ -159,6 +159,16 @@ public void encoding(String charset) { /** The files to be formatted = (target - targetExclude). */ protected FileCollection target, targetExclude; + protected boolean isLicenseHeaderStep(FormatterStep formatterStep) { + String formatterStepName = formatterStep.getName(); + + if (formatterStepName.startsWith(LicenseHeaderStep.class.getName())) { + return true; + } + + return false; + } + /** * Sets which files should be formatted. Files to be formatted = (target - targetExclude). * @@ -410,6 +420,24 @@ public class LicenseHeaderConfig { LicenseHeaderStep builder; Boolean updateYearWithLatest = null; + public LicenseHeaderConfig named(String name) { + String existingStepName = builder.getName(); + builder = builder.withName(name); + int existingStepIdx = getExistingStepIdx(existingStepName); + if (existingStepIdx != -1) { + steps.set(existingStepIdx, createStep()); + } else { + addStep(createStep()); + } + return this; + } + + public LicenseHeaderConfig onlyIfContentMatches(String contentPattern) { + builder = builder.withContentPattern(contentPattern); + replaceStep(createStep()); + return this; + } + public LicenseHeaderConfig(LicenseHeaderStep builder) { this.builder = builder; } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java index 8ea7825f69..5e528e1b79 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2021 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -127,7 +127,7 @@ protected void setupTask(SpotlessTask task) { // LicenseHeaderStep completely blows apart package-info.java/groovy - this common-sense check // ensures that it skips both. See https://github.com/diffplug/spotless/issues/1 steps.replaceAll(step -> { - if (LicenseHeaderStep.name().equals(step.getName())) { + if (isLicenseHeaderStep(step)) { return step.filterByFile(LicenseHeaderStep.unsupportedJvmFilesFilter()); } else { return step; 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 eef0987c4e..086c521737 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 @@ -217,7 +217,7 @@ protected void setupTask(SpotlessTask task) { } steps.replaceAll(step -> { - if (LicenseHeaderStep.name().equals(step.getName())) { + if (isLicenseHeaderStep(step)) { return step.filterByFile(LicenseHeaderStep.unsupportedJvmFilesFilter()); } else { return step; diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/LicenseHeaderTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/LicenseHeaderTest.java index 6ac98e08c0..4b0784832f 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/LicenseHeaderTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/LicenseHeaderTest.java @@ -74,6 +74,26 @@ void updateYearWithLatestTrue() throws IOException { testSuiteUpdateWithLatest(true); } + @Test + void filterByContentPatternTest() throws IOException { + setLicenseStep("licenseHeader('/** $YEAR */').onlyIfContentMatches('.+Test.+').updateYearWithLatest(true)"); + testSuiteUpdateWithLatest(true); + setLicenseStep("licenseHeader('/** $YEAR */').onlyIfContentMatches('missingString').updateYearWithLatest(true)"); + setFile(TEST_JAVA).toContent("/** This license header should be preserved */\n" + CONTENT); + gradleRunner().withArguments("spotlessApply", "--stacktrace").forwardOutput().build(); + assertFile(TEST_JAVA).hasContent("/** This license header should be preserved */\n" + CONTENT); + setLicenseStep("licenseHeader('/** New License Header */').named('PrimaryHeaderLicense').onlyIfContentMatches('.+Test.+')"); + setFile(TEST_JAVA).toContent(CONTENT); + gradleRunner().withArguments("spotlessApply", "--stacktrace").forwardOutput().build(); + assertFile(TEST_JAVA).hasContent("/** New License Header */\n" + CONTENT); + String multipleLicenseHeaderConfiguration = "licenseHeader('/** Base License Header */').named('PrimaryHeaderLicense').onlyIfContentMatches('Best')\n" + + "licenseHeader('/** Alternate License Header */').named('SecondaryHeaderLicense').onlyIfContentMatches('.*Test.+')"; + setLicenseStep(multipleLicenseHeaderConfiguration); + setFile(TEST_JAVA).toContent("/** 2003 */\n" + CONTENT); + gradleRunner().withArguments("spotlessApply", "--stacktrace").forwardOutput().build(); + assertFile(TEST_JAVA).hasContent("/** Alternate License Header */\n" + CONTENT); + } + @Test void ratchetFrom() throws Exception { try (Git git = Git.init().setDirectory(rootFolder()).call()) {