Skip to content
Open
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
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]
### Fixed
- `spotless:apply` no longer aborts on the first file with lints; it now formats all files and reports a single aggregated lint failure across every file, matching the Gradle plugin's behavior. ([#2937](https://github.com/diffplug/spotless/pull/2937))
### Changes
- Improved formatting performance by eliminating redundant per-step line-ending normalization in the core formatter loop. ([#2934](https://github.com/diffplug/spotless/pull/2934))

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2025 DiffPlug
* Copyright 2016-2026 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 @@ -55,6 +55,8 @@ protected void process(String name, Iterable<File> files, Formatter formatter, U
}

ImpactedFilesTracker counter = new ImpactedFilesTracker();
int totalLintCount = 0;
StringBuilder lintMessage = new StringBuilder();

for (File file : files) {
if (upToDateChecker.isUpToDate(file.toPath())) {
Expand All @@ -79,25 +81,22 @@ protected void process(String name, Iterable<File> files, Formatter formatter, U
counter.checkedButAlreadyClean();
}

// In apply mode, any lints should fail the build (matching Gradle behavior)
// In apply mode, any lints should fail the build (matching Gradle behavior).
// Collect lints across all files and fail once at the end, so a single
// linting file doesn't prevent the remaining files from being formatted.
if (hasUnsuppressedLints) {
int lintCount = lintState.getLintsByStep(formatter).values().stream()
.mapToInt(List::size)
.sum();
StringBuilder message = new StringBuilder();
message.append("There were ").append(lintCount).append(" lint error(s), they must be fixed or suppressed.");

// Build lint messages in Gradle format (using relative path, not just filename)
for (Map.Entry<String, List<Lint>> stepEntry : lintState.getLintsByStep(formatter).entrySet()) {
String stepName = stepEntry.getKey();
for (Lint lint : stepEntry.getValue()) {
String relativePath = LintSuppression.relativizeAsUnix(baseDir, file);
message.append("\n ").append(relativePath).append(":");
lint.addWarningMessageTo(message, stepName, true);
lintMessage.append("\n ").append(relativePath).append(":");
lint.addWarningMessageTo(lintMessage, stepName, true);
totalLintCount++;
}
}
message.append("\n Resolve these lints or suppress with `<lintSuppressions>`");
throw new MojoExecutionException(message.toString());
// don't mark a linting file as up-to-date; it must be revisited next run
continue;
}
} catch (IOException | RuntimeException e) {
throw new MojoExecutionException("Unable to format file " + file, e);
Expand All @@ -106,6 +105,14 @@ protected void process(String name, Iterable<File> files, Formatter formatter, U
upToDateChecker.setUpToDate(file.toPath());
}

if (totalLintCount > 0) {
StringBuilder message = new StringBuilder();
message.append("There were ").append(totalLintCount).append(" lint error(s), they must be fixed or suppressed.");
message.append(lintMessage);
message.append("\n Resolve these lints or suppress with `<lintSuppressions>`");
throw new MojoExecutionException(message.toString());
}

// We print the number of considered files which is useful when ratchetFrom is setup
if (counter.getTotal() > 0) {
getLog().info("Spotless.%s is keeping %s files clean - %s were changed to be clean, %s were already clean, %s were skipped because caching determined they were already clean".formatted(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2026 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.spotless.maven.java;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

import com.diffplug.spotless.ProcessRunner;
import com.diffplug.spotless.maven.MavenIntegrationHarness;

class ForbidWildcardImportsMultiFileStepTest extends MavenIntegrationHarness {

/**
* Regression test: in apply mode a single linting file must not abort processing of the
* remaining files. Lints across all files are collected and the build fails once at the end,
* so every file's lints are reported.
*/
@Test
void testApplyAggregatesLintsAcrossAllFiles() throws Exception {
writePomWithJavaSteps("<forbidWildcardImports/>");

String first = "src/main/java/test1.java";
String second = "src/main/java/test2.java";
setFile(first).toResource("java/forbidwildcardimports/JavaCodeWildcardsUnformatted.test");
setFile(second).toResource("java/forbidwildcardimports/JavaCodeWildcardsUnformatted.test");

ProcessRunner.Result result = mavenRunner().withArguments("spotless:apply").runHasError();
String output = result.stdOutUtf8();

// 5 wildcard imports per file across 2 files = 10, aggregated into a single failure
assertThat(output).contains("There were 10 lint error(s), they must be fixed or suppressed.");
// both files reported -> the loop did NOT abort on the first linting file
assertThat(output).contains(first + ":");
assertThat(output).contains(second + ":");
assertThat(output).contains("Resolve these lints or suppress with `<lintSuppressions>`");

// forbidWildcardImports cannot auto-fix, so both files are left untouched
assertFile(first).sameAsResource("java/forbidwildcardimports/JavaCodeWildcardsUnformatted.test");
assertFile(second).sameAsResource("java/forbidwildcardimports/JavaCodeWildcardsUnformatted.test");
}
}
Loading