* Actively discover the source files an `include` whitelist forces in. `git * ls-files` never lists gitignored files, so a filtered filesystem walk of just * the opted-in subtrees (`includeStaticRoots`) is the only way to find them. * Returns project-root-relative, normalized source-file paths.
( rootDir: string, include: Ignore, exclude: Ignore | null, roots: string[], overrides: Record<string, Language>, )
| 408 | * `.gitignore` is deliberately NOT consulted: overriding it is the whole point. |
| 409 | */ |
| 410 | function collectIncludedFiles( |
| 411 | rootDir: string, |
| 412 | include: Ignore, |
| 413 | exclude: Ignore | null, |
| 414 | roots: string[], |
| 415 | overrides: Record<string, Language>, |
| 416 | ): Set<string> { |
| 417 | const out = new Set<string>(); |
| 418 | const defaults = defaultsOnlyIgnore(); |
| 419 | const visited = new Set<string>(); |
| 420 | |
| 421 | const consider = (abs: string, rel: string, isDir: boolean): void => { |
| 422 | if (isDir) { |
| 423 | if (defaults.ignores(rel + '/')) return; // never node_modules/dist/… via include |
| 424 | // An explicit `exclude` always wins over `include`; prune the whole subtree |
| 425 | // here so a large excluded dir (a committed frontend's own vendored deps, |
| 426 | // build output, …) is never walked — the per-file guard below still catches |
| 427 | // anything a directory pattern doesn't, so this is a pure efficiency win. |
| 428 | if (exclude && exclude.ignores(rel + '/')) return; |
| 429 | walk(abs); |
| 430 | } else { |
| 431 | if (defaults.ignores(rel)) return; |
| 432 | if (!include.ignores(rel)) return; |
| 433 | if (exclude && exclude.ignores(rel)) return; |
| 434 | if (!isSourceFile(rel, overrides)) return; |
| 435 | out.add(rel); |
| 436 | } |
| 437 | }; |
| 438 | |
| 439 | function walk(absDir: string): void { |
| 440 | let realDir: string; |
| 441 | try { |
| 442 | realDir = fs.realpathSync(absDir); |
| 443 | } catch { |
| 444 | return; |
| 445 | } |
| 446 | if (visited.has(realDir)) return; // symlink-cycle guard |
| 447 | visited.add(realDir); |
| 448 | |
| 449 | let entries: fs.Dirent[]; |
| 450 | try { |
| 451 | entries = fs.readdirSync(absDir, { withFileTypes: true }); |
| 452 | } catch { |
| 453 | return; |
| 454 | } |
| 455 | for (const entry of entries) { |
| 456 | if (entry.name === '.git' || isCodeGraphDataDir(entry.name)) continue; |
| 457 | const abs = path.join(absDir, entry.name); |
| 458 | const rel = normalizePath(path.relative(rootDir, abs)); |
| 459 | if (!rel || rel.startsWith('..')) continue; |
| 460 | if (entry.isSymbolicLink()) { |
| 461 | try { |
| 462 | const st = fs.statSync(fs.realpathSync(abs)); |
| 463 | consider(abs, rel, st.isDirectory()); |
| 464 | } catch { |
| 465 | // broken symlink — skip |
| 466 | } |
| 467 | continue; |
no test coverage detected