diff --git a/packages/core/src/services/fileDiscoveryService.test.ts b/packages/core/src/services/fileDiscoveryService.test.ts index c205463bc2..63ddc06616 100644 --- a/packages/core/src/services/fileDiscoveryService.test.ts +++ b/packages/core/src/services/fileDiscoveryService.test.ts @@ -502,6 +502,50 @@ 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(); + }); }); describe('getIgnoredPaths', () => { diff --git a/packages/core/src/services/fileDiscoveryService.ts b/packages/core/src/services/fileDiscoveryService.ts index 28b55894b6..d58f31a749 100644 --- a/packages/core/src/services/fileDiscoveryService.ts +++ b/packages/core/src/services/fileDiscoveryService.ts @@ -274,7 +274,8 @@ export class FileDiscoveryService { this.defaultFilterFileOptions.respectGitIgnore ) { const gitIgnorePath = path.join(this.projectRoot, '.gitignore'); - if (fs.existsSync(gitIgnorePath)) { + const stat = fs.statSync(gitIgnorePath, { throwIfNoEntry: false }); + if (stat?.isFile()) { paths.push(gitIgnorePath); } } diff --git a/packages/core/src/utils/filesearch/ignore.ts b/packages/core/src/utils/filesearch/ignore.ts index b8b2635c19..bd5cd5d6e9 100644 --- a/packages/core/src/utils/filesearch/ignore.ts +++ b/packages/core/src/utils/filesearch/ignore.ts @@ -19,8 +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) } } diff --git a/packages/core/src/utils/ignoreFileParser.ts b/packages/core/src/utils/ignoreFileParser.ts index 991826e3f0..ee7284bfa6 100644 --- a/packages/core/src/utils/ignoreFileParser.ts +++ b/packages/core/src/utils/ignoreFileParser.ts @@ -105,7 +105,10 @@ export class IgnoreFileParser implements IgnoreFileFilter { .slice() .reverse() .map((fileName) => path.join(this.projectRoot, fileName)) - .filter((filePath) => fs.existsSync(filePath)); + .filter( + (filePath) => + fs.statSync(filePath, { throwIfNoEntry: false })?.isFile() ?? false, + ); } /**