You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
803 lines
25 KiB
803 lines
25 KiB
const { readFileSync, writeFileSync, existsSync } = require('fs') |
|
const { resolve } = require('path') |
|
const { parse } = require('@vue/compiler-sfc') |
|
const parser = require('@babel/parser') |
|
const traverse = require('@babel/traverse').default |
|
|
|
/** |
|
* 调用链追溯器 |
|
* 专门用于追溯method类型的触发源到最终的可视控件 |
|
*/ |
|
class CallChainTracer { |
|
constructor() { |
|
this.debugMode = process.env.ROUTE_MAPPING_DEBUG === 'true' || process.argv.includes('--debug') |
|
this.processedFiles = new Set() // 跟踪已处理的文件,避免重复处理 |
|
} |
|
|
|
/** |
|
* 调试日志输出 |
|
* @param {string} message - 日志消息 |
|
* @param {any} data - 附加数据 |
|
*/ |
|
debugLog(message, data = null) { |
|
if (this.debugMode) { |
|
const timestamp = new Date().toISOString() |
|
if (data) { |
|
console.log(`[${timestamp}] [CallChainTracer] ${message}`, data) |
|
} else { |
|
console.log(`[${timestamp}] [CallChainTracer] ${message}`) |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* 清理已处理文件的记录 |
|
* 在每次新的处理周期开始时调用 |
|
*/ |
|
clearProcessedFiles() { |
|
this.processedFiles.clear() |
|
this.debugLog('已清理已处理文件记录') |
|
} |
|
|
|
/** |
|
* 追溯所有method类型的触发源到可视控件 |
|
* @param {Array} apiMappings - API映射数组 |
|
* @returns {Array} 增强的API映射数组 |
|
*/ |
|
traceMethodTriggersToVisualComponents(apiMappings) { |
|
const enhancedApiMappings = apiMappings.map(module => { |
|
const enhancedApiMappings = module.apiMappings.map(api => { |
|
if (!api.triggerSources || api.triggerSources.length === 0) { |
|
return api |
|
} |
|
|
|
// 只处理triggerType为method的触发源 |
|
const enhancedTriggerSources = api.triggerSources.map(trigger => { |
|
if (trigger.triggerType === 'method') { |
|
const result = this.traceMethodToVisualComponent( |
|
trigger, |
|
module.module, |
|
module.serviceName, |
|
api.apiMethodName |
|
) |
|
return result |
|
} |
|
return trigger |
|
}) |
|
|
|
return { |
|
...api, |
|
triggerSources: enhancedTriggerSources |
|
} |
|
}) |
|
|
|
return { |
|
...module, |
|
apiMappings: enhancedApiMappings |
|
} |
|
}) |
|
|
|
return enhancedApiMappings |
|
} |
|
|
|
/** |
|
* 追溯单个method到可视控件 |
|
* @param {Object} trigger - 触发源对象 |
|
* @param {string} moduleName - 模块名称 |
|
* @param {string} serviceName - 服务名称 |
|
* @param {string} apiMethodName - API方法名称 |
|
* @returns {Object} 增强的触发源对象 |
|
*/ |
|
traceMethodToVisualComponent(trigger, moduleName, serviceName, apiMethodName) { |
|
try { |
|
// 查找组件文件 |
|
const componentPath = this.findComponentFile(trigger.component, moduleName) |
|
|
|
if (!componentPath) { |
|
return trigger |
|
} |
|
|
|
const content = readFileSync(componentPath, 'utf-8') |
|
|
|
// 解析Vue组件 |
|
const ast = this.parseVueComponent(content) |
|
|
|
if (!ast) { |
|
return trigger |
|
} |
|
|
|
// 建立函数调用关系映射 |
|
const functionCallMap = this.buildFunctionCallMap(ast) |
|
|
|
// 追溯method到可视控件 |
|
const visualContext = this.traceToVisualComponent( |
|
trigger.triggerName, |
|
functionCallMap, |
|
componentPath, |
|
trigger.component |
|
) |
|
|
|
if (visualContext) { |
|
const result = { |
|
...trigger, |
|
triggerName: visualContext.triggerName, |
|
triggerType: visualContext.triggerType |
|
} |
|
return result |
|
} |
|
|
|
return trigger |
|
} catch (error) { |
|
return trigger |
|
} |
|
} |
|
|
|
/** |
|
* 查找组件文件 |
|
* @param {string} componentName - 组件名称 |
|
* @param {string} moduleName - 模块名称 |
|
* @returns {string|null} 组件文件路径 |
|
*/ |
|
findComponentFile(componentName, moduleName) { |
|
// 优先在指定模块中查找 |
|
if (moduleName) { |
|
// 检查views目录 |
|
const viewPath = resolve(__dirname, `../../src/renderer/modules/${moduleName}/views/${componentName}.vue`) |
|
if (existsSync(viewPath)) { |
|
return viewPath |
|
} |
|
|
|
// 检查components目录 |
|
const componentPath = resolve(__dirname, `../../src/renderer/modules/${moduleName}/components/${componentName}.vue`) |
|
if (existsSync(componentPath)) { |
|
return componentPath |
|
} |
|
} |
|
|
|
return null |
|
} |
|
|
|
/** |
|
* 解析Vue组件内容 |
|
* @param {string} content - 组件内容 |
|
* @returns {Object|null} AST对象 |
|
*/ |
|
parseVueComponent(content) { |
|
try { |
|
// 提取script部分 |
|
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/) |
|
|
|
if (!scriptMatch) { |
|
return null |
|
} |
|
|
|
const scriptContent = scriptMatch[1] |
|
|
|
// 使用Babel解析JavaScript |
|
const ast = parser.parse(scriptContent, { |
|
sourceType: 'module', |
|
plugins: ['jsx', 'typescript'] |
|
}) |
|
|
|
return ast |
|
} catch (error) { |
|
return null |
|
} |
|
} |
|
|
|
/** |
|
* 建立函数调用关系映射 |
|
* @param {Object} ast - AST对象 |
|
* @returns {Map} 函数调用关系映射 |
|
*/ |
|
buildFunctionCallMap(ast) { |
|
const functionCallMap = new Map() |
|
const self = this |
|
|
|
traverse(ast, { |
|
CallExpression(path) { |
|
const { node } = path |
|
if (node.callee && node.callee.type === 'Identifier') { |
|
const functionName = node.callee.name |
|
const parentFunction = self.findParentFunction(path) |
|
|
|
if (parentFunction) { |
|
if (!functionCallMap.has(functionName)) { |
|
functionCallMap.set(functionName, { callers: [] }) |
|
} |
|
const funcInfo = functionCallMap.get(functionName) |
|
if (!funcInfo.callers) funcInfo.callers = [] |
|
funcInfo.callers.push(parentFunction) |
|
} |
|
} |
|
} |
|
}) |
|
|
|
return functionCallMap |
|
} |
|
|
|
/** |
|
* 查找父级函数 |
|
* @param {Object} path - Babel路径对象 |
|
* @returns {string|null} 父级函数名 |
|
*/ |
|
findParentFunction(path) { |
|
let currentPath = path.parentPath |
|
while (currentPath) { |
|
const { node } = currentPath |
|
|
|
if (node.type === 'FunctionDeclaration' && node.id) { |
|
return node.id.name |
|
} |
|
|
|
if (node.type === 'VariableDeclarator' && |
|
node.id && node.id.type === 'Identifier' && |
|
node.init && node.init.type === 'ArrowFunctionExpression') { |
|
return node.id.name |
|
} |
|
|
|
if (node.type === 'ObjectMethod' && node.key) { |
|
return node.key.name |
|
} |
|
|
|
currentPath = currentPath.parentPath |
|
} |
|
return null |
|
} |
|
|
|
/** |
|
* 追溯method到可视控件 |
|
* @param {string} methodName - 方法名称 |
|
* @param {Map} functionCallMap - 函数调用关系映射 |
|
* @param {string} filePath - 组件文件路径 |
|
* @param {string} componentName - 组件名称 |
|
* @returns {Object|null} 可视控件上下文 |
|
*/ |
|
traceToVisualComponent(methodName, functionCallMap, filePath, componentName) { |
|
try { |
|
// 读取组件文件内容 |
|
const content = readFileSync(filePath, 'utf-8') |
|
|
|
// 分析模板中的事件绑定 |
|
const visualContext = this.analyzeTemplateForMethod(content, methodName, componentName, filePath) |
|
|
|
if (visualContext) { |
|
return visualContext |
|
} |
|
|
|
// 如果模板中没有找到,检查是否有其他方法调用了这个方法 |
|
const callerContext = this.findMethodCaller(methodName, functionCallMap) |
|
|
|
if (callerContext) { |
|
// 检查调用者是否是生命周期钩子 |
|
if (this.isLifecycleHook(callerContext)) { |
|
return { |
|
triggerName: '', |
|
triggerType: 'page' |
|
} |
|
} |
|
|
|
// 继续递归追溯 |
|
return this.traceToVisualComponent(callerContext, functionCallMap, filePath, componentName) |
|
} |
|
} catch (error) { |
|
// 静默处理错误 |
|
} |
|
|
|
return null |
|
} |
|
|
|
/** |
|
* 分析模板中的方法调用 |
|
* @param {string} content - 组件内容 |
|
* @param {string} methodName - 方法名称 |
|
* @param {string} componentName - 组件名称 |
|
* @param {string} filePath - 组件文件路径 |
|
* @returns {Object|null} 可视控件上下文 |
|
*/ |
|
analyzeTemplateForMethod(content, methodName, componentName, filePath) { |
|
try { |
|
// 使用Vue编译器解析单文件组件 |
|
const { descriptor } = parse(content) |
|
|
|
if (!descriptor.template) { |
|
return null |
|
} |
|
|
|
// 解析模板AST |
|
const templateAst = descriptor.template.ast |
|
|
|
if (!templateAst) { |
|
return null |
|
} |
|
|
|
// 遍历模板AST,查找事件绑定 |
|
const result = this.findEventBindingInTemplate(templateAst, methodName, componentName, filePath) |
|
|
|
return result |
|
|
|
} catch (error) { |
|
return null |
|
} |
|
} |
|
|
|
/** |
|
* 在模板AST中查找事件绑定 |
|
* @param {Object} node - AST节点 |
|
* @param {string} methodName - 方法名称 |
|
* @param {string} componentName - 组件名称 |
|
* @param {string} filePath - 组件文件路径 |
|
* @returns {Object|null} 可视组件上下文 |
|
*/ |
|
findEventBindingInTemplate(node, methodName, componentName, filePath) { |
|
if (!node) { |
|
return null |
|
} |
|
|
|
// 检查当前节点是否有事件绑定 |
|
if (node.props) { |
|
for (const prop of node.props) { |
|
if (prop.type === 7) { // DIRECTIVE类型 |
|
const eventName = prop.name |
|
|
|
if (eventName === 'on' && prop.arg) { |
|
const eventType = prop.arg.content |
|
|
|
if (['click', 'submit', 'change', 'input'].includes(eventType)) { |
|
// 检查事件处理函数 |
|
if (prop.exp && this.isMethodCall(prop.exp, methodName)) { |
|
const context = this.createVisualContext(node, eventType, componentName, methodName, filePath) |
|
|
|
// 如果找到的是form,需要查找其中的submit按钮 |
|
if (context.triggerType === 'form') { |
|
const submitButton = this.findSubmitButtonInForm(node, componentName, methodName, filePath) |
|
if (submitButton) { |
|
return submitButton |
|
} |
|
} |
|
|
|
return context |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// 递归检查子节点 |
|
if (node.children) { |
|
for (const child of node.children) { |
|
if (child.type === 1) { // ELEMENT类型 |
|
const result = this.findEventBindingInTemplate(child, methodName, componentName, filePath) |
|
if (result) { |
|
return result |
|
} |
|
} |
|
} |
|
} |
|
|
|
return null |
|
} |
|
|
|
/** |
|
* 在form中查找submit按钮 |
|
* @param {Object} formNode - form AST节点 |
|
* @param {string} componentName - 组件名称 |
|
* @param {string} methodName - 方法名称 |
|
* @param {string} filePath - 组件文件路径 |
|
* @returns {Object|null} submit按钮节点和上下文 |
|
*/ |
|
findSubmitButtonInForm(formNode, componentName, methodName, filePath) { |
|
if (!formNode || !formNode.children) return null |
|
|
|
// 递归查找submit按钮 |
|
for (const child of formNode.children) { |
|
if (child.type === 1) { // ELEMENT类型 |
|
if (child.tag === 'button') { |
|
// 检查是否是submit按钮 |
|
const props = child.props || [] |
|
const attributes = {} |
|
props.forEach(prop => { |
|
if (prop.type === 6) { // ATTRIBUTE |
|
attributes[prop.name] = prop.value ? prop.value.content : '' |
|
} |
|
}) |
|
|
|
if (attributes.type === 'submit') { |
|
let triggerName = attributes.name |
|
if (!triggerName) { |
|
// 生成唯一的name属性:组件名+唯一尾缀 |
|
triggerName = `${componentName.toLowerCase()}-submit-${this.generateUniqueSuffix()}` |
|
} |
|
return { |
|
node: child, |
|
triggerName: triggerName, |
|
triggerType: 'button' |
|
} |
|
} |
|
} |
|
|
|
// 递归查找子元素 |
|
const result = this.findSubmitButtonInForm(child, componentName, methodName, filePath) |
|
if (result) return result |
|
} |
|
} |
|
|
|
return null |
|
} |
|
|
|
/** |
|
* 检查表达式是否是方法调用 |
|
* @param {Object} exp - 表达式节点 |
|
* @param {string} methodName - 方法名称 |
|
* @returns {boolean} 是否是方法调用 |
|
*/ |
|
isMethodCall(exp, methodName) { |
|
if (!exp) { |
|
return false |
|
} |
|
|
|
// 简单的方法调用:methodName |
|
if (exp.type === 4 && exp.content === methodName) { // SIMPLE_EXPRESSION |
|
return true |
|
} |
|
|
|
// 带参数的方法调用:methodName(...) - 检查content是否以methodName开头 |
|
if (exp.type === 4 && exp.content && exp.content.startsWith(methodName + '(')) { |
|
return true |
|
} |
|
|
|
// 带参数的方法调用:methodName(...) |
|
if (exp.type === 8) { // COMPOUND_EXPRESSION |
|
const children = exp.children |
|
if (children && children.length >= 1) { |
|
const firstChild = children[0] |
|
if (firstChild.type === 4 && firstChild.content === methodName) { |
|
return true |
|
} |
|
} |
|
} |
|
|
|
// 检查AST中的方法调用 |
|
if (exp.ast) { |
|
const result = this.checkAstMethodCall(exp.ast, methodName) |
|
return result |
|
} |
|
|
|
return false |
|
} |
|
|
|
/** |
|
* 检查AST中的方法调用 |
|
* @param {Object} ast - AST节点 |
|
* @param {string} methodName - 方法名称 |
|
* @returns {boolean} 是否是方法调用 |
|
*/ |
|
checkAstMethodCall(ast, methodName) { |
|
if (!ast) return false |
|
|
|
// 简单标识符:methodName |
|
if (ast.type === 'Identifier' && ast.name === methodName) { |
|
return true |
|
} |
|
|
|
// 方法调用:methodName(...) |
|
if (ast.type === 'CallExpression') { |
|
if (ast.callee && ast.callee.type === 'Identifier' && ast.callee.name === methodName) { |
|
return true |
|
} |
|
} |
|
|
|
return false |
|
} |
|
|
|
/** |
|
* 创建可视组件上下文 |
|
* @param {Object} node - AST节点 |
|
* @param {string} eventType - 事件类型 |
|
* @param {string} componentName - 组件名称 |
|
* @param {string} methodName - 方法名称 |
|
* @param {string} filePath - 组件文件路径 |
|
* @returns {Object} 可视组件上下文 |
|
*/ |
|
createVisualContext(node, eventType, componentName, methodName, filePath) { |
|
const tag = node.tag |
|
const props = node.props || [] |
|
|
|
// 提取元素属性 |
|
const attributes = {} |
|
props.forEach(prop => { |
|
if (prop.type === 6) { // ATTRIBUTE |
|
attributes[prop.name] = prop.value ? prop.value.content : '' |
|
} |
|
}) |
|
|
|
let triggerName = attributes.name || attributes.id || '' |
|
let triggerType = 'button' // 默认类型 |
|
|
|
// 根据标签类型确定触发源类型 |
|
if (tag === 'form') { |
|
triggerType = 'form' |
|
} else if (tag === 'button') { |
|
triggerType = 'button' |
|
} else if (tag === 'input') { |
|
triggerType = 'input' |
|
} else if (tag === 'select') { |
|
triggerType = 'select' |
|
} else if (tag === 'textarea') { |
|
triggerType = 'textarea' |
|
} else { |
|
// 其他元素类型 |
|
triggerType = 'element' |
|
} |
|
|
|
// 如果没有名称,生成一个 |
|
if (!triggerName) { |
|
triggerName = `${componentName.toLowerCase()}-${tag}-${this.generateUniqueSuffix()}` |
|
} |
|
|
|
// 先处理form追溯到button的逻辑 |
|
if (triggerType === 'form') { |
|
const submitButton = this.findSubmitButtonInForm(node, componentName, methodName, filePath) |
|
if (submitButton) { |
|
// 如果找到submit按钮,为真正的button节点添加属性 |
|
this.modifyVueElementWithCompiler(filePath, submitButton.node, submitButton.triggerName, submitButton.triggerType) |
|
return { |
|
triggerName: submitButton.triggerName, |
|
triggerType: submitButton.triggerType |
|
} |
|
} |
|
} |
|
|
|
// 为最终确定的控件添加name属性和权限控制 |
|
if (triggerType === 'button') { |
|
this.modifyVueElementWithCompiler(filePath, node, triggerName, triggerType) |
|
} |
|
|
|
return { |
|
triggerName: triggerName, |
|
triggerType: triggerType |
|
} |
|
} |
|
|
|
/** |
|
* 查找方法的调用者 |
|
* @param {string} methodName - 方法名称 |
|
* @param {Map} functionCallMap - 函数调用关系映射 |
|
* @returns {string|null} 调用者方法名 |
|
*/ |
|
findMethodCaller(methodName, functionCallMap) { |
|
const funcInfo = functionCallMap.get(methodName) |
|
if (funcInfo && funcInfo.callers && funcInfo.callers.length > 0) { |
|
// 返回第一个调用者 |
|
return funcInfo.callers[0] |
|
} |
|
return null |
|
} |
|
|
|
/** |
|
* 检查方法名是否是生命周期钩子 |
|
* @param {string} methodName - 方法名称 |
|
* @returns {boolean} 是否是生命周期钩子 |
|
*/ |
|
isLifecycleHook(methodName) { |
|
const lifecycleHooks = [ |
|
'onMounted', 'onCreated', 'onBeforeMount', 'onBeforeCreate', |
|
'onUpdated', 'onBeforeUpdate', 'onUnmounted', 'onBeforeUnmount', |
|
'mounted', 'created', 'beforeMount', 'beforeCreate', |
|
'updated', 'beforeUpdate', 'unmounted', 'beforeUnmount', |
|
'setup' |
|
] |
|
return lifecycleHooks.includes(methodName) |
|
} |
|
|
|
/** |
|
* 生成唯一后缀 |
|
* @returns {string} 唯一后缀 |
|
*/ |
|
generateUniqueSuffix() { |
|
return Math.random().toString(36).substr(2, 6) |
|
} |
|
|
|
/** |
|
* 使用精确位置修改Vue文件中的元素 |
|
* @param {string} filePath - Vue文件路径 |
|
* @param {Object} node - AST节点 |
|
* @param {string} nameValue - name属性值 |
|
* @param {string} triggerType - 触发类型 |
|
*/ |
|
modifyVueElementWithCompiler(filePath, node, nameValue, triggerType) { |
|
try { |
|
// 检查文件是否已经被处理过,避免重复处理 |
|
const fileKey = `${filePath}-${nameValue}` |
|
if (this.processedFiles.has(fileKey)) { |
|
this.debugLog(`文件 ${filePath} 的 ${nameValue} 已被处理过,跳过`) |
|
return |
|
} |
|
|
|
const content = readFileSync(filePath, 'utf-8') |
|
|
|
this.debugLog(`开始修改Vue文件: ${filePath}`) |
|
this.debugLog(`目标节点: ${node.tag}, name: ${nameValue}, type: ${triggerType}`) |
|
|
|
// 获取节点的位置信息 |
|
const loc = node.loc |
|
if (!loc) { |
|
this.debugLog(`节点没有位置信息,无法修改Vue元素`) |
|
return |
|
} |
|
|
|
const startLine = loc.start.line |
|
const startColumn = loc.start.column |
|
const endLine = loc.end.line |
|
const endColumn = loc.end.column |
|
|
|
this.debugLog(`节点位置: 第${startLine}行第${startColumn}列 到 第${endLine}行第${endColumn}列`) |
|
|
|
const lines = content.split('\n') |
|
|
|
// 检查是否已经存在name属性或:visible属性,避免重复修改 |
|
const targetLine = lines[startLine - 1] |
|
if (targetLine.includes(`name="`) || targetLine.includes(`name='`) || |
|
targetLine.includes(`:visible="false"`) || targetLine.includes(`:visible='false'`)) { |
|
this.debugLog(`元素已包含name或:visible属性,跳过修改`) |
|
return |
|
} |
|
|
|
// 处理单行元素 |
|
if (startLine === endLine) { |
|
this.modifySingleLineElement(lines, startLine - 1, startColumn, endColumn, nameValue, triggerType) |
|
} else { |
|
// 处理多行元素 |
|
this.modifyMultiLineElement(lines, startLine - 1, startColumn, endLine - 1, endColumn, nameValue, triggerType) |
|
} |
|
|
|
// 写回文件 |
|
const newContent = lines.join('\n') |
|
writeFileSync(filePath, newContent, 'utf-8') |
|
|
|
// 标记文件为已处理 |
|
this.processedFiles.add(fileKey) |
|
|
|
this.debugLog(`已成功修改Vue文件: ${filePath}`) |
|
this.debugLog(`添加的属性: name="${nameValue}"${triggerType === 'button' ? ', :visible="false"' : ', :disabled="true"'}`) |
|
|
|
} catch (error) { |
|
this.debugLog(`修改Vue元素时出错:`, error.message) |
|
this.debugLog(`错误堆栈:`, error.stack) |
|
} |
|
} |
|
|
|
/** |
|
* 修改单行元素 |
|
* @param {Array} lines - 文件行数组 |
|
* @param {number} lineIndex - 行索引 |
|
* @param {number} startCol - 开始列 |
|
* @param {number} endCol - 结束列 |
|
* @param {string} nameValue - name属性值 |
|
* @param {string} triggerType - 触发类型 |
|
*/ |
|
modifySingleLineElement(lines, lineIndex, startCol, endCol, nameValue, triggerType) { |
|
const line = lines[lineIndex] |
|
this.debugLog(`修改单行元素: ${line}`) |
|
|
|
// 检查是否已经存在name属性或:visible属性,避免重复修改 |
|
if (line.includes(`name="`) || line.includes(`name='`) || |
|
line.includes(`:visible="false"`) || line.includes(`:visible='false'`)) { |
|
this.debugLog(`单行元素已包含name或:visible属性,跳过修改`) |
|
return |
|
} |
|
|
|
// 找到开始标签的结束位置 |
|
// AST列号从1开始,需要转换为从0开始的索引 |
|
const tagStart = line.indexOf('<', startCol - 1) |
|
const tagEnd = line.indexOf('>', tagStart) |
|
|
|
if (tagStart !== -1 && tagEnd !== -1) { |
|
const beforeTag = line.substring(0, tagEnd) |
|
const afterTag = line.substring(tagEnd) |
|
|
|
// 构建新的属性 |
|
let newAttributes = ` name="${nameValue}"` |
|
if (triggerType === 'button') { |
|
newAttributes += ` :visible="false"` |
|
} else { |
|
newAttributes += ` :disabled="true"` |
|
} |
|
|
|
lines[lineIndex] = `${beforeTag}${newAttributes}${afterTag}` |
|
this.debugLog(`单行元素修改完成: ${lines[lineIndex]}`) |
|
} |
|
} |
|
|
|
/** |
|
* 修改多行元素 |
|
* @param {Array} lines - 文件行数组 |
|
* @param {number} startLineIndex - 开始行索引 |
|
* @param {number} startCol - 开始列 |
|
* @param {number} endLineIndex - 结束行索引 |
|
* @param {number} endCol - 结束列 |
|
* @param {string} nameValue - name属性值 |
|
* @param {string} triggerType - 触发类型 |
|
*/ |
|
modifyMultiLineElement(lines, startLineIndex, startCol, endLineIndex, endCol, nameValue, triggerType) { |
|
const startLine = lines[startLineIndex] |
|
this.debugLog(`修改多行元素开始行: ${startLine}`) |
|
|
|
// 检查整个多行元素是否已经存在name属性或:visible属性,避免重复修改 |
|
let hasExistingAttributes = false |
|
for (let i = startLineIndex; i <= endLineIndex && i < lines.length; i++) { |
|
const line = lines[i] |
|
if (line.includes(`name="`) || line.includes(`name='`) || |
|
line.includes(`:visible="false"`) || line.includes(`:visible='false'`)) { |
|
hasExistingAttributes = true |
|
break |
|
} |
|
} |
|
|
|
if (hasExistingAttributes) { |
|
this.debugLog(`多行元素已包含name或:visible属性,跳过修改`) |
|
return |
|
} |
|
|
|
// 找到开始标签的结束位置 |
|
// AST列号从1开始,需要转换为从0开始的索引 |
|
const tagStart = startLine.indexOf('<', startCol - 1) |
|
let tagEnd = startLine.indexOf('>', tagStart) |
|
let tagEndLineIndex = startLineIndex |
|
|
|
// 如果当前行没有找到结束标签,查找后续行 |
|
if (tagStart !== -1 && tagEnd === -1) { |
|
this.debugLog(`当前行没有找到结束标签,查找后续行`) |
|
for (let i = startLineIndex; i <= endLineIndex && i < lines.length; i++) { |
|
const line = lines[i] |
|
const endPos = line.indexOf('>') |
|
if (endPos !== -1) { |
|
tagEnd = endPos |
|
tagEndLineIndex = i |
|
this.debugLog(`在第${i + 1}行找到结束标签,位置: ${endPos}`) |
|
break |
|
} |
|
} |
|
} |
|
|
|
if (tagStart !== -1 && tagEnd !== -1) { |
|
// 构建新的属性 |
|
let newAttributes = ` name="${nameValue}"` |
|
if (triggerType === 'button') { |
|
newAttributes += ` :visible="false"` |
|
} else { |
|
newAttributes += ` :disabled="true"` |
|
} |
|
|
|
// 在结束标签前添加属性 |
|
const endLine = lines[tagEndLineIndex] |
|
const beforeEndTag = endLine.substring(0, tagEnd) |
|
const afterEndTag = endLine.substring(tagEnd) |
|
|
|
lines[tagEndLineIndex] = `${beforeEndTag}${newAttributes}${afterEndTag}` |
|
this.debugLog(`多行元素修改完成: ${lines[tagEndLineIndex]}`) |
|
} else { |
|
this.debugLog(`未找到有效的标签位置: tagStart=${tagStart}, tagEnd=${tagEnd}`) |
|
// 如果找不到结束标签,尝试在开始标签后直接添加 |
|
if (tagStart !== -1) { |
|
this.debugLog(`尝试在开始标签后直接添加属性`) |
|
const beforeTag = startLine.substring(0, tagStart + 1) // 包含 < 字符 |
|
const afterTag = startLine.substring(tagStart + 1) |
|
|
|
let newAttributes = ` name="${nameValue}"` |
|
if (triggerType === 'button') { |
|
newAttributes += ` :visible="false"` |
|
} else { |
|
newAttributes += ` :disabled="true"` |
|
} |
|
|
|
lines[startLineIndex] = `${beforeTag}${newAttributes} ${afterTag}` |
|
this.debugLog(`多行元素修改完成(备用方案): ${lines[startLineIndex]}`) |
|
} |
|
} |
|
} |
|
|
|
} |
|
|
|
module.exports = CallChainTracer
|
|
|