- 开发工具
- CLI
- Lint
- 格式化
- 静态分析
- 代码质量
- 构建工具
【免费下载链接】tools
Unified developer tools for JavaScript, TypeScript, and the web
xtask/codegen是 Rome(现 Biome)仓库中负责"以代码生成代码"的核心工具集,它把.ungram语法 DSL、解析器内联注释测试、Unicode 官方数据等人工难以维护的输入,自动转化为rome_js_syntax等 crate 中的 AST 类型、SyntaxKind 定义、语法工厂、测试用例与 Unicode 查表代码。阅读本文后,你将掌握cargo codegen全家桶中每个子命令的作用与底层实现,理解仓库内语法编写约定(manual__前缀、union 标签、Bogus 节点命名),并能复现从修改语法到重新生成代码的完整开发工作流。
一、codegen 工具集的定位与入口
本仓库是一个 Rust workspace,其中xtask/codegen(crate 名为xtask_codegen,见 Cargo.toml)是一个publish = false的本地开发工具,核心职责在 README.md 中一句话概括:"This crate contains local commands used to auto-generate source code."
其设计借鉴了 rust-analyzer 的xtask/codegen模式(见 lib.rs 的注释:"Derived from Rust analyzer's codegen"),将"手工编写大量重复、易错、需与语法保持同步的 AST 代码"变成"运行一条命令自动重写"。
入口位于 main.rs:它通过pico_args解析第一个子命令,然后分发到对应的生成函数。为了让命令更短,仓库在 .cargo/config.toml 中注册了 Cargo alias,因此实际使用中cargo codegen xxx等价于cargo run -p xtask_codegen -- xxx:
codegen = "run -p xtask_codegen --" codegen-bindings = "run -p xtask_codegen --features schema -- bindings" codegen-configuration = "run -p xtask_codegen --features configuration -- configuration" codegen-schema = "run -p xtask_codegen --features schema -- schema" codegen-website = "run -p xtask_codegen --features website -- website"不传任何参数直接运行cargo codegen会打印全部子命令的用法清单(见 main.rs),包括aria、analyzer、configuration、schema、bindings、grammar、formatter、test、unicode、newlintrule、all。原文档重点讲解的grammar、test、unicode三个命令构成了代码生成流水线的核心,下面逐一展开。
二、cargo codegen grammar:把 .ungram 语法变成 AST 代码
2.1 背后的语法定义语言:ungrammar
cargo codegen grammar的作用是把.ungram文件转换为rome_js_syntax等语法 crate。项目使用ungrammar(依赖声明见 Cargo.toml)这门 DSL 来定义语言语法:ungrammar 只描述"具体语法树(CST)的结构",不关心解析规则——歧义、优先级等都不在它的职责范围内。
这一点在 js.ungram 的文件头注释中表达得很清楚:它"specifies the structure of Rust's concrete syntax tree",并给出了 DSL 的完整图例,这是编写语法的第一手参考:
// Name = -- non-terminal definition // 'ident' -- token (terminal) // A B -- sequence // A | B -- alternation // A* -- zero or more repetition // (A (',' A)* ','?) -- repetition of node A separated by ',' allowing a trailing comma // A? -- zero or one repetition // label:A -- suggested name for field of AST node当前仓库共有三份语法文件,分别对应三种语言:
- js.ungram —— JavaScript/TypeScript(约 2400 行);
- css.ungram —— CSS;
- json.ungram —— JSON。
代码生成的第一步是先解析 DSL:load_js_ast、load_css_ast、load_json_ast通过include_str!把.ungram编译进程序,再用ungrammar::Grammar解析(见 ast.rs)。随后make_ast遍历语法中的每个节点,把顶层产生式分类为四种形态(见 ast.rs):
- Union:形如
A = B | C的联合类型,生成枚举(enum); - Node:包含 token 或子节点的常规节点,生成结构体;
- Bogus:形如
A = SyntaxElement*的容错节点; - List:形如
A = B*或带分隔符的列表节点。
2.2 一条命令生成的六类文件
generate_syntax(见 ast.rs)负责把解析出的 AST 信息落盘到两个目录——crates/{语言}_syntax/src/generated/与crates/{语言}_factory/src/generated/,共六个文件:
| 生成文件 | 内容 | 写入路径示例(JS) |
|---|---|---|
nodes.rs | 每个语法节点/枚举的强类型 AST 结构体 | crates/rome_js_syntax/src/generated/nodes.rs |
nodes_mut.rs | 节点的可变访问器实现 | crates/rome_js_syntax/src/generated/nodes_mut.rs |
kind.rs | SyntaxKind枚举(由各语言的KINDS_SRC常量生成) | crates/rome_js_syntax/src/generated/kind.rs |
macros.rs | AST 相关宏 | crates/rome_js_syntax/src/generated/macros.rs |
syntax_factory.rs | 从 green tree 构建节点的语法工厂 | crates/rome_js_factory/src/generated/syntax_factory.rs |
node_factory.rs | 面向开发者的节点构造工厂 | crates/rome_js_factory/src/generated/node_factory.rs |
每个文件生成后都通过update函数(见 lib.rs)与磁盘现有内容比对:内容相同则跳过(返回NotUpdated),不同则覆盖写入(返回Updated)。这是整个 codegen 工具保证"生成结果幂等、可重复提交"的关键机制。
grammar子命令还支持按需指定语言:从 main.rs 可以看到,cargo codegen grammar会把剩余参数当作语言列表传入generate_ast,例如cargo codegen grammar js css只生成这两种语言;不传参数则默认处理ALL_LANGUAGE_KIND(Js、Css、Json三种,见 lib.rs)。语言名不合法时(如cargo codegen grammar python)会打印红色错误提示并跳过,合法取值只有js、css、json(见 lib.rs)。
2.3 编写语法的三条内部约定
原文档强调,编写 grammar 时必须遵守一组内部约定,它们直接决定生成代码的质量与后续手工实现的方式:
约定一:用manual__前缀标记手工实现的方法。
MyDeclaration = manual__decl:Body这样定义后,意味着你需要在代码中为MyDeclaration手工补一个同名方法:
impl MyDeclaration { fn decl(self) -> Option<Body> { // custom logic goes here } }manual__前缀是给生成器与开发者看的"信号":这些字段不是简单地从语法树机械取出,而是需要人工编写解析/访问逻辑(例如涉及语义判断或嵌套查找的场景)。
约定二:token 的 union 必须带 label。
BinExpr = left: Expr op: ('+' | '-' | '*') right: Expr给 token 联合体加上op:标签后,代码生成器能正确处理并生成更优的 AST 访问器——它会为op生成一个在多个候选 token 中查找的SyntaxToken访问器:
impl BinExpr { fn op(self) -> Option<SyntaxToken> { // custom logic goes here support::find_token( &self.syntax, &[ T![+], T![-], T![*], ], ) } }这一机制对应源码中的handle_rule/字段处理逻辑:带 label 的 alternation 会被收集为 token 候选集合。另外,check_unions(见 ast.rs)会在生成前用 BFS 检查所有联合类型:既防止同一个变体被两个枚举重复引用,也防止联合类型之间出现循环依赖,一旦发现会直接panic!并打印完整的引用栈。
约定三:用于追踪损坏代码的节点名必须包含Bogus字样(大小写敏感)。
JsBogus = SyntaxElement*之所以需要专门区分,是因为它会被归类为Bogus而非普通 Node,从而生成不同类型(更宽容)的代码,用于在源码出现错误时保留与追踪无法解析的片段。这一点在 js.ungram 中可以看到完整的 Bogus 家族定义:JsBogus、JsBogusStatement、JsBogusExpression、JsBogusMember、JsBogusBinding、JsBogusAssignment等,它们都是SyntaxElement*形态。SyntaxElement本身是通用数据结构,能同时容纳节点与 token——Bogus 节点正是靠它来无差别地吸收错误区域里的任意语法元素。
三、cargo codegen test:把解析器内联注释测试提取为测试数据
第二个核心命令是测试数据生成器:它把rome_js_parser源码中以//开头的内联注释测试块,提取为rome_js_parser/test_data/目录下的独立测试文件(实现在 parser_tests.rs)。
提取逻辑extract_comment_blocks(见 parser_tests.rs)按行扫描源码:凡是以//前缀开头(trim_start后)的连续注释行被归并为一个 block,注释内容即测试源码;遇到非注释行则结束当前 block,并记录下一个 block 的起始行号。
生成流程generate_parser_tests会扫描crates/rome_js_parser/src目录(见 parser_tests.rs),把提取出的测试分别安装到两个目录:
crates/rome_js_parser/test_data/inline/ok/—— 预期解析成功的用例;crates/rome_js_parser/test_data/inline/err/—— 预期解析报错的用例。
每个用例按语言扩展名落盘(如.js、.ts、.jsx、.json等),若测试带有额外选项,还会生成同名的.options.json文件。此外,一旦有任何文件被更新,该工具会通过filetime刷新crates/rome_js_parser/src/tests.rs的修改时间(见 parser_tests.rs),从而触发测试重新编译。值得注意的是:如果发现某个既有测试文件不再被任何内联注释引用,工具会直接panic!("Test is deleted: ..."),强制开发者不要静默删除测试。
原文档给出了标准的日常工作流,这是每次修改解析器后都要走一遍的循环:
# (modify inline comment tests inside the parser) cargo codegen test cargo test parser # for checking failed tests UPDATE_EXPECT=1 cargo test parser # for committing the changes即:先改解析器源码里的内联注释测试 → 运行cargo codegen test重新生成测试数据 → 用cargo test parser检查哪些用例失败 → 确认无误后以UPDATE_EXPECT=1环境变量重跑测试来更新快照、提交变更。仓库中crates/rome_js_parser/test_data/inline/下已积累了 1269 个测试文件(327 个.js、256 个.ts等),正是这套机制长期运转的产物。
四、cargo codegen unicode:从官方数据生成 Unicode 表
第三个命令处理 JavaScript 标识符校验所需的 Unicode 属性表。原文档说明其作用为:从 unicode.org 下载 Unicode 数据并写入词法分析器使用的tables.rs。
源码实现(见 unicode.rs)比文档描述更精确,实际流程是:
- 获取数据:
Properties::cached_or_fetch优先读取本地缓存target/DerivedCoreProperties.txt,缓存缺失时通过 HTTP 从 unicode.org 的DerivedCoreProperties.txt下载并保存缓存(见 unicode.rs); - 提取属性:从数据中提取
ID_Continue与ID_Start两个属性的码点区间(char 范围对); - 生成代码:为每个属性生成
pub const XXX_table: &[(char, char)]区间表与pub fn XXX(c: char) -> bool查询函数,查询通过bsearch_range_table二分查找实现(注释还特别说明把 ASCII 区间放在表首、优先命中Greater分支以优化常见字符的查找速度); - 落盘:格式化后写入
crates/rome_js_unicode_table/src/tables.rs。
生成文件头部自带说明(见 unicode.rs):"Autogenerated file, do not edit by hand. Runcargo codegen unicodeand recommit this file when Unicode support has changed."——因此当 JavaScript 的 Unicode 支持需要随标准更新时,只需重跑cargo codegen unicode并重新提交该文件。
补充说明:原文档将输出路径写作crates/rome_js_lexer/src/tables.rs,而当前仓库源码中实际写入路径为crates/rome_js_unicode_table/src/tables.rs(见 unicode.rs),仓库中也确实存在该文件;文档与实现不一致时,以当前源码为准。
五、更多 codegen 子命令:从 lint 规则到配置与网站
除上述三个核心命令外,main.rs 还注册了多个面向其他领域(主要是 lint 规则体系)的生成命令,共同构成完整的开发工具链:
cargo codegen analyzer:为 analyzer 生成工厂函数与 analyzer 的配置(generate_analyzer);cargo codegen formatter:为每种语言生成 formatter 代码(generate_formatters);cargo codegen newlintrule --path <目录> --name <规则名>:生成一条新 lint 规则的模板(见 generate_new_lintrule.rs)。它强制新规则必须放在nursery目录下,并一次性完成四件事:生成规则实现模板(含declare_rule!宏、Ruletrait 实现骨架、示例文档注释)、把规则类别写入crates/rome_diagnostics_categories/src/categories.rs的nursery区块(自动排序以降低并行贡献的冲突)、创建crates/rome_js_analyze/tests/specs/nursery/{rule}/测试目录、写入valid.js("should not generate diagnostics")与invalid.js两个测试样例文件;cargo codegen promoterule --rule <规则名> --group <组名>:把规则从 nursery 提升(promote)到正式规则组(见 promote_rule.rs);cargo codegen configuration(需configurationfeature):生成依赖元数据的配置部分;cargo codegen schema/cargo codegen bindings(需schemafeature):生成 Rome 配置文件的 JSON Schema 以及 Workspace API 的 TypeScript 绑定定义;cargo codegen website(需websitefeature):生成网站相关文件;cargo codegen all:一次性按序运行全部生成器(Unicode 表 → 语法 → 解析器测试 → formatter → analyzer → 配置 → Schema → 绑定等),是 CI 与发布前最常用的"全量重生成"命令。
六、把 codegen 接入日常工作流
仓库根目录的 justfile 把上述命令编排成了更上层的开发任务:
codegen: cargo codegen all cargo codegen-configuration just codegen-bindings codegen-linter: cargo codegen analyzer cargo codegen-configuration just codegen-bindings新增 lint 规则的完整流程是just new-lintrule path=... rulename=...(内部先跑newlintrule,再跑just codegen-linter),提升规则则用just promote-rule rulename=... group=...(见 justfile)。提交代码前运行just codegen可确保所有生成文件与当前语法/配置保持一致。
七、总结与最佳实践
xtask/codegen用一条cargo codegen命令,把三类最易出错、最耗人工的维护工作全部自动化:
- 语法与 AST 同步:
grammar命令基于 ungrammar DSL,从 js.ungram、css.ungram、json.ungram 生成六个语法/工厂文件;写语法时务必遵守manual__前缀、union 打 label、Bogus 命名三条约定,它们直接决定生成代码的正确性与可维护性; - 测试数据同步:
test命令把解析器内联注释测试提取为独立用例文件,配合cargo test parser与UPDATE_EXPECT=1形成闭环; - Unicode 标准同步:
unicode命令从官方数据源生成 ID_Start/ID_Continue 查表代码,写入 tables.rs。
所有生成文件都遵循"幂等更新"原则:内容未变不写盘、内容变了才覆盖,因此生成结果可以安全地作为常规代码提交进版本库。对任何要长期维护自己语法树与解析器的 Rust 项目来说,这套"DSL 定义 + 代码生成 + 测试数据提取"的工具链架构都是极具参考价值的样板。
- 开发工具
- CLI
- Lint
- 格式化
- 静态分析
- 代码质量
- 构建工具
【免费下载链接】tools
Unified developer tools for JavaScript, TypeScript, and the web
相关推荐
GoMock与代码生成工具链集成:构建自动化测试流水线
GoMock与代码生成工具链集成:构建自动化测试流水线 你是否还在为Go项目中编写大量重复的测试代码而烦恼?是否希望有一套工具能够自动生成可靠的模拟对象,让测试
测试代码生成fhEVM Codegen 代码生成器完全指南:从 FHE.sol 到自动化测试套件的一键生成
fhEVM Codegen 代码生成器完全指南:从 FHE.sol 到自动化测试套件的一键生成 导读 在 fhEVM 仓库中, FHE.sol 、 Impl.s
密码学隐私计算区块链后端终极显卡风扇控制指南:用FanControl打造静音高效散热方案
终极显卡风扇控制指南:用FanControl打造静音高效散热方案 FanControl是一款高度可定制的Windows风扇控制软件,专为解决电脑散热噪音与性能平
开发工具CLILint格式化静态分析代码质量构建工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考