“继续学习操作AST”
我们今天来看看,如何将一个 BinaryExpression 类型的节点转换成
CallExpression 类型 的节点。即将代码:
var a = 123 | 456;
转换为:
var a = function (s, h) {
return s | h;
}(123, 456);
为什么要这么做,因为一个BinaryExpression 类型的节点(操作符两边都是 Literal 类型的节点)很容易就给还原了,如果将其转变成一个CallExpression 类型 的节点的话,似乎还原要困难点。
01
—
节点比对
这是一个技巧,就是将一个节点转变成另外一个节点的时候,可以分别将其解析,看看节点之间有什么变化,然后再缺啥补啥即可。
首先来看 var a = 123 | 456; 这段代码解析成AST是怎样的结构:
就是一个简单的变量定义,只不过其 init 是一个 BinaryExpression 类型的节点,再看
var a = function (s, h) {
return s | h;
}(123, 456);
这段代码解析成AST是怎样的:
除了 init,其他的没什么变化,因此只需要对 init 进行操作即可。
02
—
一步一步,缺啥补啥
从上面的截图可以看到,init 下面的节点变化了,因此我们需要构造这些节点再进行替换。
在这里遍历 VariableDeclarator,写出如下的插件:
const visitor = {
"VariableDeclarator"(path)
{
binary2func(path)
},
}
再完成这个 binary2func 函数,当然是先判断类型了,如下:
function binary2func(path)
{
const init_path = path.get('init');
if (!init_path.isBinaryExpression()) return;
}
init 的类型变了,所以将 type直接修改下:
init_path.node.type = "CallExpression";
还需要构造两个节点,CallExpression 和 arguments。
先看 arguments 节点,有两个 elements 这 两个 节点就是 BinaryExpression 节点的 left与 right 子节点,如下图。
left 与 right :
arguments :
因此,可以写出如下的代码:
let {operator,left,right} = init_path.node;
init_path.node.arguments = [left,right];
再来看 CallExpression 节点:
如上图,这个节点略微复杂了点,其实也不难,构造就完事了。
1.id 的值是 null,一样的生成一个 null节点:
let id = null;
2.params 包含两个 元素,也简单:
let frist_arg = t.Identifier('s');
let second_arg = t.Identifier('h');
let params = [frist_arg,second_arg];
3.再看 body下面的body节点,可以看到,它只包含了一个元素:
一个 ReturnStatement 类型的节点,因此先构造这个,但是里面还有一个 节点,所以由小及大,优先创建 BinaryExpression 节点:
BinaryExpression 节点 已经在之前的文章中多次创建了,只需要关注 operator 、left和right。
这在上面都已经给出了:
let args = t.BinaryExpression(operator,frist_arg,second_arg);
再通过构造好的 节点,创建一个 ReturnStatement 节点:
let return_state = t.ReturnStatement(args);
再往上看 body,是一个BlockStatement 的节点:
构造起来也很容易:
let body = t.BlockStatement([return_state]);
继续往上走,是一个 FunctionExpression 类型 的节点,构造也容易:
在这里是 init_path.node.callee,因此直接赋值:
init_path.node.callee = t.FunctionExpression(id ,params,body);
4.所有的工作都已完成,下面是完整的代码:
结果如下:
希望文章对大家有帮助,谢谢阅读。