6个版本 (破坏性更新)
0.5.7 | 2024年3月12日 |
---|---|
0.4.0 | 2024年1月9日 |
0.3.1 | 2023年11月26日 |
0.1.0 | 2023年9月28日 |
0.0.2 | 2023年9月28日 |
#1512 in 开发工具
每月下载量 2,455
用于 dprint-plugin-biome
6MB
140K SLoC
biome_js_formatter
Biome的JavaScript格式化工具实现。请参考文档。
lib.rs
:
Biome的官方JavaScript格式化工具。
实现格式化工具
我们的格式化工具基于node。这意味着每个AST节点都知道如何格式化自己。为了实现格式化,节点必须实现FormatNode
特质。
biome
具有自动代码生成功能,可以自动从语法中创建文件。默认情况下,所有实现都将进行逐字格式化,这意味着格式化工具将按照原样打印标记和 trivia(format_verbatim
)。
我们的格式化工具有自己的内部IR,它从AST中创建自己的抽象。
开发者不会直接创建这个IR,但他们将使用一系列工具,这些工具将帮助创建这个IR。整个IR由enum
FormatElement
表示。
最佳实践
-
使用
*Fields
结构提取所有标记/节点#[derive(Debug, Clone, Default)] pub struct FormatJsExportDefaultExpressionClause; impl FormatNodeRule<JsExportDefaultExpressionClause> for FormatJsExportDefaultExpressionClauses { fn fmt_fields(&self, node: &JsExportDefaultExpressionClause, f: &mut JsFormatter) -> FormatResult<()> { let JsExportDefaultExpressionClauseFields { default_token, expression, semicolon_token, } = node.as_fields(); } }
-
当使用
.as_fields()
进行解构时,不要使用..
功能。优先提取所有字段并使用_
忽略它们#[derive(Debug, Clone, Default)] pub struct FormatJsExportDefaultExpressionClause; impl FormatNodeRule<JsExportDefaultExpressionClause> for FormatJsExportDefaultExpressionClauses { fn fmt_fields(&self, node: &JsExportDefaultExpressionClause, f: &mut JsFormatter) -> FormatResult<()> { let JsExportDefaultExpressionClauseFields { default_token, expression: _, semicolon_token } = node.as_fields(); } }
我们推广这种模式的原因是我们希望明确表示某个标记/节点被排除;
-
使用
builders.rs
、formatter
和format_extensions.rs
提供的API。builders.rs
公开了一系列工具来构建格式化IR;请参阅其内部文档了解这些工具的用途;formatter
提供了一组函数来帮助格式化一些常见的模式;请参阅它们的内部文档以了解如何使用它们以及何时使用;format_extensions.rs
:通过这些特质,我们赋予节点和标记根据其类型实现某些方法的能力。如果您有一个良好的 IDE 支持,这个功能将帮助您。例如
#[derive(Debug, Clone, Default)] pub struct FormatJsExportDefaultExpressionClause; impl FormatNodeRule<JsExportDefaultExpressionClause> for FormatJsExportDefaultExpressionClauses{ fn fmt_fields(&self, node: &JsExportDefaultExpressionClause, f: &mut JsFormatter) -> FormatResult<()> { let JsExportDefaultExpressionClauseFields { default_token, expression, // it's a mandatory node semicolon_token, // this is not a mandatory node } = node.as_fields(); let element = expression.format(); if let Some(expression) = &expression? { write!(f, [expression.format(), space()])?; } if let Some(semicolon) = &semicolon_token { write!(f, [semicolon.format()])?; } else { write!(f, [space()])?; } } }
-
使用 playground 来检查您想要格式化的代码。这有助于您了解需要实现/修改哪些节点才能实现格式化。或者,您可以根据以下说明在本地运行 playground:playground 指令。
-
使用
tests/
目录中的quick_test.rs
文件来测试您的代码片段,而无需运行整个测试套件。这个测试是故意忽略的,所以您不必担心 CI 会中断。
测试
我们使用 insta.rs 进行快照测试,请确保您阅读了它的文档以了解快照测试的基础。您应该安装辅助命令 cargo-insta
以帮助进行快照审查。
目录按语言划分,因此在创建新的测试文件时,请确保将正确的文件放在正确的文件夹下
JavaScript
=>js/
目录TypeScript
=>ts/
目录JSX
=>jsx/
目录TSX
=>ts/
目录
要为 JavaScript 创建新的快照测试,请创建一个新的文件到 crates/biome_js_formatter/tests/specs/js/
,例如 arrow_with_spaces..js
const foo = () => {
return bar
}
作为模块处理的文件必须放在 module/
目录中,作为脚本处理的文件必须放在 script/
目录中。
运行以下命令以生成新的快照(快照测试是通过过程宏生成的,因此我们需要重新编译测试)
touch crates/biome_js_formatter/tests/spec_tests.rs && cargo test -p biome_js_formatter formatter
为了更好的测试驱动开发流程,使用 cargo-watch
启动格式化测试
cargo watch -i '*.new' -x 'test -p biome_js_formatter formatter'
测试执行后,您将得到一个新的 arrow.js.snap.new
文件。
要实际更新快照,运行 cargo insta review
以交互式审查并接受挂起的快照。arrow.js.snap.new
将被替换为 arrow.js.snap
有时,您需要验证不同的情况/选项的格式化。为了做到这一点,创建一个包含您需要验证的情况的文件夹。例如,如果我们需要遵循前面的例子
- 创建一个名为
arrow_with_spaces/
的文件夹,并将 JS 文件移到那里; - 然后创建一个名为
options.json
的文件 - 内容可能是这样的
{ "cases": [ { "line_width": 120, "indent_style": {"Space": 4} } ] }
cases
关键字是必需的;- 然后数组的每个对象都将包含您想要测试的选项矩阵。在这种情况下,测试套件将运行一个带有
line_width
为 120 和ident_style
为 4 个空格的 第二个测试用例 - 当测试套件运行时,您将在快照中获得两个输出:默认输出和自定义输出
调试测试失败
测试不正确的情况有四种
-
你尝试多次打印/格式化相同的标记;格式化器将在测试运行时进行检查;
-
一些标记尚未打印;通常你会在快照中的“未实现标记/节点”部分找到这个信息,如下所示:
"Unimplemented tokens/nodes"
;为了有效,测试不能包含该部分;如果删除标记是实际的行为(删除一些括号或分号),那么正确的方式是使用格式化器API biome_formatter::trivia::format_removed;
-
生成的代码不再是有效的程序,测试套件将再次解析生成的代码,如果存在语法错误则将失败;
-
再次格式化后的生成代码与原始代码不同;这通常发生在删除/添加新元素且分组设置不正确的情况下;
依赖关系
~9-19MB
~247K SLoC