如何使用Python版esprima替换AST指定节点且不影响其余节点
问题原因
核心错误是复用了同一个可变AST节点实例:
Python中esprima生成的AST节点是可变对象,遵循引用传递规则。你提前解析存在accessory_function_expression_statements字典中的表达式节点是全局唯一实例,每次匹配到Lg()调用时,你都直接修改这个实例的left/right属性再返回给AST。
第一次匹配到Lg(GP5["length"], 5)时,你把该实例的左右操作数设为对应参数,插入到if判断条件位置;第二次匹配到Lg(Jj, 21)时,你再次修改同一个实例的左右操作数,此时之前插入到if条件位置的节点因为引用的是同一个对象,值会同步被覆盖,最终所有替换位置都显示最后一次匹配的参数结果。
另外你代码中对参数做重复序列化、反序列化的操作完全没有必要,直接引用原节点的参数即可,还能避免额外的解析开销和潜在格式问题。
修复方法
每次匹配到目标节点时,为替换操作生成独立的新节点实例,不要全局复用同一个节点对象,两种实现方式可选:
- 对提前准备的模板节点做深拷贝,再修改拷贝后实例的属性
- 每次替换时直接构造全新的目标AST节点
修正后可运行代码
from __future__ import print_function import copy import esprima from jscodegen_py import jscodegen generator = jscodegen.CodeGenerator(indent = 2) def js2ast(js: str): return esprima.parseScript(js) def ast2js(ast: dict): return generator.generate(ast) # 预存替换模板 af = {'Lg': {'RawValue': 'WN5 > AN5', 'operator': '>'}} accessory_function_templates = {} for name in af: accessory_function_templates[name] = esprima.parse(af[name]['RawValue']).body[0].expression class MyVisitor(esprima.NodeVisitor): def transform_CallExpression(self, node, metadata): # 先判断callee是否有name属性,避免非标识符调用的属性报错 if hasattr(node.callee, 'name') and node.callee.name == 'Lg': # 深拷贝模板生成独立新节点,不修改全局模板 new_node = copy.deepcopy(accessory_function_templates['Lg']) # 直接引用原调用的参数节点,无需重复序列化解析 new_node.left = node.arguments[0] new_node.right = node.arguments[1] return self.transform_Object(new_node, metadata) # 非目标节点保持原有遍历逻辑 return self.generic_visit(node, metadata) visitor = MyVisitor() tree = esprima.parse(""" if (Lg(GP5["length"], 5)) { var kP5 = window["parseInt"](GP5[5], 10); lP5 = window["isNaN"](kP5) || dK(hA(1), kP5) ? window["Number"]["MAX_VALUE"] : kP5, lP5 = kP5; var abc = Boolean(Lg(Jj, 21)) } """, delegate=visitor) print(ast2js(tree.toDict()))
运行输出(符合预期)
if (GP5["length"] > 5) { var kP5 = window["parseInt"](GP5[5], 10); lP5 = window["isNaN"](kP5) || dK(hA(1), kP5) ? window["Number"]["MAX_VALUE"] : kP5, lP5 = kP5; var abc = Boolean(Jj > 21); }
内容的提问来源于stack exchange,提问作者JJ Cauton
相关产品推荐
相关产品推荐

