import { EditorState } from '@codemirror/state'; import { syntaxTree } from '@codemirror/language'; import type { SyntaxNode } from '@lezer/common'; import { VariableResolver } from './variable-resolver'; /** * HTTP 请求模型 */ export interface HttpRequest { /** 请求方法 */ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'; /** 请求 URL */ url: string; /** 请求头 */ headers: Record; /** 请求体类型 */ bodyType?: 'json' | 'formdata' | 'urlencoded' | 'text' | 'params' | 'xml' | 'html' | 'javascript' | 'binary'; /** 请求体内容 */ body?: any; /** 原始文本位置信息(用于调试) */ position: { from: number; to: number; line: number; }; } /** * 节点类型常量 */ const NODE_TYPES = { REQUEST_STATEMENT: 'RequestStatement', METHOD: 'Method', URL: 'Url', BLOCK: 'Block', PROPERTY: 'Property', PROPERTY_NAME: 'PropertyName', STRING_LITERAL: 'StringLiteral', NUMBER_LITERAL: 'NumberLiteral', IDENTIFIER: 'identifier', AT_RULE: 'AtRule', JSON_RULE: 'JsonRule', FORMDATA_RULE: 'FormDataRule', URLENCODED_RULE: 'UrlEncodedRule', TEXT_RULE: 'TextRule', PARAMS_RULE: 'ParamsRule', XML_RULE: 'XmlRule', HTML_RULE: 'HtmlRule', JAVASCRIPT_RULE: 'JavaScriptRule', BINARY_RULE: 'BinaryRule', JSON_KEYWORD: 'JsonKeyword', FORMDATA_KEYWORD: 'FormDataKeyword', URLENCODED_KEYWORD: 'UrlEncodedKeyword', TEXT_KEYWORD: 'TextKeyword', PARAMS_KEYWORD: 'ParamsKeyword', XML_KEYWORD: 'XmlKeyword', HTML_KEYWORD: 'HtmlKeyword', JAVASCRIPT_KEYWORD: 'JavaScriptKeyword', BINARY_KEYWORD: 'BinaryKeyword', JSON_BLOCK: 'JsonBlock', JSON_PROPERTY: 'JsonProperty', XML_BLOCK: 'XmlBlock', HTML_BLOCK: 'HtmlBlock', JAVASCRIPT_BLOCK: 'JavaScriptBlock', BINARY_BLOCK: 'BinaryBlock', XML_KEY: 'XmlKey', HTML_KEY: 'HtmlKey', JAVASCRIPT_KEY: 'JavaScriptKey', BINARY_KEY: 'BinaryKey', VARIABLE_REF: 'VariableRef', } as const; /** * HTTP 请求解析器 */ export class HttpRequestParser { private variableResolver: VariableResolver | null = null; /** * 构造函数 * @param state EditorState * @param blockRange 块的范围(可选),如果提供则只解析该块内的变量 */ constructor( private state: EditorState, private blockRange?: { from: number; to: number } ) { } /** * 获取或创建变量解析器(懒加载) */ private getVariableResolver(): VariableResolver { if (!this.variableResolver) { this.variableResolver = new VariableResolver(this.state, this.blockRange); } return this.variableResolver; } /** * 解析指定位置的 HTTP 请求 * @param pos 光标位置或请求起始位置 * @returns 解析后的 HTTP 请求对象,如果解析失败返回 null */ parseRequestAt(pos: number): HttpRequest | null { const tree = syntaxTree(this.state); // 查找包含该位置的 RequestStatement 节点 const requestNode = this.findRequestNode(tree, pos); if (!requestNode) { return null; } return this.parseRequest(requestNode); } /** * 查找包含指定位置的 RequestStatement 节点 */ private findRequestNode(tree: any, pos: number): SyntaxNode | null { let foundNode: SyntaxNode | null = null; tree.iterate({ enter: (node: any) => { if (node.name === NODE_TYPES.REQUEST_STATEMENT) { if (node.from <= pos && pos <= node.to) { foundNode = node.node; return false; // 停止迭代 } } } }); return foundNode; } /** * 解析 RequestStatement 节点 */ private parseRequest(node: SyntaxNode): HttpRequest | null { // 使用 Lezer API 直接获取子节点 const methodNode = node.getChild(NODE_TYPES.METHOD); const urlNode = node.getChild(NODE_TYPES.URL); const blockNode = node.getChild(NODE_TYPES.BLOCK); // 验证必需节点 if (!methodNode || !urlNode || !blockNode) { return null; } const method = this.getNodeText(methodNode).toUpperCase(); const url = this.parseUrl(urlNode); // 验证 URL 非空 if (!url) { return null; } const headers: Record = {}; let bodyType: HttpRequest['bodyType'] = undefined; let body: any = undefined; // 解析 Block this.parseBlock(blockNode, headers, (type, content) => { bodyType = type; body = content; }); const line = this.state.doc.lineAt(node.from); return { method: method as HttpRequest['method'], url, headers, bodyType, body, position: { from: node.from, to: node.to, line: line.number, }, }; } /** * 解析 URL 节点 */ private parseUrl(node: SyntaxNode): string { const urlText = this.getNodeText(node); // 移除引号 const url = urlText.replace(/^["']|["']$/g, ''); // 替换变量 return this.getVariableResolver().replaceVariables(url); } /** * 解析 Block 节点(包含 headers 和 body) */ private parseBlock( node: SyntaxNode, headers: Record, onBody: (type: HttpRequest['bodyType'], content: any) => void ): void { // 遍历 Block 的子节点 for (let child = node.firstChild; child; child = child.nextSibling) { if (child.name === NODE_TYPES.PROPERTY) { // HTTP Header 属性 const { name, value } = this.parseProperty(child); if (name && value !== null) { headers[name] = value; } } else if (child.name === NODE_TYPES.AT_RULE) { // AtRule 节点,直接获取第一个子节点(JsonRule, FormDataRule等) const ruleChild = child.firstChild; if (ruleChild) { const { type, content } = this.parseBodyRule(ruleChild); if (type) { // 只有有效的类型才处理 onBody(type, content); } } } } } /** * 解析请求体规则 */ private parseBodyRule(node: SyntaxNode): { type: HttpRequest['bodyType']; content: any } { // 类型映射表 const typeMap: Record = { [NODE_TYPES.JSON_RULE]: 'json', [NODE_TYPES.FORMDATA_RULE]: 'formdata', [NODE_TYPES.URLENCODED_RULE]: 'urlencoded', [NODE_TYPES.TEXT_RULE]: 'text', [NODE_TYPES.PARAMS_RULE]: 'params', [NODE_TYPES.XML_RULE]: 'xml', [NODE_TYPES.HTML_RULE]: 'html', [NODE_TYPES.JAVASCRIPT_RULE]: 'javascript', [NODE_TYPES.BINARY_RULE]: 'binary', }; const type = typeMap[node.name]; // 根据不同的规则类型解析不同的块 let content: any = null; if (node.name === NODE_TYPES.XML_RULE) { const blockNode = node.getChild(NODE_TYPES.XML_BLOCK); content = blockNode ? this.parseFixedKeyBlock(blockNode, 'xml') : null; } else if (node.name === NODE_TYPES.HTML_RULE) { const blockNode = node.getChild(NODE_TYPES.HTML_BLOCK); content = blockNode ? this.parseFixedKeyBlock(blockNode, 'html') : null; } else if (node.name === NODE_TYPES.JAVASCRIPT_RULE) { const blockNode = node.getChild(NODE_TYPES.JAVASCRIPT_BLOCK); content = blockNode ? this.parseFixedKeyBlock(blockNode, 'javascript') : null; } else if (node.name === NODE_TYPES.BINARY_RULE) { const blockNode = node.getChild(NODE_TYPES.BINARY_BLOCK); content = blockNode ? this.parseFixedKeyBlock(blockNode, 'binary') : null; } else { // json, formdata, urlencoded, text, params 使用 JsonBlock const blockNode = node.getChild(NODE_TYPES.JSON_BLOCK); content = blockNode ? this.parseJsonBlock(blockNode) : null; } return { type, content }; } /** * 解析固定 key 的块(xml, html, javascript, binary) * 格式:{ key: "value" } 或 {}(空块) */ private parseFixedKeyBlock(node: SyntaxNode, keyName: string): any { // 查找固定的 key 节点 const keyNode = node.getChild( keyName === 'xml' ? NODE_TYPES.XML_KEY : keyName === 'html' ? NODE_TYPES.HTML_KEY : keyName === 'javascript' ? NODE_TYPES.JAVASCRIPT_KEY : NODE_TYPES.BINARY_KEY ); // 如果没有 key,返回空对象(支持空块) if (!keyNode) { return {}; } // 查找值节点(冒号后面的内容) let value: any = null; for (let child = node.firstChild; child; child = child.nextSibling) { if (child.name === NODE_TYPES.STRING_LITERAL || child.name === NODE_TYPES.VARIABLE_REF) { value = this.parseValue(child); break; } } // 返回格式:{ xml: "value" } 或 { html: "value" } 等 return value !== null ? { [keyName]: value } : {}; } /** * 解析 JsonBlock(用于 @json, @form, @urlencoded) */ private parseJsonBlock(node: SyntaxNode): any { const result: any = {}; // 遍历 JsonProperty for (let child = node.firstChild; child; child = child.nextSibling) { if (child.name === NODE_TYPES.JSON_PROPERTY) { const { name, value } = this.parseJsonProperty(child); if (name && value !== null) { result[name] = value; } } } return result; } /** * 解析 JsonProperty */ private parseJsonProperty(node: SyntaxNode): { name: string | null; value: any } { // 使用 API 获取属性名 const nameNode = node.getChild(NODE_TYPES.PROPERTY_NAME); if (!nameNode) { return { name: null, value: null }; } const name = this.getNodeText(nameNode); // 尝试获取值节点(String, Number, JsonBlock, VariableRef) let value: any = null; for (let child = node.firstChild; child; child = child.nextSibling) { if (child.name === NODE_TYPES.STRING_LITERAL || child.name === NODE_TYPES.NUMBER_LITERAL || child.name === NODE_TYPES.JSON_BLOCK || child.name === NODE_TYPES.VARIABLE_REF || child.name === NODE_TYPES.IDENTIFIER) { value = this.parseValue(child); return { name, value }; } } // 回退:从文本中提取值(用于 true/false 等标识符) const fullText = this.getNodeText(node); const colonIndex = fullText.indexOf(':'); if (colonIndex !== -1) { const valueText = fullText.substring(colonIndex + 1).trim().replace(/,$/, '').trim(); value = this.parseValueFromText(valueText); } return { name, value }; } /** * 从文本解析值 */ private parseValueFromText(text: string): any { // 布尔值 if (text === 'true') return true; if (text === 'false') return false; if (text === 'null') return null; // 数字 if (/^-?\d+(\.\d+)?$/.test(text)) { return parseFloat(text); } // 字符串(带引号) if ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) { return text.slice(1, -1); } // 其他标识符 return text; } /** * 解析 Property(HTTP Header) */ private parseProperty(node: SyntaxNode): { name: string | null; value: any } { const nameNode = node.getChild(NODE_TYPES.PROPERTY_NAME); if (!nameNode) { return { name: null, value: null }; } const name = this.getNodeText(nameNode); let value: any = null; // 查找值节点(跳过冒号和逗号) for (let child = node.firstChild; child; child = child.nextSibling) { if (child.name !== NODE_TYPES.PROPERTY_NAME && child.name !== ':' && child.name !== ',') { value = this.parseValue(child); break; } } // HTTP Header 的值必须转换为字符串 if (value !== null && value !== undefined) { value = String(value); } return { name, value }; } /** * 解析值节点(字符串、数字、标识符、嵌套块、变量引用) */ private parseValue(node: SyntaxNode): any { if (node.name === NODE_TYPES.STRING_LITERAL) { const text = this.getNodeText(node); // 移除引号 const value = text.replace(/^["']|["']$/g, ''); // 替换字符串中的变量 return this.getVariableResolver().replaceVariables(value); } else if (node.name === NODE_TYPES.NUMBER_LITERAL) { const text = this.getNodeText(node); return parseFloat(text); } else if (node.name === NODE_TYPES.VARIABLE_REF) { // 处理变量引用节点 const text = this.getNodeText(node); const resolver = this.getVariableResolver(); const ref = resolver.parseVariableRef(text); if (ref) { return resolver.resolveVariable(ref.name, ref.defaultValue); } return text; } else if (node.name === NODE_TYPES.IDENTIFIER) { const text = this.getNodeText(node); // 处理布尔值 if (text === 'true') return true; if (text === 'false') return false; // 处理 null if (text === 'null') return null; // 其他标识符作为字符串 return text; } else if (node.name === NODE_TYPES.JSON_BLOCK) { // 嵌套对象 return this.parseJsonBlock(node); } else { // 未知类型,返回原始文本 return this.getNodeText(node); } } /** * 获取节点的文本内容 */ private getNodeText(node: SyntaxNode): string { return this.state.doc.sliceString(node.from, node.to); } } /** * 便捷函数:解析指定位置的 HTTP 请求 * @param state EditorState * @param pos 光标位置 * @param blockRange 块的范围(可选),如果提供则只解析该块内的变量 */ export function parseHttpRequest( state: EditorState, pos: number, blockRange?: { from: number; to: number } ): HttpRequest | null { const parser = new HttpRequestParser(state, blockRange); return parser.parseRequestAt(pos); }