diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index d87cc8bedc..d849a9e99a 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -4,6 +4,10 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] +### Changes +- Add support to apply alternate license header within same format ([#872](https://github.com/diffplug/spotless/issues/872)) +- Add support to skip license header application based on source file content pattern ([#650](https://github.com/diffplug/spotless/issues/650)). + ## [3.9.0] - 2026-07-27 ### Added - Add support for Java formatting via [`prince-of-space`](https://github.com/agustafson/prince-of-space) with the new `` step. ([#2991](https://github.com/diffplug/spotless/pull/2991)) diff --git a/plugin-maven/README.md b/plugin-maven/README.md index efb82242e2..eea3348b1b 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -1953,6 +1953,25 @@ Some files have fixed header lines (e.g. `^#!.+?$` to skip shebangs). +### Skip headers and multiple license headers + +Sometimes it may be necessary to disable a license rule for specific files, or to maintain dual licenses. + +To define alternate replacements: + +```xml + + PrimaryHeaderLicense + /** Base License Header */ + Best + + + SecondaryHeaderLicense + /** Alternate License Header */ + .*Test.+ + +``` + diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/LicenseHeader.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/LicenseHeader.java index 617dfe328a..637e34ec58 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/LicenseHeader.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/LicenseHeader.java @@ -28,6 +28,12 @@ public class LicenseHeader implements FormatterStepFactory { + @Parameter + private String name; + + @Parameter + private String onlyIfContentMatches; + @Parameter private String file; @@ -57,12 +63,17 @@ public final FormatterStep newFormatterStep(FormatterStepConfig config) { boolean updateYear = config.getRatchetFrom().isPresent(); yearMode = updateYear ? YearMode.UPDATE_TO_TODAY : YearMode.PRESERVE; } - return LicenseHeaderStep.headerDelimiter(() -> readFileOrContent(config), delimiterString) - .withYearMode(yearMode) - .withSkipLinesMatching(skipLinesMatching) - .withYearStingFormat(yearStrFmt) - .build() - .filterByFile(LicenseHeaderStep.unsupportedJvmFilesFilter()); + LicenseHeaderStep builder = LicenseHeaderStep.headerDelimiter(() -> readFileOrContent(config), delimiterString) + .withYearMode(yearMode) + .withSkipLinesMatching(skipLinesMatching) + .withYearStingFormat(yearStrFmt); + if (name != null) { + builder = builder.withName(name); + } + if (onlyIfContentMatches != null) { + builder = builder.withContentPattern(onlyIfContentMatches); + } + return builder.build().filterByFile(LicenseHeaderStep.unsupportedJvmFilesFilter()); } else { throw new IllegalArgumentException("Must specify exactly one of 'file' or 'content'."); } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/generic/LicenseHeaderTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/generic/LicenseHeaderTest.java index 92d74bc659..09982775a9 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/generic/LicenseHeaderTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/generic/LicenseHeaderTest.java @@ -22,6 +22,8 @@ class LicenseHeaderTest extends MavenIntegrationHarness { private static final String KEY_LICENSE = "license/TestLicense"; private static final String KOTLIN_LICENSE_HEADER = "// Hello, I'm Kotlin license header"; + private static final String TEST_JAVA = "src/main/java/pkg/Test.java"; + private static final String CONTENT = "package pkg;\npublic class Test {}"; @Test void fromFileJava() throws Exception { @@ -33,6 +35,64 @@ void fromFileJava() throws Exception { runTest(); } + /** + * When {@code onlyIfContentMatches} matches the file content, the license header is applied. + */ + @Test + void onlyIfContentMatchesAppliesWhenPatternMatches() throws Exception { + writePomWithJavaSteps( + "", + " /** New License Header */", + " .+Test.+", + ""); + + setFile(TEST_JAVA).toContent(CONTENT); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(TEST_JAVA).hasContent("/** New License Header */\n" + CONTENT); + } + + /** + * When {@code onlyIfContentMatches} does not match the file content, the existing + * content (including any existing header) is left unchanged. + */ + @Test + void onlyIfContentMatchesSkipsWhenPatternDoesNotMatch() throws Exception { + writePomWithJavaSteps( + "", + " /** Should Not Be Applied */", + " missingString", + ""); + + String existing = "/** This license header should be preserved */\n" + CONTENT; + setFile(TEST_JAVA).toContent(existing); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(TEST_JAVA).hasContent(existing); + } + + /** + * Multiple named license-header steps with different {@code onlyIfContentMatches} + * patterns: the step whose pattern matches is applied (parity with Gradle + * {@code filterByContentPatternTest}). + */ + @Test + void multipleNamedLicenseHeadersSelectByContentPattern() throws Exception { + writePomWithJavaSteps( + "", + " PrimaryHeaderLicense", + " /** Base License Header */", + " Best", + "", + "", + " SecondaryHeaderLicense", + " /** Alternate License Header */", + " .*Test.+", + ""); + + setFile(TEST_JAVA).toContent("/** 2003 */\n" + CONTENT); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(TEST_JAVA).hasContent("/** Alternate License Header */\n" + CONTENT); + } + @Test void fromContentCpp() throws Exception { String cppLicense = "//my license";