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 = \"\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對象還原需要滿足如下條件:

  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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM