diff --git a/packages/core/package.json b/packages/core/package.json index 46d3b93..4f41917 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -12,5 +12,10 @@ "@types/node": "^25.5.0", "typescript": "^5.7.0", "vitest": "^3.1.0" + }, + "dependencies": { + "tree-sitter-javascript": "^0.25.0", + "tree-sitter-typescript": "^0.23.2", + "web-tree-sitter": "^0.26.6" } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 37327d1..3ab702e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,2 +1,3 @@ export * from "./types.js"; export * from "./persistence/index.js"; +export { TreeSitterPlugin } from "./plugins/tree-sitter-plugin.js"; diff --git a/packages/core/src/plugins/tree-sitter-plugin.test.ts b/packages/core/src/plugins/tree-sitter-plugin.test.ts new file mode 100644 index 0000000..00724f5 --- /dev/null +++ b/packages/core/src/plugins/tree-sitter-plugin.test.ts @@ -0,0 +1,282 @@ +import { describe, it, expect, beforeAll } from "vitest"; +import { TreeSitterPlugin } from "./tree-sitter-plugin.js"; + +describe("TreeSitterPlugin", () => { + let plugin: TreeSitterPlugin; + + beforeAll(async () => { + plugin = new TreeSitterPlugin(); + await plugin.init(); + }); + + describe("analyzeFile", () => { + it("should extract function declarations from TypeScript", () => { + const code = ` +function greet(name: string): string { + return "Hello " + name; +} + +function add(a: number, b: number): number { + return a + b; +} +`; + const result = plugin.analyzeFile("test.ts", code); + + expect(result.functions).toHaveLength(2); + expect(result.functions[0].name).toBe("greet"); + expect(result.functions[0].params).toEqual(["name"]); + expect(result.functions[0].returnType).toBe("string"); + expect(result.functions[0].lineRange[0]).toBeGreaterThan(0); + + expect(result.functions[1].name).toBe("add"); + expect(result.functions[1].params).toEqual(["a", "b"]); + expect(result.functions[1].returnType).toBe("number"); + }); + + it("should extract arrow functions assigned to variables", () => { + const code = ` +const multiply = (a: number, b: number): number => a * b; +const log = (msg: string) => { console.log(msg); }; +`; + const result = plugin.analyzeFile("test.ts", code); + + expect(result.functions).toHaveLength(2); + expect(result.functions[0].name).toBe("multiply"); + expect(result.functions[0].params).toEqual(["a", "b"]); + expect(result.functions[0].returnType).toBe("number"); + + expect(result.functions[1].name).toBe("log"); + expect(result.functions[1].params).toEqual(["msg"]); + }); + + it("should extract class declarations with methods and properties", () => { + const code = ` +class Calculator { + private value: number; + public name: string; + + constructor(initial: number) { + this.value = initial; + } + + add(n: number): number { + return this.value + n; + } + + subtract(n: number): number { + return this.value - n; + } +} +`; + const result = plugin.analyzeFile("test.ts", code); + + expect(result.classes).toHaveLength(1); + const cls = result.classes[0]; + expect(cls.name).toBe("Calculator"); + expect(cls.methods).toContain("constructor"); + expect(cls.methods).toContain("add"); + expect(cls.methods).toContain("subtract"); + expect(cls.properties).toContain("value"); + expect(cls.properties).toContain("name"); + expect(cls.lineRange[0]).toBeGreaterThan(0); + }); + + it("should extract import statements with specifiers and source", () => { + const code = ` +import { foo, bar } from './utils'; +import * as path from 'path'; +import type { MyType } from './types'; +import defaultExport from './module'; +`; + const result = plugin.analyzeFile("test.ts", code); + + expect(result.imports).toHaveLength(4); + + expect(result.imports[0].source).toBe("./utils"); + expect(result.imports[0].specifiers).toEqual(["foo", "bar"]); + expect(result.imports[0].lineNumber).toBeGreaterThan(0); + + expect(result.imports[1].source).toBe("path"); + expect(result.imports[1].specifiers).toEqual(["* as path"]); + + expect(result.imports[2].source).toBe("./types"); + expect(result.imports[2].specifiers).toEqual(["MyType"]); + + expect(result.imports[3].source).toBe("./module"); + expect(result.imports[3].specifiers).toEqual(["defaultExport"]); + }); + + it("should extract export names", () => { + const code = ` +export function greet(name: string): string { + return "Hello " + name; +} + +export const add = (a: number, b: number): number => a + b; + +export class Logger {} + +const helper = () => true; +export { helper }; +`; + const result = plugin.analyzeFile("test.ts", code); + + const exportNames = result.exports.map((e) => e.name); + expect(exportNames).toContain("greet"); + expect(exportNames).toContain("add"); + expect(exportNames).toContain("Logger"); + expect(exportNames).toContain("helper"); + expect(result.exports.length).toBe(4); + }); + + it("should extract default exports", () => { + const code = ` +export default class AppController {} +`; + const result = plugin.analyzeFile("test.ts", code); + + expect(result.classes).toHaveLength(1); + expect(result.classes[0].name).toBe("AppController"); + + const exportNames = result.exports.map((e) => e.name); + expect(exportNames).toContain("default"); + }); + + it("should extract functions from JavaScript files", () => { + const code = ` +function hello() { + return "world"; +} + +const double = (x) => x * 2; +`; + const result = plugin.analyzeFile("test.js", code); + + expect(result.functions).toHaveLength(2); + expect(result.functions[0].name).toBe("hello"); + expect(result.functions[0].params).toEqual([]); + + expect(result.functions[1].name).toBe("double"); + expect(result.functions[1].params).toEqual(["x"]); + }); + + it("should handle a comprehensive TypeScript file", () => { + const code = ` +import { EventEmitter } from 'events'; +import type { Config } from './config'; + +export interface Options { + timeout: number; +} + +export class Server extends EventEmitter { + private port: number; + + constructor(port: number) { + super(); + this.port = port; + } + + start(): void { + console.log("Starting on port " + this.port); + } + + stop(): Promise { + return Promise.resolve(); + } +} + +export function createServer(port: number): Server { + return new Server(port); +} + +export const DEFAULT_PORT = 3000; +`; + const result = plugin.analyzeFile("server.ts", code); + + expect(result.imports.length).toBeGreaterThanOrEqual(2); + expect(result.classes).toHaveLength(1); + expect(result.classes[0].name).toBe("Server"); + expect(result.classes[0].methods).toContain("constructor"); + expect(result.classes[0].methods).toContain("start"); + expect(result.classes[0].methods).toContain("stop"); + expect(result.classes[0].properties).toContain("port"); + + expect(result.functions.some((f) => f.name === "createServer")).toBe(true); + + const exportNames = result.exports.map((e) => e.name); + expect(exportNames).toContain("Server"); + expect(exportNames).toContain("createServer"); + }); + }); + + describe("resolveImports", () => { + it("should resolve relative imports to absolute paths", () => { + const code = ` +import { foo } from './utils'; +import { bar } from '../shared/helpers'; +import * as path from 'path'; +`; + const result = plugin.resolveImports("/project/src/index.ts", code); + + expect(result).toHaveLength(3); + + // Relative imports should be resolved + expect(result[0].source).toBe("./utils"); + expect(result[0].resolvedPath).toContain("utils"); + expect(result[0].specifiers).toEqual(["foo"]); + + expect(result[1].source).toBe("../shared/helpers"); + expect(result[1].resolvedPath).toContain("shared"); + expect(result[1].specifiers).toEqual(["bar"]); + + // External packages keep their original path + expect(result[2].source).toBe("path"); + expect(result[2].resolvedPath).toBe("path"); + expect(result[2].specifiers).toEqual(["* as path"]); + }); + }); + + describe("extractCallGraph", () => { + it("should extract function calls within functions", () => { + const code = ` +function greet(name: string): string { + return formatMessage("Hello " + name); +} + +function formatMessage(msg: string): string { + return msg.trim(); +} + +function main() { + const result = greet("World"); + console.log(result); +} +`; + const result = plugin.extractCallGraph!("test.ts", code); + + expect(result.length).toBeGreaterThan(0); + + const greetCall = result.find( + (e) => e.caller === "main" && e.callee === "greet", + ); + expect(greetCall).toBeDefined(); + + const formatCall = result.find( + (e) => e.caller === "greet" && e.callee === "formatMessage", + ); + expect(formatCall).toBeDefined(); + }); + }); + + describe("plugin metadata", () => { + it("should have correct name", () => { + expect(plugin.name).toBe("tree-sitter"); + }); + + it("should support typescript and javascript", () => { + expect(plugin.languages).toContain("typescript"); + expect(plugin.languages).toContain("javascript"); + }); + }); +}); diff --git a/packages/core/src/plugins/tree-sitter-plugin.ts b/packages/core/src/plugins/tree-sitter-plugin.ts new file mode 100644 index 0000000..81897c7 --- /dev/null +++ b/packages/core/src/plugins/tree-sitter-plugin.ts @@ -0,0 +1,652 @@ +import { createRequire } from "node:module"; +import { dirname, resolve, extname } from "node:path"; +import type { + AnalyzerPlugin, + StructuralAnalysis, + ImportResolution, + CallGraphEntry, +} from "../types.js"; + +// web-tree-sitter uses CJS internally; we need createRequire for .wasm resolution +const require = createRequire(import.meta.url); + +type TreeSitterParser = import("web-tree-sitter").Parser; +type TreeSitterLanguage = import("web-tree-sitter").Language; +type TreeSitterNode = import("web-tree-sitter").Node; + +function languageKeyFromPath(filePath: string): string { + const ext = extname(filePath).toLowerCase(); + switch (ext) { + case ".ts": + return "typescript"; + case ".tsx": + return "tsx"; + case ".js": + case ".mjs": + case ".cjs": + case ".jsx": + return "javascript"; + default: + throw new Error(`Unsupported file extension: ${ext}`); + } +} + +/** + * Recursively traverse an AST tree, calling the visitor for each node. + */ +function traverse( + node: TreeSitterNode, + visitor: (node: TreeSitterNode) => void, +): void { + visitor(node); + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child) traverse(child, visitor); + } +} + +/** + * Extract the string fragment (unquoted value) from a string node. + */ +function getStringValue(node: TreeSitterNode): string { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child && child.type === "string_fragment") { + return child.text; + } + } + // Fallback: strip quotes + const text = node.text; + return text.replace(/^['"`]|['"`]$/g, ""); +} + +/** + * Extract parameter names from a formal_parameters node. + */ +function extractParams(paramsNode: TreeSitterNode | null): string[] { + if (!paramsNode) return []; + const params: string[] = []; + for (let i = 0; i < paramsNode.childCount; i++) { + const child = paramsNode.child(i); + if (!child) continue; + if ( + child.type === "required_parameter" || + child.type === "optional_parameter" + ) { + const ident = + child.childForFieldName("pattern") ?? + child.childForFieldName("name"); + if (ident) { + params.push(ident.text); + } else { + // Fallback: first identifier child + for (let j = 0; j < child.childCount; j++) { + const c = child.child(j); + if (c && c.type === "identifier") { + params.push(c.text); + break; + } + } + } + } else if (child.type === "identifier") { + // JavaScript parameters (no type annotation) + params.push(child.text); + } else if ( + child.type === "rest_pattern" || + child.type === "rest_element" + ) { + const ident = child.children.find( + (c) => c.type === "identifier", + ); + if (ident) params.push("..." + ident.text); + } + } + return params; +} + +/** + * Extract return type annotation from a function-like node. + */ +function extractReturnType( + node: TreeSitterNode, +): string | undefined { + const typeAnnotation = node.childForFieldName("return_type"); + if (typeAnnotation && typeAnnotation.type === "type_annotation") { + const text = typeAnnotation.text; + return text.startsWith(":") ? text.slice(1).trim() : text; + } + return undefined; +} + +/** + * Extract import specifiers from an import_clause node. + */ +function extractImportSpecifiers( + importClause: TreeSitterNode, +): string[] { + const specifiers: string[] = []; + + for (let i = 0; i < importClause.childCount; i++) { + const child = importClause.child(i); + if (!child) continue; + + if (child.type === "named_imports") { + for (let j = 0; j < child.childCount; j++) { + const spec = child.child(j); + if (spec && spec.type === "import_specifier") { + const alias = spec.childForFieldName("alias"); + const name = spec.childForFieldName("name"); + specifiers.push( + alias ? alias.text : name ? name.text : spec.text, + ); + } + } + } else if (child.type === "namespace_import") { + const ident = child.children.find( + (c) => c.type === "identifier", + ); + if (ident) specifiers.push("* as " + ident.text); + } else if (child.type === "identifier") { + // default import: import foo from '...' + specifiers.push(child.text); + } + } + + return specifiers; +} + +export class TreeSitterPlugin implements AnalyzerPlugin { + readonly name = "tree-sitter"; + readonly languages = ["typescript", "javascript"]; + + // Pre-loaded parser constructor and languages (set by init()) + private _ParserClass: + | (new () => TreeSitterParser) + | null = null; + private _languages = new Map(); + private _initialized = false; + + /** + * Initialize the plugin by loading the WASM module and all language grammars. + * Must be called (and awaited) before any synchronous methods. + */ + async init(): Promise { + if (this._initialized) return; + + const mod = await import("web-tree-sitter"); + const ParserCls = mod.Parser; + const LanguageCls = mod.Language; + + await ParserCls.init(); + this._ParserClass = ParserCls as unknown as new () => TreeSitterParser; + + // Pre-load all supported language grammars + const tsWasm = require.resolve( + "tree-sitter-typescript/tree-sitter-typescript.wasm", + ); + const tsxWasm = require.resolve( + "tree-sitter-typescript/tree-sitter-tsx.wasm", + ); + const jsWasm = require.resolve( + "tree-sitter-javascript/tree-sitter-javascript.wasm", + ); + + const [tsLang, tsxLang, jsLang] = await Promise.all([ + LanguageCls.load(tsWasm), + LanguageCls.load(tsxWasm), + LanguageCls.load(jsWasm), + ]); + + this._languages.set("typescript", tsLang); + this._languages.set("tsx", tsxLang); + this._languages.set("javascript", jsLang); + this._initialized = true; + } + + /** + * Create a parser set to the appropriate language for the given file. + * This is synchronous because all languages are pre-loaded during init(). + */ + private getParser(filePath: string): TreeSitterParser { + if (!this._initialized || !this._ParserClass) { + throw new Error( + "TreeSitterPlugin.init() must be called before use", + ); + } + const langKey = languageKeyFromPath(filePath); + const lang = this._languages.get(langKey); + if (!lang) { + throw new Error(`Language not loaded: ${langKey}`); + } + const parser = new this._ParserClass(); + parser.setLanguage(lang); + return parser; + } + + analyzeFile( + filePath: string, + content: string, + ): StructuralAnalysis { + const parser = this.getParser(filePath); + const tree = parser.parse(content); + if (!tree) { + parser.delete(); + return { functions: [], classes: [], imports: [], exports: [] }; + } + + const functions: StructuralAnalysis["functions"] = []; + const classes: StructuralAnalysis["classes"] = []; + const imports: StructuralAnalysis["imports"] = []; + const exports: StructuralAnalysis["exports"] = []; + const exportedNames = new Set(); + + const root = tree.rootNode; + for (let i = 0; i < root.childCount; i++) { + const node = root.child(i); + if (!node) continue; + this.processTopLevelNode( + node, + functions, + classes, + imports, + exports, + exportedNames, + ); + } + + tree.delete(); + parser.delete(); + + return { functions, classes, imports, exports }; + } + + resolveImports( + filePath: string, + content: string, + ): ImportResolution[] { + const analysis = this.analyzeFile(filePath, content); + const dir = dirname(filePath); + + return analysis.imports.map((imp) => { + let resolvedPath: string; + if ( + imp.source.startsWith("./") || + imp.source.startsWith("../") + ) { + resolvedPath = resolve(dir, imp.source); + } else { + resolvedPath = imp.source; + } + return { + source: imp.source, + resolvedPath, + specifiers: imp.specifiers, + }; + }); + } + + extractCallGraph( + filePath: string, + content: string, + ): CallGraphEntry[] { + const parser = this.getParser(filePath); + const tree = parser.parse(content); + if (!tree) { + parser.delete(); + return []; + } + + const entries: CallGraphEntry[] = []; + const functionStack: string[] = []; + + const walkForCalls = (node: TreeSitterNode) => { + const isFunctionLike = + node.type === "function_declaration" || + node.type === "method_definition" || + node.type === "arrow_function" || + node.type === "function_expression"; + + let pushedName = false; + if (isFunctionLike) { + let name: string | undefined; + if (node.type === "function_declaration") { + name = ( + node.childForFieldName("name") ?? + node.children.find((c) => c.type === "identifier") + )?.text; + } else if (node.type === "method_definition") { + name = node.children.find( + (c) => c.type === "property_identifier", + )?.text; + } else if ( + node.type === "arrow_function" || + node.type === "function_expression" + ) { + const parent = node.parent; + if (parent && parent.type === "variable_declarator") { + name = parent.childForFieldName("name")?.text; + } + } + if (name) { + functionStack.push(name); + pushedName = true; + } + } + + if (node.type === "call_expression") { + const callee = node.childForFieldName("function"); + if (callee && functionStack.length > 0) { + entries.push({ + caller: functionStack[functionStack.length - 1], + callee: callee.text, + lineNumber: node.startPosition.row + 1, + }); + } + } + + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child) walkForCalls(child); + } + + if (pushedName) { + functionStack.pop(); + } + }; + + walkForCalls(tree.rootNode); + + tree.delete(); + parser.delete(); + + return entries; + } + + // ---- Private extraction helpers ---- + + private processTopLevelNode( + node: TreeSitterNode, + functions: StructuralAnalysis["functions"], + classes: StructuralAnalysis["classes"], + imports: StructuralAnalysis["imports"], + exports: StructuralAnalysis["exports"], + exportedNames: Set, + ): void { + switch (node.type) { + case "function_declaration": + this.extractFunction(node, functions); + break; + + case "class_declaration": + this.extractClass(node, classes); + break; + + case "lexical_declaration": + case "variable_declaration": + this.extractVariableDeclarations(node, functions); + break; + + case "import_statement": + this.extractImport(node, imports); + break; + + case "export_statement": + this.processExportStatement( + node, + functions, + classes, + imports, + exports, + exportedNames, + ); + break; + } + } + + private extractFunction( + node: TreeSitterNode, + functions: StructuralAnalysis["functions"], + ): void { + const nameNode = + node.childForFieldName("name") ?? + node.children.find((c) => c.type === "identifier"); + if (!nameNode) return; + + const params = extractParams( + node.childForFieldName("parameters") ?? + node.children.find( + (c) => c.type === "formal_parameters", + ) ?? + null, + ); + const returnType = extractReturnType(node); + + functions.push({ + name: nameNode.text, + lineRange: [ + node.startPosition.row + 1, + node.endPosition.row + 1, + ], + params, + returnType, + }); + } + + private extractClass( + node: TreeSitterNode, + classes: StructuralAnalysis["classes"], + ): void { + const nameNode = node.children.find( + (c) => + c.type === "type_identifier" || c.type === "identifier", + ); + if (!nameNode) return; + + const methods: string[] = []; + const properties: string[] = []; + + const classBody = node.children.find( + (c) => c.type === "class_body", + ); + if (classBody) { + for (let j = 0; j < classBody.childCount; j++) { + const member = classBody.child(j); + if (!member) continue; + + if (member.type === "method_definition") { + const methodName = member.children.find( + (c) => c.type === "property_identifier", + ); + if (methodName) methods.push(methodName.text); + } else if ( + member.type === "public_field_definition" || + member.type === "property_definition" + ) { + const propName = member.children.find( + (c) => c.type === "property_identifier", + ); + if (propName) properties.push(propName.text); + } + } + } + + classes.push({ + name: nameNode.text, + lineRange: [ + node.startPosition.row + 1, + node.endPosition.row + 1, + ], + methods, + properties, + }); + } + + private extractVariableDeclarations( + node: TreeSitterNode, + functions: StructuralAnalysis["functions"], + ): void { + for (let j = 0; j < node.childCount; j++) { + const child = node.child(j); + if (!child || child.type !== "variable_declarator") continue; + + const nameNode = child.childForFieldName("name"); + const valueNode = child.childForFieldName("value"); + + if ( + nameNode && + valueNode && + (valueNode.type === "arrow_function" || + valueNode.type === "function_expression" || + valueNode.type === "function") + ) { + const params = extractParams( + valueNode.childForFieldName("parameters") ?? + valueNode.children.find( + (c) => c.type === "formal_parameters", + ) ?? + null, + ); + const returnType = extractReturnType(valueNode); + + functions.push({ + name: nameNode.text, + lineRange: [ + node.startPosition.row + 1, + node.endPosition.row + 1, + ], + params, + returnType, + }); + } + } + } + + private extractImport( + node: TreeSitterNode, + imports: StructuralAnalysis["imports"], + ): void { + const sourceNode = node.children.find( + (c) => c.type === "string", + ); + if (!sourceNode) return; + + const source = getStringValue(sourceNode); + const specifiers: string[] = []; + + const importClause = node.children.find( + (c) => c.type === "import_clause", + ); + if (importClause) { + specifiers.push(...extractImportSpecifiers(importClause)); + } + + imports.push({ + source, + specifiers, + lineNumber: node.startPosition.row + 1, + }); + } + + private processExportStatement( + node: TreeSitterNode, + functions: StructuralAnalysis["functions"], + classes: StructuralAnalysis["classes"], + _imports: StructuralAnalysis["imports"], + exports: StructuralAnalysis["exports"], + exportedNames: Set, + ): void { + for (let j = 0; j < node.childCount; j++) { + const child = node.child(j); + if (!child) continue; + + switch (child.type) { + case "function_declaration": { + this.extractFunction(child, functions); + const nameNode = + child.childForFieldName("name") ?? + child.children.find((c) => c.type === "identifier"); + if (nameNode && !exportedNames.has(nameNode.text)) { + exports.push({ + name: nameNode.text, + lineNumber: node.startPosition.row + 1, + }); + exportedNames.add(nameNode.text); + } + break; + } + + case "class_declaration": { + this.extractClass(child, classes); + const nameNode = child.children.find( + (c) => + c.type === "type_identifier" || + c.type === "identifier", + ); + if (nameNode && !exportedNames.has(nameNode.text)) { + const isDefault = node.children.some( + (c) => c.type === "default", + ); + const exportName = isDefault + ? "default" + : nameNode.text; + exports.push({ + name: exportName, + lineNumber: node.startPosition.row + 1, + }); + exportedNames.add(exportName); + } + break; + } + + case "lexical_declaration": + case "variable_declaration": { + this.extractVariableDeclarations(child, functions); + for (let k = 0; k < child.childCount; k++) { + const declarator = child.child(k); + if ( + declarator && + declarator.type === "variable_declarator" + ) { + const nameNode = + declarator.childForFieldName("name"); + if ( + nameNode && + !exportedNames.has(nameNode.text) + ) { + exports.push({ + name: nameNode.text, + lineNumber: node.startPosition.row + 1, + }); + exportedNames.add(nameNode.text); + } + } + } + break; + } + + case "export_clause": { + for (let k = 0; k < child.childCount; k++) { + const spec = child.child(k); + if (spec && spec.type === "export_specifier") { + const alias = spec.childForFieldName("alias"); + const name = spec.childForFieldName("name"); + const exportName = alias + ? alias.text + : name + ? name.text + : spec.text; + if (!exportedNames.has(exportName)) { + exports.push({ + name: exportName, + lineNumber: node.startPosition.row + 1, + }); + exportedNames.add(exportName); + } + } + } + break; + } + } + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6eb4d75..0493e4d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,6 +16,16 @@ importers: version: 3.2.4(@types/node@25.5.0) packages/core: + dependencies: + tree-sitter-javascript: + specifier: ^0.25.0 + version: 0.25.0 + tree-sitter-typescript: + specifier: ^0.23.2 + version: 0.23.2 + web-tree-sitter: + specifier: ^0.26.6 + version: 0.26.6 devDependencies: '@types/node': specifier: ^25.5.0 @@ -429,6 +439,14 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + node-addon-api@8.6.0: + resolution: {integrity: sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q==} + engines: {node: ^18 || ^20 || >= 21} + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -490,6 +508,30 @@ packages: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} + tree-sitter-javascript@0.23.1: + resolution: {integrity: sha512-/bnhbrTD9frUYHQTiYnPcxyHORIw157ERBa6dqzaKxvR/x3PC4Yzd+D1pZIMS6zNg2v3a8BZ0oK7jHqsQo9fWA==} + peerDependencies: + tree-sitter: ^0.21.1 + peerDependenciesMeta: + tree-sitter: + optional: true + + tree-sitter-javascript@0.25.0: + resolution: {integrity: sha512-1fCbmzAskZkxcZzN41sFZ2br2iqTYP3tKls1b/HKGNPQUVOpsUxpmGxdN/wMqAk3jYZnYBR1dd/y/0avMeU7dw==} + peerDependencies: + tree-sitter: ^0.25.0 + peerDependenciesMeta: + tree-sitter: + optional: true + + tree-sitter-typescript@0.23.2: + resolution: {integrity: sha512-e04JUUKxTT53/x3Uq1zIL45DoYKVfHH4CZqwgZhPg5qYROl5nQjV+85ruFzFGZxu+QeFVbRTPDRnqL9UbU4VeA==} + peerDependencies: + tree-sitter: ^0.21.0 + peerDependenciesMeta: + tree-sitter: + optional: true + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -571,6 +613,9 @@ packages: jsdom: optional: true + web-tree-sitter@0.26.6: + resolution: {integrity: sha512-fSPR7VBW/fZQdUSp/bXTDLT+i/9dwtbnqgEBMzowrM4U3DzeCwDbY3MKo0584uQxID4m/1xpLflrlT/rLIRPew==} + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -864,6 +909,10 @@ snapshots: nanoid@3.3.11: {} + node-addon-api@8.6.0: {} + + node-gyp-build@4.8.4: {} + pathe@2.0.3: {} pathval@2.0.1: {} @@ -936,6 +985,22 @@ snapshots: tinyspy@4.0.4: {} + tree-sitter-javascript@0.23.1: + dependencies: + node-addon-api: 8.6.0 + node-gyp-build: 4.8.4 + + tree-sitter-javascript@0.25.0: + dependencies: + node-addon-api: 8.6.0 + node-gyp-build: 4.8.4 + + tree-sitter-typescript@0.23.2: + dependencies: + node-addon-api: 8.6.0 + node-gyp-build: 4.8.4 + tree-sitter-javascript: 0.23.1 + typescript@5.9.3: {} undici-types@7.18.2: {} @@ -1014,6 +1079,8 @@ snapshots: - tsx - yaml + web-tree-sitter@0.26.6: {} + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0