背景
AST 是非常有用的
。
上午一直在搬砖,下午听了小组一个老哥做的AST分享
,觉得做的很好。
为了加深印象,就写了篇总结,顺便分享给大家,希望能给朋友们一些帮助和启发
。
可能小伙伴要问,AST真有这么厉害?
口说无凭, 且看几个具体的案例
。
且不说:
Vue => React
React => Vue
的代码转换方法,
我们就看一个可以无痛升级旧版React
的工具:
react-codemod
代码地址:https://github.com/reactjs/react-codemod
这个工具,功能十分强大,使用起来也很方便,只需要运行一行命令:
代码语言:javascript复制npx react-codemod <transform> <path> [...options]
这些功能的实现,无不借助了AST
。
下面我们就进入今天的内容。
正文
本文的主要内容包括:
- 1 理论: AST
基本概念
- 2 实践: 使用 AST 实现一个代码转换工具, 把
var
转换成let
- 3 实践: 使用 AST 实现一个Eslint 插件,
禁用 console
- 4 实践: 使用 AST 实现一个Babel插件,
过滤 Debugger
1. AST 基本概念
AST 是什么?
AST is a hierarchical program representation that presents source code structure according to the grammar of a programming language, each AST node corresponds to an item of a source code.
在计算机科学中,抽象语法
和抽象语法树
其实是源代码的抽象语法结构的树状表现形式
。
常用的浏览器就是通过将js代码转化为抽象语法树
来进行下一步的分析等其他操作。
所以将js转化为抽象语法树更利于程序的分析
。
AST 能做什么
- 代码语法的检查
- 代码风格的检查
- 代码的格式化
- 代码的高亮
- 代码错误提示
- 代码自动补全
- 等等。
AST 三板斧
- 生成AST
- 遍历和更新AST
- 将AST重新生成源码
为了便于理解, 我们看一个具体的例子。
这里顺便给大家介绍一个十分有用的网站:
https://astexplorer.net/
譬如:
声明, 变量, 类型等各种信息一应俱全。
而且这里也提供了各种插件模版供你选择:
十分的方便。
我们就根据这个例子, 我们做个小的实践
。
2. 实践: 使用AST实现一个代码转换工具, 把var
转换成let
比如, 现在要重构一个老项目, 你要把项目里的var 全部替换成let
, 你会怎么做?
手动替换?
或者借助工具一键替换?
现在就教你一招:一键替换大法
。
首先还是要介绍一把大杀器:
jscodeshift
它是一个 Javscript Codemod 工具,官方对 Codemod 的解释是:
Codemod is a tool/library to assist you with large-scale codebase refactors that can be partially automated but still require human oversight and occasional intervention.
jscodeshift 也是基于 esprima
的,其通过 path 可以很容易的在 AST 上遍历 node
。
现在我们就开始替换项目中的var.
首先,到 https://astexplorer.net
里面编写代码.
模板我们选:jscodeshift
官方自带的例子, 把变量名字反转:
我们现在要改变量, 这个工具很贴心的一点是可以高亮实时对照,
现在, 找到kind === var
的对象, 替换成let
:
得到如下代码:
代码语言:javascript复制export default function transformer(file, api) {
const j = api.jscodeshift;
return j(file.source)
.find(j.VariableDeclaration, { kind: 'var'})
.forEach(path => {
const letStatement = j.variableDeclaration('let', path.node.declarations)
j(path).replaceWith(letStatement)
})
.toSource();
}
这样也可以:
代码语言:javascript复制path.node.kind = 'let'; // 传入的实际是一个引用
实际效果:
大功告成!
假如我项目里有几个文件也需要相同的操作:
简单安装:
sudo npm install -g jscodeshift
执行:
jscodeshift -t transform.js ./src/demo.js --dry --print
这里用了--dry 和 --print
--dry
加上之后,不会立刻把新生成的代码覆盖源文件
--print
是打印出来看看
在实际项目里, 你需要在独立的分支里
操作,新生成代码之后, 需要你再检查检查
, review没有问题
之后才能合并。
3. 使用AST实现一个Eslint 插件, 禁用console
和上面的类似, 我们也可以做一个eslint 插件, 功能也很简单: 检查到使用console的时候就报错
。
期望达到的效果:
代码语言:javascript复制// Do not use console methods (at 1:9)
console.log('haha')
// --------^
我们这次选择 babel-eslint
模版。
代码实现:
代码语言:javascript复制const disallowMethods = ["log", "info", "warn", "error", "dir"];
export default function(context) {
return {
Identifier(node) {
const isConsoleMethod =
disallowMethods.includes(node.name) &&
node.parent.type === "MemberExpression" &&
node.parent.object.name === "console";
if (!isConsoleMethod) return;
context.report({
node,
message: "Do not use console methods"
});
}
};
}
实际效果:
简单有效。
不过你要是非要玩什么骚操作,比如自定义一个log, 那就没得搞了。
最后, 你可以把这段代码封装成一个完整的插件:
教你如何编写 Eslint 插件:
https://juejin.im/post/5d91be23f265da5ba532a07e
你可以自行实践。
4. 使用AST实现一个Babel插件, 过滤debugger
最后一个是过滤源代码中的debugger, Transform 我们选择babelv7
这个插件,我们期望达到的效果是:
代码语言:javascript复制var a = 1
debugger
function test() {
debugger
a
}
debugger
到:
代码语言:javascript复制var a = 1;
function test() {
a ;
}
这也是一个十分有用的功能。
代码实现:
代码语言:javascript复制export default function (babel) {
const {
types: t
} = babel;
return {
name: "ast-transform",
visitor: {
DebuggerStatement(path) {
path.remove()
}
}
};
}
实际效果:
符合预期。
总结
内容大概就是这么多,没什么难度,重在讲述理论和入门
。
对AST还不熟练的同学, 希望这篇可以帮助到你。
后面还有会AST在我们实际项目中的应用
, 我也会写一个实战篇
, 敬请期待!
以上。