Skip to content

Commit 09930e2

Browse files
committed
fix: respect tsconfig exclude patterns in file watcher
- Add getProjectConfig() to language server to expose parsed tsconfig - Process wildcard directories in svelte-check to determine watch paths - Support both recursive and non-recursive directory watching based on TypeScript's configuration - Handle relative paths correctly for directories outside workspace This ensures svelte-check only watches directories included by the tsconfig, improving performance and avoiding unnecessary file watching.
1 parent 23db5a4 commit 09930e2

File tree

3 files changed

+80
-16
lines changed

3 files changed

+80
-16
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export interface LanguageServiceContainer {
6060
getResolvedProjectReferences(): TsConfigInfo[];
6161
openVirtualDocument(document: Document): void;
6262
isShimFiles(filePath: string): boolean;
63+
getProjectConfig(): ts.ParsedCommandLine;
6364
dispose(): void;
6465
}
6566

@@ -458,6 +459,7 @@ async function createLanguageService(
458459
getResolvedProjectReferences,
459460
openVirtualDocument,
460461
isShimFiles,
462+
getProjectConfig,
461463
dispose
462464
};
463465

@@ -1249,6 +1251,10 @@ async function createLanguageService(
12491251
function isShimFiles(filePath: string) {
12501252
return svelteTsxFilesToOriginalCasing.has(getCanonicalFileName(normalizePath(filePath)));
12511253
}
1254+
1255+
function getProjectConfig() {
1256+
return projectConfig;
1257+
}
12521258
}
12531259

12541260
/**

packages/language-server/src/svelte-check.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,4 +353,25 @@ export class SvelteCheck {
353353
}
354354
return this.lsAndTSDocResolver.getTSService(tsconfigPath);
355355
}
356+
357+
/**
358+
* Gets the watch directories based on the tsconfig include patterns.
359+
* Returns null if no tsconfig is specified.
360+
*/
361+
async getWatchDirectories(): Promise<{ path: string; recursive: boolean }[] | null> {
362+
if (!this.options.tsconfig) {
363+
return null;
364+
}
365+
const lsContainer = await this.getLSContainer(this.options.tsconfig);
366+
const projectConfig = lsContainer.getProjectConfig();
367+
368+
if (!projectConfig.wildcardDirectories) {
369+
return null;
370+
}
371+
372+
return Object.entries(projectConfig.wildcardDirectories).map(([dir, flags]) => ({
373+
path: dir,
374+
recursive: !!(flags & ts.WatchDirectoryFlags.Recursive)
375+
}));
376+
}
356377
}

packages/svelte-check/src/index.ts

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -143,49 +143,81 @@ async function getDiagnostics(
143143
}
144144
}
145145

146+
const FILE_ENDING_REGEX = /\.(svelte|d\.ts|ts|js|jsx|tsx|mjs|cjs|mts|cts)$/;
147+
const VITE_CONFIG_REGEX = /vite\.config\.(js|ts)\.timestamp-/;
148+
146149
class DiagnosticsWatcher {
147150
private updateDiagnostics: any;
151+
private watcher: any;
152+
private currentWatchedDirs = new Set<string>();
153+
private userIgnored: Array<(path: string) => boolean>;
148154

149155
constructor(
150156
private workspaceUri: URI,
151157
private svelteCheck: SvelteCheck,
152158
private writer: Writer,
153159
filePathsToIgnore: string[],
154-
ignoreInitialAdd: boolean
160+
private ignoreInitialAdd: boolean
155161
) {
156-
const fileEnding = /\.(svelte|d\.ts|ts|js|jsx|tsx|mjs|cjs|mts|cts)$/;
157-
const viteConfigRegex = /vite\.config\.(js|ts)\.timestamp-/;
158-
const userIgnored = createIgnored(filePathsToIgnore);
159-
const offset = workspaceUri.fsPath.length + 1;
162+
this.userIgnored = createIgnored(filePathsToIgnore);
160163

161-
watch(workspaceUri.fsPath, {
164+
// Create watcher with initial paths
165+
this.watcher = watch([], {
162166
ignored: (path, stats) => {
163167
if (
164168
path.includes('node_modules') ||
165169
path.includes('.git') ||
166-
(stats?.isFile() && (!fileEnding.test(path) || viteConfigRegex.test(path)))
170+
(stats?.isFile() && (!FILE_ENDING_REGEX.test(path) || VITE_CONFIG_REGEX.test(path)))
167171
) {
168172
return true;
169173
}
170174

171-
if (userIgnored.length !== 0) {
172-
path = path.slice(offset);
173-
for (const i of userIgnored) {
174-
if (i(path)) {
175+
if (this.userIgnored.length !== 0) {
176+
// Make path relative to workspace for user ignores
177+
const workspaceRelative = path.startsWith(this.workspaceUri.fsPath)
178+
? path.slice(this.workspaceUri.fsPath.length + 1)
179+
: path;
180+
for (const i of this.userIgnored) {
181+
if (i(workspaceRelative)) {
175182
return true;
176183
}
177184
}
178185
}
179186

180187
return false;
181188
},
182-
ignoreInitial: ignoreInitialAdd
189+
ignoreInitial: this.ignoreInitialAdd
183190
})
184191
.on('add', (path) => this.updateDocument(path, true))
185192
.on('unlink', (path) => this.removeDocument(path))
186193
.on('change', (path) => this.updateDocument(path, false));
187194

188-
if (ignoreInitialAdd) {
195+
this.updateWatchedDirectories();
196+
}
197+
198+
private async updateWatchedDirectories() {
199+
const watchDirs = await this.svelteCheck.getWatchDirectories();
200+
const dirsToWatch = watchDirs || [{ path: this.workspaceUri.fsPath, recursive: true }];
201+
const newDirs = new Set(dirsToWatch.map(d => d.path));
202+
203+
// Fast diff: find directories to add and remove
204+
const toAdd = [...newDirs].filter(dir => !this.currentWatchedDirs.has(dir));
205+
const toRemove = [...this.currentWatchedDirs].filter(dir => !newDirs.has(dir));
206+
207+
// Add new directories
208+
if (toAdd.length > 0) {
209+
this.watcher.add(toAdd);
210+
}
211+
212+
// Remove old directories
213+
if (toRemove.length > 0) {
214+
this.watcher.unwatch(toRemove);
215+
}
216+
217+
// Update current set
218+
this.currentWatchedDirs = newDirs;
219+
220+
if (this.ignoreInitialAdd) {
189221
this.scheduleDiagnostics();
190222
}
191223
}
@@ -210,10 +242,15 @@ class DiagnosticsWatcher {
210242
this.scheduleDiagnostics();
211243
}
212244

213-
scheduleDiagnostics() {
245+
scheduleDiagnostics(updateWatchers = false) {
214246
clearTimeout(this.updateDiagnostics);
215247
this.updateDiagnostics = setTimeout(
216-
() => getDiagnostics(this.workspaceUri, this.writer, this.svelteCheck),
248+
async () => {
249+
if (updateWatchers) {
250+
await this.updateWatchedDirectories();
251+
}
252+
getDiagnostics(this.workspaceUri, this.writer, this.svelteCheck);
253+
},
217254
1000
218255
);
219256
}
@@ -264,7 +301,7 @@ parseOptions(async (opts) => {
264301
};
265302

266303
if (opts.watch) {
267-
svelteCheckOptions.onProjectReload = () => watcher.scheduleDiagnostics();
304+
svelteCheckOptions.onProjectReload = () => watcher.scheduleDiagnostics(true);
268305
const watcher = new DiagnosticsWatcher(
269306
opts.workspaceUri,
270307
new SvelteCheck(opts.workspaceUri.fsPath, svelteCheckOptions),

0 commit comments

Comments
 (0)