• 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

  • 相关阅读:
    no space left on device
    功能测试用例
    数据库命令
    移动APP测试用例设计实践经验(转载)
    15个常用sql命令
    将Windows文件夹挂载到Linux上
    英语学习方法
    三种特质 做领导
    扬州之行 第一天
    list、dict、str虽然是Iterable,却不是Iterator
  • 原文地址:https://www.cnblogs.com/gaoyongjian/p/13246736.html
Copyright © 2020-2023  润新知