diff --git a/CHANGES.md b/CHANGES.md index 7344279dcb..a6f494e7e0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,10 @@ This document is intended for Spotless developers. We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`). ## [Unreleased] +### Added +* `LicenseHeaderStep` now has an `updateYearWithLatest` parameter which can update copyright headers to today's date. ([#593](https://github.com/diffplug/spotless/pull/593)) + * Parsing of existing years from headers is now more lenient. + * The `LicenseHeaderStep` constructor is now public, which allows capturing its state lazily, which is helpful for setting defaults based on `ratchetFrom`. ## [1.32.0] - 2020-06-01 ### Added 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 384738af84..0a9b0cabb6 100644 --- a/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java +++ b/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java @@ -16,7 +16,6 @@ package com.diffplug.spotless.generic; import java.io.File; -import java.io.IOException; import java.io.Serializable; import java.nio.charset.Charset; import java.nio.file.Files; @@ -28,6 +27,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.annotation.Nullable; + import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.LineEnding; import com.diffplug.spotless.SerializableFileFilter; @@ -43,14 +44,6 @@ public final class LicenseHeaderStep implements Serializable { private static final SerializableFileFilter UNSUPPORTED_JVM_FILES_FILTER = SerializableFileFilter.skipFilesNamed( "package-info.java", "package-info.groovy", "module-info.java"); - private final String licenseHeader; - private final boolean hasYearToken; - private final Pattern delimiterPattern; - private final Pattern yearMatcherPattern; - private final String licenseHeaderBeforeYearToken; - private final String licenseHeaderAfterYearToken; - private final String licenseHeaderWithYearTokenReplaced; - /** Creates a FormatterStep which forces the start of each file to match a license header. */ public static FormatterStep createFromHeader(String licenseHeader, String delimiter) { return createFromHeader(licenseHeader, delimiter, DEFAULT_YEAR_DELIMITER); @@ -83,7 +76,7 @@ public static FormatterStep createFromFile(File licenseHeaderFile, Charset encod Objects.requireNonNull(delimiter, "delimiter"); Objects.requireNonNull(yearSeparator, "yearSeparator"); return FormatterStep.createLazy(LicenseHeaderStep.NAME, - () -> new LicenseHeaderStep(licenseHeaderFile, encoding, delimiter, yearSeparator), + () -> new LicenseHeaderStep(new String(Files.readAllBytes(licenseHeaderFile.toPath()), encoding), delimiter, yearSeparator), step -> step::format); } @@ -99,8 +92,19 @@ public static SerializableFileFilter unsupportedJvmFilesFilter() { return UNSUPPORTED_JVM_FILES_FILTER; } - /** The license that we'd like enforced. */ + private final Pattern delimiterPattern; + private final String yearSepOrFull; + private final @Nullable String yearToday; + private final @Nullable String beforeYear; + private final @Nullable String afterYear; + private final boolean updateYearWithLatest; + private LicenseHeaderStep(String licenseHeader, String delimiter, String yearSeparator) { + this(licenseHeader, delimiter, yearSeparator, false); + } + + /** The license that we'd like enforced. */ + public LicenseHeaderStep(String licenseHeader, String delimiter, String yearSeparator, boolean updateYearWithLatest) { if (delimiter.contains("\n")) { throw new IllegalArgumentException("The delimiter must not contain any newlines."); } @@ -109,25 +113,27 @@ private LicenseHeaderStep(String licenseHeader, String delimiter, String yearSep if (!licenseHeader.endsWith("\n")) { licenseHeader = licenseHeader + "\n"; } - this.licenseHeader = licenseHeader; this.delimiterPattern = Pattern.compile('^' + delimiter, Pattern.UNIX_LINES | Pattern.MULTILINE); Optional yearToken = getYearToken(licenseHeader); - this.hasYearToken = yearToken.isPresent(); - if (hasYearToken) { + if (yearToken.isPresent()) { + yearToday = String.valueOf(YearMonth.now().getYear()); int yearTokenIndex = licenseHeader.indexOf(yearToken.get()); - licenseHeaderBeforeYearToken = licenseHeader.substring(0, yearTokenIndex); - licenseHeaderAfterYearToken = licenseHeader.substring(yearTokenIndex + 5); - licenseHeaderWithYearTokenReplaced = licenseHeader.replace(yearToken.get(), String.valueOf(YearMonth.now().getYear())); - yearMatcherPattern = Pattern.compile("[0-9]{4}(" + Pattern.quote(yearSeparator) + "[0-9]{4})?"); + beforeYear = licenseHeader.substring(0, yearTokenIndex); + afterYear = licenseHeader.substring(yearTokenIndex + yearToken.get().length()); + yearSepOrFull = yearSeparator; + this.updateYearWithLatest = updateYearWithLatest; } else { - licenseHeaderBeforeYearToken = null; - licenseHeaderAfterYearToken = null; - licenseHeaderWithYearTokenReplaced = null; - yearMatcherPattern = null; + yearToday = null; + beforeYear = null; + afterYear = null; + this.yearSepOrFull = licenseHeader; + this.updateYearWithLatest = false; } } + private static final Pattern patternYearSingle = Pattern.compile("[0-9]{4}"); + /** * Get the first place holder token being used in the * license header for specifying the year @@ -139,39 +145,66 @@ private static Optional getYearToken(String licenseHeader) { return YEAR_TOKENS.stream().filter(licenseHeader::contains).findFirst(); } - /** Reads the license file from the given file. */ - private LicenseHeaderStep(File licenseFile, Charset encoding, String delimiter, String yearSeparator) throws IOException { - this(new String(Files.readAllBytes(licenseFile.toPath()), encoding), delimiter, yearSeparator); - } - /** Formats the given string. */ public String format(String raw) { - Matcher matcher = delimiterPattern.matcher(raw); - if (!matcher.find()) { + Matcher contentMatcher = delimiterPattern.matcher(raw); + if (!contentMatcher.find()) { throw new IllegalArgumentException("Unable to find delimiter regex " + delimiterPattern); } else { - if (hasYearToken) { - if (matchesLicenseWithYearToken(raw, matcher)) { - // that means we have the license like `licenseHeaderBeforeYearToken 1990-2015 licenseHeaderAfterYearToken` + if (yearToday == null) { + // the no year case is easy + if (contentMatcher.start() == yearSepOrFull.length() && raw.startsWith(yearSepOrFull)) { + // if no change is required, return the raw string without + // creating any other new strings for maximum performance return raw; } else { - return licenseHeaderWithYearTokenReplaced + raw.substring(matcher.start()); + // otherwise we'll have to add the header + return yearSepOrFull + raw.substring(contentMatcher.start()); } - } else if (matcher.start() == licenseHeader.length() && raw.startsWith(licenseHeader)) { - // if no change is required, return the raw string without - // creating any other new strings for maximum performance - return raw; } else { - // otherwise we'll have to add the header - return licenseHeader + raw.substring(matcher.start()); + // the yes year case is a bit harder + int beforeYearIdx = raw.indexOf(beforeYear); + int afterYearIdx = raw.indexOf(afterYear, beforeYearIdx + beforeYear.length() + 1); + + if (beforeYearIdx >= 0 && afterYearIdx >= 0 && afterYearIdx + afterYear.length() <= contentMatcher.start()) { + boolean noPadding = beforeYearIdx == 0 && afterYearIdx + afterYear.length() == contentMatcher.start(); // allows fastpath return raw + String parsedYear = raw.substring(beforeYearIdx + beforeYear.length(), afterYearIdx); + if (parsedYear.equals(yearToday)) { + // it's good as is! + return noPadding ? raw : beforeYear + yearToday + afterYear + raw.substring(contentMatcher.start()); + } else if (patternYearSingle.matcher(parsedYear).matches()) { + if (updateYearWithLatest) { + // expand from `2004` to `2004-2020` + return beforeYear + parsedYear + yearSepOrFull + yearToday + afterYear + raw.substring(contentMatcher.start()); + } else { + // it's already good as a single year + return noPadding ? raw : beforeYear + parsedYear + afterYear + raw.substring(contentMatcher.start()); + } + } else { + Matcher yearMatcher = patternYearSingle.matcher(parsedYear); + if (yearMatcher.find()) { + String firstYear = yearMatcher.group(); + String newYear; + String secondYear; + if (updateYearWithLatest) { + secondYear = firstYear.equals(yearToday) ? null : yearToday; + } else if (yearMatcher.find(yearMatcher.end() + 1)) { + secondYear = yearMatcher.group(); + } else { + secondYear = null; + } + if (secondYear == null) { + newYear = firstYear; + } else { + newYear = firstYear + yearSepOrFull + secondYear; + } + return noPadding && newYear.equals(parsedYear) ? raw : beforeYear + newYear + afterYear + raw.substring(contentMatcher.start()); + } + } + } + // at worst, we just say that it was made today + return beforeYear + yearToday + afterYear + raw.substring(contentMatcher.start()); } } } - - private boolean matchesLicenseWithYearToken(String raw, Matcher matcher) { - int startOfTheSecondPart = raw.indexOf(licenseHeaderAfterYearToken); - return startOfTheSecondPart > licenseHeaderBeforeYearToken.length() - && (raw.startsWith(licenseHeaderBeforeYearToken) && startOfTheSecondPart + licenseHeaderAfterYearToken.length() == matcher.start()) - && yearMatcherPattern.matcher(raw.substring(licenseHeaderBeforeYearToken.length(), startOfTheSecondPart)).matches(); - } } diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 5e22b36f27..111b763119 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -3,6 +3,12 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`). ## [Unreleased] +### Added +* If you use `ratchetFrom` and `licenseHeader`, the year in your license header will now be automatically kept up-to-date for changed files. For example, if the current year is 2020: ([#593](https://github.com/diffplug/spotless/pull/593)) + * `/** Copyright 2020 */` -> unchanged + * `/** Copyright 1990 */` -> `/** Copyright 1990-2020 */` + * `/** Copyright 1990-1993 */` -> `/** Copyright 1990-2020 */` + * You can disable this behavior with `licenseHeader(...).updateYearWithLatest(false)`, or you can enable it without using `ratchetFrom` by using `updateYearWithLatest(true)` (not recommended). ### Fixed * `ratchetFrom` had a bug (now fixed) such that it reported all files outside the root directory as changed. ([#594](https://github.com/diffplug/spotless/pull/594)) diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index d6972bdd2b..3fa531ca85 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -543,32 +543,15 @@ to true. ## License header options -If the license header (specified with `licenseHeader` or `licenseHeaderFile`) contains `$YEAR` or `$today.year`, then that token will be replaced with the current 4-digit year. For example, if Spotless is launched in 2017, then `/* Licensed under Apache-2.0 $YEAR. */` will produce `/* Licensed under Apache-2.0 2017. */` +If the license header (specified with `licenseHeader` or `licenseHeaderFile`) contains `$YEAR` or `$today.year`, then that token will be replaced with the current 4-digit year. For example, if Spotless is launched in 2020, then `/* Licensed under Apache-2.0 $YEAR. */` will produce `/* Licensed under Apache-2.0 2020. */` -The `licenseHeader` and `licenseHeaderFile` steps will generate license headers with automatic years according to the following rules: -* A generated license header will be updated with the current year when - * the generated license header is missing - * the generated license header is not formatted correctly -* A generated license header will _not_ be updated when - * a single year is already present, e.g. - `/* Licensed under Apache-2.0 1990. */` - * a year range is already present, e.g. - `/* Licensed under Apache-2.0 1990-2003. */` - * the `$YEAR` token is otherwise missing +Once a file's license header has a valid year, whether it is a year (`2020`) or a year range (`2017-2020`), it will not be changed. If you want the date to be updated when it changes, enable the [`ratchetFrom` functionality](#ratchet), and the year will be automatically set to today's year according to the following table (assuming the current year is 2020): -The separator for the year range defaults to the hyphen character, e.g `1990-2003`, but can be customized with the `yearSeparator` property. +* No license header -> `2020` +* `2017` -> `2017-2020` +* `2017-2019` -> `2017-2020` -For instance, the following configuration treats `1990, 2003` as a valid year range. - -```gradle -spotless { - java { - licenseHeader('Licensed under Apache-2.0 $YEAR').yearSeparator(', ') - } -} -``` - -To update the copyright notice only for changed files, use the [`ratchetFrom` functionality](#ratchet). +See the [javadoc](https://javadoc.io/static/com.diffplug.spotless/spotless-plugin-gradle/4.1.0/com/diffplug/gradle/spotless/FormatExtension.LicenseHeaderConfig.html) for a complete listing of options. 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 7e65fa5371..cfb259dedd 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 @@ -18,8 +18,10 @@ import static com.diffplug.gradle.spotless.PluginGradlePreconditions.requireElementsNonNull; import java.io.File; +import java.io.IOException; import java.io.Serializable; import java.nio.charset.Charset; +import java.nio.file.Files; import java.util.*; import java.util.stream.Stream; @@ -406,9 +408,15 @@ public void indentWithTabs() { addStep(IndentStep.Type.TAB.create()); } + /** + * Created by {@link FormatExtension#licenseHeader(String, String)} or {@link FormatExtension#licenseHeaderFile(Object, String)}. + * For most language-specific formats (e.g. java, scala, etc.) you can omit the second `delimiter` argument, because it is supplied + * automatically ({@link HasBuiltinDelimiterForLicense}). + */ public abstract class LicenseHeaderConfig { String delimiter; String yearSeparator = LicenseHeaderStep.defaultYearDelimiter(); + Boolean updateYearWithLatest = null; public LicenseHeaderConfig(String delimiter) { this.delimiter = Objects.requireNonNull(delimiter, "delimiter"); @@ -434,11 +442,29 @@ public LicenseHeaderConfig yearSeparator(String yearSeparator) { return this; } - abstract FormatterStep createStep(); - } + /** + * @param updateYearWithLatest + * Will turn `2004` into `2004-2020`, and `2004-2019` into `2004-2020` + * Default value is false, unless {@link SpotlessExtension#ratchetFrom(String)} is used, in which case default value is true. + */ + public LicenseHeaderConfig updateYearWithLatest(boolean overwriteYearLatest) { + this.updateYearWithLatest = overwriteYearLatest; + replaceStep(createStep()); + return this; + } - public class LicenseStringHeaderConfig extends LicenseHeaderConfig { + protected abstract String licenseHeader() throws IOException; + FormatterStep createStep() { + return FormatterStep.createLazy(LicenseHeaderStep.name(), () -> { + // by default, we should update the year if the user is using ratchetFrom + boolean updateYear = updateYearWithLatest == null ? FormatExtension.this.root.getRatchetFrom() != null : updateYearWithLatest; + return new LicenseHeaderStep(licenseHeader(), delimiter, yearSeparator, updateYear); + }, step -> step::format); + } + } + + private class LicenseStringHeaderConfig extends LicenseHeaderConfig { private String header; LicenseStringHeaderConfig(String delimiter, String header) { @@ -446,13 +472,13 @@ public class LicenseStringHeaderConfig extends LicenseHeaderConfig { this.header = Objects.requireNonNull(header, "header"); } - FormatterStep createStep() { - return LicenseHeaderStep.createFromHeader(header, delimiter, yearSeparator); + @Override + protected String licenseHeader() { + return header; } } - public class LicenseFileHeaderConfig extends LicenseHeaderConfig { - + private class LicenseFileHeaderConfig extends LicenseHeaderConfig { private Object headerFile; LicenseFileHeaderConfig(String delimiter, Object headerFile) { @@ -460,10 +486,10 @@ public class LicenseFileHeaderConfig extends LicenseHeaderConfig { this.headerFile = Objects.requireNonNull(headerFile, "headerFile"); } - FormatterStep createStep() { - return LicenseHeaderStep - .createFromFile(getProject().file(headerFile), getEncoding(), delimiter, - yearSeparator); + @Override + protected String licenseHeader() throws IOException { + byte[] content = Files.readAllBytes(getProject().file(headerFile).toPath()); + return new String(content, getEncoding()); } } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinExtensionTest.java index e1661e1ee2..39137fc06e 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinExtensionTest.java @@ -18,7 +18,6 @@ import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; -import java.time.YearMonth; import org.gradle.testkit.runner.BuildResult; import org.junit.Test; @@ -193,7 +192,7 @@ public void testWithNonStandardYearSeparator() throws IOException { matcher.startsWith("// License Header 2012, 2014"); }); assertFile("src/main/kotlin/test2.kt").matches(matcher -> { - matcher.startsWith(HEADER_WITH_YEAR.replace("$YEAR", String.valueOf(YearMonth.now().getYear()))); + matcher.startsWith("// License Header 2012, 2014"); }); } @@ -223,7 +222,7 @@ public void testWithNonStandardYearSeparatorKtfmt() throws IOException { matcher.startsWith("// License Header 2012, 2014"); }); assertFile("src/main/kotlin/test2.kt").matches(matcher -> { - matcher.startsWith(HEADER_WITH_YEAR.replace("$YEAR", String.valueOf(YearMonth.now().getYear()))); + matcher.startsWith("// License Header 2012, 2014"); }); } } 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 new file mode 100644 index 0000000000..efb810f761 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/LicenseHeaderTest.java @@ -0,0 +1,95 @@ +/* + * 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 java.time.YearMonth; + +import org.eclipse.jgit.api.Git; +import org.junit.Test; + +public class LicenseHeaderTest extends GradleIntegrationTest { + private static final String NOW = String.valueOf(YearMonth.now().getYear()); + + private static final String TEST_JAVA = "src/main/java/pkg/Test.java"; + private static final String CONTENT = "package pkg;\npublic class Test {}"; + + private void setLicenseStep(String licenseLine) throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.gradle.spotless'", + " id 'java'", + "}", + "spotless {", + " java {", + licenseLine, + " }", + "}"); + } + + private void assertUnchanged(String year) throws IOException { + assertTransform(year, year); + } + + private void assertTransform(String yearBefore, String yearAfter) throws IOException { + setFile(TEST_JAVA).toContent("/** " + yearBefore + " */\n" + CONTENT); + gradleRunner().withArguments("spotlessApply", "--stacktrace").build(); + assertFile(TEST_JAVA).hasContent("/** " + yearAfter + " */\n" + CONTENT); + } + + private void testSuiteUpdateWithLatest(boolean update) throws IOException { + if (update) { + assertTransform("2003", "2003-" + NOW); + assertTransform("2003-2005", "2003-" + NOW); + } else { + assertUnchanged("2003"); + assertUnchanged("2003-2005"); + } + assertUnchanged(NOW); + assertTransform("", NOW); + } + + @Test + public void normal() throws IOException { + setLicenseStep("licenseHeader('/** $YEAR */')"); + testSuiteUpdateWithLatest(false); + } + + @Test + public void updateYearWithLatestTrue() throws IOException { + setLicenseStep("licenseHeader('/** $YEAR */').updateYearWithLatest(true)"); + testSuiteUpdateWithLatest(true); + } + + @Test + public void ratchetFrom() throws Exception { + try (Git git = Git.init().setDirectory(rootFolder()).call()) { + git.commit().setMessage("First commit").call(); + } + setLicenseStep("licenseHeader('/** $YEAR */')\nratchetFrom 'HEAD'"); + testSuiteUpdateWithLatest(true); + } + + @Test + public void ratchetFromButUpdateFalse() throws Exception { + try (Git git = Git.init().setDirectory(rootFolder()).call()) { + git.commit().setMessage("First commit").call(); + } + Git.init().setDirectory(rootFolder()).call(); + setLicenseStep("licenseHeader('/** $YEAR */').updateYearWithLatest(false)\nratchetFrom 'HEAD'"); + testSuiteUpdateWithLatest(false); + } +} diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index 46047a566a..e2874d243b 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -3,6 +3,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`). ## [Unreleased] +### Fixed +* `licenseHeader` is now more robust when parsing years from existing license headers. ([#593](https://github.com/diffplug/spotless/pull/593)) ## [1.31.2] - 2020-06-01 ### Fixed diff --git a/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java b/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java index 96b05ba5f9..65c6e61989 100644 --- a/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java @@ -38,12 +38,12 @@ public class LicenseHeaderStepTest extends ResourceHarness { private static final String KEY_LICENSE_WITH_PLACEHOLDER = "license/LicenseHeaderWithPlaceholder"; private static final String KEY_FILE_WITH_LICENSE_AND_PLACEHOLDER = "license/FileWithLicenseHeaderAndPlaceholder.test"; // Licenses to test $YEAR token replacement - private static final String LICENSE_HEADER_YEAR = "This is a fake license, $YEAR. ACME corp."; + private static final String HEADER_WITH_YEAR = "This is a fake license, $YEAR. ACME corp."; // License to test $today.year token replacement - private static final String LICENSE_HEADER_YEAR_INTELLIJ_TOKEN = "This is a fake license, $today.year. ACME corp."; + private static final String HEADER_WITH_YEAR_INTELLIJ = "This is a fake license, $today.year. ACME corp."; // Special case where the characters immediately before and after the year token are the same, // start position of the second part might overlap the end position of the first part. - private static final String LICENSE_HEADER_YEAR_VARIANT = "This is a fake license. Copyright $YEAR ACME corp."; + private static final String HEADER_WITH_YEAR_VARIANT = "This is a fake license. Copyright $YEAR ACME corp."; // If this constant changes, don't forget to change the similarly-named one in // plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java as well @@ -63,81 +63,78 @@ public void fromFile() throws Throwable { @Test public void should_apply_license_containing_YEAR_token() throws Throwable { - FormatterStep step = LicenseHeaderStep.createFromFile(createLicenseWith(LICENSE_HEADER_YEAR), StandardCharsets.UTF_8, LICENSE_HEADER_DELIMITER); - - StepHarness.forStep(step) - .test(getTestResource(KEY_FILE_WITHOUT_LICENSE), fileWithLicenseContaining(LICENSE_HEADER_YEAR, currentYear())) - .testUnaffected(fileWithLicenseContaining(LICENSE_HEADER_YEAR, currentYear())) - .testUnaffected(fileWithLicenseContaining(LICENSE_HEADER_YEAR, "2003")) - .testUnaffected(fileWithLicenseContaining(LICENSE_HEADER_YEAR, "1990-2015")) - .test(fileWithLicenseContaining("Something before license.*/\n/* \n * " + LICENSE_HEADER_YEAR, "2003"), fileWithLicenseContaining(LICENSE_HEADER_YEAR, currentYear())) - .test(fileWithLicenseContaining(LICENSE_HEADER_YEAR + "\n **/\n/* Something after license.", "2003"), fileWithLicenseContaining(LICENSE_HEADER_YEAR, currentYear())) - .test(fileWithLicenseContaining(LICENSE_HEADER_YEAR, "not a year"), fileWithLicenseContaining(LICENSE_HEADER_YEAR, currentYear())); - + StepHarness.forStep(LicenseHeaderStep.createFromHeader(licenseWith(HEADER_WITH_YEAR), LICENSE_HEADER_DELIMITER)) + .test(getTestResource(KEY_FILE_WITHOUT_LICENSE), fileContainingYear(HEADER_WITH_YEAR, currentYear())) + .testUnaffected(fileContainingYear(HEADER_WITH_YEAR, currentYear())) + .testUnaffected(fileContainingYear(HEADER_WITH_YEAR, "2003")) + .testUnaffected(fileContainingYear(HEADER_WITH_YEAR, "1990-2015")) + .test(fileContainingYear("Something before license.*/\n/* \n * " + HEADER_WITH_YEAR, "2003"), fileContainingYear(HEADER_WITH_YEAR, currentYear())) + .test(fileContainingYear(HEADER_WITH_YEAR + "\n **/\n/* Something after license.", "2003"), fileContainingYear(HEADER_WITH_YEAR, "2003")) + .test(fileContainingYear(HEADER_WITH_YEAR, "not a year"), fileContainingYear(HEADER_WITH_YEAR, currentYear())); // Check with variant - step = LicenseHeaderStep.createFromFile(createLicenseWith(LICENSE_HEADER_YEAR_VARIANT), StandardCharsets.UTF_8, LICENSE_HEADER_DELIMITER); - - StepHarness.forStep(step) - .test(getTestResource(KEY_FILE_WITHOUT_LICENSE), fileWithLicenseContaining(LICENSE_HEADER_YEAR_VARIANT, currentYear())) - .testUnaffected(fileWithLicenseContaining(LICENSE_HEADER_YEAR_VARIANT, currentYear())) - .test(fileWithLicenseContaining("This is a fake license. Copyright "), fileWithLicenseContaining(LICENSE_HEADER_YEAR_VARIANT, currentYear())) - .test(fileWithLicenseContaining(" ACME corp."), fileWithLicenseContaining(LICENSE_HEADER_YEAR_VARIANT, currentYear())) - .test(fileWithLicenseContaining("This is a fake license. Copyright ACME corp."), fileWithLicenseContaining(LICENSE_HEADER_YEAR_VARIANT, currentYear())) - .test(fileWithLicenseContaining("This is a fake license. CopyrightACME corp."), fileWithLicenseContaining(LICENSE_HEADER_YEAR_VARIANT, currentYear())); + StepHarness.forStep(LicenseHeaderStep.createFromHeader(licenseWith(HEADER_WITH_YEAR_VARIANT), LICENSE_HEADER_DELIMITER)) + .test(getTestResource(KEY_FILE_WITHOUT_LICENSE), fileContainingYear(HEADER_WITH_YEAR_VARIANT, currentYear())) + .testUnaffected(fileContainingYear(HEADER_WITH_YEAR_VARIANT, currentYear())) + .test(fileContaining("This is a fake license. Copyright "), fileContainingYear(HEADER_WITH_YEAR_VARIANT, currentYear())) + .test(fileContaining(" ACME corp."), fileContainingYear(HEADER_WITH_YEAR_VARIANT, currentYear())) + .test(fileContaining("This is a fake license. Copyright ACME corp."), fileContainingYear(HEADER_WITH_YEAR_VARIANT, currentYear())) + .test(fileContaining("This is a fake license. CopyrightACME corp."), fileContainingYear(HEADER_WITH_YEAR_VARIANT, currentYear())); //Check when token is of the format $today.year - step = LicenseHeaderStep.createFromFile(createLicenseWith(LICENSE_HEADER_YEAR_INTELLIJ_TOKEN), StandardCharsets.UTF_8, LICENSE_HEADER_DELIMITER); + StepHarness.forStep(LicenseHeaderStep.createFromHeader(licenseWith(HEADER_WITH_YEAR_INTELLIJ), LICENSE_HEADER_DELIMITER)) + .test(fileContaining(HEADER_WITH_YEAR_INTELLIJ), fileWithLicenseContaining(HEADER_WITH_YEAR_INTELLIJ, currentYear(), "$today.year")); + } + private String fileWithLicenseContaining(String license, String yearContent, String token) throws IOException { + return getTestResource(KEY_FILE_WITH_LICENSE_AND_PLACEHOLDER).replace("__LICENSE_PLACEHOLDER__", license).replace(token, yearContent); + } + + @Test + public void updateYearWithLatest() throws Throwable { + LicenseHeaderStep stepState = new LicenseHeaderStep(licenseWith(HEADER_WITH_YEAR), LICENSE_HEADER_DELIMITER, "-", true); + FormatterStep step = FormatterStep.create(LicenseHeaderStep.name(), stepState, s -> s::format); StepHarness.forStep(step) - .test(fileWithLicenseContaining(LICENSE_HEADER_YEAR_INTELLIJ_TOKEN), fileWithLicenseContaining(LICENSE_HEADER_YEAR_INTELLIJ_TOKEN, currentYear(), "$today.year")); + .testUnaffected(fileContainingYear(HEADER_WITH_YEAR, currentYear())) + .test(fileContainingYear(HEADER_WITH_YEAR, "2003"), fileContainingYear(HEADER_WITH_YEAR, "2003-" + currentYear())) + .test(fileContainingYear(HEADER_WITH_YEAR, "1990-2015"), fileContainingYear(HEADER_WITH_YEAR, "1990-" + currentYear())); } @Test public void should_apply_license_containing_YEAR_token_with_non_default_year_separator() throws Throwable { - FormatterStep step = LicenseHeaderStep.createFromFile(createLicenseWith(LICENSE_HEADER_YEAR), StandardCharsets.UTF_8, LICENSE_HEADER_DELIMITER, ", "); - - StepHarness.forStep(step) - .testUnaffected(fileWithLicenseContaining(LICENSE_HEADER_YEAR, "1990, 2015")) - .test(fileWithLicenseContaining(LICENSE_HEADER_YEAR, "1990-2015"), fileWithLicenseContaining(LICENSE_HEADER_YEAR, currentYear())); + StepHarness.forStep(LicenseHeaderStep.createFromHeader(licenseWith(HEADER_WITH_YEAR), LICENSE_HEADER_DELIMITER, ", ")) + .testUnaffected(fileContainingYear(HEADER_WITH_YEAR, "1990, 2015")) + .test(fileContainingYear(HEADER_WITH_YEAR, "1990-2015"), fileContainingYear(HEADER_WITH_YEAR, "1990, 2015")); } @Test public void should_apply_license_containing_YEAR_token_with_special_character_in_year_separator() throws Throwable { - FormatterStep step = LicenseHeaderStep.createFromFile(createLicenseWith(LICENSE_HEADER_YEAR), StandardCharsets.UTF_8, LICENSE_HEADER_DELIMITER, "("); - - StepHarness.forStep(step) - .testUnaffected(fileWithLicenseContaining(LICENSE_HEADER_YEAR, "1990(2015")) - .test(fileWithLicenseContaining(LICENSE_HEADER_YEAR, "1990-2015"), fileWithLicenseContaining(LICENSE_HEADER_YEAR, currentYear())); + StepHarness.forStep(LicenseHeaderStep.createFromHeader(licenseWith(HEADER_WITH_YEAR), LICENSE_HEADER_DELIMITER, "(")) + .testUnaffected(fileContainingYear(HEADER_WITH_YEAR, "1990(2015")) + .test(fileContainingYear(HEADER_WITH_YEAR, "1990-2015"), fileContainingYear(HEADER_WITH_YEAR, "1990(2015")); } @Test public void should_apply_license_containing_YEAR_token_with_custom_separator() throws Throwable { - FormatterStep step = LicenseHeaderStep.createFromFile(createLicenseWith(LICENSE_HEADER_YEAR), StandardCharsets.UTF_8, LICENSE_HEADER_DELIMITER); - - StepHarness.forStep(step) - .test(getTestResource(KEY_FILE_WITHOUT_LICENSE), fileWithLicenseContaining(LICENSE_HEADER_YEAR, currentYear())) - .testUnaffected(fileWithLicenseContaining(LICENSE_HEADER_YEAR, currentYear())) - .testUnaffected(fileWithLicenseContaining(LICENSE_HEADER_YEAR, "2003")) - .testUnaffected(fileWithLicenseContaining(LICENSE_HEADER_YEAR, "1990-2015")) - .test(fileWithLicenseContaining(LICENSE_HEADER_YEAR, "not a year"), fileWithLicenseContaining(LICENSE_HEADER_YEAR, currentYear())); + StepHarness.forStep(LicenseHeaderStep.createFromHeader(licenseWith(HEADER_WITH_YEAR), LICENSE_HEADER_DELIMITER)) + .test(getTestResource(KEY_FILE_WITHOUT_LICENSE), fileContainingYear(HEADER_WITH_YEAR, currentYear())) + .testUnaffected(fileContainingYear(HEADER_WITH_YEAR, currentYear())) + .testUnaffected(fileContainingYear(HEADER_WITH_YEAR, "2003")) + .testUnaffected(fileContainingYear(HEADER_WITH_YEAR, "1990-2015")) + .test(fileContainingYear(HEADER_WITH_YEAR, "not a year"), fileContainingYear(HEADER_WITH_YEAR, currentYear())); } - private File createLicenseWith(String contents) throws IOException { - return createTestFile(KEY_LICENSE_WITH_PLACEHOLDER, c -> c.replace("__LICENSE_PLACEHOLDER__", contents)); + private String licenseWith(String contents) throws IOException { + return getTestResource(KEY_LICENSE_WITH_PLACEHOLDER).replace("__LICENSE_PLACEHOLDER__", contents); } - private String fileWithLicenseContaining(String license) throws IOException { - return fileWithLicenseContaining(license, ""); + private String fileContaining(String license) throws IOException { + return fileContainingYear(license, ""); } - private String fileWithLicenseContaining(String license, String yearContent) throws IOException { + private String fileContainingYear(String license, String yearContent) throws IOException { return getTestResource(KEY_FILE_WITH_LICENSE_AND_PLACEHOLDER).replace("__LICENSE_PLACEHOLDER__", license).replace("$YEAR", yearContent); } - private String fileWithLicenseContaining(String license, String yearContent, String token) throws IOException { - return getTestResource(KEY_FILE_WITH_LICENSE_AND_PLACEHOLDER).replace("__LICENSE_PLACEHOLDER__", license).replace(token, yearContent); - } - private String currentYear() { return String.valueOf(YearMonth.now().getYear()); } @@ -174,6 +171,8 @@ public void equality() { new SerializableEqualityTester() { String header = "LICENSE"; String delimiter = "package"; + String yearSep = "-"; + boolean updateYearWithLatest = false; @Override protected void setupTest(API api) { @@ -182,16 +181,23 @@ protected void setupTest(API api) { delimiter = "crate"; api.areDifferentThan(); - header = "APACHE"; + header = "APACHE $YEAR"; api.areDifferentThan(); delimiter = "package"; api.areDifferentThan(); + + yearSep = " - "; + api.areDifferentThan(); + + updateYearWithLatest = true; + api.areDifferentThan(); } @Override protected FormatterStep create() { - return LicenseHeaderStep.createFromHeader(header, delimiter); + LicenseHeaderStep stepState = new LicenseHeaderStep(header, delimiter, yearSep, updateYearWithLatest); + return FormatterStep.create(LicenseHeaderStep.name(), stepState, s -> s::format); } }.testEquals(); }