最近在分析微信小程序,须要统计之前代码中所使用过的wx.api。思路为对js文件进行语法分析,而后找出调用者为wx的成员表达式。node
首先来看一下什么是抽象语法树。抽象语法树(Abstract Syntax Tree)也称为AST语法树,指的是源代码语法所对应的树状结构。也就是说,对于一种具体编程语言下的源代码,经过构建语法树的形式将源代码中的语句映射到树中的每个节点上。
能够经过一个demo来看一下什么是AST。
js代码为express
var a = 1; function main() { console.log('this is a function'); } main();
这段代码的AST长这样: npm
能够发现,代码被映射成了一颗语法树,有三个节点,不一样语句映射成不一样的节点。那么咱们即可以经过操做语法树来准确的得到代码中的某个节点,对代码进行分析等。编程
Espsrima是一个较为成熟的JavaScript语法解析开源项目。使用Espsrima对js代码进行语法解析的步骤以下:小程序
咱们使用node来构建可以在命令行中运行的js代码。因此首先确保安装了node环境。
而后建立项目目录。微信小程序
cd espsrima
。api
npm install esprima --save npm install estraverse --save
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');
esprima 将代码解析为语法树,Estraverse则用来对节点进行遍历。咱们仍以这段代码为例微信
var a = 1; function main() {} wx.laod();
咱们看下这段代码节点遍历的结果,结果太长截图其中一部分: 编程语言
从图中看出,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。
首先须要一个对象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);
能够看到相似如下的处理结果