* Find ALL symbols matching a name. Used by callers/callees/impact to aggregate * results across all matching symbols (e.g., multiple classes with an `execute` method).
(cg: CodeGraph, symbol: string)
| 4733 | * results across all matching symbols (e.g., multiple classes with an `execute` method). |
| 4734 | */ |
| 4735 | private findAllSymbols(cg: CodeGraph, symbol: string): { nodes: Node[]; note: string } { |
| 4736 | // Nix option paths: the declaration is stored as `options.<path>` and |
| 4737 | // config writes carry longer/quoted tails (`<path>."git/config".text`), |
| 4738 | // so a dotted option token (`xdg.configFile`, `launchd.user.agents`) has |
| 4739 | // no exact-name node and would degrade to bare-tail FTS soup — burying |
| 4740 | // the declaration hub the nix-option-path edges hang off. Resolve the |
| 4741 | // convention directly: declaration first, then the exact write, then a |
| 4742 | // capped prefix scan of write sites. Three index hits; non-nix graphs |
| 4743 | // fall straight through. |
| 4744 | if (/^[a-z][\w'-]*(?:\.[\w'-]+)+$/.test(symbol)) { |
| 4745 | const optionHits = [ |
| 4746 | ...cg.getNodesByName(`options.${symbol}`), |
| 4747 | ...cg.getNodesByName(symbol), |
| 4748 | ...cg.getNodesByNamePrefix(`${symbol}.`, 12), |
| 4749 | ].filter((n) => n.language === 'nix'); |
| 4750 | if (optionHits.length > 0) { |
| 4751 | const seen = new Set<string>(); |
| 4752 | const nodes = optionHits.filter((n) => !seen.has(n.id) && !!seen.add(n.id)).slice(0, 10); |
| 4753 | return { nodes, note: '' }; |
| 4754 | } |
| 4755 | } |
| 4756 | let results = cg.searchNodes(symbol, { limit: 50 }); |
| 4757 | |
| 4758 | // Mirror the fallback in `findSymbol` for qualified queries — FTS |
| 4759 | // strips colons, so a module-qualified lookup needs a second pass |
| 4760 | // by the bare last part. |
| 4761 | if (results.length === 0 && /[.\/]|::/.test(symbol)) { |
| 4762 | const tail = lastQualifierPart(symbol); |
| 4763 | if (tail && tail !== symbol) results = cg.searchNodes(tail, { limit: 50 }); |
| 4764 | } |
| 4765 | |
| 4766 | if (results.length === 0) { |
| 4767 | return { nodes: [], note: '' }; |
| 4768 | } |
| 4769 | |
| 4770 | const exactMatches = results.filter(r => this.matchesSymbol(r.node, symbol)); |
| 4771 | |
| 4772 | if (exactMatches.length <= 1) { |
| 4773 | const node = exactMatches[0]?.node ?? results[0]!.node; |
| 4774 | return { nodes: [node], note: '' }; |
| 4775 | } |
| 4776 | |
| 4777 | // Same generated-file down-rank as findSymbol — keeps callers/callees |
| 4778 | // /impact aggregation aligned (a query against "Send" returns the |
| 4779 | // hand-written implementations before the protobuf scaffold). |
| 4780 | const ranked = [...exactMatches].sort((a, b) => { |
| 4781 | const aGen = isGeneratedFile(a.node.filePath) ? 1 : 0; |
| 4782 | const bGen = isGeneratedFile(b.node.filePath) ? 1 : 0; |
| 4783 | return aGen - bGen; |
| 4784 | }); |
| 4785 | |
| 4786 | const locations = ranked.map(r => |
| 4787 | `${r.node.kind} at ${r.node.filePath}:${r.node.startLine}` |
| 4788 | ); |
| 4789 | const note = `\n\n> **Note:** Aggregated results across ${ranked.length} symbols named "${symbol}": ${locations.join(', ')}`; |
| 4790 | return { nodes: ranked.map(r => r.node), note }; |
| 4791 | } |
| 4792 |
no test coverage detected