AST之path常用属性和方法总结笔记

本文总结了Babel中Path对象的一些常用属性和方法,如`path.node`,`path.scope`,`path.replaceWith`等,这些在处理抽象语法树(AST)时非常关键。通过示例展示了如何利用这些属性和方法进行变量查找、作用域遍历、节点替换与删除等操作,对于理解和编写Babel插件十分有帮助。
摘要由CSDN通过智能技术生成


这一块儿可参考的总结资料不多,参考着蔡老板的文章学习一下,做下记录总结

1. path常用属性总结

path相关的源代码在这个js文件中,大家可以直接照着源码学习:

\node_modules\@babel\traverse\lib\path

这里选出部分常用的属性供大家参考。

path的属性定义:

class NodePath {
  constructor(hub, parent) {
    this.contexts = [];
    this.state = null;
    this.opts = null;
    this._traverseFlags = 0;
    this.skipKeys = null;
    this.parentPath = null;
    this.container = null;
    this.listKey = null;
    this.key = null;
    this.node = null;
    this.type = null;
    this.parent = parent;
    this.hub = hub;
    this.data = null;
    this.context = null;
    this.scope = null;
  }
  ......
}

1.1 path.node

表示当前path下的node节点,通常写插件,函数体的第一行代码就是:

let {node,scope} = path;

1.2 path.scope

表示当前path下的作用域,这个也是写插件经常会用到的。

具体的可以参考蔡老板这篇文章 : Scope和Binding常用方法及属性总结

scope相关:
1.scope.block   

	表示当前作用域下的所有node,参考上面的 this.block = node;

2.scope.dump()  

	输出当前每个变量的作用域信息。调用后直接打印,不需要加打印函数

3.scope.crawl()   

	重构scope,在某种情况下会报错,不过还是建议在每一个插件的最后一行加上。

4.scope.rename(oldName, newName, block)   

	修改当前作用域下的的指定的变量名,oldname、newname表示替换前后的变量名,为字符串。注意,oldName需要有binding,否则无法重命名。

5.scope.traverse(node, opts, state)

 	遍历当前作用域下的某些(个)插件。和全局的traverse用法一样。

6.scope.getBinding(name)

	获取某个变量的binding,可以理解为其生命周期。包含引用,修改之类的信息

Binding:
目前我看到的只有 变量定义 和 函数定义 拥有binding,其他的获取binding都是undefined。

let binding = scope.getBinding(name);
例如:
var a = 123; 这里的 a 就拥有 binding。

而 function test(a,b,c) {};

函数名test以及形参a,b,c均拥有 binding。


1.binding.path

	用于定位初始拥有binding的path;

2.binding.constant

	用于判断当前变量是否被更改,true表示未改变,false表示有更改变量值。

3.binding.referenced

	用于判断当前变量是否被引用,true表示代码下面有引用该变量的地方,false表示没有地方引用该变量。注意,引用和改变是分开的。

4.binding.referencePaths

	它是一个Array类型,包含所有引用的path,多用于替换。

5. binding.constantViolations

	它是一个Array类型,包含所有改变的path,多用于判断。

例如我们可以利用Scope.getBinding()方法来获取Binding对象, 判断其引用情况来对语法树进行修改,

小例子,需求:想对以下js代码进行修改,删除所有定义了, 却从未使用的变量

var a = 1;
var b = 2;
function squire(){
  var c = 3;
  var d = 4;
  return a * d;
  var e = 5;
}
var f = 6;

可以这样写插件:

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;

const jscode = `
var a = 1;
var b = 2;
function squire(){
  var c = 3;
  var d = 4;
  return a * d;
  var e = 5;
}
var f = 6;
`;
let ast = parser.parse(jscode);
const visitor = {
    VariableDeclarator(path)
    {
        const func_name = path.node.id.name;
        const binding = path.scope.getBinding(func_name);
        // 如果变量没有被引用过,那么删除也没关系
        //   此处不能用有无修改过进行判断,因为没有被修改过并不意味着没用
        if(binding && !binding.referenced){
            path.remove();
        }
    },
}


traverse(ast, visitor);
console.log(generator(ast)['code']);

运行出来的结果:

在这里插入图片描述

1.3 path.parentPath

用于获取当前path下的父path,多用于判断节点类型。

1.4 path.parent

用于获取当前path下的父node,多用于判断节点类型。其中:

path.parent == path.parentPath.node;//这两者是等价的

1.5 path.container

用于获取当前path下的所有兄弟节点(包括自身),container翻译过来是容器的意思,它是一个Array类型,可以写个简单的插件来看看效果:

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
let jscode = "var a = 1,b = 2;var c = 3;";
let ast = parser.parse(jscode);
const getcontainer  =
{
  VariableDeclarator(path)
  {
      console.log(path.toString())
      console.log(path.container.length);
  }
}

traverse(ast,getcontainer);

在这里插入图片描述

代码中a,b互为兄弟节点,c只有一个节点,所以结果显示2,2,1。

这里要注意,是获取所有的所有兄弟节点(包括自身),不是path类型。

1.6 path.type

用于获取当前path的节点类型,写个简单的插件来看看效果 :

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
let jscode = "var a = 1 + 3;";
let ast = parser.parse(jscode)
const getType  =
{
  "VariableDeclarator|BinaryExpression|Identifier"(path)
  {
      console.log(path.toString());
      console.log(path.type);

  }
}

traverse(ast,getType);

在这里插入图片描述

1.7 path.key

用于获取当前path的key值,key通常用于path.get函数,当然还有更多的用法等待大家去挖掘 ,写个简单的插件来看看效果 :

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;


let jscode = "var a = 1 + 2;";
let ast = parser.parse(jscode)
const getPathKey  =
{
  "VariableDeclarator|BinaryExpression|Identifier"(path)
  {   
      console.log('path:',path.toString());
      console.log(path.key);

  }
}

traverse(ast,getPathKey);

在这里插入图片描述

第一个遍历的 path 是 VariableDeclarator 类型,对应源代码 是 a = 1+2 ;它的父节点是 VariableDeclaration 类型,而它处在 declarations 的第0的位置,因此输出是 0;

第二个遍历的 path 是 Identifier类型,对应源代码 是 a ;它是 id 节点,因此它的输出是 id;

第三个遍历的 path 是 BinaryExpression 类型,对应源代码 是 1+2 ;它是 init 节点,因此它的输出是 init。

2. path常用方法总结

2.1 path.toString

用来获取当前遍历path的js源代码,调用方式:

let sourceCode = path.toString();

这个非常有用,经常用于定于插件遍历的path,以便分析问题。

如果想通过 path.node来获取源代码,可以使用 generator 函数来获取:

let sourceCode = generator(path.node).code;

2.2 path.replaceWith

(单)节点替换函数,调用方式:

path.replaceWith(newNode);

实参一般是 node 类型,即将当前遍历的path替换为 实参里的 新节点。

注意,它不能用于 Array 的替换,即实参不能是Array类型。

2.3 path.replaceWithMultiple

(多)节点替换函数,调用方式:

path.rreplaceWithMultiple(ArrayNode);

实参一般是 Array 类型,它只能用于 Array 的替换。

即所有需要替换的节点在一个Array里面,常见的替换如 Block 类型节点里的 body.body。

2.4 path.remove

节点的删除,调用方式:

path.remove();

直接调用即可将当前遍历的所有符合条件的路径全部删除,所以使用的时候需要注意。

2.5 path.insertBefore

在当前节点前面插入新的节点。调用方式:

path.insertBefore(newNode);

2.6 path.insertAfter

在当前节点后面插入新的节点。调用方式:

path.insertAfter(newNode);

2.7 path.traverse

在当前节点下遍历其他的节点,比如有如下for循环:

for(;;)
{
  a = b;
  b = c;
  c =d;
}

想要遍历这个for循环下面的 赋值语句,可以借助该函数进行遍历:

const visitFor = 
{
  ForStatement(path)
  {
    ......
    
    path.traverse({
      AssignmentExpression(_path)
      {
        ......
      },
    }),
    
    ......
  },
}

文章到此结束,感谢您的阅读,下篇文章见!

AST学习课程推荐:
蔡老板和风佬课程:AST入门实战+零基础JavaScript补环境课程
也可以看蔡老板的知识星球学习:AST入门与实战

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰履踏青云

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值