formatter-development
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePurpose
用途
Use this skill when implementing or modifying Biome's formatters. It covers the trait-based formatting system, IR generation, comment handling, and testing with Prettier comparison.
在实现或修改Biome的格式化工具时使用本指南。内容涵盖基于 trait 的格式化系统、IR生成、注释处理以及与Prettier对比的测试方法。
Prerequisites
前置条件
- Install required tools: (includes
just install-toolsandwasm-bindgen-cli)wasm-opt - Language-specific crates must exist: ,
biome_{lang}_syntaxbiome_{lang}_formatter - For Prettier comparison: Install and run
bunin repo rootpnpm install
- 安装所需工具:(包含
just install-tools和wasm-bindgen-cli)wasm-opt - 必须存在对应语言的 crate:、
biome_{lang}_syntaxbiome_{lang}_formatter - 若要与Prettier对比:安装并在仓库根目录执行
bunpnpm install
Common Workflows
常见工作流
Generate Formatter Boilerplate
生成格式化工具模板
For a new language (e.g., HTML):
shell
just gen-formatter htmlThis generates implementations for all syntax nodes. Initial implementations use (formats code as-is).
FormatNodeRuleformat_verbatim_node针对新语言(如HTML):
shell
just gen-formatter html此命令会为所有语法节点生成实现。初始实现使用(按原样格式化代码)。
FormatNodeRuleformat_verbatim_nodeImplement FormatNodeRule for a Node
为节点实现FormatNodeRule
Example: Formatting :
JsIfStatementrust
use crate::prelude::*;
use biome_formatter::write;
use biome_js_syntax::{JsIfStatement, JsIfStatementFields};
#[derive(Debug, Clone, Default)]
pub(crate) struct FormatJsIfStatement;
impl FormatNodeRule<JsIfStatement> for FormatJsIfStatement {
fn fmt_fields(&self, node: &JsIfStatement, f: &mut JsFormatter) -> FormatResult<()> {
let JsIfStatementFields {
if_token,
l_paren_token,
test,
r_paren_token,
consequent,
else_clause,
} = node.as_fields();
write!(
f,
[
if_token.format(),
space(),
l_paren_token.format(),
test.format(),
r_paren_token.format(),
space(),
consequent.format(),
]
)?;
if let Some(else_clause) = else_clause {
write!(f, [space(), else_clause.format()])?;
}
Ok(())
}
}示例:格式化:
JsIfStatementrust
use crate::prelude::*;
use biome_formatter::write;
use biome_js_syntax::{JsIfStatement, JsIfStatementFields};
#[derive(Debug, Clone, Default)]
pub(crate) struct FormatJsIfStatement;
impl FormatNodeRule<JsIfStatement> for FormatJsIfStatement {
fn fmt_fields(&self, node: &JsIfStatement, f: &mut JsFormatter) -> FormatResult<()> {
let JsIfStatementFields {
if_token,
l_paren_token,
test,
r_paren_token,
consequent,
else_clause,
} = node.as_fields();
write!(
f,
[
if_token.format(),
space(),
l_paren_token.format(),
test.format(),
r_paren_token.format(),
space(),
consequent.format(),
]
)?;
if let Some(else_clause) = else_clause {
write!(f, [space(), else_clause.format()])?;
}
Ok(())
}
}Using IR Primitives
使用IR原语
Common formatting building blocks:
rust
use biome_formatter::{format_args, write};
write!(f, [
token("if"), // Static text
space(), // Single space
soft_line_break(), // Break if line is too long
hard_line_break(), // Always break
// Grouping and indentation
group(&format_args![
token("("),
soft_block_indent(&format_args![
node.test.format(),
]),
token(")"),
]),
// Conditional formatting
format_with(|f| {
if condition {
write!(f, [token("something")])
} else {
write!(f, [token("other")])
}
}),
])?;常用格式化构建块:
rust
use biome_formatter::{format_args, write};
write!(f, [
token("if"), // Static text
space(), // Single space
soft_line_break(), // Break if line is too long
hard_line_break(), // Always break
// Grouping and indentation
group(&format_args![
token("("),
soft_block_indent(&format_args![
node.test.format(),
]),
token(")"),
]),
// Conditional formatting
format_with(|f| {
if condition {
write!(f, [token("something")])
} else {
write!(f, [token("other")])
}
}),
])?;Handle Comments
处理注释
rust
use biome_formatter::format_args;
use biome_formatter::prelude::*;
impl FormatNodeRule<JsObjectExpression> for FormatJsObjectExpression {
fn fmt_fields(&self, node: &JsObjectExpression, f: &mut JsFormatter) -> FormatResult<()> {
let JsObjectExpressionFields {
l_curly_token,
members,
r_curly_token,
} = node.as_fields();
write!(
f,
[
l_curly_token.format(),
block_indent(&format_args![
members.format(),
// Handle dangling comments (comments not attached to any node)
format_dangling_comments(node.syntax()).with_soft_block_indent()
]),
r_curly_token.format(),
]
)
}
}Leading and trailing comments are handled automatically by the formatter infrastructure.
rust
use biome_formatter::format_args;
use biome_formatter::prelude::*;
impl FormatNodeRule<JsObjectExpression> for FormatJsObjectExpression {
fn fmt_fields(&self, node: &JsObjectExpression, f: &mut JsFormatter) -> FormatResult<()> {
let JsObjectExpressionFields {
l_curly_token,
members,
r_curly_token,
} = node.as_fields();
write!(
f,
[
l_curly_token.format(),
block_indent(&format_args![
members.format(),
// Handle dangling comments (comments not attached to any node)
format_dangling_comments(node.syntax()).with_soft_block_indent()
]),
r_curly_token.format(),
]
)
}
}前置和后置注释会由格式化工具基础设施自动处理。
Compare Against Prettier
与Prettier对比
After implementing formatting, validate against Prettier:
shell
undefined实现格式化后,与Prettier进行验证:
shell
undefinedCompare a code snippet
对比代码片段
bun packages/prettier-compare/bin/prettier-compare.js --rebuild 'const x={a:1,b:2}'
bun packages/prettier-compare/bin/prettier-compare.js --rebuild 'const x={a:1,b:2}'
Compare with explicit language
指定语言进行对比
bun packages/prettier-compare/bin/prettier-compare.js --rebuild -l ts 'const x: number = 1'
bun packages/prettier-compare/bin/prettier-compare.js --rebuild -l ts 'const x: number = 1'
Compare a file
对比文件
bun packages/prettier-compare/bin/prettier-compare.js --rebuild -f path/to/file.tsx
bun packages/prettier-compare/bin/prettier-compare.js --rebuild -f path/to/file.tsx
From stdin (useful for editor selections)
从标准输入读取(适用于编辑器选中内容)
echo 'const x = 1' | bun packages/prettier-compare/bin/prettier-compare.js --rebuild -l js
**Always use `--rebuild`** to ensure WASM bundle matches your Rust changes.echo 'const x = 1' | bun packages/prettier-compare/bin/prettier-compare.js --rebuild -l js
**务必使用`--rebuild`参数**,确保WASM包与你的Rust代码修改一致。Create Snapshot Tests
创建快照测试
Create test files in organized by feature:
tests/specs/crates/biome_js_formatter/tests/specs/js/
├── statement/
│ ├── if_statement/
│ │ ├── basic.js
│ │ ├── nested.js
│ │ └── with_comments.js
│ └── for_statement/
│ └── various.jsExample test file :
basic.jsjavascript
if (condition) {
doSomething();
}
if (condition) doSomething();
if (condition) {
doSomething();
} else {
doOther();
}Run tests:
shell
cd crates/biome_js_formatter
cargo testReview snapshots:
shell
cargo insta review在目录下按功能组织测试文件:
tests/specs/crates/biome_js_formatter/tests/specs/js/
├── statement/
│ ├── if_statement/
│ │ ├── basic.js
│ │ ├── nested.js
│ │ └── with_comments.js
│ └── for_statement/
│ └── various.js示例测试文件:
basic.jsjavascript
if (condition) {
doSomething();
}
if (condition) doSomething();
if (condition) {
doSomething();
} else {
doOther();
}运行测试:
shell
cd crates/biome_js_formatter
cargo test查看快照:
shell
cargo insta reviewTest with Custom Options
使用自定义选项测试
Create in the test folder:
options.jsonjson
{
"formatter": {
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 80
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded"
}
}
}This applies to all test files in that folder.
在测试目录下创建:
options.jsonjson
{
"formatter": {
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 80
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded"
}
}
}此配置会应用到该目录下的所有测试文件。
Format and Build
格式化与构建
After changes:
shell
just f # Format Rust code
just l # Lint
just gen-formatter # Regenerate formatter infrastructure if needed修改代码后执行:
shell
just f # 格式化Rust代码
just l # 代码检查
just gen-formatter # 若需要,重新生成格式化工具基础设施Tips
提示
- format_verbatim_node: Initial generated code uses this - replace it with proper IR as you implement formatting
- Space tokens: Use instead of
space()for semantic spacingtoken(" ") - Breaking: Use for optional breaks,
soft_line_break()for mandatory breakshard_line_break() - Grouping: Wrap related elements in to keep them together when possible
group() - Indentation: Use for block-level indentation,
block_indent()for inlineindent() - Lists: Use or
join_nodes_with_soft_line()for formatting listsjoin_nodes_with_hardline() - Mandatory tokens: Use for tokens that exist in AST, not
node.token().format()token("(") - Debugging: Use macro (like
dbg_write!) to see IR elements:dbg!dbg_write!(f, [token("hello")])?; - Don't fix code: Formatter should format existing code, not attempt to fix syntax errors
- format_verbatim_node:初始生成的代码会使用该方法,在实现格式化时需替换为合适的IR实现
- 空格令牌:使用而非
space()来实现语义化空格token(" ") - 换行:使用实现可选换行,
soft_line_break()实现强制换行hard_line_break() - 分组:将相关元素包裹在中,尽可能保持内容在同一行
group() - 缩进:使用实现块级缩进,
block_indent()实现行内缩进indent() - 列表:使用或
join_nodes_with_soft_line()格式化列表join_nodes_with_hardline() - 必填令牌:对于AST中存在的令牌,使用而非
node.token().format()token("(") - 调试:使用宏(类似
dbg_write!)查看IR元素:dbg!dbg_write!(f, [token("hello")])?; - 不要修复代码:格式化工具应仅格式化现有代码,不要尝试修复语法错误
IR Primitives Reference
IR原语参考
rust
// Whitespace
space() // Single space
soft_line_break() // Break if needed
hard_line_break() // Always break
soft_line_break_or_space() // Space or break
// Indentation
indent(&content) // Indent content
block_indent(&content) // Block-level indent
soft_block_indent(&content) // Indent with soft breaks
// Grouping
group(&content) // Keep together if possible
conditional_group(&content) // Advanced grouping
// Text
token("text") // Static text
dynamic_token(&text, pos) // Dynamic text with position
// Utility
format_with(|f| { ... }) // Custom formatting function
format_args![a, b, c] // Combine multiple items
if_group_breaks(&content) // Only if group breaks
if_group_fits_on_line(&content) // Only if fitsrust
// Whitespace
space() // Single space
soft_line_break() // Break if needed
hard_line_break() // Always break
soft_line_break_or_space() // Space or break
// Indentation
indent(&content) // Indent content
block_indent(&content) // Block-level indent
soft_block_indent(&content) // Indent with soft breaks
// Grouping
group(&content) // Keep together if possible
conditional_group(&content) // Advanced grouping
// Text
token("text") // Static text
dynamic_token(&text, pos) // Dynamic text with position
// Utility
format_with(|f| { ... }) // Custom formatting function
format_args![a, b, c] // Combine multiple items
if_group_breaks(&content) // Only if group breaks
if_group_fits_on_line(&content) // Only if fitsReferences
参考资料
- Full guide:
crates/biome_formatter/CONTRIBUTING.md - JS-specific:
crates/biome_js_formatter/CONTRIBUTING.md - Prettier comparison tool:
packages/prettier-compare/ - Examples: for real implementations
crates/biome_js_formatter/src/js/
- 完整指南:
crates/biome_formatter/CONTRIBUTING.md - JavaScript专属指南:
crates/biome_js_formatter/CONTRIBUTING.md - Prettier对比工具:
packages/prettier-compare/ - 示例:下的真实实现
crates/biome_js_formatter/src/js/