Skip to content

fix: allow concurrent prettier setups #2462

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

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
### Changed
* Use palantir-java-format 2.57.0 on Java 21. ([#2447](https://github.com/diffplug/spotless/pull/2447))
* Re-try `npm install` with `--prefer-online` after `ERESOLVE` error. ([#2448](https://github.com/diffplug/spotless/pull/2448))
* **BREAKING** Assert that each npm-based step uses its own `node_modules` directory, even when configured exactly the same, to prevent race conditions when running in parallel. For this, the API of npm-based formatter steps has been changed. ([#2462](https://github.com/diffplug/spotless/pull/2462))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically a breaking change to the api, so added the **BREAKING** here.


## [3.1.0] - 2025-02-20
### Added
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 @@ -68,13 +68,14 @@ public static Map<String, String> defaultDevDependenciesWithEslint(String versio
return Collections.singletonMap("eslint", version);
}

public static FormatterStep create(Map<String, String> devDependencies, Provisioner provisioner, File projectDir, File buildDir, File cacheDir, NpmPathResolver npmPathResolver, EslintConfig eslintConfig) {
public static FormatterStep create(String formatName, Map<String, String> devDependencies, Provisioner provisioner, File projectDir, File buildDir, File cacheDir, NpmPathResolver npmPathResolver, EslintConfig eslintConfig) {
requireNonNull(devDependencies);
requireNonNull(provisioner);
requireNonNull(projectDir);
requireNonNull(buildDir);
return FormatterStep.createLazy(NAME,
() -> new State(NAME, devDependencies, projectDir, buildDir, cacheDir, npmPathResolver, eslintConfig),
final String prefixedName = String.format("%s-%s", formatName, NAME);
return FormatterStep.createLazy(prefixedName,
() -> new State(prefixedName, devDependencies, projectDir, buildDir, cacheDir, npmPathResolver, eslintConfig),
State::createFormatterFunc);
}

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 @@ -69,7 +69,7 @@ public static class Runtime {

Runtime(NpmFormatterStepStateBase parent) {
this.parent = parent;
this.nodeServerLayout = new NodeServerLayout(parent.locations.buildDir(), parent.npmConfig.getPackageJsonContent());
this.nodeServerLayout = new NodeServerLayout(new File(parent.locations.buildDir(), parent.stepName), parent.npmConfig.getPackageJsonContent());
this.nodeServeApp = new NodeServeApp(nodeServerLayout, parent.npmConfig, parent.locations);
}

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 @@ -51,12 +51,13 @@ public static final Map<String, String> defaultDevDependenciesWithPrettier(Strin
return Collections.singletonMap("prettier", version);
}

public static FormatterStep create(Map<String, String> devDependencies, Provisioner provisioner, File projectDir, File buildDir, File cacheDir, NpmPathResolver npmPathResolver, PrettierConfig prettierConfig) {
public static FormatterStep create(String formatName, Map<String, String> devDependencies, Provisioner provisioner, File projectDir, File buildDir, File cacheDir, NpmPathResolver npmPathResolver, PrettierConfig prettierConfig) {
requireNonNull(devDependencies);
requireNonNull(provisioner);
requireNonNull(buildDir);
return FormatterStep.createLazy(NAME,
() -> new State(NAME, devDependencies, projectDir, buildDir, cacheDir, npmPathResolver, prettierConfig),
final String prefixedName = String.format("%s-%s", formatName, NAME);
return FormatterStep.createLazy(prefixedName,
() -> new State(prefixedName, devDependencies, projectDir, buildDir, cacheDir, npmPathResolver, prettierConfig),
State::createFormatterFunc);
}

Expand Down
1 change: 1 addition & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
* Use palantir-java-format 2.57.0 on Java 21. ([#2447](https://github.com/diffplug/spotless/pull/2447))
* Re-try `npm install` with `--prefer-online` after `ERESOLVE` error. ([#2448](https://github.com/diffplug/spotless/pull/2448))
* Apply Gradle's strict plugin types validation to the Spotless plugin. ([#2454](https://github.com/diffplug/spotless/pull/2454))
* Assert that each npm-based step uses its own `node_modules` directory, even when configured exactly the same to prevent race conditions when running in parallel. ([#2462](https://github.com/diffplug/spotless/pull/2462))

## [7.0.2] - 2025-01-14
### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,7 @@ public PrettierConfig config(final Map<String, Object> prettierConfig) {
@Override
protected FormatterStep createStep() {
final Project project = getProject();
return PrettierFormatterStep.create(devDependencies, provisioner(), project.getProjectDir(),
return PrettierFormatterStep.create(formatName(), devDependencies, provisioner(), project.getProjectDir(),
project.getLayout().getBuildDirectory().getAsFile().get(), npmModulesCacheOrNull(),
new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(),
Arrays.asList(project.getProjectDir(), project.getRootDir())),
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 @@ -106,7 +106,7 @@ public JavascriptEslintConfig(Map<String, String> devDependencies) {
public FormatterStep createStep() {
final Project project = getProject();

return EslintFormatterStep.create(devDependencies, provisioner(), project.getProjectDir(),
return EslintFormatterStep.create(NAME, devDependencies, provisioner(), project.getProjectDir(),
project.getLayout().getBuildDirectory().getAsFile().get(), npmModulesCacheOrNull(),
new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(),
Arrays.asList(project.getProjectDir(), project.getRootDir())),
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 @@ -214,7 +214,7 @@ public TypescriptEslintConfig tsconfigFile(Object path) {
public FormatterStep createStep() {
final Project project = getProject();

return EslintFormatterStep.create(devDependencies, provisioner(), project.getProjectDir(),
return EslintFormatterStep.create(NAME, devDependencies, provisioner(), project.getProjectDir(),
project.getLayout().getBuildDirectory().getAsFile().get(), npmModulesCacheOrNull(),
new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(),
Arrays.asList(project.getProjectDir(), project.getRootDir())),
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 All @@ -15,10 +15,18 @@
*/
package com.diffplug.gradle.spotless;

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

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.assertj.core.api.Assertions;
import org.gradle.testkit.runner.BuildResult;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

Expand Down Expand Up @@ -51,7 +59,7 @@ void useInlineConfig(String prettierVersion) throws IOException {
"}");
setFile("test.ts").toResource("npm/prettier/config/typescript.dirty");
final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build();
Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
switch (prettierVersion) {
case PRETTIER_VERSION_2:
assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile_prettier_2.clean");
Expand Down Expand Up @@ -81,7 +89,7 @@ void verifyCleanSpotlessCheckWorks(String prettierVersion) throws IOException {
"}");
setFile("test.ts").toResource("npm/prettier/config/typescript.dirty");
final BuildResult spotlessCheckFailsGracefully = gradleRunner().withArguments("--stacktrace", "spotlessCheck").buildAndFail();
Assertions.assertThat(spotlessCheckFailsGracefully.getOutput()).contains("> The following files had format violations:");
assertThat(spotlessCheckFailsGracefully.getOutput()).contains("> The following files had format violations:");

gradleRunner().withArguments("--stacktrace", "spotlessApply").build();
gradleRunner().withArguments("--stacktrace", "spotlessCheck").build();
Expand All @@ -104,7 +112,7 @@ void useFileConfig(String prettierVersion) throws IOException {
"}");
setFile("test.ts").toResource("npm/prettier/config/typescript.dirty");
final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build();
Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
switch (prettierVersion) {
case PRETTIER_VERSION_2:
assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile_prettier_2.clean");
Expand All @@ -131,7 +139,7 @@ void chooseParserBasedOnFilename(String prettierVersion) throws IOException {
"}");
setFile("dirty.json").toResource("npm/prettier/filename/dirty.json");
final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build();
Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
assertFile("dirty.json").sameAsResource("npm/prettier/filename/clean.json");
}

Expand Down Expand Up @@ -169,7 +177,7 @@ void useJavaCommunityPlugin(String prettierVersion) throws IOException {
"}");
setFile("JavaTest.java").toResource("npm/prettier/plugins/java-test.dirty");
final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build();
Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
assertFile("JavaTest.java").sameAsResource("npm/prettier/plugins/java-test.clean");
}

Expand Down Expand Up @@ -202,7 +210,7 @@ void useJavaCommunityPluginFileConfig(String prettierVersion) throws IOException
"}");
setFile("JavaTest.java").toResource("npm/prettier/plugins/java-test.dirty");
final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build();
Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
assertFile("JavaTest.java").sameAsResource("npm/prettier/plugins/java-test.clean");
}

Expand All @@ -226,8 +234,8 @@ void suggestsMissingJavaCommunityPlugin(String prettierVersion) throws IOExcepti
"}");
setFile("JavaTest.java").toResource("npm/prettier/plugins/java-test.dirty");
final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").buildAndFail();
Assertions.assertThat(spotlessApply.getOutput()).contains("Could not infer a parser");
Assertions.assertThat(spotlessApply.getOutput()).contains("prettier-plugin-java");
assertThat(spotlessApply.getOutput()).contains("Could not infer a parser");
assertThat(spotlessApply.getOutput()).contains("prettier-plugin-java");
}

@ParameterizedTest(name = "{index}: usePhpCommunityPlugin with prettier {0}")
Expand Down Expand Up @@ -264,7 +272,7 @@ void usePhpCommunityPlugin(String prettierVersion) throws IOException {
"}");
setFile("php-example.php").toResource("npm/prettier/plugins/php.dirty");
final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build();
Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
assertFile("php-example.php").sameAsResource("npm/prettier/plugins/php.clean");
}

Expand Down Expand Up @@ -324,9 +332,9 @@ void usePhpAndJavaCommunityPlugin(String prettierVersion) throws IOException {
setFile("php-example.php").toResource("npm/prettier/plugins/php.dirty");
setFile("JavaTest.java").toResource("npm/prettier/plugins/java-test.dirty");
final BuildResult spotlessApply = gradleRunner().forwardOutput().withArguments("--stacktrace", "--info", "spotlessApply").build();
Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
final BuildResult spotlessApply2 = gradleRunner().forwardOutput().withArguments("--stacktrace", "--info", "spotlessApply").build();
Assertions.assertThat(spotlessApply2.getOutput()).contains("BUILD SUCCESSFUL");
assertThat(spotlessApply2.getOutput()).contains("BUILD SUCCESSFUL");
assertFile("php-example.php").sameAsResource("npm/prettier/plugins/php.clean");
assertFile("JavaTest.java").sameAsResource("npm/prettier/plugins/java-test.clean");
}
Expand Down Expand Up @@ -355,7 +363,7 @@ void autodetectNpmrcFileConfig(String prettierVersion) throws IOException {
"}");
setFile("test.ts").toResource("npm/prettier/config/typescript.dirty");
final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").buildAndFail();
Assertions.assertThat(spotlessApply.getOutput()).containsPattern("Running npm command.*npm install.* failed with exit code: 1");
assertThat(spotlessApply.getOutput()).containsPattern("Running npm command.*npm install.* failed with exit code: 1");
}

@ParameterizedTest(name = "{index}: verifyCleanAndSpotlessWorks with prettier {0}")
Expand All @@ -377,9 +385,9 @@ void verifyCleanAndSpotlessWorks(String prettierVersion) throws IOException {
"}");
setFile("test.ts").toResource("npm/prettier/config/typescript.dirty");
final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "clean", "spotlessApply").build();
Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
final BuildResult spotlessApply2 = gradleRunner().withArguments("--stacktrace", "clean", "spotlessApply").build();
Assertions.assertThat(spotlessApply2.getOutput()).contains("BUILD SUCCESSFUL");
assertThat(spotlessApply2.getOutput()).contains("BUILD SUCCESSFUL");
}

@ParameterizedTest(name = "{index}: verifyCleanAndSpotlessWithNpmInstallCacheWorks with prettier {0}")
Expand All @@ -401,9 +409,9 @@ void verifyCleanAndSpotlessWithNpmInstallCacheWorks(String prettierVersion) thro
"}");
setFile("test.ts").toResource("npm/prettier/config/typescript.dirty");
final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "clean", "spotlessApply").build();
Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");
final BuildResult spotlessApply2 = gradleRunner().withArguments("--stacktrace", "clean", "spotlessApply").build();
Assertions.assertThat(spotlessApply2.getOutput()).contains("BUILD SUCCESSFUL");
assertThat(spotlessApply2.getOutput()).contains("BUILD SUCCESSFUL");
}

@ParameterizedTest(name = "{index}: autodetectNpmrcFileConfig with prettier {0}")
Expand All @@ -430,6 +438,51 @@ void pickupNpmrcFileConfig(String prettierVersion) throws IOException {
"}");
setFile("test.ts").toResource("npm/prettier/config/typescript.dirty");
final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").buildAndFail();
Assertions.assertThat(spotlessApply.getOutput()).containsPattern("Running npm command.*npm install.* failed with exit code: 1");
assertThat(spotlessApply.getOutput()).containsPattern("Running npm command.*npm install.* failed with exit code: 1");
}

@Test
void multiplePrettierSetupsDoNotIntersectOnNpmDir() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"def prettierConfig = [:]",
"prettierConfig['printWidth'] = 120",
"spotless {",
" format 'mytypescript', {",
" target 'test.ts'",
" prettier().config(prettierConfig)",
" }",
" format 'json', {",
" target 'test.json'",
" prettier().config(prettierConfig)",
" }",
" javascript {",
" target 'test.js'",
" prettier().config(prettierConfig)",
" }",
"}");

setFile("test.ts").toResource("npm/prettier/config/typescript.dirty");
setFile("test.json").toResource("npm/prettier/filetypes/json/json.dirty");
setFile("test.js").toResource("npm/prettier/filetypes/javascript-es5/javascript-es5.dirty");

final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build();
assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL");

File buildFolder = new File(rootFolder(), "build");
assertThat(buildFolder).isNotEmptyDirectory();

// verify it contains 3 folders containing "spotless-prettier" in it (recursively) - one for each format
try (Stream<Path> pathStream = Files.walk(buildFolder.toPath())) {
List<Path> nodeModulesDirs = pathStream
.sorted()
.filter(Files::isDirectory)
.filter(path -> path.getFileName().toString().contains("spotless-prettier"))
.collect(Collectors.toList());
assertThat(nodeModulesDirs).hasSize(3);
}
}
}
1 change: 1 addition & 0 deletions plugin-maven/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
* Use palantir-java-format 2.57.0 on Java 21. ([#2447](https://github.com/diffplug/spotless/pull/2447))
* Re-try `npm install` with `--prefer-online` after `ERESOLVE` error. ([#2448](https://github.com/diffplug/spotless/pull/2448))
* Assert that each npm-based step uses its own `node_modules` directory, even when configured exactly the same to prevent race conditions when running in parallel. ([#2462](https://github.com/diffplug/spotless/pull/2462))

## [2.44.3] - 2025-02-20
* Support for `clang-format` ([#2406](https://github.com/diffplug/spotless/pull/2406))
Expand Down
Loading
Loading