ast入门 (一)

拓展

JavaScript 教程
ES6 入门教程

百度在线字体编辑器
奇Q在线字体编辑器
fonttools

AST在线解析网站
babel库 GitHub
babel库 docs
Babel插件开发手册
AST入门网站

查看JavaScript代码流程
GitHub地址

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

https://nodejs.org/zh-cn/

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对象还原需要满足如下条件:

  1. Array对象里面的元素最好都是字面量
  2. 定义Array对象的变量没有被改变

思路

  1. 大部分Array对象都是通过 var 来定义的。因此,需要遍历 VariableDeclarator 节点,如果初始化是赋值语句,没有使用 var 定义,则可以将赋值语句先变成 声明语句(VariableDeclaration).
  2. 通过scope.getBinding来获取引用该Array对象的地方
  3. 因为Array对象取值一般都是MemberExpression表达式,因此找出它的MemberExpression父节点
  4. 判断父节点的property是否为字面量(一般为数字,即索引值)
  5. 通过索引值取出对应的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

原文地址:https://www.cnblogs.com/gaoyongjian/p/13246736.html