JavaScript打印所有被执行函数堆栈

需求

  1. 学习已有项目/框架时, 想要分析代码所有运行;
  2. 开发项目逻辑分析, 排错.

项目地址

Github

效果

在这里插入图片描述

思路

  1. 将代码转换为抽象语法树
  2. 根据需要对语法树进行修改
  3. 将语法树转换为代码

实现

const fs = require('fs')
const escodegen = require("escodegen");
const esprima = require('esprima');

const srcPath = "./demo/three.js";
const destPath = './demo/three_log.js';
/** 是否显示堆栈信息 */
const isShowStack = false;
let prevHandleTime = 0;

main(srcPath, destPath);

function main(srcPath, destPath) {
    log('读取文件');
    const program = fs.readFileSync(srcPath, "utf-8");
    log('文本转AST');
    let tree = esprima.parseScript(program);
    log('处理AST');
    handleTree(tree);
    log('AST转文本');
    const result = escodegen.generate(tree);
    // console.log(result);
    log('写入新文本');
    fs.writeFileSync(destPath, result);
    log('Finish');
}

function handleTree(tree, methodName = "") {
    if (!tree) return;
    if (checkIsObject(tree)) {
        switch (tree.type) {
            case 'BlockStatement':
                if (tree.body && checkIsArray(tree.body)) {

                    let logStr = '';
                    if (isShowStack) {
                        logStr =
                            `
                                console.groupCollapsed("---------------------------------${methodName}");  
                                console.trace("${methodName}");
                                console.groupEnd(); 
                            `;
                    } else {
                        logStr = `console.log("---------------------------------${methodName}");`;
                    }
                    const consoleBody = esprima.parseScript(logStr).body;
                    if (tree.body.length > 0) {
                        // 空方法不添加log 
                        for (let i = consoleBody.length - 1; i >= 0; i--)
                            tree.body.unshift(consoleBody[i]);
                    }

                    handleTree(tree.body, methodName);
                    return;
                }
                break;
            case 'AssignmentExpression':
                tree.right && handleTree(tree.right, getName(methodName, tree.left));
                break;
            case 'CallExpression':
                if (tree.callee) {
                    handleTree(tree.callee, methodName);
                }
                if (tree.arguments) {
                    handleTree(tree.arguments, methodName);
                }
                break;
            case 'ClassDeclaration':
                tree.body && handleTree(tree.body, getName(methodName, tree));
                break;
            default:
                tree.body && handleTree(tree.body, methodName);
                break;
        }
        return;
    }
    if (checkIsArray(tree)) {
        for (let i = 0; i < tree.length; i++) {
            let item = tree[i];
            switch (item.type) {
                case 'MethodDefinition':
                    if (item.kind === 'get' || item.kind === 'set') {
                        // 过滤 get/set 方法
                    } else {
                        item.value && handleTree(item.value, getName(methodName, item));
                    }
                    break;
                case 'ExpressionStatement':
                    item.expression && handleTree(item.expression, methodName);
                    break;
                case 'ClassDeclaration':
                    item.body && handleTree(item.body, getName(methodName, item));
                    break;
                case 'ForStatement':
                case 'ForInStatement':
                    // for 语句不添加
                    break;
                case 'FunctionDeclaration':
                    // 方法声明
                    item.body && handleTree(item.body, getName(methodName, item));
                    break;
                default:
                    item.body && handleTree(item.body, methodName);
                    break;
            }
        }
        return;
    }
    console.error('什么鬼');
}

function getName(methodName, item) {
    let itemName = '';
    if (item) {
        if (item.id)
            itemName = item.id.name;
        else if (item.key)
            itemName = item.key.name;
        else if (item.property)
            itemName = item.property.name;
    }
    let dot = '';
    if (methodName && itemName)
        dot = '.';
    return methodName + dot + itemName;
}

function checkIsArray(arr) {
    return Object.prototype.toString.call(arr) === '[object Array]';
}

function checkIsObject(obj) {
    return Object.prototype.toString.call(obj) === '[object Object]';
}

function getTime() {
    const now = Date.now();
    let result = 0;
    if (prevHandleTime !== 0) {
        result = now - prevHandleTime;
    }
    prevHandleTime = now;
    return '  ' + result + ' ms';
}

function log(str) {
    console.log(str, getTime());
}

拓展

灵活使用, 可大幅提高分析效率, 如:

  1. 字符串排除打印;
  2. 重复不打印;
  3. 指定时间打印;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值