import { Parser, Language, type Node } from 'web-tree-sitter'; import * as path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // 解析后的命令结构 export interface ParsedCommand { name: string; // 命令名,如 "git" subcommand?: string; // 子命令,如 "push" args: string[]; // 参数列表 text: string; // 原始命令文本 } // 解析结果 export interface ParseResult { commands: ParsedCommand[]; // 所有解析出的命令 success: boolean; error?: string; } // 单例解析器 let parserInstance: Parser | null = null; let bashLanguage: Language | null = null; let initPromise: Promise | null = null; /** * 获取 wasm 文件路径 */ function getWasmPath(filename: string): string { // 从 node_modules 加载 const nodeModulesPath = path.resolve(__dirname, '../../node_modules'); if (filename === 'tree-sitter.wasm') { return path.join(nodeModulesPath, 'web-tree-sitter', filename); } else if (filename === 'tree-sitter-bash.wasm') { return path.join(nodeModulesPath, 'tree-sitter-bash', filename); } throw new Error(`Unknown wasm file: ${filename}`); } /** * 初始化解析器(懒加载,只初始化一次) */ async function initParser(): Promise { if (parserInstance && bashLanguage) { return; } if (initPromise) { return initPromise; } initPromise = (async () => { try { // 初始化 tree-sitter await Parser.init({ locateFile: (scriptName: string) => { return getWasmPath(scriptName); }, }); // 创建解析器实例 parserInstance = new Parser(); // 加载 bash 语言 const bashWasmPath = getWasmPath('tree-sitter-bash.wasm'); bashLanguage = await Language.load(bashWasmPath); parserInstance.setLanguage(bashLanguage); } catch (error) { initPromise = null; throw error; } })(); return initPromise; } /** * 从语法树节点中提取命令信息 */ function extractCommandFromNode(node: Node): ParsedCommand { const parts: string[] = []; for (let i = 0; i < node.childCount; i++) { const child = node.child(i); if (!child) continue; // 提取命令名和参数 if ( child.type === 'command_name' || child.type === 'word' || child.type === 'string' || child.type === 'raw_string' || child.type === 'concatenation' || child.type === 'simple_expansion' || child.type === 'expansion' ) { // 对于字符串类型,提取内部文本(去掉引号) if (child.type === 'string' || child.type === 'raw_string') { const text = child.text; if ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) { parts.push(text.slice(1, -1)); } else { parts.push(text); } } else { parts.push(child.text); } } } const name = parts[0] || ''; // 找到第一个非 flag 参数作为子命令 let subcommand: string | undefined; const args: string[] = []; for (let i = 1; i < parts.length; i++) { const part = parts[i]; if (!subcommand && !part.startsWith('-')) { subcommand = part; } else { args.push(part); } } return { name, subcommand, args, text: node.text, }; } /** * 递归查找所有命令节点 */ function findCommandNodes(node: Node): Node[] { const commands: Node[] = []; if (node.type === 'command') { commands.push(node); } for (let i = 0; i < node.childCount; i++) { const child = node.child(i); if (child) { commands.push(...findCommandNodes(child)); } } return commands; } /** * 解析 bash 命令字符串 */ export async function parseBashCommand(command: string): Promise { try { await initParser(); if (!parserInstance) { return { commands: [], success: false, error: 'Parser not initialized', }; } const tree = parserInstance.parse(command); if (!tree) { return { commands: [], success: false, error: 'Failed to parse command', }; } const commandNodes = findCommandNodes(tree.rootNode); const commands = commandNodes.map(extractCommandFromNode); return { commands, success: true, }; } catch (error) { return { commands: [], success: false, error: error instanceof Error ? error.message : String(error), }; } } /** * 简单解析(用于降级,当 tree-sitter 不可用时) */ export function parseCommandSimple(command: string): ParsedCommand { const parts = command.trim().split(/\s+/); const name = parts[0] || ''; let subcommand: string | undefined; const args: string[] = []; for (let i = 1; i < parts.length; i++) { const part = parts[i]; if (!subcommand && !part.startsWith('-')) { subcommand = part; } else { args.push(part); } } return { name, subcommand, args, text: command }; } /** * 检查解析器是否已初始化 */ export function isParserInitialized(): boolean { return parserInstance !== null && bashLanguage !== null; }