Path 对象
Path & Node 的区别
Path 是描述
父子节点之间的链接
,Path
除了下面这些属性外,还包含添加
、更新
、移动
、删除
等方法Node 对象 是 Path 对象 中的一个属性。
path.node
能取出当前遍历到
的节点各种具体信息
,不能
使用 Path 对象的各种方法
/* NodePath { contexts : Array(1) [TraversalContext], // 上下文 内容嵌套自己 state : undefined, opts : Object {Identifier: Object, _exploded: true, _verified: true}, _traverseFlags : 0, skipKeys : null, parentPath : NodePath {contexts: Array(1), state: undefined, opts: Object, _traverseFlags: 0, skipKeys: null, ...}, // 父节点 Path对象 container : Node {type: "VariableDeclarator", start: 4, end: 162, loc: SourceLocation, id: Node, ...}, // 容器 里面是兄弟节点,有 数组(有兄弟) 和 单个对象(没有兄弟) listKey : undefined, // 有兄弟节点时,兄弟节点的 容器的名字 key : "id", // 有兄弟节点时,代表 兄弟容器索引,没有兄弟节点,代表 对象属性名 node : Node {type: "Identifier", start: 4, end: 7, loc: SourceLocation, name: "obj"}, // Node 对象 type : "Identifier", parent : Node {type: "VariableDeclarator", start: 4, end: 162, loc: SourceLocation, id: Node, ...}, // 父节点 Node对象 hub : undefined, data : null, context : TraversalContext {queue: Array(1), priorityQueue: Array(0), parentPath: NodePath, scope: Scope, state: undefined, ...}, scope : Scope {uid: 0, path: NodePath, block: Node, labels: Map(0), inited: true, ...}, // 作用域 } */
Path 对象 中的方法
获取当前节点
为了获取到 AST节点的属性值,需要先 访问到该 节点,然后用
path.node.xxx
(xxx 相关属性) 获取
获取 Node
对象
let visitor = { Identifier(path) { console.log(path.node) // 获取当前 节点 Node 对象 console.log(path.node.left) // 获取 Node 节点具体属性值 } }
获取 Path
对象,但是有些 属性值就没有必要包装 operator
、name
…
let visitor = { BinaryExpression(path) { console.log(path.get('node')) // 会把 node 包装成 Path 对象返回 console.log(path.get('node.left')) // 支持 多级访问 } }
判断 Path 类型
let visitor = { BinaryExpression(path) { console.log(path.get('left').isIdentifier()) console.log(path.get('right').isNumericLiteral({ value: 1000 })) console.log(path.get('left').assertIdentifier()) } } /* false true 报错 */
节点转代码
代码比较复杂,需要
动态调试
,就可以使用该方法,方便查看某一小段代码
,就不需要到最后才全部再转换成代码,再看
let visitor = { FunctionExpression(path) { console.log(generator(path.node).code) console.log(path.toString()) // 因为 Path 对象,重写了 toString() 方法 } } /* function (a, b) { return a + b + 1000; } function (a, b) { return a * b + 1000; } */
替换节点属性
替换需要属性,需要符合实际逻辑,在允许的范围替换,不能随便替换,造成逻辑错误
visitor = { BinaryExpression(path) { path.node.left = type.identifier(name="x") path.node.right = type.identifier(name="y") } } /* add: function (a, b) { return x + y; }, mul: function (a, b) { return x + y; } */
替换节点
replaceWith()
使用一个节点
替换一个节点
,一对一
的替换
visitor = { BinaryExpression(path) { path.replaceWith(type.valueToNode('mimi')) } } /* add: function (a, b) { return "mimi"; }, mul: function (a, b) { return "mimi"; } */
replaceWithMultiple()
为多个节点
替换一个节点
两点特别说明:
- 当替换的表达式替换后在一行时,最好使用
type.expressionStatement()
包裹一层 - 新替换的节点
traverse()
也会去遍历,所以 注意别陷入不合理的递归死循环
遍历ReturnStatement()
替换,替换里 又有ReturnStatement()
,陷入递归死循环
,所以使用path.stop()
停止
visitor = { ReturnStatement(path) { path.replaceWithMultiple([ type.expressionStatement(type.valueToNode('ZZ')), type.expressionStatement(type.valueToNode('XX')), type.returnStatement(type.valueToNode('YY')), ]) path.stop(); } } traverse(ast, visitor) /* add: function (a, b) { "ZZ"; "XX"; return "YY"; }, */
replaceInline()
,接受一个参数
,作用相对于是replaceWith()
replaceWithMultiple()
的综合
当接受的参数是 一个节点
,就是 replaceWith()
当接受的参数是 一个节点数组
,就是 replaceWithMultiple()
添加有 path.stop()
的原因与上面一样
visitor = { StringLiteral(path){ path.replaceInline( type.valueToNode('Helle mimi') ) path.stop() }, ReturnStatement(path) { path.replaceInline([ type.expressionStatement(type.valueToNode('ZZ')), type.expressionStatement(type.valueToNode('XX')), type.returnStatement(type.valueToNode('YY')), ]) path.stop() } } traverse(ast, visitor)
replaceWithSourceString()
接受字符串
作为参数,然后解析
到 AST 里面
replaceWithSourceString()
替换
后的节点也会被解析
,所以也需要 path.stop()
防止陷入递归死循环
// 把函数 改为 `闭包形式` visitor = { ReturnStatement(path) { let argumentPath = path.get('argument') argumentPath.replaceWithSourceString( 'function(){ return ' + argumentPath + '}' // 字符串 拼接 会自动调用 toString() ) path.stop() } } /* add: function (a, b) { return function () { return a + b + 1000; }; }, */
删除节点
visitor = { EmptyStatement(path){ // 空语句 也就是多余的分号 path.remove() } } traverse(ast, visitor)
插入节点
插入兄弟节点
insertAfter()
当前节点
之前
,insertBefore()
当前节点
之后
如果要 操作ReturnStatement
,可以先判断,直接 return
,注意该是用 path.stop()
是不行的
visitor = { ReturnStatement(path) { path.insertAfter(type.valueToNode("XX")) path.insertBefore(type.valueToNode('MM')); } } traverse(ast, visitor)
父级 Path
在 Path 对象中有两个属性
parentPath
、parent
,其中parentPath
是 NodePath 类型,是父级的 Path 对象
,parent
是父级的 Node 对象
path.findParent & path.find
参数都是一个 函数,返回值是 Path 对象 ,没有找到
返回 null
从当前节点,一直向上遍历,知道满足
对应条件
path.findParent()
常用,查找范围不包含
当前节点path.find()
不常用,查找范围包含
当前节点visitor = { ReturnStatement(path) { console.log(path.findParent((path)=>{return path.isObjectExpression()})) } } /* NodePath {...} */
path.getFunctionParent
向上查找 与 当前节点
最接近的
父函数节点,返回值是Path
对象
visitor = { ReturnStatement(path) { console.log(path.getFunctionParent()) } } traverse(ast, visitor) /* NodePath {...} NodePath {...} */
path.getStatementParent
向上遍历找到
父语句节点
, 如:return
,if
,whitch
,while
…
visitor = { ReturnStatement(path) { console.log(path.getStatementParent()) // 会从 当前节点开始 // console.log(path.parentPath.getStatementParent()) // 取巧 } } traverse(ast, visitor)
其他方法
替换
path.parentPath.replaceWith() path.parentPath.replaceWithMultiple() path.parentPath.replaceInline() path.parentPath.replaceWithSourceString()删除
path.parentPath.remove()
同级节点
了解同级节点,首要了解
Path 对象
的这几个属性:container
、listKey
、key
container
:有
兄弟节点时,是兄弟节点 数组
,没有
兄弟节点时 就是当前节点 Node 对象
listKey
:有
兄弟节点时,为容器名字
,没有
时 是undefined
key
:有
兄弟节点时,为当前节点
,在容器中
的索引
,没有
时,是 当前节点属性名
相关属性
visitor = { ReturnStatement(path) { console.log(path.inList) // true console.log(path.listKey) // body console.log(path.key) // 0 console.log(path.container) // [Node {...}] } } traverse(ast, visitor)
获取兄弟节点 path.getSibling
参数是 容器中节点的索引,
visitor = { ReturnStatement(path) { console.log(path.getSibling(path.key)) // Node {...} } } traverse(ast, visitor)
网容器添加节点
path.parentPath.unshiftContainer()
: 在容器头部
添加节点path.parentPath.pushContainer()
: 在容器尾部
添加节点
添加之后的节点,也会再 进行遍历,所以还是需要path.stop()
,以防止进入 递归死循环
visitor = { Property(path) { path.parentPath.unshiftContainer(path.listKey, [ type.objectProperty( key = type.identifier(name = "unshift"), // 第一个属性 value = type.stringLiteral(value = "OOO")) // 第一个属性值) ] ) path.parentPath.pushContainer(path.listKey, [ type.objectProperty( key = type.identifier(name = "push"), // 第一个属性 value = type.stringLiteral(value = "MMM")) // 第一个属性值) ] ) path.stop() } } traverse(ast, visitor)
版权声明:《 【AST 基础】三、AST 的 Path 和 Node 对象 》为明妃原创文章,转载请注明出处!
最后编辑:2022-4-9 09:04:02