重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
npx skills add https://github.com/biomejs/biome --skill formatter-development在实现或修改 Biome 的格式化器时使用此技能。它涵盖了基于特征的格式化系统、IR 生成、注释处理以及与 Prettier 对比的测试。
just install-tools(包含 wasm-bindgen-cli 和 wasm-opt)biome_{lang}_syntax、biome_{lang}_formatterbun 并运行 pnpm install对于新语言(例如 HTML):
just gen-formatter html
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
这会为所有语法节点生成 FormatNodeRule 实现。初始实现使用 format_verbatim_node(按原样格式化代码)。
示例:格式化 JsIfStatement:
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(())
}
}
常见的格式化构建块:
use biome_formatter::{format_args, write};
write!(f, [
token("if"), // 静态文本
space(), // 单个空格
soft_line_break(), // 如果行太长则换行
hard_line_break(), // 总是换行
// 分组和缩进
group(&format_args![
token("("),
soft_block_indent(&format_args![
node.test.format(),
]),
token(")"),
]),
// 条件格式化
format_with(|f| {
if condition {
write!(f, [token("something")])
} else {
write!(f, [token("other")])
}
}),
])?;
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(),
// 处理悬空注释(未附加到任何节点的注释)
format_dangling_comments(node.syntax()).with_soft_block_indent()
]),
r_curly_token.format(),
]
)
}
}
前导和尾随注释由格式化器基础设施自动处理。
实现格式化后,与 Prettier 进行验证:
# 对比代码片段
bun packages/prettier-compare/bin/prettier-compare.js --rebuild 'const x={a:1,b:2}'
# 使用显式语言进行对比
bun packages/prettier-compare/bin/prettier-compare.js --rebuild -l ts 'const x: number = 1'
# 对比文件
bun packages/prettier-compare/bin/prettier-compare.js --rebuild -f path/to/file.tsx
# 从标准输入(对编辑器选择内容有用)
echo 'const x = 1' | bun packages/prettier-compare/bin/prettier-compare.js --rebuild -l js
始终使用 --rebuild 以确保 WASM 包与你的 Rust 更改匹配。
在 tests/specs/ 中按功能组织创建测试文件:
crates/biome_js_formatter/tests/specs/js/
├── statement/
│ ├── if_statement/
│ │ ├── basic.js
│ │ ├── nested.js
│ │ └── with_comments.js
│ └── for_statement/
│ └── various.js
示例测试文件 basic.js:
if (condition) {
doSomething();
}
if (condition) doSomething();
if (condition) {
doSomething();
} else {
doOther();
}
运行测试:
cd crates/biome_js_formatter
cargo test
审查快照:
cargo insta review
在测试文件夹中创建 options.json:
{
"formatter": {
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 80
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded"
}
}
}
这适用于该文件夹中的所有测试文件。
更改后:
just f # 格式化 Rust 代码
just l # 代码检查
just gen-formatter # 如果需要,重新生成格式化器基础设施
space() 而不是 token(" ") 以获得语义上的间距soft_line_break() 进行可选换行,使用 hard_line_break() 进行强制换行group() 中,以便在可能时保持在一起block_indent() 进行块级缩进,使用 indent() 进行内联缩进join_nodes_with_soft_line() 或 join_nodes_with_hardline() 来格式化列表node.token().format(),而不是 token("(")dbg_write! 宏(类似于 dbg!)来查看 IR 元素:dbg_write!(f, [token("hello")])?;// 空白
space() // 单个空格
soft_line_break() // 需要时换行
hard_line_break() // 总是换行
soft_line_break_or_space() // 空格或换行
// 缩进
indent(&content) // 缩进内容
block_indent(&content) // 块级缩进
soft_block_indent(&content) // 使用软换行缩进
// 分组
group(&content) // 如果可能则保持在一起
conditional_group(&content) // 高级分组
// 文本
token("text") // 静态文本
dynamic_token(&text, pos) // 带位置的动态文本
// 实用工具
format_with(|f| { ... }) // 自定义格式化函数
format_args![a, b, c] // 组合多个项
if_group_breaks(&content) // 仅当分组换行时
if_group_fits_on_line(&content) // 仅当适合一行时
crates/biome_formatter/CONTRIBUTING.mdcrates/biome_js_formatter/CONTRIBUTING.mdpackages/prettier-compare/crates/biome_js_formatter/src/js/ 查看实际实现每周安装次数
43
仓库
GitHub 星标数
24.0K
首次出现
2026年2月18日
安全审计
安装于
opencode43
github-copilot43
codex43
kimi-cli43
gemini-cli43
amp43
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.
just install-tools (includes wasm-bindgen-cli and wasm-opt)biome_{lang}_syntax, biome_{lang}_formatterbun and run pnpm install in repo rootFor a new language (e.g., HTML):
just gen-formatter html
This generates FormatNodeRule implementations for all syntax nodes. Initial implementations use format_verbatim_node (formats code as-is).
Example: Formatting JsIfStatement:
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(())
}
}
Common formatting building blocks:
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")])
}
}),
])?;
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.
After implementing formatting, validate against Prettier:
# Compare a code snippet
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'
# Compare a file
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.
Create test files in tests/specs/ organized by feature:
crates/biome_js_formatter/tests/specs/js/
├── statement/
│ ├── if_statement/
│ │ ├── basic.js
│ │ ├── nested.js
│ │ └── with_comments.js
│ └── for_statement/
│ └── various.js
Example test file basic.js:
if (condition) {
doSomething();
}
if (condition) doSomething();
if (condition) {
doSomething();
} else {
doOther();
}
Run tests:
cd crates/biome_js_formatter
cargo test
Review snapshots:
cargo insta review
Create options.json in the test folder:
{
"formatter": {
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 80
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded"
}
}
}
This applies to all test files in that folder.
After changes:
just f # Format Rust code
just l # Lint
just gen-formatter # Regenerate formatter infrastructure if needed
space() instead of token(" ") for semantic spacingsoft_line_break() for optional breaks, hard_line_break() for mandatory breaksgroup() to keep them together when possibleblock_indent() for block-level indentation, indent() for inlinejoin_nodes_with_soft_line() or for formatting lists// 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 fits
crates/biome_formatter/CONTRIBUTING.mdcrates/biome_js_formatter/CONTRIBUTING.mdpackages/prettier-compare/crates/biome_js_formatter/src/js/ for real implementationsWeekly Installs
43
Repository
GitHub Stars
24.0K
First Seen
Feb 18, 2026
Security Audits
Gen Agent Trust HubPassSocketFailSnykPass
Installed on
opencode43
github-copilot43
codex43
kimi-cli43
gemini-cli43
amp43
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
125,600 周安装
join_nodes_with_hardline()node.token().format() for tokens that exist in AST, not token("(")dbg_write! macro (like dbg!) to see IR elements: dbg_write!(f, [token("hello")])?;