AST-web端javascript逆向杀器之常用API
来了,前面一篇文章的相关api,常用的使用方法和参数来了。
本文转自:点我
@babel/parse
解析函数
babelParser.parse(code, [options])
将提供的代码作为一个完整的ECMAScript
程序进行解析
babelParser.parseExpression(code, [options])
用于解析单个Expression
,性能比parse()
要高
options 函数参数
-
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
属性中
输出 Output
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
官方文档:https://babeljs.io/docs/en/babel-generator
generate(ast, options, code);
函数用于根据ast生成代码,可以传入一些参数
options 参数
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 |
@babel/traverse
index
NodePath基础属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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); |
你会发现其中有不少值都是没有定义的,这是因为很多值都是懒加载的
而且会给与专门的方法进行获取,并不是这样直接获取的
ancestry
父级/祖先相关
NodePath.findParent(callback)
@return NodePath | None
逐级递归寻找父级节点的Path,并将Path
作为参数传入的判断函数进行判断
当判断函数返回true
, 则Path.findParent(callback)
返回对应Path
当判断函数返回false
, 则递归继续寻找父级, 进行判断。若已无父级,则返回null
例: 寻找当前Path的父级函数节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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); |
得到的输出结果
1 2 3 4 5 6 7 8 9 | 当前路径源码: a = b + 1 to_parent_function_path 最终路径源码: function f() { var b = 123; a = b + 1; } to_null 最终路径: null |
NodePath.find(callback)
@return NodePath | None
此函数与 Path.findParent
基本相同, 但这个判断包含对 当前Path 的判断
它会先对 当前Path 进行一次判断. 如果自身符合条件,那就返回 当前Path,然后才递归调用父级进行判断
例子:当前或父级Path
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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); |
得到的输出结果
1 2 3 4 5 6 7 8 9 | 当前路径源码: a = b + 1 to_path最终路径源码: a = b + 1 to_parent_function_path最终路径源码: function f() { var b = 123; a = b + 1; } |
NodePath.getFunctionParent()
@return NodePath | None
得到当前节点的第一个 父级/祖先 函数声明节点的Path
此方法通过调用 Path.findParent(callback)
传入内置的判断函数,来得到对应的结果
例: 寻找 父级/祖先 函数声明节点的Path
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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); |
得到对应结果
1 2 3 4 5 6 7 | 当前路径源码: a = b + 1 最终路径源码: function f() { var b = 123; a = b + 1; } |
NodePath.getStatementParent()
@return NodePath
返回第一个 父级/祖先 声明节点的Path
声明节点所包含的节点类型见:Github文档
若找不到目标,会报错
例:返回第一个 父级/祖先 声明节点的 Path
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | const parser = require( "@babel/parser" ); const traverse = require( "@babel/traverse" ). default ; var jscode = ` function f2(){ var b = 123; a = b + 1; }`; const visitor = { BinaryExpression(path){ console.log( '当前路径源码:\n' , path.toString()); the_path = path.getStatementParent() console.log( '最终路径源码:\n' , the_path.toString()) } } let ast = parser.parse(jscode); traverse(ast, visitor); |
得到的结果
1 2 3 4 | 当前路径源码: b + 1 最终路径源码: a = b + 1; |
NodePath.getAncestry()
@return Array
返回所有 父级/祖先 的Path
例:得到当前Path的所有 父级/祖先 的Path
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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); |
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 当前路径源码: 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; } |
NodePath.isDescendant(path)
@return bool
判断当前 Path 是否是指定 Path 的后代
此方法通过调用 Path.findParent()
来进行判断,得到结果
例:辈分判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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); |
结果:
1 2 3 4 5 | 当前路径源码: a = b + 1 儿子是爸爸的后代: true 儿子是爷爷的后代: true 儿子是孙子的后代: false |
NodePath.isAncestor(path)
@return bool
判断当前 Path 是否是指定 Path 的后代
此方法是调用 传入的path的Path.isDescendant()
来进行判断的
例:判断是否是后代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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); |
结果:
1 2 3 4 5 | 当前路径源码: a = b + 1 儿子是爸爸的祖先: false 儿子是爷爷的祖先: false 儿子是孙子的祖先: true |
NodePath.inType(**NodeType_str)
@return bool
判断当前Path
对应节点,或其 父/祖先 节点 是否包含特定类型的节点
可以一次性传入多个类型,只要有一个符合就会返回 true
, 否则返回 false
例: 是否包含特定类型的节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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); |
结果:
1 2 3 4 | 当前路径源码: a = b + 1 父级或自身包含函数声明节点: true 父级或自身包含 with 或 debugger: false |
NodePath.getDeepestCommonAncestorFrom(paths, filter)
@return NodePath | 自定义
获取传入的Path
对应节点的 最大深度共同祖先节点的Path
- 当
paths
不存在length
属性时,报错 - 当
paths
长度为0时,返回null
- 当
paths
长度为1时,返回唯一的Path
- 当
paths
大于1如果并不存在共同的祖先节点,报错- 计算 最大深度共同祖先节点 的
Path
并返回 - 当传入一个
filter
函数,那么返回结果会作为参数进行回调。返回结果变为filter(最大深度共同祖先节点Path:NodePath, 深度:int, 所有path的祖先信息:list);
- 计算 最大深度共同祖先节点 的
例:最大深度的共同祖先节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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); |
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 路径源码: 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; } |
NodePath.getEarliestCommonAncestorFrom(paths)
@return NodePath
获取paths
中最早出现的共同祖先
方法会遍历计算,共同祖先一旦出现, 则返回,不再继续计算所有的path
此方法是调用 getDeepestCommonAncestorFrom(paths, filter) 方法,传入固定的filter
函数来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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); |
family
主要用于获取同级/前后 NodePath
NodePath.getSibling(key)
@return NodePath
通过父级,获取同级节点的 NodePath
或 其它内容
-
如果传入数字,则尝试获取 同级节点 指定位置的
NodePath
-
如果传入数字,则尝试获取 父级节点 指定位置的
NodePath
-
也可以传入一些特殊的key, 获取一些特殊的内容。
可以使用
NodePath.listKey
属性 查看可以获取的key
例: 寻找其它内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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); |
结果:
1 2 3 4 5 6 7 8 9 10 | 当前节点源码: 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 --------------------------------------------- |
NodePath.getOpposite()
@return Node
获取相对的对位节点 (left 与 right)
此函数通过调用 NodePath.getSibling(key)
, 传入 当前节点的 left
或 right
key
实现
例:获取对位节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 2 3 4 5 6 7 8 9 10 | 当前节点源码: 1 对应节点源码: 9 ---------------- 当前节点源码: 9 对应节点源码: 1 ---------------- |
NodePath.getPrevSibling()
@return Node
获取同级前一个节点的 NodePath
此函数源码就一句 return this.getSibling(this.key - 1);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | const parser = require(js_env + "@babel/parser" ); const traverse = require(js_env + "@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); |
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 当前节点源码: a = a + a; 同级前一个节点源码: var a = 1 + 9; ---------------- 当前节点源码: console.log(a); 同级前一个节点源码: a = a + a; ---------------- 当前节点源码: console.log(b); 同级前一个节点源码: console.log(a); ---------------- |
NodePath.getNextSibling()
@return Node
获取同级后一个节点的 NodePath
此函数源码就一句 return this.getSibling(this.key + 1);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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); |
输出结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 当前节点源码: a = a + a; 同级前一个节点源码: console.log(a); ---------------- 当前节点源码: console.log(a); 同级前一个节点源码: console.log(b); ---------------- 当前节点源码: console.log(b); 同级前一个节点源码: ---------------- |
NodePath.getAllPrevSiblings()
@return Array
获取当前节点前的兄弟节点的 NodePath
,结果存放在一个数组中返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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); |
输出结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 当前节点源码: 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; ---------------- |
NodePath.get(key, context)
@return NodePath
用于获取子孙节点
如果不传入 context
参数, 则以当前 path
对应节点为起点
如果传入,则以传入的 path
对应节点为起点
如果想要获取更多层级的子孙,可以用’.’隔开进行获取
-
获取某个单个属性节点
.名字
-
获取某个节点的第 x 个节点
.x
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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); |
输出结果:
1 2 3 4 5 6 7 | body 子节点源码: { var a = 1; return 1 + 1; } body.body.0 子节点源码: var a = 1; |
removal
移除相关
NodePath.remove()
@return null
删除路径对应的节点
删除以后,对应的removed
标识为会被设定,内容会被设定为只读
如果再次执行remove
方法,则会报错
例:删除 path 对应的节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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' ]) |
得到结果:
1 2 3 | function square(n) { return 1 + 1; } |
scope
此模块与作用域相关
Scope
和 作用域 相关的内容被定义在了 Scope类 中
这个类定义位于 @babel/traverse/lib/scope/index.js
文件中
Scope属性
例:输出一些属性,一般不会直接使用,但可以留个印象,后面的函数可能会使用属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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
对象的属性,它本身也提供了一些方法来访问
Scope方法
Scope.parent
@return Scope | undefined
获取当前作用域的父级作用域
此方法通过引用其 Scope.path
属性的 PathNode.findParent()
方法 获取对应PathNode
后再次获取作用域的方式获取
例:获取父级作用域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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); |
结果
Scope.dump()
return null
输出到自底向上的 作用域与被绑定量的信息
执行后会得到类似于这样的输出信息
1 2 3 4 5 | # 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
被绑定量 以最前方设置-
来标识,一般显示其中的4种信息
- constant
量 在声明后,在作用域内是否为常量
实际上对应对应量的Binding
对象的Binding.constant
属性 - references
被引用次数
实际上对应对应量的Binding
对象的Binding.references
属性 - violations
量 被 重新定义/赋值 的次数
实际上对应对应量的Binding
对象的Binding.constantViolations
的长度。这个属性被用于记录变更位置(每次变更都添加内容) - kind
函数声明类型。常见的有:hoisted
提升,var
变量,local
内部
实际上对应对应量的Binding
对象的Binding.kind
属性
实际上这些信息大部分 (以一个被绑定量,一个 Binding
对象的方式)储存在 Scope.bindings
这个属性中
例:使用案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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); |
得到结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 这里是函数 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' } ------------------------------------------------------------ |
Scope.parentBlock(name)
@return Node
获取 作用域路径 的父级
它的源码就一句 return this.path.parent;
例: 获取作用域路径的父级
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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); |
结果
Scope.getBinding(name)
@return Binding
获取指定 被绑定量 的 Binding
对象
如果在 当前作用域 找不到指定的 被绑定量,那么就会递归在父级作用域中寻找
例:获取指定的 被绑定量对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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); |
Scope.getOwnBinding(name)
@return Binding
传入一个名称,从当前的 作用域 中拿到指定的 被绑定量对象Binding
实际上方法的源码就一句return this.bindings[name];
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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); |
Scope.getBindingIdentifier(name)
@return Node | void 0
获取指定的 Binding
的定义节点Node
方法作用域获取 Binding
,再通过这个 Binding
获取其定义的节点
这个方法通过 Scope.getBinding(name)
方法获取 Binding
,所以会存在递归向上的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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); |
Scope.getOwnBindingIdentifier(name)
@return Node|void 0
获取指定的 Binding
,并通过这个 Binding
获取其定义的节点
这个方法只关注 当前作用域,并不会向上寻找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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); |
得到结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 这里是 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' } |
Scope.hasOwnBinding(name)
@return bool
获知当前作用域是否有某个被绑定变量得到结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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); |
1 2 3 4 5 6 7 | 这里是 return g; 当前作用域有 被绑定变量 z: false 当前作用域有 被绑定变量 g: false 这里是 return z; 当前作用域有 被绑定变量 z: true 当前作用域有 被绑定变量 g: false |
两个函数的作用域内都不会有g的绑定,因为它被绑定在更上级作用域中
Scope.hasBinding(name, noGlobals)
@return bool
向上递归作用域,获知是否有某个被绑定变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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); |
Binding
Binding
对象用于存储 被绑定在作用域的量 的信息
你可以在 @babel/traverse/lib/scope/binding.js
查看到它的定义
Binding属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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); |
comments
注释相关
NodePath.addComment(type, content, line)
@return None
添加注释
实际上只是调用types.addComment
的方法而已
参数:
-
type
str
指定添加的注释方式,如果填入"leading"
,则添加的注释会插入已有注释之前,否则在原有注释之后 -
content
str
注释内容 -
line
bool
插入行注释还是块注释
例:插入注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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' ]) |
@babel/types
utils
Types.shallowEqual(actual, expected)
@return bool
对比函数,expected
传入一个字典进行 key
, value
遍历
获取 actual
.key
的值与 value
进行对比
如果有一个不一致,那么返回 false
, 否则返回 true
其定义在: @babel/types/lib/validators/generated/index.js
例:判断节点的name是否为
a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); |
Types.isNodeType(node, opts)validators
@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)
例: 判断节点是否符合判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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); |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】