Skip to content

Commit 61b4780

Browse files
ntottenclaude
andauthored
Fix file system watcher not detecting config changes on Windows (#3938)
* Fix file system watcher not detecting config changes on Windows Replace the single file system watcher using brace expansion pattern with multiple watchers using simpler glob patterns. VS Code's createFileSystemWatcher doesn't reliably support complex {a,b,c} patterns across all platforms, particularly on Windows. Fixes config file change detection for .prettierrc, .prettierrc.*, prettier.config.*, and .editorconfig files. See: microsoft/vscode#177617 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add test for config file watcher Verifies that modifying .prettierrc triggers the file watcher and applies the new formatting configuration. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Clear Prettier config cache when config file changes When a Prettier config file is modified, the file watcher now calls clearModuleCache to clear Prettier's internal config cache. This ensures the updated configuration is applied on the next format. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ba9f83b commit 61b4780

6 files changed

Lines changed: 119 additions & 34 deletions

File tree

src/ModuleResolverWeb.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,10 @@ export class ModuleResolver implements ModuleResolverInterface {
380380
return null;
381381
}
382382

383+
async clearModuleCache(_filePath: string): Promise<void> {
384+
// No cache to clear in browser
385+
}
386+
383387
dispose() {
384388
// nothing to do
385389
}

src/PrettierEditService.ts

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -104,32 +104,6 @@ interface ISelectors {
104104
languageSelector: ReadonlyArray<DocumentFilter>;
105105
}
106106

107-
/**
108-
* Prettier reads configuration from files
109-
*/
110-
const PRETTIER_CONFIG_FILES = [
111-
".prettierrc",
112-
".prettierrc.json",
113-
".prettierrc.json5",
114-
".prettierrc.yaml",
115-
".prettierrc.yml",
116-
".prettierrc.toml",
117-
".prettierrc.js",
118-
".prettierrc.cjs",
119-
".prettierrc.mjs",
120-
".prettierrc.ts",
121-
".prettierrc.cts",
122-
".prettierrc.mts",
123-
"package.json",
124-
"prettier.config.js",
125-
"prettier.config.cjs",
126-
"prettier.config.mjs",
127-
"prettier.config.ts",
128-
"prettier.config.cts",
129-
"prettier.config.mts",
130-
".editorconfig",
131-
];
132-
133107
export default class PrettierEditService implements Disposable {
134108
private formatterHandler: undefined | Disposable;
135109
private rangeFormatterHandler: undefined | Disposable;
@@ -168,12 +142,22 @@ export default class PrettierEditService implements Disposable {
168142
}
169143
});
170144

171-
const prettierConfigWatcher = workspace.createFileSystemWatcher(
172-
`**/{${PRETTIER_CONFIG_FILES.join(",")}}`,
173-
);
174-
prettierConfigWatcher.onDidChange(this.prettierConfigChanged);
175-
prettierConfigWatcher.onDidCreate(this.prettierConfigChanged);
176-
prettierConfigWatcher.onDidDelete(this.prettierConfigChanged);
145+
// Create individual watchers for different config file patterns
146+
// Using multiple watchers instead of brace expansion because VS Code's
147+
// createFileSystemWatcher doesn't reliably support complex {a,b,c} patterns
148+
// across all platforms (especially Windows). See: https://github.com/microsoft/vscode/issues/177617
149+
const prettierConfigWatchers = [
150+
workspace.createFileSystemWatcher("**/.prettierrc"),
151+
workspace.createFileSystemWatcher("**/.prettierrc.*"),
152+
workspace.createFileSystemWatcher("**/prettier.config.*"),
153+
workspace.createFileSystemWatcher("**/.editorconfig"),
154+
];
155+
156+
for (const watcher of prettierConfigWatchers) {
157+
watcher.onDidChange(this.prettierConfigChanged);
158+
watcher.onDidCreate(this.prettierConfigChanged);
159+
watcher.onDidDelete(this.prettierConfigChanged);
160+
}
177161

178162
const textEditorChange = window.onDidChangeActiveTextEditor(
179163
this.handleActiveTextEditorChangedSync,
@@ -184,7 +168,7 @@ export default class PrettierEditService implements Disposable {
184168
return [
185169
packageWatcher,
186170
configurationWatcher,
187-
prettierConfigWatcher,
171+
...prettierConfigWatchers,
188172
textEditorChange,
189173
];
190174
}
@@ -223,7 +207,11 @@ export default class PrettierEditService implements Disposable {
223207
}
224208
};
225209

226-
private prettierConfigChanged = async (uri: Uri) => this.resetFormatters(uri);
210+
private prettierConfigChanged = async (uri: Uri) => {
211+
// Clear Prettier's internal config cache so it re-reads the config file
212+
await this.moduleResolver.clearModuleCache(uri.fsPath);
213+
this.resetFormatters(uri);
214+
};
227215

228216
private resetFormatters = (uri?: Uri) => {
229217
if (uri) {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import * as assert from "assert";
2+
import * as fs from "fs";
3+
import * as path from "path";
4+
import { promisify } from "util";
5+
import {
6+
format,
7+
getWorkspaceFolderUri,
8+
moveRootPrettierRC,
9+
putBackPrettierRC,
10+
} from "./formatTestUtils.js";
11+
import { ensureExtensionActivated } from "./testUtils.js";
12+
13+
const writeFileAsync = promisify(fs.writeFile);
14+
const readFileAsync = promisify(fs.readFile);
15+
16+
/**
17+
* Helper to wait for a specified number of milliseconds
18+
*/
19+
function delay(ms: number): Promise<void> {
20+
return new Promise((resolve) => setTimeout(resolve, ms));
21+
}
22+
23+
describe("Test config file watcher", () => {
24+
const configDir = "config-watcher";
25+
const configFile = ".prettierrc";
26+
let configPath: string;
27+
let originalConfig: string;
28+
29+
before(async () => {
30+
await ensureExtensionActivated();
31+
await moveRootPrettierRC();
32+
33+
// Store original config
34+
const base = getWorkspaceFolderUri("config");
35+
configPath = path.join(base.fsPath, configDir, configFile);
36+
originalConfig = await readFileAsync(configPath, "utf8");
37+
});
38+
39+
after(async () => {
40+
// Restore original config
41+
await writeFileAsync(configPath, originalConfig, "utf8");
42+
await putBackPrettierRC();
43+
});
44+
45+
it("detects config file changes and applies new formatting", async function () {
46+
// This test may need more time due to file watcher delays
47+
this.timeout(30000);
48+
49+
// Format with initial config (tabWidth: 2)
50+
const { actual: initialFormat } = await format(
51+
"config",
52+
`${configDir}/test.js`,
53+
);
54+
55+
// Verify initial format uses tabWidth: 2 (2-space indentation)
56+
assert.ok(
57+
initialFormat.includes(" console.log"),
58+
`Initial format should use 2-space indentation, got:\n${initialFormat}`,
59+
);
60+
61+
// Change config to tabWidth: 4
62+
const newConfig = JSON.stringify({ tabWidth: 4 }, null, 2) + "\n";
63+
await writeFileAsync(configPath, newConfig, "utf8");
64+
65+
// Wait for file watcher to detect the change
66+
// The watcher should clear the formatter cache
67+
await delay(2000);
68+
69+
// Format again - should now use tabWidth: 4
70+
const { actual: newFormat } = await format(
71+
"config",
72+
`${configDir}/test.js`,
73+
);
74+
75+
// Verify new format uses tabWidth: 4 (4-space indentation)
76+
assert.ok(
77+
newFormat.includes(" console.log"),
78+
`After config change, format should use 4-space indentation, got:\n${newFormat}`,
79+
);
80+
assert.ok(
81+
!newFormat.includes(" console.log(") ||
82+
newFormat.includes(" console.log"),
83+
"Should not have 2-space indentation after config change",
84+
);
85+
});
86+
});

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export type ModuleResolverInterface = {
6969
doc: TextDocument,
7070
vscodeConfig: PrettierVSCodeConfig,
7171
): Promise<"error" | "disabled" | PrettierOptions | null>;
72+
clearModuleCache(filePath: string): Promise<void>;
7273
dispose(): void;
7374
resolveConfig(
7475
prettierInstance: {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"tabWidth": 2
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function foo() {
2+
console.log("test");
3+
}

0 commit comments

Comments
 (0)