| 625 | * Single source of truth for indexer and watcher scope — they must not diverge. |
| 626 | */ |
| 627 | export class ScopeIgnore { |
| 628 | private embedded: Array<{ root: string; matcher: Ignore }>; |
| 629 | private defaults: Ignore = defaultsOnlyIgnore(); |
| 630 | constructor( |
| 631 | private rootMatcher: Ignore, |
| 632 | embedded: Array<{ root: string; matcher: Ignore }>, |
| 633 | /** |
| 634 | * Project `codegraph.json` `exclude` patterns (#999), matched against the |
| 635 | * full root-relative path. Wins over everything else — an explicit user |
| 636 | * exclude applies even to tracked files and even inside embedded repos. |
| 637 | */ |
| 638 | private exclude: Ignore | null = null, |
| 639 | /** |
| 640 | * Project `codegraph.json` `include` patterns — first-party source forced |
| 641 | * INTO the index despite `.gitignore`. When a path matches, it is NOT |
| 642 | * ignored (so the watcher watches it), overriding `.gitignore`/`rootMatcher` |
| 643 | * — but never `exclude` (checked first) and never a built-in default-ignored |
| 644 | * dir. `includeRoots` are the static prefixes so a gitignored ANCESTOR |
| 645 | * directory of an included subtree still isn't pruned by the directory |
| 646 | * walker/watcher. |
| 647 | */ |
| 648 | private include: Ignore | null = null, |
| 649 | private includeRoots: string[] = [], |
| 650 | ) { |
| 651 | // Longest root first so paths in nested embedded repos hit the innermost matcher. |
| 652 | this.embedded = [...embedded].sort((a, b) => b.root.length - a.root.length); |
| 653 | } |
| 654 | |
| 655 | ignores(rel: string): boolean { |
| 656 | // User `exclude` (#999) is checked first and against the full root-relative |
| 657 | // path: it must drop git-TRACKED paths (which `.gitignore` can't) and apply |
| 658 | // everywhere, including ancestors of embedded repos. |
| 659 | if (this.exclude && this.exclude.ignores(rel)) return true; |
| 660 | // User `include`: force first-party source in despite `.gitignore`. Never |
| 661 | // resurfaces a built-in default-ignored dir (node_modules/dist/…), so an |
| 662 | // include pattern can't accidentally pull in dependency/build trees. |
| 663 | if (this.include && !this.defaults.ignores(rel)) { |
| 664 | if (rel.endsWith('/')) { |
| 665 | // A directory on (or leading to) an included subtree must stay walkable |
| 666 | // so the watcher/walker descends to reach the forced-in files. |
| 667 | if (this.includeRoots.some((r) => r.startsWith(rel) || rel.startsWith(r))) return false; |
| 668 | } else if (this.include.ignores(rel)) { |
| 669 | return false; |
| 670 | } |
| 671 | } |
| 672 | for (const { root, matcher } of this.embedded) { |
| 673 | if (rel.startsWith(root)) { |
| 674 | const inner = rel.slice(root.length); |
| 675 | if (inner === '') return false; |
| 676 | // Built-in defaults apply to the FULL path uniformly (#407) — an |
| 677 | // embedded repo inside node_modules (an npm git-dependency) must stay |
| 678 | // excluded even though its own rules wouldn't ignore its files. |
| 679 | return this.defaults.ignores(rel) || matcher.ignores(inner); |
| 680 | } |
| 681 | } |
| 682 | // Never prune a directory that leads to an embedded repo. |
| 683 | if (rel.endsWith('/') && this.embedded.some(({ root }) => root.startsWith(rel))) { |
| 684 | return false; |
nothing calls this directly
no test coverage detected