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 eec6274c8d..e78e9dec6f 100644
--- a/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java
+++ b/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java
@@ -20,6 +20,7 @@
import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.file.Files;
+import java.time.YearMonth;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -35,6 +36,11 @@ public final class LicenseHeaderStep implements Serializable {
private final String licenseHeader;
private final Pattern delimiterPattern;
+ private Pattern yearMatcherPattern;
+ private boolean hasYearToken;
+ private String licenseHeaderBeforeYEARToken;
+ private String licenseHeaderAfterYEARToken;
+ private 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) {
@@ -74,6 +80,14 @@ private LicenseHeaderStep(String licenseHeader, String delimiter) {
}
this.licenseHeader = licenseHeader;
this.delimiterPattern = Pattern.compile('^' + delimiter, Pattern.UNIX_LINES | Pattern.MULTILINE);
+ hasYearToken = licenseHeader.contains("$YEAR");
+ if (hasYearToken) {
+ int yearTokenIndex = licenseHeader.indexOf("$YEAR");
+ licenseHeaderBeforeYEARToken = licenseHeader.substring(0, yearTokenIndex);
+ licenseHeaderAfterYEARToken = licenseHeader.substring(yearTokenIndex + 5, licenseHeader.length());
+ licenseHeaderWithYEARTokenReplaced = licenseHeader.replace("$YEAR", String.valueOf(YearMonth.now().getYear()));
+ this.yearMatcherPattern = Pattern.compile("[0-9]{4}(-[0-9]{4})?");
+ }
}
/** Reads the license file from the given file. */
@@ -87,7 +101,14 @@ public String format(String raw) {
if (!matcher.find()) {
throw new IllegalArgumentException("Unable to find delimiter regex " + delimiterPattern);
} else {
- if (matcher.start() == licenseHeader.length() && raw.startsWith(licenseHeader)) {
+ if (hasYearToken) {
+ if (matchesLicenseWithYearToken(raw, matcher)) {
+ //that means we have the license like `licenseHeaderBeforeYEARToken 1990-2015 licenseHeaderAfterYEARToken`
+ return raw;
+ } else {
+ return licenseHeaderWithYEARTokenReplaced + raw.substring(matcher.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;
@@ -97,4 +118,10 @@ public String format(String raw) {
}
}
}
+
+ private boolean matchesLicenseWithYearToken(String raw, Matcher matcher) {
+ int startOfTheSecondPart = raw.indexOf(licenseHeaderAfterYEARToken);
+ return (raw.startsWith(licenseHeaderBeforeYEARToken) && startOfTheSecondPart + licenseHeaderAfterYEARToken.length() == matcher.start())
+ && yearMatcherPattern.matcher(raw.substring(licenseHeaderBeforeYEARToken.length(), startOfTheSecondPart)).matches();
+ }
}
diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md
index c921e86228..c3c1a5b4b2 100644
--- a/plugin-gradle/README.md
+++ b/plugin-gradle/README.md
@@ -273,6 +273,32 @@ spotless {
}
```
+
+
+## License header options
+
+The license header can contains a `$YEAR` variable that will be replaced by the current year.
+
+For example:
+```
+/* Licensed under Apache-2.0 $YEAR. */
+```
+will produce
+```
+/* Licensed under Apache-2.0 2017. */
+```
+if build is launched in 2017
+
+
+The step will change the license according to the following rules
+* It replace the license using the current year when
+ * The license is missing
+ * The license is not formatted correctly
+* It will *not* replace the license when
+ * The year variable is already present and is a single year, e.g. `/* Licensed under Apache-2.0 1990. */`
+ * The year variable is already present and is a year span, e.g. `/* Licensed under Apache-2.0 1990-2003. */`
+
+
## Custom rules
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 5be629bc0f..61c42f6bdc 100644
--- a/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java
+++ b/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java
@@ -16,8 +16,11 @@
package com.diffplug.spotless.generic;
import java.io.File;
+import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.time.YearMonth;
+import com.diffplug.spotless.StepHarness;
import org.junit.Assert;
import org.junit.Test;
@@ -29,6 +32,11 @@ public class LicenseHeaderStepTest extends ResourceHarness {
private static final String KEY_LICENSE = "license/TestLicense";
private static final String KEY_FILE_NOTAPPLIED = "license/MissingLicense.test";
private static final String KEY_FILE_APPLIED = "license/HasLicense.test";
+ private static final String KEY_LICENSE_WITH_YEAR = "license/TestLicencseWithYear";
+ private static final String KEY_FILE_WITHOUT_LICENSE = "license/MissLicenseWithYear.test";
+ private static final String KEY_FILE_WITH_PREVIOUS_YEAR = "license/LicenseWithPreviousYear.test";
+ private static final String KEY_FILE_WITH_PREVIOUS_YEARS = "license/LicenseWithPreviousYears.test";
+ private static final String KEY_FILE_WITH_CURRENT_YEAR = "license/LicenseWithYear.test";
// 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
@@ -46,6 +54,27 @@ public void fromFile() throws Throwable {
assertOnResources(step, KEY_FILE_NOTAPPLIED, KEY_FILE_APPLIED);
}
+ @Test
+ public void should_apply_license_containing_YEAR_variable() throws Throwable {
+ FormatterStep step = LicenseHeaderStep.createFromFile(createTestFile(KEY_LICENSE_WITH_YEAR), StandardCharsets.UTF_8, LICENSE_HEADER_DELIMITER);
+
+
+ StepHarness.forStep(step)
+ .test(getTestResource(KEY_FILE_WITHOUT_LICENSE), getFileContentWithYEAR(currentYear()))
+ .testUnaffected(getFileContentWithYEAR(currentYear()))
+ .testUnaffected(getFileContentWithYEAR("2003"))
+ .testUnaffected(getFileContentWithYEAR("1990-2015"))
+ .test(getFileContentWithYEAR("not a year"), getFileContentWithYEAR(currentYear()));
+ }
+
+ private String getFileContentWithYEAR(String year) throws IOException {
+ return getTestResource(KEY_FILE_WITH_CURRENT_YEAR).replace("__YEAR_to_replace_in_tests__", year);
+ }
+
+ private String currentYear() {
+ return String.valueOf(YearMonth.now().getYear());
+ }
+
@Test
public void efficient() throws Throwable {
FormatterStep step = LicenseHeaderStep.createFromHeader("LicenseHeader\n", "contentstart");
diff --git a/testlib/src/test/resources/license/LicenseWithYear.test b/testlib/src/test/resources/license/LicenseWithYear.test
new file mode 100644
index 0000000000..6a4905bf08
--- /dev/null
+++ b/testlib/src/test/resources/license/LicenseWithYear.test
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) __YEAR_to_replace_in_tests__. ACME corp.
+ * This library is free software; you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Foundation
+ * version 2.1 of the License.
+ * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ * You should have received a copy of the GNU Lesser General Public License along with this
+ * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
+ * Floor, Boston, MA 02110-1301, USA.
+ **/
+package com.acme;
+
+import java.util.function.Function;
+
+
+public class Java8Test {
+ public void doStuff() throws Exception {
+ Function example = Integer::parseInt;
+ example.andThen(val -> {
+ return val + 2;
+ } );
+ SimpleEnum val = SimpleEnum.A;
+ switch (val) {
+ case A:
+ break;
+ case B:
+ break;
+ case C:
+ break;
+ default:
+ throw new Exception();
+ }
+ }
+
+ public enum SimpleEnum {
+ A, B, C;
+ }
+}
diff --git a/testlib/src/test/resources/license/MissLicenseWithYear.test b/testlib/src/test/resources/license/MissLicenseWithYear.test
new file mode 100644
index 0000000000..297c16a214
--- /dev/null
+++ b/testlib/src/test/resources/license/MissLicenseWithYear.test
@@ -0,0 +1,28 @@
+package com.acme;
+
+import java.util.function.Function;
+
+
+public class Java8Test {
+ public void doStuff() throws Exception {
+ Function example = Integer::parseInt;
+ example.andThen(val -> {
+ return val + 2;
+ } );
+ SimpleEnum val = SimpleEnum.A;
+ switch (val) {
+ case A:
+ break;
+ case B:
+ break;
+ case C:
+ break;
+ default:
+ throw new Exception();
+ }
+ }
+
+ public enum SimpleEnum {
+ A, B, C;
+ }
+}
diff --git a/testlib/src/test/resources/license/TestLicencseWithYear b/testlib/src/test/resources/license/TestLicencseWithYear
new file mode 100644
index 0000000000..39e6b98a52
--- /dev/null
+++ b/testlib/src/test/resources/license/TestLicencseWithYear
@@ -0,0 +1,12 @@
+/*
+ * Copyright (C) $YEAR. ACME corp.
+ * This library is free software; you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Foundation
+ * version 2.1 of the License.
+ * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ * You should have received a copy of the GNU Lesser General Public License along with this
+ * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
+ * Floor, Boston, MA 02110-1301, USA.
+ **/
\ No newline at end of file