没有AST, IDE中的错误提示、自动补全、重构、语法检查......都玩不转了
作者:CQITer小编 时间:2019-02-20 16:49

张大胖一上班,领导就扔了一个任务给他,把项目中的JavaScript代码做点“小小”的改变:
1. 把 == 改为全等 ===
2. 把parsetInt不标准的调用改为标准用法 parseInt(xxx)-> parseInt(xxx,10)
对不熟悉JS的同学稍微解释一下:
JS在比较两个变量的时候,双等号将执行类型转换; 三等号将进行相同的比较,而不进行类型转换 (如果类型不同, 只是总会返回 false );
parseInt(a,10) 表示以十进制的方式来解析。
对于这些任务,张大胖脑海中马上闪现出了解决办法:字符串替换。
对第一个任务: 找到'==',替换成'==='就行 。
对第二个任务: parseInt(xxx) 改成parseInt(xxx,10), 没法直接替换,得写个正则表达式,找到那些只有一个参数的parseInt字符串,然后加上一个新的参数:10 。
张大胖对自己的正则表达式能力不太自信,如果考虑得不周全,代码就可能被改坏了。
有没有别的办法?
01抽象语法树
使用正则表达式,只能把JavaScript源代码当做文本来处理,能力很弱,无法触及到JavaScript的语法层面,正则表达式没法知道这个地方是变量,那个地方是函数名.....
如果能把JavaScript源码转化成结构化的对象,就可以精确地知道一段代码中有哪些变量名,函数名,参数...... 这样就可以写程序就可以进行处理了。
张大胖想起来自己没有考及格的《编译原理》,里边讲到了抽象语法树(AST)不就是所谓结构化的东西吗?
比如表达式 result = 6+7*3 , 用抽象语法树来表示就是:
如果把所有的JavaScript代码都转化成这样一颗AST的树,那代码的一切都尽在掌握, 可以任意修改了。

但是这其中有三个问题:
1. 怎么从文本形式的源代码形成这么一个AST ?
让自己写程序实现那就太难了,得做词法分析,语法分析等等。
2. 如何遍历这个AST,来修改这颗树的枝枝叶叶?
比如我想在AST这棵树中添加一个新的节点,该怎么做?
3. 修改完成以后,怎么再次把AST变成文本的源代码?
张大胖赶紧打开Google 搜索,很快便找到了三个开源的工具,正好完成对应的三个功能:
esprima : 从JavaScript源代码形成AST
estraverse:遍历树的节点并修改
escodegen : 把修改完的AST再次转化为源代码。
02创建AST
说干就干,张大胖准备了一段代码来做实验:
//源码
function fun1(opt) {
if (opt.status == 1) {
console.log('1');
}
if (opt.status == 2) {
console.log('2');
}
}
function fun2(age) {
if (parseInt(age) >= 18) {
console.log('ok 你已经成年');
}
}
使用esprima,轻轻松松就把它转化成了抽象语法树。
//JS语法树模块
const esprima = require('esprima');
//创建AST
const AST = esprima.parseScript(jsCode);
(由于转成树后结构非常大,这里不再展示了, 感兴趣的同学自己可以到 去玩一把, 很有趣。 )
比如: if (parseInt(age) >= 18) 这一句,就被转化成了这样:

03遍历修改AST
有了AST,就可以就是遍历和修改了,还是使用开源的工具。
//JS语法树遍历各节点
const estraverse = require('estraverse');
//从JS语法树生成源代码
const escodegen = require('escodegen');
function walkIn(ast){
estraverse.traverse(ast, {
enter: (node) => {
toEqual(node);//把 == 改为全等 ===
setParseInt(node); //parseInt(a)-> parseInt(a,10)
}
});
}
这个函数负责把‘==’改成‘===’
function toEqual(node) {
if (node.operator === '==') {
node.operator = '===';
}
}
这个函数负责把parseInt改成标准调用:
function setParseInt(node) {
//判断节点类型 方法名称,方法的参数的数量,数量为1就增加第二个参数。




