Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
* `GitPrePushHookInstaller` uses a lock to run gracefully if it is called many times in parallel. ([#2570](https://github.com/diffplug/spotless/pull/2570))
### Added
* Add a `lint` mode to `ReplaceRegexStep` ([#2571](https://github.com/diffplug/spotless/pull/2571))
* `LintSuppression` now enforces unix-style paths in its `setPath` and `relativizeAsUnix` methods. ([#2629](https://github.com/diffplug/spotless/pull/2629))

## [3.3.1] - 2025-07-21
### Fixed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2024 DiffPlug
* Copyright 2016-2025 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 @@ -292,7 +292,7 @@ private static Map.Entry<Integer, String> diffWhitespaceLineEndings(String dirty
}

private static int getLineOfFirstDifference(EditList edits) {
return edits.stream().mapToInt(Edit::getBeginA).min().getAsInt();
return edits.stream().mapToInt(Edit::getBeginA).min().orElse(0);
}

private static final CharMatcher NEWLINE_MATCHER = CharMatcher.is('\n');
Expand Down
24 changes: 23 additions & 1 deletion lib/src/main/java/com/diffplug/spotless/LintSuppression.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 DiffPlug
* Copyright 2024-2025 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,8 +15,11 @@
*/
package com.diffplug.spotless;

import java.io.File;
import java.util.Objects;

import javax.annotation.Nullable;

public class LintSuppression implements java.io.Serializable {
private static final long serialVersionUID = 1L;

Expand All @@ -30,6 +33,9 @@ public String getPath() {
}

public void setPath(String path) {
if (path.indexOf('\\') != -1) {
throw new IllegalArgumentException("Path must use only unix style path separator `/`, this was " + path);
}
this.path = Objects.requireNonNull(path);
}

Expand Down Expand Up @@ -90,4 +96,20 @@ public String toString() {
", code='" + shortCode + '\'' +
'}';
}

/**
* Returns the relative path between root and dest, or null if dest is not a
* child of root. Guaranteed to only have unix-separators.
*/
public static @Nullable String relativizeAsUnix(File root, File dest) {
String rootPath = root.getAbsolutePath();
String destPath = dest.getAbsolutePath();
if (!destPath.startsWith(rootPath)) {
return null;
} else {
String relativized = destPath.substring(rootPath.length());
String unixified = relativized.replace('\\', '/');
return unixified.startsWith("/") ? unixified.substring(1) : unixified;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,12 @@ public List<Lint> lint(String raw, File file) {
var matcher = regex.matcher(raw);
while (matcher.find()) {
int line = 1 + (int) raw.codePoints().limit(matcher.start()).filter(c -> c == '\n').count();
lints.add(Lint.atLine(line, matcher.group(0), lintDetail));
String errorCode = matcher.group(0).trim();
int firstNewline = errorCode.indexOf("\n");
if (firstNewline != -1) {
errorCode = errorCode.substring(0, firstNewline);
}
lints.add(Lint.atLine(line, errorCode, lintDetail));
}
return lints;
}
Expand Down
2 changes: 1 addition & 1 deletion plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
### Changed
* **BREAKING** Bump the required Gradle to `7.3` and required Java to `17`. ([#2375](https://github.com/diffplug/spotless/issues/2375), [#2540](https://github.com/diffplug/spotless/pull/2540))
* **BREAKING** `spotlessInstallGitPrePushHook` task is now installed only on the root project. ([#2570](https://github.com/diffplug/spotless/pull/2570))
* **BREAKING** `LintSuppression` now enforces unix-style paths in its `setPath` method. ([#2629](https://github.com/diffplug/spotless/pull/2629))
* Running `spotlessCheck` with violations unilaterally produces the error message `Run './gradlew spotlessApply' to fix these violations`. ([#2592](https://github.com/diffplug/spotless/issues/2592))
* Bump JGit from `6.10.1` to `7.3.0` ([#2257](https://github.com/diffplug/spotless/pull/2257))
* Adds support for worktrees (fixes [#1765](https://github.com/diffplug/spotless/issues/1765))
Expand All @@ -16,7 +17,6 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
* **BREAKING** use `TrailingCommaManagementStrategy` enum instead of `manageTrailingCommas` boolean configuration option
* Bump default `ktlint` version to latest `1.5.0` -> `1.7.1`. ([#2555](https://github.com/diffplug/spotless/pull/2555))
* Bump default `palantir-java-format` version to latest `2.57.0` -> `2.71.0`.

### Fixed
* Respect system gitconfig when performing git operations ([#2404](https://github.com/diffplug/spotless/issues/2404))
* Fix `spaceBeforeSeparator` in Jackson formatter. ([#2103](https://github.com/diffplug/spotless/pull/2103))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,27 +347,12 @@ private final FileCollection parseTargetIsExclude(Object target, boolean isExclu
}

private static void relativizeIfSubdir(List<String> relativePaths, File root, File dest) {
String relativized = relativize(root, dest);
String relativized = LintSuppression.relativizeAsUnix(root, dest);
if (relativized != null) {
relativePaths.add(relativized);
}
}

/**
* Returns the relative path between root and dest, or null if dest is not a
* child of root.
*/
static @Nullable String relativize(File root, File dest) {
String rootPath = root.getAbsolutePath();
String destPath = dest.getAbsolutePath();
if (!destPath.startsWith(rootPath)) {
return null;
} else {
String relativized = destPath.substring(rootPath.length());
return relativized.startsWith("/") || relativized.startsWith("\\") ? relativized.substring(1) : relativized;
}
}

/** The steps that need to be added. */
protected final List<FormatterStep> steps = new ArrayList<>();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2024 DiffPlug
* Copyright 2016-2025 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 @@ -45,6 +45,7 @@
import com.diffplug.spotless.Formatter;
import com.diffplug.spotless.Lint;
import com.diffplug.spotless.LintState;
import com.diffplug.spotless.LintSuppression;
import com.diffplug.spotless.extra.GitRatchet;

@CacheableTask
Expand Down Expand Up @@ -101,7 +102,7 @@ public void performAction(InputChanges inputs) throws Exception {
for (FileChange fileChange : inputs.getFileChanges(target)) {
File input = fileChange.getFile();
File projectDir = getProjectDir().get().getAsFile();
String relativePath = FormatExtension.relativize(projectDir, input);
String relativePath = LintSuppression.relativizeAsUnix(projectDir, input);
if (relativePath == null) {
throw new IllegalArgumentException(StringPrinter.buildString(printer -> {
printer.println("Spotless error! All target files must be within the project dir.");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2021 DiffPlug
* Copyright 2016-2025 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,8 +15,6 @@
*/
package com.diffplug.gradle.spotless;

import static com.diffplug.gradle.spotless.FormatExtension.relativize;

import java.io.File;
import java.io.IOException;

Expand All @@ -26,6 +24,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import com.diffplug.spotless.LintSuppression;
import com.diffplug.spotless.ResourceHarness;
import com.diffplug.spotless.TestProvisioner;

Expand All @@ -51,7 +50,7 @@ void absolutePathDoesntWork() throws IOException {
void relativePathDoes() throws IOException {
File someFile = setFile("someFolder/someFile").toContent("");
File someFolder = someFile.getParentFile();
fileTree.exclude(relativize(rootFolder(), someFolder));
fileTree.exclude(LintSuppression.relativizeAsUnix(rootFolder(), someFolder));
Assertions.assertThat(fileTree).containsExactlyInAnyOrder();
}
}
2 changes: 2 additions & 0 deletions plugin-maven/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
* Bump default `palantir-java-format` version to latest `2.57.0` -> `2.71.0`.
### Fixed
* Fix `spaceBeforeSeparator` in Jackson formatter. ([#2103](https://github.com/diffplug/spotless/pull/2103))
### Added
* `<lintSupressions>` API ([#2309](https://github.com/diffplug/spotless/issues/2309))

## [2.46.1] - 2025-07-21
### Fixed
Expand Down
58 changes: 58 additions & 0 deletions plugin-maven/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1944,6 +1944,64 @@ By default, `spotless:check` is bound to the `verify` phase. You might want to
- set `-Dspotless.check.skip=true` at the command line
- set `spotless.check.skip` to `true` in the `<properties>` section of the `pom.xml`

### Suppressing lint errors

Sometimes Spotless will encounter lint errors that can't be auto-fixed. For example, if you run `mvn spotless:check`, you might see:

```
[ERROR] Unable to format file src/main/java/com/example/App.java
[ERROR] Step 'removeWildcardImports' found problem in 'App.java':
[ERROR] Do not use wildcard imports
```

To suppress these lints, you can use the `<lintSuppressions>` configuration:

```xml
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>${spotless.version}</version>
<configuration>
<java>
<removeWildcardImports/>
</java>
<lintSuppressions>
<lintSuppression>
<path>src/main/java/com/example/App.java</path>
<step>removeWildcardImports</step>
<shortCode>*</shortCode>
</lintSuppression>
</lintSuppressions>
</configuration>
</plugin>
```

Each `<lintSuppression>` can match by:
- `<path>` - file path (supports wildcards like `*`)
- `<step>` - step name (supports wildcards like `*`)
- `<shortCode>` - specific error code (supports wildcards like `*`)

You can suppress multiple patterns:

```xml
<lintSuppressions>
<!-- Suppress all wildcard import errors in legacy code -->
<lintSuppression>
<path>src/main/java/com/example/legacy/*</path>
<step>removeWildcardImports</step>
<shortCode>*</shortCode>
</lintSuppression>
<!-- Suppress all errors from a specific step -->
<lintSuppression>
<path>*</path>
<step>removeWildcardImports</step>
<shortCode>*</shortCode>
</lintSuppression>
</lintSuppressions>
```

Spotless is primarily a formatter, _not_ a linter. In our opinion, a linter is just a broken formatter. But formatters do break sometimes, and representing these failures as lints that can be suppressed is more useful than just giving up.

<a name="preview"></a>

## How do I preview what `mvn spotless:apply` will do?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
import com.diffplug.spotless.Formatter;
import com.diffplug.spotless.Jvm;
import com.diffplug.spotless.LineEnding;
import com.diffplug.spotless.LintState;
import com.diffplug.spotless.LintSuppression;
import com.diffplug.spotless.Provisioner;
import com.diffplug.spotless.generic.LicenseHeaderStep;
import com.diffplug.spotless.maven.antlr4.Antlr4;
Expand Down Expand Up @@ -213,15 +215,31 @@ public abstract class AbstractSpotlessMojo extends AbstractMojo {
@Parameter
private UpToDateChecking upToDateChecking = UpToDateChecking.enabled();

@Parameter
private List<LintSuppression> lintSuppressions = new ArrayList<>();

/**
* If set to {@code true} will also run on incremental builds (i.e. within Eclipse with m2e).
* Otherwise this goal is skipped in incremental builds and only runs on full builds.
*/
@Parameter(defaultValue = "false")
protected boolean m2eEnableForIncrementalBuild;

protected List<LintSuppression> getLintSuppressions() {
return lintSuppressions;
}

protected abstract void process(String name, Iterable<File> files, Formatter formatter, UpToDateChecker upToDateChecker) throws MojoExecutionException;

protected LintState calculateLintState(Formatter formatter, File file) throws IOException {
String relativePath = LintSuppression.relativizeAsUnix(baseDir, file);
if (relativePath == null) {
// File is not within baseDir, use absolute path as fallback
relativePath = file.getAbsolutePath();
}
return LintState.of(formatter, file).withRemovedSuppressions(formatter, relativePath, lintSuppressions);
}

private static final int MINIMUM_JRE = 11;

protected AbstractSpotlessMojo() {
Expand Down Expand Up @@ -378,7 +396,7 @@ private FormatterConfig getFormatterConfig() {
FileLocator fileLocator = getFileLocator();
final Optional<String> optionalRatchetFrom = Optional.ofNullable(this.ratchetFrom)
.filter(ratchet -> !RATCHETFROM_NONE.equals(ratchet));
return new FormatterConfig(baseDir, encoding, lineEndings, optionalRatchetFrom, provisioner, fileLocator, formatterStepFactories, Optional.ofNullable(setLicenseHeaderYearsFromGitHistory));
return new FormatterConfig(baseDir, encoding, lineEndings, optionalRatchetFrom, provisioner, fileLocator, formatterStepFactories, Optional.ofNullable(setLicenseHeaderYearsFromGitHistory), lintSuppressions);
}

private FileLocator getFileLocator() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2020 DiffPlug
* Copyright 2016-2025 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -22,6 +22,7 @@
import java.util.Optional;

import com.diffplug.spotless.LineEnding;
import com.diffplug.spotless.LintSuppression;
import com.diffplug.spotless.Provisioner;

public class FormatterConfig {
Expand All @@ -33,16 +34,18 @@ public class FormatterConfig {
private final FileLocator fileLocator;
private final List<FormatterStepFactory> globalStepFactories;
private final Optional<String> spotlessSetLicenseHeaderYearsFromGitHistory;
private final List<LintSuppression> lintSuppressions;

public FormatterConfig(File baseDir, String encoding, LineEnding lineEndings, Optional<String> ratchetFrom, Provisioner provisioner,
FileLocator fileLocator, List<FormatterStepFactory> globalStepFactories, Optional<String> spotlessSetLicenseHeaderYearsFromGitHistory) {
FileLocator fileLocator, List<FormatterStepFactory> globalStepFactories, Optional<String> spotlessSetLicenseHeaderYearsFromGitHistory, List<LintSuppression> lintSuppressions) {
this.encoding = encoding;
this.lineEndings = lineEndings;
this.ratchetFrom = ratchetFrom;
this.provisioner = provisioner;
this.fileLocator = fileLocator;
this.globalStepFactories = globalStepFactories;
this.spotlessSetLicenseHeaderYearsFromGitHistory = spotlessSetLicenseHeaderYearsFromGitHistory;
this.lintSuppressions = lintSuppressions;
}

public String getEncoding() {
Expand Down Expand Up @@ -72,4 +75,8 @@ public Optional<String> getSpotlessSetLicenseHeaderYearsFromGitHistory() {
public FileLocator getFileLocator() {
return fileLocator;
}

public List<LintSuppression> getLintSuppressions() {
return unmodifiableList(lintSuppressions);
}
}
Loading
Loading