拓展
AST在线解析网站
babel库 GitHub
babel库 docs
Babel插件开发手册
AST入门网站
https://github.com/babel/babylon/blob/master/ast/spec.md
http://www.alloyteam.com/2017/04/analysis-of-babel-babel-overview/
https://fed.taobao.org/blog/taofed/do71ct/babel-plugins/
http://www.alloyteam.com/2016/05/babel-code-into-a-bird-like/
安装
node
babel
npm install @babel/core
基本框架
const fs = require('fs');
const {parse} = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;
let jscode = fs.readFileSync("./demo.js", {
encoding: "utf-8"
});
let ast = parse(jscode);
const visitor =
{
//TODO write your code here!
}
//some function code
traverse(ast,visitor);
let {code} = generator(ast);
fs.writeFile('decode.js', code, (err)=>{});
节点含义
节点的一些方法
节点的插入
在当前节点前插入:
path.insertBefore(nodes);
在当前节点后插入:
path.insertAfter(nodes);
在所有同级节点前插入:
path.container.unshift(nodes);
在所有同级节点后插入:
path.container.push(nodes);
插入操作时,一定要注意 需要遍历的节点
节点属性及方法
使用
变量替换
原代码:
var s=92
var a = s+5
var b=func(1324801, a)
替换后:
var s = 92;
var a = 97;
var b = func(1324801, 97);
通过 path.evaluate()
来进行计算,替换代码:
const visitor =
{
"Identifier|BinaryExpression"(path) {
let {confident, value} = path.evaluate();
// console.log(path.type, confident, value)
if (confident) {
// console.log(path.node);
path.replaceInline(t.valueToNode(value))
}
},
}
构建 BinaryExpression
类型的节点
注释的是不调用库函数创建的方法
const visitor =
{
"VariableDeclarator"(path){
const {init} = path.node;
// let node = {
// type: "BinaryExpression",
// operator: "*",
// left: {
// type: "NumericLiteral",
// value: 20,
// },
// right: {
// type: "NumericLiteral",
// value: 20,
// }
// }
//
// init || path.set("init", node)
init || path.set("init", t.binaryExpression('*',t.valueToNode(20),t.valueToNode(30)))
}
}
a['length']
转换为 a.length
const visitor =
{
"MemberExpression"(path){
let property = path.get('property');
if(property.isStringLiteral()){
let value = property.node.value;
path.node.computed = false;
property.replaceWith(t.Identifier(value))
}
}
}
严格模式
const visitor =
{
"FunctionExpression"(path){
let body = path.node.body;
body.directives[0] = t.directiveLiteral('use strict')
}
}
字符串
let jscode = "var s = "x48x65x6cx6cx6f"";
let ast = parse(jscode);
const visitor =
{
"StringLiteral"(path){
path.get('extra').remove();
}
}
逗号表达式
a = 1, 3, 5
转换代码
const visitor =
{
ExpressionStatement(path){
let {expression} = path.node;
if(!t.isSequenceExpression(expression)){
return;
}
let tmp = [];
expression.expressions.forEach(express=>{
tmp.push(t.ExpressionStatement(express))
})
path.replaceInline(tmp)
}
}
删除多余的空格和空行
var a = 123;
;
var b = 456;
const visitor =
{
EmptyStatement(path)
{
path.remove();
},
}
删除未使用的变量
删除 由var,let,const定义 的未使用的垃圾变量
const visitor =
{
VariableDeclarator(path) {
const {id} = path.node;
const binding = path.scope.getBinding(id.name);
//如果变量被修改过,则不能进行删除动作。
if (!binding || binding.constantViolations.length > 0) {
return;
}
//长度为0,说明变量没有被使用过。
if (binding.referencePaths.length === 0) {
path.remove();
}
},
}
删除未使用过的函数
const visitor =
{
FunctionDeclaration(path) {
// path.scope.dump();
const {id} = path.node;
const binding = path.scope.parent.getBinding(id.name);
if (!binding || binding.constantViolations.length > 0) {
return;
}
if (binding.referencePaths.length === 0) {
path.remove();
}
},
}
还原定义的字面量
还原一个由var(let,const) 定义的变量
var s=92;b=Z(1324801,s);
定义了一个变量s,调用了一个函数Z,如果不将s的值带入到Z函数,你是得不到此处Z函数的值的
这个就是之前的变量替换
const visitor = {
"Identifier"(path)
{
const {confident,value} = path.evaluate();
confident && path.replaceInline(t.valueToNode(value));
},
}
替换完了之后,var s = 92; 就没什么用了,将其删除
前面有删除的插件
const visitor = {
VariableDeclarator(path)
{//还原var、let、const 定义的变量
const {id,init} = path.node;
if (!t.isLiteral(init)) return;//只处理字面量
const binding = path.scope.getBinding(id.name);
if (!binding || binding.constantViolations.length > 0)
{//如果该变量的值被修改则不能处理
return;
}
for (const refer_path of binding.referencePaths)
{
refer_path.replaceWith(init);
}
path.remove();
},
}
还原Array对象
var _2$SS = function (_SSz, _1111) {
var _l1L1 = [46222, 'x74x61x43x61x70x74x63x68x61x42x6cx6fx62', 'x74', 'x61', 'x73', 'x6c', 'x64', 'x69', .3834417654519915, 'x65x6ex63x72x79x70x74x4a', 'x73x6f', 'x6e', 49344];
var _2Szs = _l1L1[5] + _l1L1[7] + (_l1L1[4] + _l1L1[2]),
_I1il1 = _l1L1[9] + (_l1L1[10] + _l1L1[11]);
var _0ooQoO = _l1L1[0];
var _$Z22 = _l1L1[12],
_2sS2 = _l1L1[8];
return _l1L1[6] + _l1L1[3] + _l1L1[1];
};
将Array对象还原需要满足如下条件:
- Array对象里面的元素最好都是字面量
- 定义Array对象的变量没有被改变
思路
- 大部分Array对象都是通过 var 来定义的。因此,需要遍历 VariableDeclarator 节点,如果初始化是赋值语句,没有使用 var 定义,则可以将赋值语句先变成 声明语句(VariableDeclaration).
- 通过scope.getBinding来获取引用该Array对象的地方
- 因为Array对象取值一般都是MemberExpression表达式,因此找出它的MemberExpression父节点
- 判断父节点的property是否为字面量(一般为数字,即索引值)
- 通过索引值取出对应的Array对象,然后替换这个父节点即可。
const visitor =
{
VariableDeclarator(path){
// 还原数组对象
const {id, init} = path.node;
// 非Array或者没有元素, 返回
if (!t.isArrayExpression(init) || init.elements.length===0) return;
let elements = init.elements;
// 获取binding实例
const binding = path.scope.getBinding(id.name);
for ( const ref_path of binding.referencePaths){
// 获取 MemberExpression 父节点
let member_path = ref_path.findParent(p=>p.isMemberExpression());
let property = member_path.get('property');
// 索引值不是 NumericLiteral 类型的不处理
if(!property.isNumericLiteral()){
continue;
}
// 获取索引值
let index = property.node.value;
// 获取索引值对应的节点, 并替换
let arr_ele = elements[index];
member_path.replaceWith(arr_ele)
}
}
}
和前面的搭配使用,效果如下:
未完待续
下一章 ast指北二
全部代码在GitHub -> GitHub