拓展
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 = \"\x48\x65\x6c\x6c\x6f\"";
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, '\x74\x61\x43\x61\x70\x74\x63\x68\x61\x42\x6c\x6f\x62', '\x74', '\x61', '\x73', '\x6c', '\x64', '\x69', .3834417654519915, '\x65\x6e\x63\x72\x79\x70\x74\x4a', '\x73\x6f', '\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