Skip to content

Commit 920cdcc

Browse files
committed
Merge remote-tracking branch 'upstream/main' into feat/ktlint-editor-config
2 parents 9e7b390 + 1effefb commit 920cdcc

File tree

11 files changed

+110
-22
lines changed

11 files changed

+110
-22
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1212
## [Unreleased]
1313
### Added
1414
* Add option `editorConfigFile` for `ktLint` [#142](https://github.com/diffplug/spotless/issues/142)
15+
* 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)).
1516
### Fixed
1617
* 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))
18+
1719
### Changes
1820
* Bump the dev version of Gradle from `7.5.1` to `7.6` ([#1409](https://github.com/diffplug/spotless/pull/1409))
1921
* We also removed the no-longer-required dependency `org.codehaus.groovy:groovy-xml`

lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2021 DiffPlug
2+
* Copyright 2016-2023 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -51,7 +51,7 @@ public static LicenseHeaderStep headerDelimiter(String header, String delimiter)
5151
}
5252

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

5757
final String name;
@@ -60,50 +60,56 @@ public static LicenseHeaderStep headerDelimiter(ThrowingEx.Supplier<String> head
6060
final String delimiter;
6161
final String yearSeparator;
6262
final Supplier<YearMode> yearMode;
63+
final @Nullable String skipLinesMatching;
6364

64-
private LicenseHeaderStep(@Nullable String name, @Nullable String contentPattern, ThrowingEx.Supplier<String> headerLazy, String delimiter, String yearSeparator, Supplier<YearMode> yearMode) {
65+
private LicenseHeaderStep(@Nullable String name, @Nullable String contentPattern, ThrowingEx.Supplier<String> headerLazy, String delimiter, String yearSeparator, Supplier<YearMode> yearMode, @Nullable String skipLinesMatching) {
6566
this.name = sanitizeName(name);
66-
this.contentPattern = sanitizeContentPattern(contentPattern);
67+
this.contentPattern = sanitizePattern(contentPattern);
6768
this.headerLazy = Objects.requireNonNull(headerLazy);
6869
this.delimiter = Objects.requireNonNull(delimiter);
6970
this.yearSeparator = Objects.requireNonNull(yearSeparator);
7071
this.yearMode = Objects.requireNonNull(yearMode);
72+
this.skipLinesMatching = sanitizePattern(skipLinesMatching);
7173
}
7274

7375
public String getName() {
7476
return name;
7577
}
7678

7779
public LicenseHeaderStep withName(String name) {
78-
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode);
80+
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching);
7981
}
8082

8183
public LicenseHeaderStep withContentPattern(String contentPattern) {
82-
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode);
84+
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching);
8385
}
8486

8587
public LicenseHeaderStep withHeaderString(String header) {
8688
return withHeaderLazy(() -> header);
8789
}
8890

8991
public LicenseHeaderStep withHeaderLazy(ThrowingEx.Supplier<String> headerLazy) {
90-
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode);
92+
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching);
9193
}
9294

9395
public LicenseHeaderStep withDelimiter(String delimiter) {
94-
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode);
96+
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching);
9597
}
9698

9799
public LicenseHeaderStep withYearSeparator(String yearSeparator) {
98-
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode);
100+
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching);
99101
}
100102

101103
public LicenseHeaderStep withYearMode(YearMode yearMode) {
102104
return withYearModeLazy(() -> yearMode);
103105
}
104106

105107
public LicenseHeaderStep withYearModeLazy(Supplier<YearMode> yearMode) {
106-
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode);
108+
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching);
109+
}
110+
111+
public LicenseHeaderStep withSkipLinesMatching(@Nullable String skipLinesMatching) {
112+
return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching);
107113
}
108114

109115
public FormatterStep build() {
@@ -112,7 +118,7 @@ public FormatterStep build() {
112118
if (yearMode.get() == YearMode.SET_FROM_GIT) {
113119
formatterStep = FormatterStep.createNeverUpToDateLazy(name, () -> {
114120
boolean updateYear = false; // doesn't matter
115-
Runtime runtime = new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear);
121+
Runtime runtime = new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear, skipLinesMatching);
116122
return FormatterFunc.needsFile(runtime::setLicenseHeaderYearsFromGitHistory);
117123
});
118124
} else {
@@ -130,7 +136,7 @@ public FormatterStep build() {
130136
default:
131137
throw new IllegalStateException(yearMode.toString());
132138
}
133-
return new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear);
139+
return new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear, skipLinesMatching);
134140
}, step -> step::format);
135141
}
136142

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

158164
@Nullable
159-
private String sanitizeContentPattern(@Nullable String contentPattern) {
160-
if (contentPattern == null) {
161-
return contentPattern;
165+
private String sanitizePattern(@Nullable String pattern) {
166+
if (pattern == null) {
167+
return pattern;
162168
}
163169

164-
contentPattern = contentPattern.trim();
170+
pattern = pattern.trim();
165171

166-
if (contentPattern.isEmpty()) {
172+
if (pattern.isEmpty()) {
167173
return null;
168174
}
169175

170-
return contentPattern;
176+
return pattern;
171177
}
172178

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

197203
private final Pattern delimiterPattern;
204+
private final @Nullable Pattern skipLinesMatching;
198205
private final String yearSepOrFull;
199206
private final @Nullable String yearToday;
200207
private final @Nullable String beforeYear;
@@ -203,7 +210,7 @@ private static class Runtime implements Serializable {
203210
private final boolean licenseHeaderWithRange;
204211

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

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

255263
/** Formats the given string. */
256264
private String format(String raw) {
265+
if (skipLinesMatching == null) {
266+
return addOrUpdateLicenseHeader(raw);
267+
} else {
268+
String[] lines = raw.split("\n");
269+
StringBuilder skippedLinesBuilder = new StringBuilder();
270+
StringBuilder remainingLinesBuilder = new StringBuilder();
271+
boolean lastMatched = true;
272+
for (String line : lines) {
273+
if (lastMatched) {
274+
Matcher matcher = skipLinesMatching.matcher(line);
275+
if (matcher.find()) {
276+
skippedLinesBuilder.append(line).append('\n');
277+
} else {
278+
remainingLinesBuilder.append(line).append('\n');
279+
lastMatched = false;
280+
}
281+
} else {
282+
remainingLinesBuilder.append(line).append('\n');
283+
}
284+
}
285+
return skippedLinesBuilder + addOrUpdateLicenseHeader(remainingLinesBuilder.toString());
286+
}
287+
}
288+
289+
private String addOrUpdateLicenseHeader(String raw) {
257290
Matcher contentMatcher = delimiterPattern.matcher(raw);
258291
if (!contentMatcher.find()) {
259292
throw new IllegalArgumentException("Unable to find delimiter regex " + delimiterPattern);

plugin-gradle/CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
55
## [Unreleased]
66
### Added
77
* Add option `editorConfigFile` for `ktLint` [#142](https://github.com/diffplug/spotless/issues/142)
8+
* 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))
89
### Fixed
910
* 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))
1011
* 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))

plugin-gradle/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,12 @@ See the [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-g
851851
852852
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.
853853
854+
### Files with fixed header lines
855+
856+
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.
857+
858+
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).
859+
854860
<a name="ratchet"></a>
855861
856862
## How can I enforce formatting gradually? (aka "ratchet")

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2022 DiffPlug
2+
* Copyright 2016-2023 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -462,6 +462,12 @@ public LicenseHeaderConfig yearSeparator(String yearSeparator) {
462462
return this;
463463
}
464464

465+
public LicenseHeaderConfig skipLinesMatching(String skipLinesMatching) {
466+
builder = builder.withSkipLinesMatching(skipLinesMatching);
467+
replaceStep(createStep());
468+
return this;
469+
}
470+
465471
/**
466472
* @param updateYearWithLatest
467473
* Will turn {@code 2004} into {@code 2004-2020}, and {@code 2004-2019} into {@code 2004-2020}

plugin-maven/CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
55
## [Unreleased]
66
### Added
77
* Add option `editorConfigFile` for `ktLint` [#142](https://github.com/diffplug/spotless/issues/142)
8+
* 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))
89
### Fixed
910
* 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))
1011
### Changes

plugin-maven/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,12 @@ Once a file's license header has a valid year, whether it is a year (`2020`) or
10191019

10201020
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.
10211021

1022+
### Files with fixed header lines
1023+
1024+
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.
1025+
1026+
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).
1027+
10221028
<a name="invisible"></a>
10231029

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

plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/LicenseHeader.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 DiffPlug
2+
* Copyright 2016-2023 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -37,6 +37,9 @@ public class LicenseHeader implements FormatterStepFactory {
3737
@Parameter
3838
private String delimiter;
3939

40+
@Parameter
41+
private String skipLinesMatching;
42+
4043
@Override
4144
public final FormatterStep newFormatterStep(FormatterStepConfig config) {
4245
String delimiterString = delimiter != null ? delimiter : config.getLicenseHeaderDelimiter();
@@ -53,6 +56,7 @@ public final FormatterStep newFormatterStep(FormatterStepConfig config) {
5356
}
5457
return LicenseHeaderStep.headerDelimiter(() -> readFileOrContent(config), delimiterString)
5558
.withYearMode(yearMode)
59+
.withSkipLinesMatching(skipLinesMatching)
5660
.build()
5761
.filterByFile(LicenseHeaderStep.unsupportedJvmFilesFilter());
5862
} else {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0"?>
2+
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
3+
4+
<module name="Checker">
5+
<module name="ThisIsNotARealCheckstyleConfigFolks">
6+
<property name="goodAdvice" value="dontTryItAnakin"/>
7+
<property name="adviceGiver" value="generalKenobi"/>
8+
</module>
9+
</module>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0"?>
2+
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
3+
<!--
4+
-- This is a fake license header.
5+
-- All rights reserved.
6+
-->
7+
8+
<module name="Checker">
9+
<module name="ThisIsNotARealCheckstyleConfigFolks">
10+
<property name="goodAdvice" value="dontTryItAnakin"/>
11+
<property name="adviceGiver" value="generalKenobi"/>
12+
</module>
13+
</module>

0 commit comments

Comments
 (0)