/* eslint-disable @typescript-eslint/no-explicit-any */ import type { SymbolInfo } from '@/contracts/code-intelligence'; /** Extract a declaration, unwrapping export/decorator containers. */ export function declarationSymbol(node: any, source: string, kinds: ReadonlyMap): SymbolInfo | undefined { const wrapped = node.namedChildren?.find((child: any) => kinds.has(child.type)); const declaration = wrapped ?? node; const kind = kinds.get(declaration.type); if (!kind) return undefined; const nameNode = declaration.childForFieldName?.('name') ?? declaration.childForFieldName?.('left'); const name = nameNode?.text ?? declarationName(declaration) ?? (declaration.type === 'impl_item' ? implName(declaration) : undefined); if (!name) return undefined; const start = declaration.startPosition.row; const end = Math.max(start, declaration.endPosition.row); const firstLineEnd = source.indexOf('\n', declaration.startIndex); const line = source.slice(declaration.startIndex, firstLineEnd < 0 ? source.length : firstLineEnd); const signature = compactSignature(line); return { name, kind, range: [start, end], signature }; } function declarationName(node: any): string | undefined { const preferred = new Set(['type_identifier', 'identifier', 'field_identifier', 'property_identifier']); const visit = (candidate: any): string | undefined => { if (preferred.has(candidate.type)) return candidate.text; const named = candidate.childForFieldName?.('name'); if (named?.text) return named.text; for (const child of candidate.namedChildren ?? []) { const found = visit(child); if (found) return found; } return undefined; }; return visit(node); } function implName(node: any): string | undefined { const target = node.childForFieldName?.('type')?.text; if (target) return target; const text = node.text.replace(/^impl\s+/, '').replace(/\s*\{[\s\S]*$/, '').trim(); return text.split(/\s+for\s+/).pop()?.trim() || 'impl'; } function compactSignature(line: string): string { return line .trim() .replace(/\s+/g, ' ') .replace(/\s*\{.*$/, '') .slice(0, 240); } /** Collect a top-level declaration and selected nested declarations in source order. */ export function declarationSymbols( node: any, source: string, kinds: ReadonlyMap, nestedKinds: ReadonlyMap, containers: ReadonlySet = new Set(), ): SymbolInfo[] { const symbols: SymbolInfo[] = []; const outer = declarationSymbol(node, source, kinds); if (outer) symbols.push(outer); collectContainerMembers(node, source, nestedKinds, containers, symbols); return symbols.sort((a, b) => (a.range?.[0] ?? 0) - (b.range?.[0] ?? 0)); } function collectContainerMembers( node: any, source: string, nestedKinds: ReadonlyMap, containers: ReadonlySet, output: SymbolInfo[], ): void { if (containers.has(node.type)) { collectNested(node, source, nestedKinds, output); return; } for (const child of node.namedChildren ?? []) { collectContainerMembers(child, source, nestedKinds, containers, output); } } function collectNested( node: any, source: string, nestedKinds: ReadonlyMap, output: SymbolInfo[], ): void { for (const child of node.namedChildren ?? []) { const symbol = declarationSymbol(child, source, nestedKinds); if (symbol) output.push(symbol); else collectNested(child, source, nestedKinds, output); } }