文档浏览:https://evilrecluse.top/Babel-traverse-api-doc/#/
推荐使用 ↑↑↑GitPage↑↑↑ 进行浏览
注意:页面会被浏览器缓存下来,查看前建议清理缓存,不然文档可能不会更新,一直在旧版本
由于 Babel 官方文档中没有 @babel/traverse API文档,只有一份奇怪的操作说明书 Babel Plugin Handbook
所以我自行编写了一个简略的API记录
這些記錄是我自行查閲文檔/源代碼,來進行編寫的,大多數都會編寫一個小例子來進行説明
内容并不一定準確,如果發現有什麽問題,歡迎通過此文档 github 聯係
此处会对 @babel/parse 与 @babel/generator 进行简单的说明
二者皆有官方文档可以参阅,若无兴趣,可以跳过
@babel/parse 是用于将代码解析语法树所使用的库 将代码进行解析,转变为语法树,是进行语法树操作的必要前置步骤
将提供的代码作为一个完整的ECMAScript
程序进行解析
用于解析单个Expression
,性能比parse()
要高
参数 | 说明 |
---|---|
allowImportExportEverywhere | 默认情况下,import 和 export 声明语句只能出现在程序的最顶层把这个设置为 true ,可以使得语句在任何地方都可以声明 |
allowAwaitOutsideFunction | 默认情况下,仅在 异步函数内部 或 启用topLevelAwait插件 时 在模块的顶层内允许使用await 把这个设置为 true ,可以使得语句在任何地方都可以声明 |
allowReturnOutsideFunction | 默认情况下,如果在顶层中使用return 语句会引起错误把这个设置为 true ,就不会报错 |
allowSuperOutsideMethod | 默认情况下,在类和对象方法之外不允许使用super 把这个设置为 true 就可以声明 |
allowUndeclaredExports | 默认情况下,export 一个在当前作用域下未声明的内容会报错把这个设置为 true 就可以防止解析器过早地抛出未声明的错误 |
createParenthesizedExpressions | 默认情况下,parser 会在expression 节点设置extra.parenthesized 把这个设置为 true ,则会设置ParenthesizedExpression AST节点 |
errorRecovery | 默认情况下,如果Babel 发现一些 不正常的代码 就会抛出错误把这个设置为 true ,则会在保存解析错误的同时继续解析代码,错误的记录将被保存在 最终生成的AST的errors 属性中注意,那些严重的错误依然会终止解析 |
plugins | 记录希望启动的插件的数组 |
sourceType | 代码的解析方式,你可以填入"script" (默认),"module" 或 "unambiguous" 如果设置为"unambiguous",那么系统会根据ES6语法中的 imports 和export 来判断是"module" 还是"script" |
sourceFilename | 将输出的AST节点与其源文件名相关联 在你处理多个文件时,这个功能会很有用 |
startLine | 默认情况下,第一行代码就是line 1 。你可以传入一个数字,作为起始行数这个功能在你整合其他插件的时候会很有用 |
strictMode | 默认情况下,只有在声明了"use strict"条件下,ECMAScript代码才会被严格解析 将此选项设置为 true 则始终以严格模式解析文件 |
ranges | 添加ranges属性到每一个节点中 ranges: [node.start, node.end] |
tokens | 将所有已经解析的tokens 保存到File 节点的tokens 属性中 |
Babel parser
是根据 Babel AST format 创建AST的
而Babel AST format
是基于 ESTree 规范 建立的
ESTree 代码生成对应节点文档 Babel parser 代码生成对应节点文档
Babel parser
与ESTree
的不同之处
- 用
StringLiteral
,NumericLiteral
,BooleanLiteral
,NullLiteral
,RegExpLiteral
取代Literal
- 用
ObjectProperty
和ObjectMethod
取代Property
- 用
MethodDefinition
取代ClassMethod
Program
andBlockStatement
包含的directives
用Directive
和DirectiveLiteral
来填充FunctionExpression
中的ClassMethod
,ObjectProperty
,ObjectMethod
属性被引入到main方法
节点中
@babel/generator 是用于根据语法树构建代码的库,作用与 @babel/parse 正好相反
官方文档:https://babeljs.io/docs/en/babel-generator
函数用于根据ast生成代码,可以传入一些参数
name 参数名 | type 类型 | default 默认值 | description 描述 |
---|---|---|---|
auxiliaryCommentAfter | string | Optional 在输出文件内容末尾添加的注释块文字 | |
auxiliaryCommentBefore | string | Optional 在输出文件内容头部添加的注释块文字 | |
comments | boolean | true |
输出内容是否包含注释 |
compact | boolean or 'auto' |
opts.minified |
是否不添加空格来让代码看起来紧密 |
concise | boolean | false |
是否减少空格来让代码看起来紧凑一些 只是减少空格,而不是不添加 |
decoratorsBeforeExport | boolean | 是否在导出之前print 一下装饰器 |
|
filename | string | Used in warning messages | |
jsescOption | object | Use jsesc to process literals. jsesc is applied to numbers only if jsescOption.numbers (added in v7.9.0 ) is present. You can customize jsesc by passing options to it. |
|
jsonCompatibleStrings | boolean | false |
Set to true to run jsesc with "json": true to print "\u00A9" vs. "©"; |
minified | boolean | false |
Should the output be minified 是否压缩代码 |
retainFunctionParens | boolean | false |
Retain parens around function expressions (could be used to change engine parsing behavior) |
retainLines | boolean | false |
尝试在输出代码中使用与源代码中相同的行号(用于追踪堆栈) |
shouldPrintComment | function | opts.comments |
Function that takes a comment (as a string) and returns true if the comment should be included in the output. By default, comments are included if opts.comments is true or if opts.minified is false and the comment contains @preserve or @license |
例:去除代码中所有的注释
function delete_comments(sourceCode) {
// 用于删除所有注释, genrator实现
// @return String
let ast = parser.parse(sourceCode);
return generator(ast, {'comments': false})['code']
}
@babel/traverse 是用于对语法树进行各种操作的库
对应路径 @babel/traverse/lib/path/index.js
此文件主要包括 NodePath
的定义和一些基本方法
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
var jscode = `
function f(){
var b = 123;
a = ['a', 'b'];
}`;
const visitor = {
BlockStatement(path)
{
console.log('当前路径 源码:\n', path.toString());
console.log('当前路径 节点:\n', path.node)
console.log('当前路径 父级节点:\n', path.parent);
console.log('当前路径 父级路径:\n', path.parentPath)
console.log('当前路径 类型:\n', path.type)
console.log('当前路径 contexts:\n', path.contexts);
console.log('当前路径 hub:\n', path.hub);
console.log('当前路径 state:\n', path.state);
console.log('当前路径 opts:\n', path.opts)
console.log('当前路径 skipKeys:\n', path.skipKeys)
console.log('当前路径 container:\n', path.container)
console.log('当前路径 key:\n', path.key)
console.log('当前路径 scope:\n', path.scope)
}
}
let ast = parser.parse(jscode);
traverse(ast, visitor);
你会发现其中有不少值都是没有定义的,这是因为很多值都是懒加载的
而且会给与专门的方法进行获取,并不是这样直接获取的
@return bool
是否在列表中/是否存在兄弟节点
一般只有那些能存放多个节点的节点才会将节点存放在列表中
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const jscode = `function square(n) {
var a = 1;
a += 2;
if(a + 1 == 4){return a-2}
return a;
}`;
const ast = parser.parse(jscode);
const visitor = {
Statement(path) {
console.log('当前节点:', path.toString())
console.log('当前语句', path.getStatementParent().toString())
console.log('是否在列表中/是否存在兄弟节点', path.inList)
console.log('---------------------------------')
}
}
traverse(ast, visitor);
对应路径 @babel/traverse/lib/path/replacement.js
替换语法树节点相关的函数
方法用于将传入的 Node
替换对应 NodePath
的 Node
此方法只能用一个节点替换一个节点
- 如果不传入
replacement
会提示:NodePath.removed()
才是用来删除节点的方法 - 如果传入数组会提示:
NodePath.replaceWithMultiple()
才是一次性用多个节点替换目标节点的方法 - 如果传入字符串会提示:
NodePath.replaceWithSourceString()
才是用源码替换目标节点的方法 - 只能用
Program
类型的节点替换 根节点Program
const t = require("@babel/types");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
const jscode = `function square(n) {
return n + 1;
}`;
const ast = parser.parse(jscode);
const visitor = {
BinaryExpression(path) {
path.replaceWith(
t.BinaryExpression("**", path.node.left, t.NumericLiteral(2))
);
// 由于是用 BinaryExpression 代替 BinaryExpression
// visitor会认为替换后的节点是新的节点,会传入,所以这里直接停止,防止递归进入
path.stop();
}
}
traverse(ast, visitor);
console.log(generator(ast)['code'])
方法用于将传入的 多个 Node
替换对应 NodePath
的 Node
此方法能用多个节点替换一个节点
const t = require("@babel/types");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
const jscode = `function square(n) {
return n + 1;
}`;
const ast = parser.parse(jscode);
const visitor = {
ReturnStatement(path) {
let nodes = [
t.expressionStatement(t.stringLiteral("who")),
t.expressionStatement(t.stringLiteral("I")),
t.expressionStatement(t.stringLiteral("am")),
]
path.replaceWithMultiple(nodes);
}
}
traverse(ast, visitor);
console.log(generator(ast)['code'])
此方法用 传入的源码字符串 解析成对应 Node
后 替换 对应 NodePath
的 Node
写入的内容解析成 Node
后,必须为 Expression
类型
此函数方便但是性能较差,不建议使用
const t = require("@babel/types");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
const jscode = `function square(n) {
return n + 1;
}`;
const ast = parser.parse(jscode);
const visitor = {
ReturnStatement(path) {
path.replaceWithSourceString('1 + 1');
}
}
traverse(ast, visitor);
console.log(generator(ast)['code'])
用传入的一个或多个 Node
替换对应 Node
此函数视传入的内容去调用 NodePath.replaceWithMultiple() 或 NodePath.replaceWith() ,相当于同时有了这两个函数的功能
例子:使用 NodePath.replaceInline 替换目标节点
const t = require("@babel/types");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
const jscode = `function square(n) {
return 1 + 1;
}`;
const ast = parser.parse(jscode);
const visitor = {
BinaryExpression(path) {
var result = eval(path.toString()) // 计算表达式结果
var node = t.NumericLiteral(result) // 使用 types 来生成一个数字节点
path.replaceInline(node); // 用新的节点来替换表达式内容
}
}
traverse(ast, visitor);
console.log(generator(ast)['code'])
父级/祖先相关
@return NodePath | None
逐级递归寻找父级节点的Path,并将 NodePath
作为参数传入的判断函数进行判断
当判断函数返回 true
, 则 NodePath.findParent(callback)
返回对应 NodePath
当判断函数返回 false
, 则递归继续寻找父级, 进行判断。若已无父级,则返回null
例: 寻找当前Path的父级函数节点
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
var jscode = `
function f(){
var b = 123;
a = b + 1;
}`;
const visitor = {
AssignmentExpression(path){
console.log('当前路径源码:\n', path.toString());
// 寻找父级
function to_parent_function_path(x){ // 进行判断是否是函数声明节点的判断函数
if(x.isFunctionDeclaration()){return true}else{return false}
}
// 将判断函数传入,进行递归寻找父级path
the_path = path.findParent(to_parent_function_path)
console.log('to_parent_function_path 最终路径源码:\n', the_path.toString())
// 递归后如果没有发现符合要求的父级
function to_null(x){return false}
the_path = path.findParent(to_null)
console.log('to_null 最终路径:\n', the_path)
}
}
let ast = parser.parse(jscode);
traverse(ast, visitor);
结果
当前路径源码: a = b + 1 to_parent_function_path 最终路径源码: function f() { var b = 123; a = b + 1; } to_null 最终路径: null
@return NodePath | None
此函数与 NodePath.findParent
基本相同, 但这个判断包含对 当前 NodePath
的判断
它会先对 当前 NodePath
进行一次判断. 如果自身符合条件,那就返回 当前 NodePath
,然后才递归调用父级进行判断
例子:当前或父级
NodePath
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
var jscode = `
function f(){
var b = 123;
a = b + 1;
}`;
const visitor = {
AssignmentExpression(path){
console.log('当前路径源码:\n', path.toString());
function to_path(x){
if(x.isAssignmentExpression()){return true}else{return false}
}
the_path = path.find(to_path)
console.log('to_path最终路径源码:\n', the_path.toString())
// 寻找父级
function to_parent_function_path(x){ // 进行判断是否是函数声明节点的判断函数
if(x.isFunctionDeclaration()){return true}else{return false}
}
the_path = path.find(to_parent_function_path)
console.log('to_parent_function_path最终路径源码:\n', the_path.toString())
}
}
let ast = parser.parse(jscode);
traverse(ast, visitor);
结果
当前路径源码: a = b + 1 to_path最终路径源码: a = b + 1 to_parent_function_path最终路径源码: function f() { var b = 123; a = b + 1; }
@return NodePath | None
得到当前节点的第一个 父级/祖先 函数声明节点 NodePath
此方法通过调用 NodePath.findParent(callback)
传入内置的判断函数,来得到对应的结果
例: 寻找 父级/祖先 函数声明节点的
NodePath
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
var jscode = `
function f(){
var b = 123;
a = b + 1;
}`;
const visitor = {
AssignmentExpression(path){
console.log('当前路径源码:\n', path.toString());
the_path = path.getFunctionParent()
console.log('最终路径源码:\n', the_path.toString())
}
}
let ast = parser.parse(jscode);
traverse(ast, visitor);
结果
当前路径源码: a = b + 1 最终路径源码: function f() { var b = 123; a = b + 1; }
@return NodePath
返回第一个 父级/祖先 声明节点的NodePath
(向上遍历语法树,直到找到在列表中的父/祖先节点NodePath
)
声明节点所包含的节点类型见:Github文档
若找不到目标,会报错
例:返回第一个 父级/祖先 声明节点的 Path
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
var jscode = `
function f2(){
var b = 123;
return b + 1;
}`;
const visitor = {
Identifier(path){
console.log('当前路径源码:\n', path.toString());
the_path = path.getStatementParent()
console.log('最终路径源码:\n', the_path.toString())
console.log('------------------------------------')
}
}
let ast = parser.parse(jscode);
traverse(ast, visitor);
结果
当前路径源码: f2 最终路径源码: function f2() { var b = 123; return b + 1; } ------------------------------------ 当前路径源码: b 最终路径源码: var b = 123; ------------------------------------ 当前路径源码: b 最终路径源码: return b + 1; ------------------------------------
@return Array
返回所有 父级/祖先 NodePath
例:得到当前Path的所有 父级/祖先
NodePath
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
var jscode = `
function f2(){
var b = 123;
a = b + 1;
}`;
const visitor = {
AssignmentExpression(path){
console.log('当前路径源码:\n', path.toString());
the_paths = path.getAncestry()
console.log('返回类型:\n', the_paths instanceof Array)
console.log('结果路径源码:\n', the_paths.join('\n\n'))
}
}
let ast = parser.parse(jscode);
traverse(ast, visitor);
结果
当前路径源码: a = b + 1 返回类型: true 结果路径源码: a = b + 1 a = b + 1; { var b = 123; a = b + 1; } function f2() { var b = 123; a = b + 1; } function f2() { var b = 123; a = b + 1; }
@return bool
判断当前 NodePath
是否是指定 NodePath
的后代
此方法通过调用 NodePath.findParent(callback)
来进行判断,得到结果
例:辈分判断
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
var jscode = `
function f2(){
var b = 123;
a = b + 1;
}`;
const visitor = {
AssignmentExpression(path){
console.log('当前路径源码:\n', path.toString());
console.log('儿子是爸爸的后代:', path.isDescendant(path.parentPath))
console.log('儿子是爷爷的后代:', path.isDescendant(path.parentPath.parentPath))
console.log('儿子是孙子的后代:', path.isDescendant(path.get('left')))
}
}
let ast = parser.parse(jscode);
traverse(ast, visitor);
结果
当前路径源码: a = b + 1 儿子是爸爸的后代: true 儿子是爷爷的后代: true 儿子是孙子的后代: false
@return bool
判断当前 NodePath 是否是指定 NodePath 的后代
此方法是调用 传入的 NodePath
的 NodePath.isDescendant()
来进行判断的
例:判断是否是后代
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
var jscode = `
function f2(){
var b = 123;
a = b + 1;
}`;
const visitor = {
AssignmentExpression(path){
console.log('当前路径源码:\n', path.toString());
console.log('儿子是爸爸的祖先:', path.isAncestor(path.parentPath))
console.log('儿子是爷爷的祖先:', path.isAncestor(path.parentPath.parentPath))
console.log('儿子是孙子的祖先:', path.isAncestor(path.get('left')))
}
}
let ast = parser.parse(jscode);
traverse(ast, visitor);
结果
当前路径源码: a = b + 1 儿子是爸爸的祖先: false 儿子是爷爷的祖先: false 儿子是孙子的祖先: true
@return bool
判断当前Path
对应节点,或其 父/祖先 节点 是否包含特定类型的节点
可以一次性传入多个类型,只要有一个符合就会返回 true
, 否则返回 false
例: 是否包含特定类型的节点
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
var jscode = `
function f2(){
var b = 123;
a = b + 1;
}`;
const visitor = {
AssignmentExpression(path){
console.log('当前路径源码:\n', path.toString());
_is = path.inType('FunctionDeclaration')
console.log('父级或自身包含函数声明节点:', _is);
_is = path.inType('WithStatement', 'DebuggerStatement')
console.log('父级或自身包含 with 或 debugger:', path.inType(_is));
}
}
let ast = parser.parse(jscode);
traverse(ast, visitor);
结果
当前路径源码: a = b + 1 父级或自身包含函数声明节点: true 父级或自身包含 with 或 debugger: false
@return NodePath | 自定义
获取传入的Path
对应节点的 最大深度共同祖先节点的Path
- 当
paths
不存在length
属性时,报错 - 当
paths
长度为0时,返回null
- 当
paths
长度为1时,返回唯一的Path
- 当
paths
大于1- 计算 最大深度共同祖先节点 的
Path
并返回 - 当传入一个
filter
函数,那么返回结果会作为参数进行回调。返回结果变为filter(最大深度共同祖先节点Path:NodePath, 深度:int, 所有path的祖先信息:list);
- 计算 最大深度共同祖先节点 的
- 如果并不存在共同的祖先节点,报错
例:最大深度的共同祖先节点
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
var jscode = `
function f(){
function f3(){
function f1(){return 1;}
function f2(){return 2;}
return 3;
}
}`;
let paths = []
const visitor = {
ReturnStatement(path){
console.log('路径源码:\n', path.toString());
paths.push(path)
if (paths.length > 1){
_is = path.getDeepestCommonAncestorFrom(paths)
console.log('最大深度的共同祖先节点 源代码:', _is.toString());
}
}
}
let ast = parser.parse(jscode);
traverse(ast, visitor);
结果
路径源码: return 1; 路径源码: return 2; 最大深度的共同祖先节点 源代码: { function f1() { return 1; } function f2() { return 2; } return 3; } 路径源码: return 3; 最大深度的共同祖先节点 源代码: { function f1() { return 1; } function f2() { return 2; } return 3; }
@return NodePath
获取多个 NodePath
对象的 最早出现的共同祖先
方法会遍历计算,共同祖先一旦出现, 则返回,不再继续计算所有的 NodePath
此方法是调用 NodePath.getDeepestCommonAncestorFrom(paths, filter) 方法,传入固定的 filter
函数来实现
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
var jscode = `
function f(){
function f3(){
function f1(){return 1;}
function f2(){return 2;}
return 3;
}
}`;
let paths = []
const visitor = {
ReturnStatement(path){
console.log('路径源码:\n', path.toString());
paths.push(path)
if (paths.length > 1){
_is = path.getEarliestCommonAncestorFrom(paths)
console.log('最早的共同祖先节点 源代码:', _is.toString());
}
}
}
let ast = parser.parse(jscode);
traverse(ast, visitor);
主要用于获取同级/前后 NodePath
@return NodePath
通过父级,获取同级节点的 NodePath
或 其它内容
-
如果传入数字,则尝试获取 同级节点 指定位置的
NodePath
-
如果传入数字,则尝试获取 父级节点 指定位置的
NodePath
-
也可以传入一些特殊的key, 获取一些特殊的内容
可以使用
NodePath.listKey
属性 查看可以获取的key
例: 寻找其它内容
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
function x(){
console.log('code 1');
console.log('code 2');
var a = 1;
console.log('code 3');
console.log('code 4');
}
`;
const ast = parser.parse(jscode);
const visitor = {
VariableDeclaration(path) {
console.log('当前节点源码:\n', path.toString());
console.log('---------------------------------------------');
console.log('第1个兄弟的源码', path.getSibling(0).toString());
console.log('第2个兄弟的源码', path.getSibling(1).toString());
console.log('第3个兄弟的源码', path.getSibling(2).toString());
console.log('第4个兄弟的源码', path.getSibling(3).toString());
console.log('第5个兄弟的源码', path.getSibling(4).toString());
console.log(path.listKey)
console.log('---------------------------------------------');
}
}
traverse(ast, visitor);
结果
当前节点源码: var a = 1; --------------------------------------------- 第1个兄弟的源码 console.log('code 1'); 第2个兄弟的源码 console.log('code 2'); 第3个兄弟的源码 var a = 1; 第4个兄弟的源码 console.log('code 3'); 第5个兄弟的源码 console.log('code 4'); body ---------------------------------------------
@return Node
获取相对的对位节点 (left
与 right
)
此函数通过调用 NodePath.getSibling(key)
, 传入 当前节点的 left
或 right
key
实现
例:获取对位节点
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
var a = 1 + 9;
`;
const ast = parser.parse(jscode);
const visitor = {
NumericLiteral(path) {
console.log('当前节点源码:\n', path.toString())
console.log('对应节点源码:\n', path.getOpposite().toString())
console.log('----------------')
}
}
traverse(ast, visitor);
结果
当前节点源码: 1 对应节点源码: 9 ---------------- 当前节点源码: 9 对应节点源码: 1 ----------------
@return Node
获取同级前一个节点的 NodePath
此函数源码就一句 return this.getSibling(this.key - 1);
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
var a = 1 + 9;
a = a + a;
console.log(a);
console.log(b);
`;
const ast = parser.parse(jscode);
const visitor = {
ExpressionStatement(path) {
console.log('当前节点源码:\n', path.toString())
console.log('同级前一个节点源码:\n', path.getPrevSibling().toString())
console.log('----------------')
}
}
traverse(ast, visitor);
结果
当前节点源码: a = a + a; 同级前一个节点源码: var a = 1 + 9; ---------------- 当前节点源码: console.log(a); 同级前一个节点源码: a = a + a; ---------------- 当前节点源码: console.log(b); 同级前一个节点源码: console.log(a); ----------------
@return Node
获取同级后一个节点的 NodePath
此函数源码就一句 return this.getSibling(this.key + 1);
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
var a = 1 + 9;
a = a + a;
console.log(a);
console.log(b);
`;
const ast = parser.parse(jscode);
const visitor = {
ExpressionStatement(path) {
console.log('当前节点源码:\n', path.toString())
console.log('同级后一个节点源码:\n', path.getNextSibling().toString())
console.log('----------------')
}
}
traverse(ast, visitor);
结果
当前节点源码: a = a + a; 同级前一个节点源码: console.log(a); ---------------- 当前节点源码: console.log(a); 同级前一个节点源码: console.log(b); ---------------- 当前节点源码: console.log(b); 同级前一个节点源码: ----------------
@return Array
获取当前节点前的兄弟节点的 NodePath
,结果存放在一个数组中返回
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
var a = 1 + 9;
a = a + a;
console.log(a);
console.log(b);
`;
const ast = parser.parse(jscode);
const visitor = {
ExpressionStatement(path) {
console.log('当前节点源码:\n', path.toString())
const pre_nodepath = path.getAllPrevSiblings()
console.log('前面的兄弟节点源码:')
for(var nodepath of pre_nodepath){
console.log(nodepath.toString())
}
console.log('----------------')
}
}
traverse(ast, visitor);
结果
当前节点源码: a = a + a; 前面的兄弟节点源码: var a = 1 + 9; ---------------- 当前节点源码: console.log(a); 前面的兄弟节点源码: a = a + a; var a = 1 + 9; ---------------- 当前节点源码: console.log(b); 前面的兄弟节点源码: console.log(a); a = a + a; var a = 1 + 9; ----------------
@return NodePath
用于获取子孙节点
如果不传入 context
参数, 则以当前 NodePath
对应节点为起点
如果传入,则以传入的 NodePath
对应节点为起点
如果想要获取更多层级的子孙,可以用 .
隔开进行获取
-
获取某个单个属性节点
.名字
-
获取某个节点的第 x 个节点
.x
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `function square(n) {
var a = 1;
return 1 + 1;
}`;
const ast = parser.parse(jscode);
const visitor = {
FunctionDeclaration(path) { // 找到变量声明节点,删除
var p1 = path.get('body')
console.log('body 子节点源码:\n', p1.toString())
var p2 = path.get('body.body.0')
console.log('body.body.0 子节点源码:\n', p2.toString())
}
}
traverse(ast, visitor);
结果
body 子节点源码: { var a = 1; return 1 + 1; } body.body.0 子节点源码: var a = 1;
@return dict(Node}
用于获取当前 NodePath
所包含的 Identifier
节点
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const jscode = `function square(n) {
var a = 1;
a += 2;
return a;
}`;
const ast = parser.parse(jscode);
const visitor = {
exit(path) {
if (t.isProgram(path)){return;}
console.log('当前节点:', path.toString())
console.log('当前语句', path.getStatementParent().toString())
let _ids = path.getBindingIdentifiers()
if(_ids == null){return;}
console.log(_ids)
console.log('--------------------------------')
}
}
traverse(ast, visitor);
判断节点是否存在 body
, 如果存在 body
但其中的节点并非 BlockStament
则创建 BlockStament
进行包裹
如果 NodePath
本身没有 body
属性则会报错Can't convert node without a body
如果 NodePath.body
是数组(或许本身就是BlockStament
) 则会报错 Can't convert array path to a block statement
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
var jscode = `
while(1==1)
console.log('While')
for(;1==1;)
console.log("For")
do
console.log("do...while")
while(1==1)
`;
const visitor = {
WhileStatement(path) { path.ensureBlock(); },
ForStatement(path) { path.ensureBlock(); },
DoWhileStatement(path) { path.ensureBlock(); },
}
let ast = parser.parse(jscode);
traverse(ast, visitor);
console.log(generator(ast)['code'])
移除相关
@return null
删除路径对应的节点
删除以后,对应的removed
标识为会被设定,内容会被设定为只读
如果再次执行remove
方法,则会报错
例:删除 path 对应的节点
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
const jscode = `function square(n) {
var a = 1;
return 1 + 1;
}`;
const ast = parser.parse(jscode);
const visitor = {
VariableDeclaration(path){ // 找到变量声明节点,删除
path.remove()
}
}
traverse(ast, visitor);
console.log(generator(ast)['code'])
得到结果:
function square(n) {
return 1 + 1;
}
此模块与作用域相关
和 作用域 相关的内容被定义在了 Scope类 中
这个类定义位于 @babel/traverse/lib/scope/index.js
文件中
例:输出一些属性,一般不会直接使用,但可以留个印象,后面的函数可能会使用属性
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
var g = 1;
function squire(i){
return i * g * i;
}
function i()
{
var i = 123;
i += 2;
return 123;
}
`;
let ast = parser.parse(jscode);
const visitor = {
VariableDeclaration(path){
console.log("\n这里是", path.toString())
console.log('--------------------------------')
sc = path.scope // 获取对应的 Scope对象
console.log('这个对象是否已经初始化:', sc.inited)
console.log('uid 属性', sc.uid)
console.log('cached 属性', sc.cached)
console.log('node 属性', sc.node)
console.log('作用域节点:', sc.block)
console.log('作用对应的path:', sc.path.node == sc.block)
console.log('labels 属性', sc.labels)
console.log('绑定 的信息:', sc.bindings)
console.log('--------------------------------')
}
}
traverse(ast, visitor);
你能够直接访问Scope
对象的属性,它本身也提供了一些方法来访问
@return Scope | undefined
获取当前作用域的父级作用域
此方法通过引用其 Scope.path
属性的 PathNode.findParent()
方法 获取对应PathNode
后再次获取作用域的方式获取
例:获取父级作用域
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
var g = 1;
function squire(i){
return i * g * i;
}
function i()
{
var i = 123;
i += 2;
return 123;
}
`;
let ast = parser.parse(jscode);
const visitor = {
VariableDeclaration(path){
console.log("\n这里是", path.toString())
console.log('--------------------------------')
sc = path.scope // 获取对应的 Scope对象
console.log('parent结果:', sc.parent)
}
}
traverse(ast, visitor);
结果
这里是 var g = 1;
--------------------------------
parent结果: undefined
这里是 var i = 123;
--------------------------------
parent结果: Scope {
uid: 0,
block: Node {
type: 'Program',
start: 0,
end: 118,
loc: SourceLocation { start: [Position], end: [Position] },
sourceType: 'script',
interpreter: null,
body: [ [Node], [Node], [Node] ],
directives: []
},
path: NodePath {
parent: Node {
type: 'File',
start: 0,
end: 118,
loc: [SourceLocation],
errors: [],
program: [Node],
comments: []
},
hub: undefined,
contexts: [ [TraversalContext] ],
data: null,
_traverseFlags: 0,
state: undefined,
opts: { VariableDeclaration: [Object], _exploded: true, _verified: true },
skipKeys: null,
parentPath: null,
context: TraversalContext {
queue: [Array],
parentPath: undefined,
scope: undefined,
state: undefined,
opts: [Object],
priorityQueue: []
},
container: Node {
type: 'File',
start: 0,
end: 118,
loc: [SourceLocation],
errors: [],
program: [Node],
comments: []
},
listKey: undefined,
key: 'program',
node: Node {
type: 'Program',
start: 0,
end: 118,
loc: [SourceLocation],
sourceType: 'script',
interpreter: null,
body: [Array],
directives: []
},
scope: [Circular],
type: 'Program'
},
labels: Map {},
inited: true,
references: [Object: null prototype] { g: true, i: true, squire: true },
bindings: [Object: null prototype] {
g: Binding {
identifier: [Node],
scope: [Circular],
path: [NodePath],
kind: 'var',
constantViolations: [],
constant: true,
referencePaths: [Array],
referenced: true,
references: 1,
hasDeoptedValue: false,
hasValue: false,
value: null
},
squire: Binding {
identifier: [Node],
scope: [Circular],
path: [NodePath],
kind: 'hoisted',
constantViolations: [],
constant: true,
referencePaths: [],
referenced: false,
references: 0,
hasDeoptedValue: false,
hasValue: false,
value: null
},
i: Binding {
identifier: [Node],
scope: [Circular],
path: [NodePath],
kind: 'hoisted',
constantViolations: [],
constant: true,
referencePaths: [],
referenced: false,
references: 0,
hasDeoptedValue: false,
hasValue: false,
value: null
}
},
globals: [Object: null prototype] {},
uids: [Object: null prototype] {},
data: [Object: null prototype] {},
crawling: false
}
更改 绑定 的 Identifier
, 更改后会改变作用域下所有引用该 绑定 的位置
通常用于改变作用域下的某个 变量名
例: 变量重命名
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
const jscode = `
function a(n){
return n * n
}
`;
let ast = parser.parse(jscode);
const visitor = {
FunctionDeclaration(path) {
path.scope.rename("n", "_x_");
}
}
traverse(ast, visitor);
console.log(generator(ast)['code'])
结果
function a(_x_) { return _x_ * _x_; }
return null
输出到自底向上的 作用域 与 绑定 的信息
执行后会得到类似于这样的输出信息
## FunctionDeclaration
- i { constant: false, references: 0, violations: 1, kind: 'var' }
## Program
- squire { constant: true, references: 0, violations: 0, kind: 'hoisted' }
- i { constant: true, references: 0, violations: 0, kind: 'hoisted' }
作用域 以#
划分,此处有两个作用域 FunctionDeclaration
与 Program
Binding 以最前方设置-
来标识,一般显示其中的4种信息
- constant
量 在声明后,在作用域内是否为常量(不会被修改)
实际上对应对应量的Binding
对象的Binding.constant
属性 - references
被引用次数
实际上对应对应量的Binding
对象的Binding.references
属性 - violations
被 重新定义/赋值 的次数
实际上对应对应量的Binding
对象的Binding.constantViolations
的长度
这个属性被用于记录变更位置(每次变更都添加内容) - kind
函数声明类型。常见的有:hoisted
提升,var
变量,local
内部
实际上对应对应量的Binding
对象的Binding.kind
属性
实际上这些信息大部分 (以一个绑定, 一个 Binding
对象的方式)储存在 Scope.bindings
这个属性中
例:使用案例
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
function squire(i){
return i * i * i;
}
function i(){
var i = 123;
i += 2;
return 123;
}
`;
let ast = parser.parse(jscode);
const visitor = {
"FunctionDeclaration"(path){
console.log("\n\n这里是函数 ", path.node.id.name + '()')
path.scope.dump();
}
}
traverse(ast, visitor);
得到结果:
结果
这里是函数 squire() ------------------------------------------------------------ ## FunctionDeclaration - i { constant: true, references: 3, violations: 0, kind: 'param' } ## Program - squire { constant: true, references: 0, violations: 0, kind: 'hoisted' } - i { constant: true, references: 0, violations: 0, kind: 'hoisted' } ------------------------------------------------------------ 这里是函数 i() ------------------------------------------------------------ ## FunctionDeclaration - i { constant: false, references: 0, violations: 1, kind: 'var' } ## Program - squire { constant: true, references: 0, violations: 0, kind: 'hoisted' } - i { constant: true, references: 0, violations: 0, kind: 'hoisted' } ------------------------------------------------------------
@return Node
获取 作用域路径 的父级
它的源码就一句 return this.path.parent;
例: 获取作用域路径的父级
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
var g = 1;
function a(){return g;}
function b(){var z=2; return z;}
`;
let ast = parser.parse(jscode);
const visitor = {
ReturnStatement(path){
var n = path.node.argument.name
console.log("\n这里是", path.toString())
console.log('结果:', path.scope.parentBlock)
}
}
traverse(ast, visitor);
结果
这里是 return g;
结果: Node {
type: 'Program',
start: 0,
end: 69,
loc: SourceLocation {
start: Position { line: 1, column: 0 },
end: Position { line: 5, column: 0 }
},
sourceType: 'script',
interpreter: null,
body: [
Node {
type: 'VariableDeclaration',
start: 1,
end: 11,
loc: [SourceLocation],
declarations: [Array],
kind: 'var'
},
Node {
type: 'FunctionDeclaration',
start: 12,
end: 35,
loc: [SourceLocation],
id: [Node],
generator: false,
async: false,
params: [],
body: [Node]
},
Node {
type: 'FunctionDeclaration',
start: 36,
end: 68,
loc: [SourceLocation],
id: [Node],
generator: false,
async: false,
params: [],
body: [Node]
}
],
directives: []
}
这里是 return z;
结果: Node {
type: 'Program',
start: 0,
end: 69,
loc: SourceLocation {
start: Position { line: 1, column: 0 },
end: Position { line: 5, column: 0 }
},
sourceType: 'script',
interpreter: null,
body: [
Node {
type: 'VariableDeclaration',
start: 1,
end: 11,
loc: [SourceLocation],
declarations: [Array],
kind: 'var'
},
Node {
type: 'FunctionDeclaration',
start: 12,
end: 35,
loc: [SourceLocation],
id: [Node],
generator: false,
async: false,
params: [],
body: [Node]
},
Node {
type: 'FunctionDeclaration',
start: 36,
end: 68,
loc: [SourceLocation],
id: [Node],
generator: false,
async: false,
params: [],
body: [Node]
}
],
directives: []
}
@return Binding
在作用域中获取指定的 Binding
对象
如果在 当前作用域 找不到指定的 绑定, 那么就会递归在父级作用域中寻找
例:获取指定的
Binding
对象
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
var g = 1;
function a(){return g;}
function b(){var z=2; return z;}
`;
let ast = parser.parse(jscode);
const visitor = {
ReturnStatement(path){
var n = path.node.argument.name
console.log("\n这里是", path.toString())
console.log('绑定:', path.scope.getBinding(n))
}
}
traverse(ast, visitor);
@return Binding
传入一个名称,从当前的 作用域 中拿到指定的 Binding
对象
实际上方法的源码就一句return this.bindings[name];
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
var g = 1;
function a(){var a=1;return g;}
`;
let ast = parser.parse(jscode);
const visitor = {
ReturnStatement(path){
var n = path.node.argument.name
console.log("\n这里是", path.toString())
console.log('获取挡墙定义域里 a的Binding,结果:', path.scope.bindings['a'])
}
}
traverse(ast, visitor);
@return Node | void 0
获取指定的 Binding
的定义节点Node
方法作用域获取 Binding
,再通过这个 Binding
获取其定义的节点
这个方法通过 Scope.getBinding(name)
方法获取 Binding
,所以会存在递归向上的情况
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
var g = 1;
function a(){return g;}
function b(){var z=2; return z;}
`;
let ast = parser.parse(jscode);
const visitor = {
ReturnStatement(path){
var n = path.node.argument.name
console.log("\n这里是", path.toString())
console.log(n, '的定义:', path.scope.getBindingIdentifier(n))
}
}
traverse(ast, visitor);
@return Node|void 0
获取指定的 Binding
,并通过这个 Binding
获取其定义的节点
这个方法只关注 当前作用域,并不会去上级作用域中寻找
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
var g = 1;
function a(){return g;}
function b(){var z=2; return z;}
`;
let ast = parser.parse(jscode);
const visitor = {
ReturnStatement(path){
var n = path.node.argument.name
console.log("\n这里是", path.toString())
console.log(n, '的定义:', path.scope.getOwnBindingIdentifier(n))
}
}
traverse(ast, visitor);
结果
这里是 return g; g 的定义: undefined 这里是 return z; z 的定义: Node { type: 'Identifier', start: 53, end: 54, loc: SourceLocation { start: Position { line: 4, column: 17 }, end: Position { line: 4, column: 18 }, identifierName: 'z' }, name: 'z' }
@return bool
获知当前作用域是否有某个 Binding
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
var g = 1;
function a(){return g;}
function b(){var z=2; return z;}
`;
let ast = parser.parse(jscode);
const visitor = {
ReturnStatement(path){
var n = path.node.argument.name
console.log("\n这里是", path.toString())
console.log('当前作用域有 绑定 z:', path.scope.hasOwnBinding('z'))
console.log('当前作用域有 绑定 g:', path.scope.hasOwnBinding('g'))
}
}
traverse(ast, visitor);
结果
这里是 return g; 当前作用域有 绑定 z: false 当前作用域有 绑定 g: false 这里是 return z; 当前作用域有 绑定 z: true 当前作用域有 绑定 g: false
两个函数的作用域内都不会有g
的 绑定,因为它在更上级作用域中
@return bool
向上递归作用域,获知是否有某个 Binding
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
var g = 1;
function a(){return g;}
function b(){var z=2; return z;}
`;
let ast = parser.parse(jscode);
const visitor = {
ReturnStatement(path){
console.log("\n这里是", path.toString())
console.log('作用域有 绑定 z:', path.scope.hasBinding('z'))
console.log('作用域有 绑定 g:', path.scope.hasBinding('g'))
}
}
traverse(ast, visitor);
用于刷新 Scope 内的数据
例如修改代码中的一些节点后, 对应 Scope.bindings
没有更新的问题
例:修正插入新的变量节点后 Scope.bindings 信息未更新的问题
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
const types = require("@babel/types");
const jscode = `
function a(n){
return n * n
}
`;
let ast = parser.parse(jscode);
const visitor = {
ReturnStatement(path) {
// 作用域内定义新的变量a(Binding)
let newNode = types.VariableDeclaration(
"var",
[
types.VariableDeclarator(
types.identifier('a'),
types.stringLiteral('well')
)
]
);
path.replaceWith(newNode);
console.log('未更新Scope信息时', Object.keys(path.scope.bindings));
path.scope.crawl();
console.log('更新Scope信息后', Object.keys(path.scope.bindings));
}
}
traverse(ast, visitor);
console.log('最终代码', generator(ast)['code'])
Binding
对象用于存储 绑定 在作用域的量 的信息
你可以在 @babel/traverse/lib/scope/binding.js
查看到它的定义
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const jscode = `
function a(){
var a = 1;
a = a + 1;
return a;
}
function b(){
var b = 1;
var c = 2;
b = b - c;
return b;
}
`;
let ast = parser.parse(jscode);
const visitor = {
BlockStatement(path){
console.log("\n此块节点源码:\n", path.toString())
console.log('----------------------------------------')
var bindings = path.scope.bindings
console.log('作用域内 绑定 的数量:', Object.keys(bindings).length)
for(var binding_ in bindings){
console.log('名字:', binding_)
binding_ = bindings[binding_];
console.log('类型:', binding_.kind)
console.log('定义:', binding_.identifier)
console.log('是否为常量:', binding_.constant)
console.log('被修改信息信息记录', binding_.constantViolations)
console.log('是否会被引用:', binding_.referenced)
console.log('被引用次数', binding_.references)
console.log('被引用信息NodePath记录', binding_.referencePaths)
}
}
}
traverse(ast, visitor);
注释相关
@return None
添加注释
实际上只是调用types.addComment
的方法而已
参数名 | 参数类型 | 说明 |
---|---|---|
type |
String |
指定添加的注释方式。如果填入"leading" ,则添加的注释会插入已有注释之前,否则在原有注释之后 |
content |
String |
注释内容 |
line |
bool |
插入行注释还是块注释 |
例:插入注释
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
const jscode = `
var a = 1 + 9;
`;
const ast = parser.parse(jscode);
const visitor = {
NumericLiteral(path) {
console.log('当前节点源码:\n', path.toString());
path.addComment('trailing', "注释", false);
}
}
traverse(ast, visitor);
console.log(generator(ast)['code'])
结果
未更新Scope信息时 [ 'n' ] 更新Scope信息后 [ 'n', 'a' ] 最终代码 function a(n) { var a = "well"; }
@return bool
对比函数,expected
传入一个字典进行 key
, value
遍历
获取 actual
.key
的值与 value
进行对比
如果有一个不一致,那么返回 false
, 否则返回 true
其定义在: @babel/types/lib/validators/generated/index.js
例:判断节点的name是否为
a
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const jscode = 'var a=1; var b=1+1;';
let ast = parser.parse(jscode);
const visitor = {
enter(path){
console.log('当前节点源码:', path.toString())
console.log('其属性name为a:', t.shallowEqual(path.node, {'name':'a'}))
}
}
traverse(ast, visitor);
@return bool
这并不是一个函数,这是一大堆由生成代码生成的函数,大约有290个
这些函数定义在 @babel/types/lib/validators/generated/index.js
函数逻辑都是类似的
-
if( 没有node ) return
false
-
if(
node.type
==声明类型
) returnfalse
-
else if( 没有opts ) return
true
-
else return types.shallowEqual(node, opts)
例: 判断节点是否符合判断
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const jscode = 'var a=1;var b=1+1;';
let ast = parser.parse(jscode);
const visitor = {
enter(path){
console.log('当前节点源码:', path.toString())
console.log('是 Identifier', t.isIdentifier(path.node))
console.log('是 Identifier 且其属性name为a:', t.isIdentifier(path.node, {'name':'a'}))
}
}
traverse(ast, visitor);