#javascript #typescript #minify #linter #ast-node #parser

oxc_traverse

使用Rust编写的JavaScript工具集合

28个版本 (12个破坏性更新)

新版本 0.25.0 2024年8月23日
0.23.1 2024年8月6日
0.22.1 2024年7月28日

1195编程语言

Download history 118/week @ 2024-05-10 166/week @ 2024-05-17 32/week @ 2024-05-24 294/week @ 2024-05-31 562/week @ 2024-06-07 898/week @ 2024-06-14 601/week @ 2024-06-21 1226/week @ 2024-06-28 1879/week @ 2024-07-05 1459/week @ 2024-07-12 1283/week @ 2024-07-19 1304/week @ 2024-07-26 1134/week @ 2024-08-02 1223/week @ 2024-08-09 931/week @ 2024-08-16

4,751 每月下载量
用于 4 个crate(2个直接使用)

MIT 许可证

2.5MB
62K SLoC

具有从访问者读取树的能力的AST遍历。

请参阅traverse_mut了解API的解释。

实现细节

这个crate中的大部分代码都是通过代码生成器生成的。代码生成器目前是用JavaScript编写的(scripts/build.mjs)。

不要编辑这些文件,因为它们将在下一次运行时被构建脚本覆盖。

允许读取树的方案是基于使违反Rust的别名规则在静态上成为不可能的。

Rust的别名规则大致如下

  1. 对于任何对象,你可以同时拥有任意数量的不可变&引用。
  2. 对于任何对象,如果你有对该对象的任何其他引用(不可变或可变),则无法获得对该对象的可变&mut引用。
  3. &/&mut ref 覆盖了对象本身及其下面的整个树。即你不能同时持有对子节点的可变引用和对父节点的任何引用(除了通过“重新借用”)。

这在修改访问者中读取树时提出了问题。在访问者中,你持有对节点的可变引用,因此无法获得对其父节点的&引用,因为父节点拥有该节点。如果你持有对父节点的&引用,这也充当了当前节点的&引用 = 同时持有对同一个节点的&&mut引用。灾难!

这个crate使用的解决方案是

  1. 在遍历AST的 walk_* 函数中不要创建引用。请使用原始指针。只在 enter_* / exit_* 方法中创建 &mut 引用。
  2. 不允许 enter_* / exit_* 访问其整个父节点或祖先,仅允许访问从父节点或祖先出发的树的其他分支。可以通过 ctx.parent() 等方式访问的当前节点以上的树的部分 不与 通过 &mut 引用在 enter_* / exit_* 中可访问的树的部分重叠。这使得同时获得相同AST节点的两个引用变得不可能。
  3. 再次提醒,在获取祖先的字段时不要创建瞬态引用。在创建 & 引用之前,使用原始指针直接访问字段。

此机制的实现是 Ancestor 枚举。每个AST节点类型都有几个 Ancestor 变体,例如 ProgramWithoutDirectivesProgramWithoutHashbangProgramWithoutBody。正如名称所暗示的,这些类型中的每一个都允许访问 Program 的所有字段,只有一个例外。

walk_* 函数遍历树时,它们会将适当的类型添加到 Ancestors 栈中,以防止访问正在遍历的路径。

walk_* 使用 TraverseCtx::retag_stack 来尽可能便宜地更新祖先栈,但这纯粹是性能优化,不是方案安全性的关键。

安全性

该crate包含大量不安全代码。整个 walk.rs 文件都是使用原始指针的不安全函数。

为了避免在编译时消耗时间解析1000多个注释,代码生成的文件不包含解释每个不安全操作的注释。但一切均按照上述原则进行。

几乎所有代码都是通过代码生成。我 (@overlookmotel) 建议继续仅使用代码生成,而不是手动编辑这些文件以处理“特殊情况”。安全方案很容易因为一个错误而完全偏离,所以在我看来,手动编辑是不明智的。

依赖项

~6–12MB
~121K SLoC