Skip to content

Commit 71c0d98

Browse files
authored
fix: prevent file watcher issue (#2859)
* fix: prevent file watcher issue * perf: check if file content changed in tsconfig file watch
1 parent b467045 commit 71c0d98

File tree

4 files changed

+65
-29
lines changed

4 files changed

+65
-29
lines changed

.changeset/green-adults-hammer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte-check': patch
3+
---
4+
5+
fix: prevent file watcher issue

.changeset/thirty-seas-post.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'svelte-language-server': patch
3+
'svelte-check': patch
4+
---
5+
6+
perf: check if file content changed in tsconfig file watch

packages/language-server/src/plugins/typescript/service.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,11 @@ async function createLanguageService(
965965
) {
966966
if (
967967
kind === ts.FileWatcherEventKind.Changed &&
968-
!configFileModified(fileName, modifiedTime ?? tsSystem.getModifiedTime?.(fileName))
968+
!configFileModified(
969+
fileName,
970+
modifiedTime ?? tsSystem.getModifiedTime?.(fileName),
971+
docContext
972+
)
969973
) {
970974
return;
971975
}
@@ -1328,7 +1332,8 @@ function createWatchDependedConfigCallback(docContext: LanguageServiceDocumentCo
13281332
kind === ts.FileWatcherEventKind.Changed &&
13291333
!configFileModified(
13301334
fileName,
1331-
modifiedTime ?? docContext.tsSystem.getModifiedTime?.(fileName)
1335+
modifiedTime ?? docContext.tsSystem.getModifiedTime?.(fileName),
1336+
docContext
13321337
)
13331338
) {
13341339
return;
@@ -1360,7 +1365,11 @@ function createWatchDependedConfigCallback(docContext: LanguageServiceDocumentCo
13601365
/**
13611366
* check if file content is modified instead of attributes changed
13621367
*/
1363-
function configFileModified(fileName: string, modifiedTime: Date | undefined) {
1368+
function configFileModified(
1369+
fileName: string,
1370+
modifiedTime: Date | undefined,
1371+
docContext: LanguageServiceDocumentContext
1372+
) {
13641373
const previousModifiedTime = configFileModifiedTime.get(fileName);
13651374
if (!modifiedTime || !previousModifiedTime) {
13661375
return true;
@@ -1371,6 +1380,20 @@ function configFileModified(fileName: string, modifiedTime: Date | undefined) {
13711380
}
13721381

13731382
configFileModifiedTime.set(fileName, modifiedTime);
1383+
1384+
const oldSourceFile =
1385+
parsedTsConfigInfo.get(fileName)?.parsedCommandLine?.options.configFile ??
1386+
docContext.extendedConfigCache.get(fileName)?.extendedResult;
1387+
1388+
if (
1389+
oldSourceFile &&
1390+
typeof oldSourceFile === 'object' &&
1391+
'kind' in oldSourceFile &&
1392+
typeof oldSourceFile.text === 'string' &&
1393+
oldSourceFile.text === docContext.tsSystem.readFile(fileName)
1394+
) {
1395+
return false;
1396+
}
13741397
return true;
13751398
}
13761399

packages/svelte-check/src/index.ts

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -194,13 +194,17 @@ class DiagnosticsWatcher {
194194
.on('unlink', (path) => this.removeDocument(path))
195195
.on('change', (path) => this.updateDocument(path, false));
196196

197-
this.updateWatchedDirectories();
198-
if (this.ignoreInitialAdd) {
199-
getDiagnostics(this.workspaceUri, this.writer, this.svelteCheck);
200-
}
197+
this.updateWildcardWatcher().then(() => {
198+
// ensuring the typescript program is built after wildcard watchers are added
199+
// so that individual file watchers added from onFileSnapshotCreated
200+
// run after the wildcard ones
201+
if (this.ignoreInitialAdd) {
202+
getDiagnostics(this.workspaceUri, this.writer, this.svelteCheck);
203+
}
204+
});
201205
}
202206

203-
private isSubdir(candidate: string, parent: string) {
207+
private isSubDir(candidate: string, parent: string) {
204208
const c = path.resolve(candidate);
205209
const p = path.resolve(parent);
206210
return c === p || c.startsWith(p + path.sep);
@@ -210,37 +214,37 @@ class DiagnosticsWatcher {
210214
const sorted = [...new Set(dirs.map((d) => path.resolve(d)))].sort();
211215
const result: string[] = [];
212216
for (const dir of sorted) {
213-
if (!result.some((p) => this.isSubdir(dir, p))) {
217+
if (!result.some((p) => this.isSubDir(dir, p))) {
214218
result.push(dir);
215219
}
216220
}
217221
return result;
218222
}
219223

220224
addWatchDirectory(dir: string) {
221-
if (!dir) return;
225+
if (!dir) {
226+
return;
227+
}
228+
222229
// Skip if already covered by an existing watched directory
223230
for (const existing of this.currentWatchedDirs) {
224-
if (this.isSubdir(dir, existing)) {
231+
if (this.isSubDir(dir, existing)) {
225232
return;
226233
}
227234
}
228-
// If new dir is a parent of existing ones, unwatch children
229-
const toRemove: string[] = [];
235+
236+
// Don't remove existing watchers, chokidar `unwatch` ignores future events from that path instead of closing the watcher in some cases
230237
for (const existing of this.currentWatchedDirs) {
231-
if (this.isSubdir(existing, dir)) {
232-
toRemove.push(existing);
238+
if (this.isSubDir(existing, dir)) {
239+
this.currentWatchedDirs.delete(existing);
233240
}
234241
}
235-
if (toRemove.length) {
236-
this.watcher.unwatch(toRemove);
237-
for (const r of toRemove) this.currentWatchedDirs.delete(r);
238-
}
242+
239243
this.watcher.add(dir);
240244
this.currentWatchedDirs.add(dir);
241245
}
242246

243-
private async updateWatchedDirectories() {
247+
private async updateWildcardWatcher() {
244248
const watchDirs = await this.svelteCheck.getWatchDirectories();
245249
const desired = this.minimizeDirs(
246250
(watchDirs?.map((d) => d.path) || [this.workspaceUri.fsPath]).map((p) =>
@@ -249,15 +253,13 @@ class DiagnosticsWatcher {
249253
);
250254

251255
const current = new Set([...this.currentWatchedDirs].map((p) => path.resolve(p)));
252-
const desiredSet = new Set(desired);
253256

254257
const toAdd = desired.filter((d) => !current.has(d));
255-
const toRemove = [...current].filter((d) => !desiredSet.has(d));
256-
257-
if (toAdd.length) this.watcher.add(toAdd);
258-
if (toRemove.length) this.watcher.unwatch(toRemove);
258+
if (toAdd.length) {
259+
this.watcher.add(toAdd);
260+
}
259261

260-
this.currentWatchedDirs = new Set(desired);
262+
this.currentWatchedDirs = new Set([...current, ...toAdd]);
261263
}
262264

263265
private async updateDocument(path: string, isNew: boolean) {
@@ -280,9 +282,9 @@ class DiagnosticsWatcher {
280282
this.scheduleDiagnostics();
281283
}
282284

283-
updateWatchers() {
285+
updateWildcardWatchers() {
284286
clearTimeout(this.pendingWatcherUpdate);
285-
this.pendingWatcherUpdate = setTimeout(() => this.updateWatchedDirectories(), 1000);
287+
this.pendingWatcherUpdate = setTimeout(() => this.updateWildcardWatcher(), 1000);
286288
}
287289

288290
scheduleDiagnostics() {
@@ -342,7 +344,7 @@ parseOptions(async (opts) => {
342344
// Wire callbacks that can reference the watcher instance created below
343345
let watcher: DiagnosticsWatcher;
344346
svelteCheckOptions.onProjectReload = () => {
345-
watcher.updateWatchers();
347+
watcher.updateWildcardWatchers();
346348
watcher.scheduleDiagnostics();
347349
};
348350
svelteCheckOptions.onFileSnapshotCreated = (filePath: string) => {

0 commit comments

Comments
 (0)