* 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)
| 4496 | * results across all matching symbols (e.g., multiple classes with an `execute` method). |
| 4497 | */ |
| 4498 | private findAllSymbols(cg: CodeGraph, symbol: string): { nodes: Node[]; note: string } { |
| 4499 | // Nix option paths: the declaration is stored as `options.<path>` and |
| 4500 | // config writes carry longer/quoted tails (`<path>."git/config".text`), |
| 4501 | // so a dotted option token (`xdg.configFile`, `launchd.user.agents`) has |
| 4502 | // no exact-name node and would degrade to bare-tail FTS soup — burying |
| 4503 | // the declaration hub the nix-option-path edges hang off. Resolve the |
| 4504 | // convention directly: declaration first, then the exact write, then a |
| 4505 | // capped prefix scan of write sites. Three index hits; non-nix graphs |
| 4506 | // fall straight through. |
| 4507 | if (/^[a-z][\w'-]*(?:\.[\w'-]+)+$/.test(symbol)) { |
| 4508 | const optionHits = [ |
| 4509 | ...cg.getNodesByName(`options.${symbol}`), |
| 4510 | ...cg.getNodesByName(symbol), |
| 4511 | ...cg.getNodesByNamePrefix(`${symbol}.`, 12), |
| 4512 | ].filter((n) => n.language === 'nix'); |
| 4513 | if (optionHits.length > 0) { |
| 4514 | const seen = new Set<string>(); |
| 4515 | const nodes = optionHits.filter((n) => !seen.has(n.id) && !!seen.add(n.id)).slice(0, 10); |
| 4516 | return { nodes, note: '' }; |
| 4517 | } |
| 4518 | } |
| 4519 | let results = cg.searchNodes(symbol, { limit: 50 }); |
| 4520 | |
| 4521 | // Mirror the fallback in `findSymbol` for qualified queries — FTS |
| 4522 | // strips colons, so a module-qualified lookup needs a second pass |
| 4523 | // by the bare last part. |
| 4524 | if (results.length === 0 && /[.\/]|::/.test(symbol)) { |
| 4525 | const tail = lastQualifierPart(symbol); |
| 4526 | if (tail && tail !== symbol) results = cg.searchNodes(tail, { limit: 50 }); |
| 4527 | } |
| 4528 | |
| 4529 | if (results.length === 0) { |
| 4530 | return { nodes: [], note: '' }; |
| 4531 | } |
| 4532 | |
| 4533 | const exactMatches = results.filter(r => this.matchesSymbol(r.node, symbol)); |
| 4534 | |
| 4535 | if (exactMatches.length <= 1) { |
| 4536 | const node = exactMatches[0]?.node ?? results[0]!.node; |
| 4537 | return { nodes: [node], note: '' }; |
| 4538 | } |
| 4539 | |
| 4540 | // Same generated-file down-rank as findSymbol — keeps callers/callees |
| 4541 | // /impact aggregation aligned (a query against "Send" returns the |
| 4542 | // hand-written implementations before the protobuf scaffold). |
| 4543 | const ranked = [...exactMatches].sort((a, b) => { |
| 4544 | const aGen = isGeneratedFile(a.node.filePath) ? 1 : 0; |
| 4545 | const bGen = isGeneratedFile(b.node.filePath) ? 1 : 0; |
| 4546 | return aGen - bGen; |
| 4547 | }); |
| 4548 | |
| 4549 | const locations = ranked.map(r => |
| 4550 | `${r.node.kind} at ${r.node.filePath}:${r.node.startLine}` |
| 4551 | ); |
| 4552 | const note = `\n\n> **Note:** Aggregated results across ${ranked.length} symbols named "${symbol}": ${locations.join(', ')}`; |
| 4553 | return { nodes: ranked.map(r => r.node), note }; |
| 4554 | } |
| 4555 |
no test coverage detected