* Rust `use` declarations, flattened to `localName → full path`. * * Rust is the one supported language with NO `ImportMapping` extraction (see * `extractImportMappings`), so this is the only channel that can tell whether * a bare type name in a Rust file was brought in by a `use`. Handles neste
(content: string)
| 2561 | * name), and `as` aliases. |
| 2562 | */ |
| 2563 | function collectRustUseBindings(content: string): Map<string, string> { |
| 2564 | const out = new Map<string, string>(); |
| 2565 | |
| 2566 | // Expand one level of `{...}` at a time so `a::{b::{C, D}, E}` flattens. |
| 2567 | const expand = (spec: string): string[] => { |
| 2568 | const open = spec.indexOf('{'); |
| 2569 | if (open === -1) return [spec.trim()]; |
| 2570 | const prefix = spec.slice(0, open); |
| 2571 | let depth = 0; |
| 2572 | let close = -1; |
| 2573 | for (let i = open; i < spec.length; i++) { |
| 2574 | if (spec[i] === '{') depth++; |
| 2575 | else if (spec[i] === '}') { |
| 2576 | depth--; |
| 2577 | if (depth === 0) { close = i; break; } |
| 2578 | } |
| 2579 | } |
| 2580 | if (close === -1) return []; |
| 2581 | const suffix = spec.slice(close + 1); |
| 2582 | const inner = spec.slice(open + 1, close); |
| 2583 | const parts: string[] = []; |
| 2584 | let depth2 = 0; |
| 2585 | let start = 0; |
| 2586 | for (let i = 0; i <= inner.length; i++) { |
| 2587 | const ch = inner[i]; |
| 2588 | if (ch === '{') depth2++; |
| 2589 | else if (ch === '}') depth2--; |
| 2590 | if (i === inner.length || (ch === ',' && depth2 === 0)) { |
| 2591 | const seg = inner.slice(start, i).trim(); |
| 2592 | if (seg) parts.push(seg); |
| 2593 | start = i + 1; |
| 2594 | } |
| 2595 | } |
| 2596 | return parts.flatMap((p) => expand(prefix + p + suffix)); |
| 2597 | }; |
| 2598 | |
| 2599 | // `use` items end at the first `;`. Attributes/visibility (`pub use`) are |
| 2600 | // irrelevant to the binding itself. |
| 2601 | const useRe = /(^|\n)\s*(?:pub(?:\([^)]*\))?\s+)?use\s+([^;]+);/g; |
| 2602 | let m: RegExpExecArray | null; |
| 2603 | while ((m = useRe.exec(content)) !== null) { |
| 2604 | for (const spec of expand(m[2]!.replace(/\s+/g, ' '))) { |
| 2605 | const aliasMatch = /^(.*?)\s+as\s+([A-Za-z_]\w*)$/.exec(spec); |
| 2606 | const rawPath = (aliasMatch ? aliasMatch[1]! : spec).trim(); |
| 2607 | if (!rawPath || rawPath.endsWith('*')) continue; |
| 2608 | const segments = rawPath.split('::').map((s) => s.trim()).filter(Boolean); |
| 2609 | const leaf = segments[segments.length - 1]; |
| 2610 | if (!leaf) continue; |
| 2611 | const local = aliasMatch ? aliasMatch[2]! : leaf; |
| 2612 | out.set(local, segments.join('::')); |
| 2613 | } |
| 2614 | } |
| 2615 | return out; |
| 2616 | } |
| 2617 | |
| 2618 | /** |
| 2619 | * Is `name`, as used in `ref`'s file, bound by an import whose module lives |