Skip to content

Commit e9207c4

Browse files
ntottenclaude
andauthored
Fix source.fixAll.prettier respecting editor.defaultFormatter (#3908) (#3937)
* Fix source.fixAll.prettier respecting editor.defaultFormatter (#3908) The code action provider now checks the user's editor settings before providing code actions. This prevents Prettier from running via source.fixAll when the user has explicitly chosen a different formatter (e.g., ESLint with Prettier plugin). The code action will now only run when: 1. source.fixAll.prettier is explicitly set to "always" or "explicit" 2. editor.defaultFormatter is not set to another extension This fix also properly handles language-specific settings (e.g., [javascript]: { "editor.defaultFormatter": "..." }). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add delay and timeout to code action tests for CI stability The tests were timing out on CI (Linux) because they need time for formatter registration when switching to a new workspace folder. - Added 1 second delay after showing document to wait for formatter registration - Increased test timeout to 10 seconds for workspace folder tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent b3f2e20 commit e9207c4

8 files changed

Lines changed: 143 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ All notable changes to the "prettier-vscode" extension will be documented in thi
66

77
## [Unreleased]
88

9+
- Fixed `source.fixAll.prettier` code action running even when `editor.defaultFormatter` was set to a different extension (#3908). The code action now respects the user's formatter choice and only runs when Prettier is the default formatter or when `source.fixAll.prettier` is explicitly enabled.
10+
911
## [12.1.1]
1012

1113
- Fixed module resolution when `prettierPath` points to a file (e.g., `.yarn/sdks/prettier/index.cjs` in Yarn PnP SDK setups)

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ You can also combine Prettier with ESLint:
255255
}
256256
```
257257

258+
> **Note:** The `source.fixAll.prettier` code action respects your `editor.defaultFormatter` setting. If you have set a different default formatter (e.g., ESLint with Prettier plugin), the Prettier code action will not run unless you explicitly enable it with `"source.fixAll.prettier": "explicit"` or `"source.fixAll.prettier": "always"`. This prevents double-formatting when using setups like `eslint-plugin-prettier`.
259+
258260
## Workspace Trust
259261

260262
This extension utilizes VS Code [Workspace Trust](https://code.visualstudio.com/docs/editor/workspace-trust) features. When this extension is run on an untrusted workspace, it will only use the built in version of prettier. No plugins, local, or global modules will be supported. Additionally, certain settings are also restricted - see each setting for details.

src/PrettierCodeActionProvider.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import {
77
Range,
88
TextDocument,
99
TextEdit,
10+
workspace,
1011
WorkspaceEdit,
1112
} from "vscode";
1213
import { ExtensionFormattingOptions } from "./types.js";
1314

15+
const EXTENSION_ID = "esbenp.prettier-vscode";
16+
1417
/**
1518
* Provides code actions for formatting with Prettier.
1619
* This enables using Prettier with codeActionsOnSave.
@@ -27,6 +30,47 @@ export class PrettierCodeActionProvider implements CodeActionProvider {
2730
) => Promise<TextEdit[]>,
2831
) {}
2932

33+
/**
34+
* Check if the Prettier code action should run for this document.
35+
* Returns true if:
36+
* 1. source.fixAll.prettier is explicitly enabled in codeActionsOnSave, OR
37+
* 2. editor.defaultFormatter is NOT set to another extension
38+
*
39+
* This prevents Prettier from running via source.fixAll when the user has
40+
* explicitly chosen a different formatter (e.g., ESLint with Prettier plugin).
41+
* See: https://github.com/prettier/prettier-vscode/issues/3908
42+
*/
43+
private shouldProvideCodeActions(document: TextDocument): boolean {
44+
// Use languageId scope to get language-specific settings (e.g., [javascript])
45+
const editorConfig = workspace.getConfiguration("editor", {
46+
uri: document.uri,
47+
languageId: document.languageId,
48+
});
49+
50+
// Check if source.fixAll.prettier is explicitly enabled - if so, always provide actions
51+
const codeActionsOnSave = editorConfig.get<Record<string, string>>(
52+
"codeActionsOnSave",
53+
{},
54+
);
55+
const prettierFixAllSetting = codeActionsOnSave["source.fixAll.prettier"];
56+
if (
57+
prettierFixAllSetting === "always" ||
58+
prettierFixAllSetting === "explicit"
59+
) {
60+
return true;
61+
}
62+
63+
// Check if editor.defaultFormatter is explicitly set to another extension
64+
// If so, don't provide code actions (user chose a different formatter)
65+
const defaultFormatter = editorConfig.get<string>("defaultFormatter");
66+
if (defaultFormatter && defaultFormatter !== EXTENSION_ID) {
67+
return false;
68+
}
69+
70+
// Default: provide code actions (either no formatter set, or Prettier is the formatter)
71+
return true;
72+
}
73+
3074
public async provideCodeActions(
3175
document: TextDocument,
3276
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -36,6 +80,11 @@ export class PrettierCodeActionProvider implements CodeActionProvider {
3680
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3781
token: CancellationToken,
3882
): Promise<CodeAction[]> {
83+
// Only provide code actions if explicitly enabled or if Prettier is the default formatter
84+
if (!this.shouldProvideCodeActions(document)) {
85+
return [];
86+
}
87+
3988
const edits = await this.provideEdits(document, {
4089
force: false,
4190
});

src/test/suite/codeAction.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import * as assert from "assert";
22
import * as vscode from "vscode";
33
import { ensureExtensionActivated } from "./testUtils.js";
4+
import { getWorkspaceFolderUri } from "./formatTestUtils.js";
5+
6+
/**
7+
* Helper to wait for a specified number of milliseconds
8+
*/
9+
function delay(ms: number): Promise<void> {
10+
return new Promise((resolve) => setTimeout(resolve, ms));
11+
}
412

513
describe("Test Prettier Code Actions", () => {
614
const unformattedCode = `const x = { a: 1, b: 2 }`;
@@ -79,4 +87,73 @@ describe("Test Prettier Code Actions", () => {
7987
"Document should be formatted correctly after applying code action",
8088
);
8189
});
90+
91+
it("does NOT provide code action when defaultFormatter is not prettier", async function () {
92+
// Increase timeout for this test as it involves workspace folder switching
93+
this.timeout(10000);
94+
95+
// The 'workspace' folder has editor.defaultFormatter set to
96+
// vscode.typescript-language-features for JavaScript files
97+
const base = getWorkspaceFolderUri("workspace");
98+
const testFilePath = vscode.Uri.joinPath(base, "test.js");
99+
const doc = await vscode.workspace.openTextDocument(testFilePath);
100+
await vscode.window.showTextDocument(doc);
101+
102+
// Wait for formatter registration to complete
103+
await delay(1000);
104+
105+
// Get code actions for the document
106+
const codeActions = await vscode.commands.executeCommand<
107+
vscode.CodeAction[]
108+
>(
109+
"vscode.executeCodeActionProvider",
110+
doc.uri,
111+
new vscode.Range(0, 0, doc.lineCount, 0),
112+
);
113+
114+
// Prettier code action should NOT be available
115+
const prettierAction = codeActions?.find(
116+
(action) => action.kind?.value === "source.fixAll.prettier",
117+
);
118+
119+
assert.ok(
120+
!prettierAction,
121+
"Prettier code action should NOT be available when defaultFormatter is not prettier",
122+
);
123+
});
124+
125+
it("provides code action when source.fixAll.prettier is explicitly enabled", async function () {
126+
// Increase timeout for this test as it involves workspace folder switching
127+
this.timeout(10000);
128+
129+
// The 'workspace-explicit-prettier' folder has:
130+
// - editor.defaultFormatter set to a different formatter
131+
// - BUT source.fixAll.prettier is explicitly set to "always"
132+
const base = getWorkspaceFolderUri("workspace-explicit-prettier");
133+
const testFilePath = vscode.Uri.joinPath(base, "test.js");
134+
const doc = await vscode.workspace.openTextDocument(testFilePath);
135+
await vscode.window.showTextDocument(doc);
136+
137+
// Wait for formatter registration to complete
138+
await delay(1000);
139+
140+
// Get code actions for the document
141+
const codeActions = await vscode.commands.executeCommand<
142+
vscode.CodeAction[]
143+
>(
144+
"vscode.executeCodeActionProvider",
145+
doc.uri,
146+
new vscode.Range(0, 0, doc.lineCount, 0),
147+
);
148+
149+
// Prettier code action SHOULD be available because explicitly enabled
150+
const prettierAction = codeActions?.find(
151+
(action) => action.kind?.value === "source.fixAll.prettier",
152+
);
153+
154+
assert.ok(
155+
prettierAction,
156+
"Prettier code action should be available when source.fixAll.prettier is explicitly enabled",
157+
);
158+
});
82159
});

test-fixtures/test.code-workspace

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@
6868
},
6969
{
7070
"path": "yarn-pnp-sdk"
71+
},
72+
{
73+
"path": "workspace-explicit-prettier"
7174
}
7275
],
7376
"settings": {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"[javascript]": {
3+
"editor.defaultFormatter": "vscode.typescript-language-features"
4+
},
5+
"editor.codeActionsOnSave": {
6+
"source.fixAll.prettier": "always"
7+
}
8+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const x = { a: 1, b: 2 }

test-fixtures/workspace/test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const x = { a: 1, b: 2 }

0 commit comments

Comments
 (0)