JavaScript语法解析与抽象语法树(AST)----Espsrima的使用方法

前言

最近在分析微信小程序,须要统计之前代码中所使用过的wx.api。思路为对js文件进行语法分析,而后找出调用者为wx的成员表达式。node

JavaScript语法解析

首先来看一下什么是抽象语法树。抽象语法树(Abstract Syntax Tree)也称为AST语法树,指的是源代码语法所对应的树状结构。也就是说,对于一种具体编程语言下的源代码,经过构建语法树的形式将源代码中的语句映射到树中的每个节点上。
能够经过一个demo来看一下什么是AST。
js代码为express

var a = 1;
function main() {
    console.log('this is a function');
}
main();

这段代码的AST长这样: npm

image

能够发现,代码被映射成了一颗语法树,有三个节点,不一样语句映射成不一样的节点。那么咱们即可以经过操做语法树来准确的得到代码中的某个节点,对代码进行分析等。编程

Espsrima

Espsrima是一个较为成熟的JavaScript语法解析开源项目。使用Espsrima对js代码进行语法解析的步骤以下:小程序

1. 准备环境

咱们使用node来构建可以在命令行中运行的js代码。因此首先确保安装了node环境。
而后建立项目目录。微信小程序

  • 新建一个文件夹,好比我新建一个‘espsrima’的文件夹。
  • 进入该文件夹

cd espsrimaapi

  • 安装库接下来要用到的库
npm install esprima --save  
npm install estraverse --save
  • 在根目录下新建index.js文件,初始代码以下
var fs = require('fs'),  
    esprima = require('esprima');  
  
function analyzeCode(code) {  
    // 1  
}  
  
// 2  
if (process.argv.length < 3) {  
    console.log('Usage: index.js file.js');  
    process.exit(1);  
}  
  
// 3  
var filename = process.argv[2];  
console.log('Reading ' + filename);  
var code = fs.readFileSync(filename);  
  
analyzeCode(code);  
console.log('Done');

2. 解析js代码并分析数据

esprima 将代码解析为语法树,Estraverse则用来对节点进行遍历。咱们仍以这段代码为例微信

var a = 1;
function main() {}
wx.laod();

咱们看下这段代码节点遍历的结果,结果太长截图其中一部分:
image编程语言

从图中看出,type表明节点类型,如函数声明FunctionDeclaration和函数调用 CallExpression。咱们的目的是找出全部的wx.xxx的函数,因此咱们主要关注函数调用类型。咱们来看基本的函数调用代码:函数

"expression": {  
    "type": "CallExpression",  
    "callee": {  
        "type": "Identifier",  
        "name": "myAwesomeFunction"  
    },  
    "arguments": []  
}

对函数调用而言,即节点类型为 CallExpression,callee指向被调用函数。
咱们再来看下形如 'wx.xxx'的代码

CallExpression: {
  type: 'CallExpression',
  callee: 
   StaticMemberExpression {
     type: 'MemberExpression',
     computed: false,
     object: Identifier { type: 'Identifier', name: 'wx' },
     property: Identifier { type: 'Identifier', name: 'laod' } },
  arguments: [] 
    
}

能够看到,节点类型仍然为 CallExpression,callee的类型为 MemberExpression, callee的object name为wx。可依此提出wx.xxx。

3. 辅助函数

首先须要一个对象functionsStats,用来存储提取的api。其次,须要一个'对象去重函数addStatsEntry'.最后须要一个函数对functionsStats进行处理,将结果写到本地。 通过以上的分析,最终的代码以下:

var fs = require('fs'),  
    esprima = require('esprima'),
    estraverse = require('estraverse');

function addStatsEntry(funcName) {
    if (!functionsStats[funcName]) {  
        functionsStats[funcName] = { calls: 0 };  
    } 
}
// 写文件
function writeResult(str) {
    fs.writeFileSync('result.txt', str, 'utf8', (err) => {
        if (err) throw err;
        console.log('It\'s saved!');
    });
}

// 结果处理函数
function processResults(results) {  
    var str = '';
    for (var name in results) {  
        if (results.hasOwnProperty(name)) {  
            var stats = results[name];
            var apiName = 'wx.'+ name;
            console.log('name:', apiName);
            str += apiName+ '\n';
        }  
    }  
    writeResult(str);
} 

function analyzeCode(code) {  
    var ast = esprima.parse(code);  
    var functionsStats = {}; //1  
  
    estraverse.traverse(ast, {  
        enter: function (node) { 
            if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') {  
                if(node.callee.object.name === 'wx') {
                    addStatsEntry(node.callee.property.name);
                    functionsStats[node.callee.property.name].calls++;
                }
            }  
        }  
    }); 
    processResults(functionsStats);  
}   

 
if (process.argv.length < 3) {  
    console.log('Usage: index.js file.js');  
    process.exit(1);  
}  
  
var filename = process.argv[2];  
console.log('Reading ' + filename);  
var code = fs.readFileSync(filename, { encoding: 'utf8' });  
  
analyzeCode(code);

能够看到相似如下的处理结果

image

相关文章
相关标签/搜索