UNPKG

pi-lens

Version:

Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo

114 lines (113 loc) • 4.1 kB
/** * Shared TreeSitterClient singleton + ext→language resolver. * * web-tree-sitter's WASM runtime is module-level — one per process. TRANSFER_BUFFER * and _ts_init are global, so every subsystem MUST share a single TreeSitterClient: * separate clients race on init and corrupt the shared WASM heap. This module is * that single seam — the dispatch tree-sitter runner, project scanner, module-report, * review-graph, and fact providers all obtain their client here, so a file parsed by * one is served from the shared tree cache for the others (one parse per write). * * Once the WASM runtime aborts (Emscripten abort()), the heap is corrupted with no * in-process recovery. markTreeSitterWasmAborted() poisons the singleton so EVERY * consumer skips further tree-sitter work (previously only the runner tracked this, * while the other subsystems kept calling the dead runtime). */ import * as path from "node:path"; import { TreeSitterClient } from "./tree-sitter-client.js"; let _shared = null; let _wasmAborted = false; /** The process-wide TreeSitterClient, or null once the WASM runtime has aborted. */ export function getSharedTreeSitterClient() { if (_wasmAborted) return null; _shared ??= new TreeSitterClient(); return _shared; } export function isTreeSitterWasmAborted() { return _wasmAborted; } /** * Poison the singleton after an unrecoverable Emscripten abort() — the module-level * WASM heap is corrupted, so no client can be used again this process. */ export function markTreeSitterWasmAborted() { _wasmAborted = true; _shared = null; } /** Test-only: reset the singleton + abort flag. */ export function _resetSharedTreeSitterClientForTests() { _shared = null; _wasmAborted = false; } // Grammar selection by extension. `.tsx` → the tsx grammar (parses JSX); `.jsx` → // the javascript grammar. NOTE: the project scanner keeps its OWN ext→lang map // because its query lookup is keyed by language id (it maps `.tsx`→typescript to // reuse typescript queries) — do not fold that one in without re-keying its queries. const EXT_TO_LANG = { ".ts": "typescript", ".mts": "typescript", ".cts": "typescript", ".tsx": "tsx", ".js": "javascript", ".mjs": "javascript", ".cjs": "javascript", ".jsx": "javascript", ".py": "python", ".go": "go", ".rs": "rust", ".rb": "ruby", ".c": "c", ".h": "c", ".cc": "cpp", ".cpp": "cpp", ".cxx": "cpp", ".c++": "cpp", ".hh": "cpp", ".hpp": "cpp", ".hxx": "cpp", ".inl": "cpp", ".ipp": "cpp", ".tpp": "cpp", ".txx": "cpp", ".cu": "cpp", ".hip": "cpp", ".cs": "csharp", ".php": "php", ".phtml": "php", ".php3": "php", ".php4": "php", ".php5": "php", ".css": "css", }; /** Resolve a tree-sitter grammar/language id from a file path's extension. */ export function resolveTreeSitterLanguage(filePath) { return EXT_TO_LANG[path.extname(filePath).toLowerCase()]; } /** * Parse `content` for `filePath` via the shared client and return the root node, * or null when the grammar is unavailable / parse fails / wasm aborted. init() * lazily loads the grammar (memoized) and must run before parseFile. */ export async function parseTreeSitterRoot(filePath, content) { const languageId = resolveTreeSitterLanguage(filePath); const client = getSharedTreeSitterClient(); if (!languageId || !client || !(await client.init())) return null; const tree = await client.parseFile(filePath, languageId, content); return tree ? tree.rootNode : null; } export function childrenOfType(node, type) { return (node.children ?? []).filter((c) => c && c.type === type); } export function firstChildOfType(node, type) { return (node.children ?? []).find((c) => c && c.type === type); } /** Depth-first walk, calling `visit` on every node (pre-order = source order). */ export function walk(node, visit) { visit(node); for (const child of node.children ?? []) { if (child) walk(child, visit); } }