Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for skipping lines before license header #1441

Merged
merged 14 commits into from
Jan 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ 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
* Added `skipLinesMatching` option to `licenseHeader` to support formats where license header cannot be immediately added to the top of the file (e.g. xml, sh). ([#1441](https://github.com/diffplug/spotless/pull/1441)).
### Fixed
* Support `ktlint` 0.48+ new rule disabling syntax ([#1456](https://github.com/diffplug/spotless/pull/1456)) fixes ([#1444](https://github.com/diffplug/spotless/issues/1444))

### Changes
* Bump the dev version of Gradle from `7.5.1` to `7.6` ([#1409](https://github.com/diffplug/spotless/pull/1409))
* We also removed the no-longer-required dependency `org.codehaus.groovy:groovy-xml`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2021 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.
Expand Down Expand Up @@ -51,7 +51,7 @@ public static LicenseHeaderStep headerDelimiter(String header, String delimiter)
}

public static LicenseHeaderStep headerDelimiter(ThrowingEx.Supplier<String> headerLazy, String delimiter) {
return new LicenseHeaderStep(null, null, headerLazy, delimiter, DEFAULT_YEAR_DELIMITER, () -> YearMode.PRESERVE);
return new LicenseHeaderStep(null, null, headerLazy, delimiter, DEFAULT_YEAR_DELIMITER, () -> YearMode.PRESERVE, null);
}

final String name;
Expand All @@ -60,50 +60,56 @@ public static LicenseHeaderStep headerDelimiter(ThrowingEx.Supplier<String> head
final String delimiter;
final String yearSeparator;
final Supplier<YearMode> yearMode;
final @Nullable String skipLinesMatching;

private LicenseHeaderStep(@Nullable String name, @Nullable String contentPattern, ThrowingEx.Supplier<String> headerLazy, String delimiter, String yearSeparator, Supplier<YearMode> yearMode) {
private LicenseHeaderStep(@Nullable String name, @Nullable String contentPattern, ThrowingEx.Supplier<String> headerLazy, String delimiter, String yearSeparator, Supplier<YearMode> yearMode, @Nullable String skipLinesMatching) {
this.name = sanitizeName(name);
this.contentPattern = sanitizeContentPattern(contentPattern);
this.contentPattern = sanitizePattern(contentPattern);
this.headerLazy = Objects.requireNonNull(headerLazy);
this.delimiter = Objects.requireNonNull(delimiter);
this.yearSeparator = Objects.requireNonNull(yearSeparator);
this.yearMode = Objects.requireNonNull(yearMode);
this.skipLinesMatching = sanitizePattern(skipLinesMatching);
}

public String getName() {
return name;
}

public LicenseHeaderStep withName(String name) {
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode);
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching);
}

public LicenseHeaderStep withContentPattern(String contentPattern) {
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode);
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching);
}

public LicenseHeaderStep withHeaderString(String header) {
return withHeaderLazy(() -> header);
}

public LicenseHeaderStep withHeaderLazy(ThrowingEx.Supplier<String> headerLazy) {
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode);
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching);
}

public LicenseHeaderStep withDelimiter(String delimiter) {
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode);
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching);
}

public LicenseHeaderStep withYearSeparator(String yearSeparator) {
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode);
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching);
}

public LicenseHeaderStep withYearMode(YearMode yearMode) {
return withYearModeLazy(() -> yearMode);
}

public LicenseHeaderStep withYearModeLazy(Supplier<YearMode> yearMode) {
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode);
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching);
}

public LicenseHeaderStep withSkipLinesMatching(@Nullable String skipLinesMatching) {
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching);
}

public FormatterStep build() {
Expand All @@ -112,7 +118,7 @@ public FormatterStep build() {
if (yearMode.get() == YearMode.SET_FROM_GIT) {
formatterStep = FormatterStep.createNeverUpToDateLazy(name, () -> {
boolean updateYear = false; // doesn't matter
Runtime runtime = new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear);
Runtime runtime = new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear, skipLinesMatching);
return FormatterFunc.needsFile(runtime::setLicenseHeaderYearsFromGitHistory);
});
} else {
Expand All @@ -130,7 +136,7 @@ public FormatterStep build() {
default:
throw new IllegalStateException(yearMode.toString());
}
return new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear);
return new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear, skipLinesMatching);
}, step -> step::format);
}

Expand All @@ -156,18 +162,18 @@ private String sanitizeName(@Nullable String name) {
}

@Nullable
private String sanitizeContentPattern(@Nullable String contentPattern) {
if (contentPattern == null) {
return contentPattern;
private String sanitizePattern(@Nullable String pattern) {
if (pattern == null) {
return pattern;
}

contentPattern = contentPattern.trim();
pattern = pattern.trim();

if (contentPattern.isEmpty()) {
if (pattern.isEmpty()) {
return null;
}

return contentPattern;
return pattern;
}

private static final String DEFAULT_NAME_PREFIX = LicenseHeaderStep.class.getName();
Expand Down Expand Up @@ -195,6 +201,7 @@ private static class Runtime implements Serializable {
private static final long serialVersionUID = 1475199492829130965L;

private final Pattern delimiterPattern;
private final @Nullable Pattern skipLinesMatching;
private final String yearSepOrFull;
private final @Nullable String yearToday;
private final @Nullable String beforeYear;
Expand All @@ -203,7 +210,7 @@ private static class Runtime implements Serializable {
private final boolean licenseHeaderWithRange;

/** The license that we'd like enforced. */
private Runtime(String licenseHeader, String delimiter, String yearSeparator, boolean updateYearWithLatest) {
private Runtime(String licenseHeader, String delimiter, String yearSeparator, boolean updateYearWithLatest, @Nullable String skipLinesMatching) {
if (delimiter.contains("\n")) {
throw new IllegalArgumentException("The delimiter must not contain any newlines.");
}
Expand All @@ -213,6 +220,7 @@ private Runtime(String licenseHeader, String delimiter, String yearSeparator, bo
licenseHeader = licenseHeader + "\n";
}
this.delimiterPattern = Pattern.compile('^' + delimiter, Pattern.UNIX_LINES | Pattern.MULTILINE);
this.skipLinesMatching = skipLinesMatching == null ? null : Pattern.compile(skipLinesMatching);

Optional<String> yearToken = getYearToken(licenseHeader);
if (yearToken.isPresent()) {
Expand Down Expand Up @@ -254,6 +262,31 @@ private static Optional<String> getYearToken(String licenseHeader) {

/** Formats the given string. */
private String format(String raw) {
if (skipLinesMatching == null) {
return addOrUpdateLicenseHeader(raw);
} else {
String[] lines = raw.split("\n");
StringBuilder skippedLinesBuilder = new StringBuilder();
StringBuilder remainingLinesBuilder = new StringBuilder();
boolean lastMatched = true;
for (String line : lines) {
if (lastMatched) {
Matcher matcher = skipLinesMatching.matcher(line);
if (matcher.find()) {
skippedLinesBuilder.append(line).append('\n');
} else {
remainingLinesBuilder.append(line).append('\n');
lastMatched = false;
}
} else {
remainingLinesBuilder.append(line).append('\n');
}
}
return skippedLinesBuilder + addOrUpdateLicenseHeader(remainingLinesBuilder.toString());
}
}

private String addOrUpdateLicenseHeader(String raw) {
Matcher contentMatcher = delimiterPattern.matcher(raw);
if (!contentMatcher.find()) {
throw new IllegalArgumentException("Unable to find delimiter regex " + delimiterPattern);
Expand Down
2 changes: 2 additions & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`).

## [Unreleased]
### Added
* Added `skipLinesMatching` option to `licenseHeader` to support formats where license header cannot be immediately added to the top of the file (e.g. xml, sh). ([#1441](https://github.com/diffplug/spotless/pull/1441))
### Fixed
* Prevent tool configurations from being resolved outside project ([#1447](https://github.com/diffplug/spotless/pull/1447) fixes [#1215](https://github.com/diffplug/spotless/issues/1215))
* Support `ktlint` 0.48+ new rule disabling syntax ([#1456](https://github.com/diffplug/spotless/pull/1456)) fixes ([#1444](https://github.com/diffplug/spotless/issues/1444))
Expand Down
6 changes: 6 additions & 0 deletions plugin-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,12 @@ See the [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-g

If your project has not been rigorous with copyright headers, and you'd like to use git history to repair this retroactively, you can do so with `-PspotlessSetLicenseHeaderYearsFromGitHistory=true`. When run in this mode, Spotless will do an expensive search through git history for each file, and set the copyright header based on the oldest and youngest commits for that file. This is intended to be a one-off sort of thing.

### Files with fixed header lines

Some files have fixed header lines (e.g. `<?xml version="1.0" ...` in XMLs, or `#!/bin/bash` in bash scripts). Comments cannot precede these, so the license header has to come after them, too.

To define what lines to skip at the beginning of such files, fill the `skipLinesMatching` option with a regular expression that matches them (e.g. `.skipLinesMatching("^#!.+?\$")` to skip shebangs).

<a name="ratchet"></a>

## How can I enforce formatting gradually? (aka "ratchet")
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -462,6 +462,12 @@ public LicenseHeaderConfig yearSeparator(String yearSeparator) {
return this;
}

public LicenseHeaderConfig skipLinesMatching(String skipLinesMatching) {
builder = builder.withSkipLinesMatching(skipLinesMatching);
replaceStep(createStep());
return this;
}

/**
* @param updateYearWithLatest
* Will turn {@code 2004} into {@code 2004-2020}, and {@code 2004-2019} into {@code 2004-2020}
Expand Down
2 changes: 2 additions & 0 deletions plugin-maven/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).

## [Unreleased]
### Added
* Added `skipLinesMatching` option to `licenseHeader` to support formats where license header cannot be immediately added to the top of the file (e.g. xml, sh). ([#1441](https://github.com/diffplug/spotless/pull/1441))
### Fixed
* Support `ktlint` 0.48+ new rule disabling syntax ([#1456](https://github.com/diffplug/spotless/pull/1456)) fixes ([#1444](https://github.com/diffplug/spotless/issues/1444))
### Changes
Expand Down
6 changes: 6 additions & 0 deletions plugin-maven/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,12 @@ Once a file's license header has a valid year, whether it is a year (`2020`) or

If your project has not been rigorous with copyright headers, and you'd like to use git history to repair this retroactively, you can do so with `-DspotlessSetLicenseHeaderYearsFromGitHistory=true`. When run in this mode, Spotless will do an expensive search through git history for each file, and set the copyright header based on the oldest and youngest commits for that file. This is intended to be a one-off sort of thing.

### Files with fixed header lines

Some files have fixed header lines (e.g. `<?xml version="1.0" ...` in XMLs, or `#!/bin/bash` in bash scripts). Comments cannot precede these, so the license header has to come after them, too.

To define what lines to skip at the beginning of such files, fill the `skipLinesMatching` option with a regular expression that matches them (e.g. `<skipLinesMatching>^#!.+?$</skipLinesMatching>` to skip shebangs).

<a name="invisible"></a>

<a name="ratchet"></a>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2020 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.
Expand Down Expand Up @@ -37,6 +37,9 @@ public class LicenseHeader implements FormatterStepFactory {
@Parameter
private String delimiter;

@Parameter
private String skipLinesMatching;

@Override
public final FormatterStep newFormatterStep(FormatterStepConfig config) {
String delimiterString = delimiter != null ? delimiter : config.getLicenseHeaderDelimiter();
Expand All @@ -53,6 +56,7 @@ public final FormatterStep newFormatterStep(FormatterStepConfig config) {
}
return LicenseHeaderStep.headerDelimiter(() -> readFileOrContent(config), delimiterString)
.withYearMode(yearMode)
.withSkipLinesMatching(skipLinesMatching)
.build()
.filterByFile(LicenseHeaderStep.unsupportedJvmFilesFilter());
} else {
Expand Down
9 changes: 9 additions & 0 deletions testlib/src/main/resources/license/SkipLines.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">

<module name="Checker">
<module name="ThisIsNotARealCheckstyleConfigFolks">
<property name="goodAdvice" value="dontTryItAnakin"/>
<property name="adviceGiver" value="generalKenobi"/>
</module>
</module>
13 changes: 13 additions & 0 deletions testlib/src/main/resources/license/SkipLinesHasLicense.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
<!--
-- This is a fake license header.
-- All rights reserved.
-->

<module name="Checker">
<module name="ThisIsNotARealCheckstyleConfigFolks">
<property name="goodAdvice" value="dontTryItAnakin"/>
<property name="adviceGiver" value="generalKenobi"/>
</module>
</module>
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -122,6 +122,13 @@ void should_remove_header_when_empty() throws Throwable {
.test(getTestResource("license/HasLicense.test"), getTestResource("license/MissingLicense.test"));
}

@Test
void should_skip_lines_matching_predefined_pattern() throws Throwable {
StepHarness.forStep(LicenseHeaderStep.headerDelimiter("<!--\n -- This is a fake license header.\n -- All rights reserved.\n -->", "^(?!<!--|\\s+--).*$")
.withSkipLinesMatching("(?i)^(<\\?xml[^>]+>|<!doctype[^>]+>)$").build())
.testResource("license/SkipLines.test", "license/SkipLinesHasLicense.test");
}

private String licenceWithAddress() {
return "Copyright &#169; $YEAR FooBar Inc. All Rights Reserved.\n" +
" *\n" +
Expand Down