资讯专栏INFORMATION COLUMN

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

Kaede / 3432人阅读

摘要:语法解析首先来看一下什么是抽象语法树。抽象语法树也称为语法树,指的是源代码语法所对应的树状结构。也就是说,对于一种具体编程语言下的源代码,通过构建语法树的形式将源代码中的语句映射到树中的每一个节点上。

前言

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

JavaScript语法解析

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

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

这段代码的AST长这样:

可以发现,代码被映射成了一颗语法树,有三个节点,不同语句映射成不同的节点。那么我们便可以通过操作语法树来准确的获得代码中的某个节点,对代码进行分析等。

Espsrima

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

1. 准备环境

我们使用node来构建能够在命令行中运行的js代码。所以首先确保安装了node环境。
然后创建项目目录。

新建一个文件夹,比如我新建一个‘espsrima’的文件夹。

进入该文件夹

cd espsrima

安装库接下来要用到的库

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();

我们看下这段代码节点遍历的结果,结果太长截图其中一部分:

从图中看出,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+ "
";
        }  
    }  
    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);  

可以看到类似以下的处理结果

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92998.html

相关文章

  • 关于JavaScript编译原理

    摘要:引擎负责整个程序的编译和执行过程编译器负责语法分析和代码生成作用域收集和维护一系列查询由所有声明的标识符组成例子声明一个变量并赋值编译器对该程序段分解成词法单元编译器对以上的词法单元解析成一个树结构抽象语法树的语法解析器提供了一个在线解析的 引擎:负责整个js程序的编译和执行过程编译器:负责语法分析和代码生成作用域:收集和维护一系列查询(由所有声明的标识符组成) 【例子:声明一个变量并...

    anquan 评论0 收藏0
  • WebAssembly 那些事儿

    摘要:的目标是对高级程序中间表示的适当低级抽象,即代码旨在由编译器生成而不是由人来写。表示把源代码变成解释器可以运行的代码所花的时间表示基线编译器和优化编 WebAssembly 那些事儿 什么是 WebAssembly? WebAssembly 是除 JavaScript 以外,另一种可以在网页中运行的编程语言,并且相比之下在某些功能和性能问题上更具优势,过去我们想在浏览器中运行代码来对网...

    邱勇 评论0 收藏0
  • JavaScript 是如何工作解析抽象语法AST)+ 提升编译速度5个技巧

    摘要:无论你使用的是解释型语言还是编译型语言,都有一个共同的部分将源代码作为纯文本解析为抽象语法树的数据结构。和抽象语法树相对的是具体语法树,通常称作分析树。这是引入字节码缓存的原因。 这是专门探索 JavaScript 及其所构建的组件的系列文章的第 14 篇。 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! 如果你错过了前面的章节,可以在这里找到它们: JavaS...

    raoyi 评论0 收藏0
  • 高级前端基础-JavaScript抽象语法AST

    摘要:本文主要介绍解析生成的抽象语法树节点,的实现也是基于的。原文地址解析器是把源码转化为抽象语法树的解析器。参考文献前端进阶之抽象语法树抽象语法树 前言 Babel为当前最流行的代码JavaScript编译器了,其使用的JavaScript解析器为babel-parser,最初是从Acorn 项目fork出来的。Acorn 非常快,易于使用,并且针对非标准特性(以及那些未来的标准特性) 设...

    verano 评论0 收藏0
  • 抽象语法(Abstract Syntax Tree)

    摘要:例如会被分解成解析语法分析这个过程是将词法单元流数组转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树,这个树就叫抽象语法树。常用的有使用生成并使用抽象语法树。 一般来说,程序中的一段源代码在执行之前会经历下面三个步骤1 分词/词法分析这个过程会将由字符组成的字符串分解成有意义的代码快,这些代码块被称为词法单元。例如 var a = 4;会被分解成 var、a、=、4、; 2 解析...

    余学文 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<