Skip to content

Commit 41cd9b4

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 41cd9b4

File tree

3 files changed

+92
-16
lines changed

3 files changed

+92
-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: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -145,47 +145,91 @@ async function getDiagnostics(
145145

146146
class DiagnosticsWatcher {
147147
private updateDiagnostics: any;
148+
private watcher: any;
149+
private currentWatchedDirs = new Set<string>();
150+
private fileEnding = /\.(svelte|d\.ts|ts|js|jsx|tsx|mjs|cjs|mts|cts)$/;
151+
private viteConfigRegex = /vite\.config\.(js|ts)\.timestamp-/;
152+
private userIgnored: Array<(path: string) => boolean>;
153+
private offset: number;
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);
163+
this.offset = workspaceUri.fsPath.length + 1;
160164

161-
watch(workspaceUri.fsPath, {
165+
// Create watcher with initial paths
166+
this.watcher = watch([], {
162167
ignored: (path, stats) => {
163168
if (
164169
path.includes('node_modules') ||
165170
path.includes('.git') ||
166-
(stats?.isFile() && (!fileEnding.test(path) || viteConfigRegex.test(path)))
171+
(stats?.isFile() && (!this.fileEnding.test(path) || this.viteConfigRegex.test(path)))
167172
) {
168173
return true;
169174
}
170175

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

180188
return false;
181189
},
182-
ignoreInitial: ignoreInitialAdd
190+
ignoreInitial: this.ignoreInitialAdd
183191
})
184192
.on('add', (path) => this.updateDocument(path, true))
185193
.on('unlink', (path) => this.removeDocument(path))
186194
.on('change', (path) => this.updateDocument(path, false));
187195

188-
if (ignoreInitialAdd) {
196+
this.updateWatchedDirectories();
197+
}
198+
199+
private async updateWatchedDirectories() {
200+
const watchDirs = await this.svelteCheck.getWatchDirectories();
201+
const dirsToWatch = watchDirs || [{ path: this.workspaceUri.fsPath, recursive: true }];
202+
203+
// Apply exclude heuristic to ensure we never watch commonly excluded directories
204+
const excludePatterns = ['node_modules', '.git', 'dist', 'build', 'out', '.svelte-kit', 'coverage', '.nyc_output'];
205+
const filteredDirs = dirsToWatch.filter(dir => {
206+
const dirPath = dir.path.replace(/\\/g, '/');
207+
return !excludePatterns.some(pattern =>
208+
dirPath.includes(`/${pattern}/`) ||
209+
dirPath.endsWith(`/${pattern}`)
210+
);
211+
});
212+
213+
const newDirs = new Set(filteredDirs.map(d => d.path));
214+
215+
// Fast diff: find directories to add and remove
216+
const toAdd = [...newDirs].filter(dir => !this.currentWatchedDirs.has(dir));
217+
const toRemove = [...this.currentWatchedDirs].filter(dir => !newDirs.has(dir));
218+
219+
// Add new directories
220+
if (toAdd.length > 0) {
221+
this.watcher.add(toAdd);
222+
}
223+
224+
// Remove old directories
225+
if (toRemove.length > 0) {
226+
this.watcher.unwatch(toRemove);
227+
}
228+
229+
// Update current set
230+
this.currentWatchedDirs = newDirs;
231+
232+
if (this.ignoreInitialAdd) {
189233
this.scheduleDiagnostics();
190234
}
191235
}
@@ -210,10 +254,15 @@ class DiagnosticsWatcher {
210254
this.scheduleDiagnostics();
211255
}
212256

213-
scheduleDiagnostics() {
257+
scheduleDiagnostics(updateWatchers = false) {
214258
clearTimeout(this.updateDiagnostics);
215259
this.updateDiagnostics = setTimeout(
216-
() => getDiagnostics(this.workspaceUri, this.writer, this.svelteCheck),
260+
async () => {
261+
if (updateWatchers) {
262+
await this.updateWatchedDirectories();
263+
}
264+
getDiagnostics(this.workspaceUri, this.writer, this.svelteCheck);
265+
},
217266
1000
218267
);
219268
}
@@ -264,7 +313,7 @@ parseOptions(async (opts) => {
264313
};
265314

266315
if (opts.watch) {
267-
svelteCheckOptions.onProjectReload = () => watcher.scheduleDiagnostics();
316+
svelteCheckOptions.onProjectReload = () => watcher.scheduleDiagnostics(true);
268317
const watcher = new DiagnosticsWatcher(
269318
opts.workspaceUri,
270319
new SvelteCheck(opts.workspaceUri.fsPath, svelteCheckOptions),

0 commit comments

Comments
 (0)