feat(core): add tree-sitter analyzer plugin for TS/JS

Add TreeSitterPlugin using web-tree-sitter (WASM-based) to extract
structural information from TypeScript and JavaScript files. The plugin
implements the AnalyzerPlugin interface and extracts functions, classes,
imports, exports, and call graphs via AST traversal.

Uses web-tree-sitter instead of native tree-sitter for cross-platform
compatibility (no native compilation required).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lum1104
2026-03-14 17:40:29 +08:00
Unverified
parent a5327ce64d
commit 801c5e7af3
5 changed files with 1007 additions and 0 deletions
+5
View File
@@ -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"
}
}
+1
View File
@@ -1,2 +1,3 @@
export * from "./types.js";
export * from "./persistence/index.js";
export { TreeSitterPlugin } from "./plugins/tree-sitter-plugin.js";
@@ -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<void> {
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");
});
});
});
@@ -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<string, TreeSitterLanguage>();
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<void> {
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<string>();
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<string>,
): 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<string>,
): 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;
}
}
}
}
}
+67
View File
@@ -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