From e12c0c5aacd88390bb37fd623678597567b22118 Mon Sep 17 00:00:00 2001 From: suhaan-24 Date: Sun, 22 Feb 2026 17:05:26 +0530 Subject: [PATCH 1/2] fix: prevent EISDIR crash when customIgnoreFilePaths contains directories (#19868) --- .../src/services/fileDiscoveryService.test.ts | 44 +++++++++++++++++++ .../core/src/services/fileDiscoveryService.ts | 8 +++- packages/core/src/utils/filesearch/ignore.ts | 6 ++- packages/core/src/utils/ignoreFileParser.ts | 8 +++- 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/packages/core/src/services/fileDiscoveryService.test.ts b/packages/core/src/services/fileDiscoveryService.test.ts index 7fbdcdead82..b14475f7635 100644 --- a/packages/core/src/services/fileDiscoveryService.test.ts +++ b/packages/core/src/services/fileDiscoveryService.test.ts @@ -494,5 +494,49 @@ describe('FileDiscoveryService', () => { const paths = service.getAllIgnoreFilePaths(); expect(paths[0]).toBe(path.join(projectRoot, '.gitignore')); }); + + it('should exclude directories from getIgnoreFilePaths (#19868)', async () => { + // Create a directory that shares a name with a customIgnoreFilePaths entry + await fs.mkdir(path.join(projectRoot, 'node_modules'), { + recursive: true, + }); + + const service = new FileDiscoveryService(projectRoot, { + customIgnoreFilePaths: ['node_modules'], + }); + const paths = service.getIgnoreFilePaths(); + + // node_modules/ is a directory, not a file — it should be excluded + expect(paths).not.toContain(path.join(projectRoot, 'node_modules')); + }); + + it('should exclude directories from getAllIgnoreFilePaths (#19868)', async () => { + await fs.mkdir(path.join(projectRoot, 'node_modules'), { + recursive: true, + }); + + const service = new FileDiscoveryService(projectRoot, { + customIgnoreFilePaths: ['node_modules'], + }); + const paths = service.getAllIgnoreFilePaths(); + + expect(paths).not.toContain(path.join(projectRoot, 'node_modules')); + // .gitignore should still be present + expect(paths).toContain(path.join(projectRoot, '.gitignore')); + }); + + it('should not crash when customIgnoreFilePaths contains directory names (#19868)', async () => { + await fs.mkdir(path.join(projectRoot, 'node_modules'), { + recursive: true, + }); + await fs.mkdir(path.join(projectRoot, 'temp'), { recursive: true }); + + // This is the exact user scenario from issue #19868 + expect(() => { + new FileDiscoveryService(projectRoot, { + customIgnoreFilePaths: ['node_modules/', 'temp/', 'cache/'], + }); + }).not.toThrow(); + }); }); }); diff --git a/packages/core/src/services/fileDiscoveryService.ts b/packages/core/src/services/fileDiscoveryService.ts index 44a28c1ff2e..7f992fa8ea1 100644 --- a/packages/core/src/services/fileDiscoveryService.ts +++ b/packages/core/src/services/fileDiscoveryService.ts @@ -184,8 +184,12 @@ export class FileDiscoveryService { this.defaultFilterFileOptions.respectGitIgnore ) { const gitIgnorePath = path.join(this.projectRoot, '.gitignore'); - if (fs.existsSync(gitIgnorePath)) { - paths.push(gitIgnorePath); + try { + if (fs.statSync(gitIgnorePath).isFile()) { + paths.push(gitIgnorePath); + } + } catch { + // File does not exist or cannot be accessed, ignore. } } return paths.concat(this.getIgnoreFilePaths()); diff --git a/packages/core/src/utils/filesearch/ignore.ts b/packages/core/src/utils/filesearch/ignore.ts index b8b2635c191..41f929e3787 100644 --- a/packages/core/src/utils/filesearch/ignore.ts +++ b/packages/core/src/utils/filesearch/ignore.ts @@ -20,7 +20,11 @@ export function loadIgnoreRules( for (const filePath of ignoreFiles) { if (fs.existsSync(filePath)) { - ignorer.add(fs.readFileSync(filePath, 'utf8')); + try { + ignorer.add(fs.readFileSync(filePath, 'utf8')); + } catch { + // Skip files that can't be read (e.g. directories, permission errors) + } } } diff --git a/packages/core/src/utils/ignoreFileParser.ts b/packages/core/src/utils/ignoreFileParser.ts index 3fbb3f45d82..5c04bc6880e 100644 --- a/packages/core/src/utils/ignoreFileParser.ts +++ b/packages/core/src/utils/ignoreFileParser.ts @@ -117,7 +117,13 @@ export class IgnoreFileParser implements IgnoreFileFilter { .slice() .reverse() .map((fileName) => path.join(this.projectRoot, fileName)) - .filter((filePath) => fs.existsSync(filePath)); + .filter((filePath) => { + try { + return fs.statSync(filePath).isFile(); + } catch { + return false; + } + }); } /** From 1d617d2d0c0e392d80f16614a1f0ceda79e05ce7 Mon Sep 17 00:00:00 2001 From: Tommaso Sciortino Date: Mon, 11 May 2026 13:32:41 -0700 Subject: [PATCH 2/2] small fixes --- packages/core/src/services/fileDiscoveryService.ts | 9 +++------ packages/core/src/utils/filesearch/ignore.ts | 10 ++++------ packages/core/src/utils/ignoreFileParser.ts | 11 ++++------- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/packages/core/src/services/fileDiscoveryService.ts b/packages/core/src/services/fileDiscoveryService.ts index 0d19d7773a4..d58f31a749e 100644 --- a/packages/core/src/services/fileDiscoveryService.ts +++ b/packages/core/src/services/fileDiscoveryService.ts @@ -274,12 +274,9 @@ export class FileDiscoveryService { this.defaultFilterFileOptions.respectGitIgnore ) { const gitIgnorePath = path.join(this.projectRoot, '.gitignore'); - try { - if (fs.statSync(gitIgnorePath).isFile()) { - paths.push(gitIgnorePath); - } - } catch { - // File does not exist or cannot be accessed, ignore. + const stat = fs.statSync(gitIgnorePath, { throwIfNoEntry: false }); + if (stat?.isFile()) { + paths.push(gitIgnorePath); } } return paths.concat(this.getIgnoreFilePaths()); diff --git a/packages/core/src/utils/filesearch/ignore.ts b/packages/core/src/utils/filesearch/ignore.ts index 41f929e3787..bd5cd5d6e9c 100644 --- a/packages/core/src/utils/filesearch/ignore.ts +++ b/packages/core/src/utils/filesearch/ignore.ts @@ -19,12 +19,10 @@ export function loadIgnoreRules( const ignoreFiles = service.getAllIgnoreFilePaths(); for (const filePath of ignoreFiles) { - if (fs.existsSync(filePath)) { - try { - ignorer.add(fs.readFileSync(filePath, 'utf8')); - } catch { - // Skip files that can't be read (e.g. directories, permission errors) - } + try { + ignorer.add(fs.readFileSync(filePath, 'utf8')); + } catch { + // Skip files that can't be read (e.g. directories, permission errors) } } diff --git a/packages/core/src/utils/ignoreFileParser.ts b/packages/core/src/utils/ignoreFileParser.ts index 9e312b2fb5e..ee7284bfa69 100644 --- a/packages/core/src/utils/ignoreFileParser.ts +++ b/packages/core/src/utils/ignoreFileParser.ts @@ -105,13 +105,10 @@ export class IgnoreFileParser implements IgnoreFileFilter { .slice() .reverse() .map((fileName) => path.join(this.projectRoot, fileName)) - .filter((filePath) => { - try { - return fs.statSync(filePath).isFile(); - } catch { - return false; - } - }); + .filter( + (filePath) => + fs.statSync(filePath, { throwIfNoEntry: false })?.isFile() ?? false, + ); } /**