// Structural provider adapter — wraps TreeSitterService into the shared // StructuralProvider contract from supi-code-runtime. import type { CalleesData, CallSite, CodeResult, ExportData, ImportData, NodeAtData, OutlineData, StructuralProvider, } from "@mrclrchtr/supi-code-runtime/api"; import type { CalleesAtResult, CallSiteMatch, ExportRecord, ImportRecord, NodeAtResult, OutlineItem, TreeSitterResult, TreeSitterService, } from "../types.ts"; /** * Create a StructuralProvider backed by a TreeSitterService. * Maps tree-sitter's nested `range` shapes into the flat range * fields used by the shared code-runtime types. */ export function createTreeSitterProvider(service: TreeSitterService): StructuralProvider { return { async calleesAt(file, line, character, depthOrOptions?) { const result = await service.calleesAt(file, line, character, depthOrOptions); return mapTreeSitterResult(result, mapCalleesAtResult); }, async exports(file, control?) { const result = control ? await service.exports(file, control) : await service.exports(file); return mapTreeSitterResult(result, mapExportRecords); }, async outline(file, control?) { const result = control ? await service.outline(file, control) : await service.outline(file); return mapTreeSitterResult(result, mapOutlineItems); }, async imports(file, control?) { const result = control ? await service.imports(file, control) : await service.imports(file); return mapTreeSitterResult(result, mapImportRecords); }, async nodeAt(file, line, character, control?) { const result = control ? await service.nodeAt(file, line, character, control) : await service.nodeAt(file, line, character); return mapTreeSitterResult(result, mapNodeAtResult); }, async callSites(file, control?) { const result = control ? await service.callSites(file, control) : await service.callSites(file); return mapTreeSitterResult(result, mapCallSites); }, }; } // ── Generic result mapper ───────────────────────────────────────────── function mapTreeSitterResult( result: TreeSitterResult, mapData: (data: T) => U, ): CodeResult { switch (result.kind) { case "success": return { kind: "success", data: mapData(result.data) }; case "unsupported-language": return { kind: "unsupported-language", file: result.file, message: result.message }; case "file-access-error": return { kind: "file-access-error", file: result.file, message: result.message }; case "validation-error": return { kind: "validation-error", message: result.message }; case "runtime-error": return { kind: "runtime-error", message: result.message }; } } // ── Data mappers (nested range → flat fields) ───────────────────────── type RangeLike = { startLine: number; startCharacter: number; endLine: number; endCharacter: number; }; function takeRange(r: RangeLike) { return { startLine: r.startLine, startCharacter: r.startCharacter, endLine: r.endLine, endCharacter: r.endCharacter, }; } function mapOutlineItems(items: OutlineItem[]): OutlineData[] { return items.map((item) => ({ name: item.name, kind: item.kind, ...takeRange(item.range), children: item.children ? mapOutlineItems(item.children) : undefined, })); } function mapExportRecords(records: ExportRecord[]): ExportData[] { return records.map((record) => ({ name: record.name, kind: record.kind, ...takeRange(record.range), moduleSpecifier: record.moduleSpecifier, })); } function mapImportRecords(records: ImportRecord[]): ImportData[] { return records.map((record) => ({ moduleSpecifier: record.moduleSpecifier, ...takeRange(record.range), })); } function mapNodeAtResult(result: NodeAtResult): NodeAtData { return { type: result.type, ...takeRange(result.range), text: result.text, ancestry: result.ancestry.map((a) => ({ type: a.type, ...takeRange(a.range), })), }; } function mapCalleesAtResult(result: CalleesAtResult): CalleesData { return { enclosingScope: { name: result.enclosingScope.name, startLine: result.enclosingScope.range.startLine, endLine: result.enclosingScope.range.endLine, }, callees: result.callees.map((c) => ({ name: c.name, startLine: c.range.startLine, })), depth: result.depth, }; } function mapCallSites(matches: CallSiteMatch[]): CallSite[] { return matches.map((m) => ({ name: m.name, startLine: m.startLine })); }