Biome Markdown 格式化器深度解析:嵌套列表与 GFM 任务列表的格式化规则
【免费下载链接】biomeA toolchain for web projects, aimed to provide functionalities to maintain them. Biome offers formatter and linter, usable via CLI and LSP.项目地址: https://gitcode.com/gh_mirrors/bi/biome
导读
Biome 的 Markdown 格式化器(biome_markdown_formatter)负责将 Markdown 源码规范化为统一风格,其中嵌套列表与GFM 任务列表(Task List / Checkbox)是最容易产生风格分歧的场景。本文以仓库测试套件中的nested-checkbox.md用例为切入点,逐行解读其输入、输出快照与 Prettier 行为差异,并深入src源码剖析列表标记归一化、缩进重排、空行处理与[X]/[x]状态归一化的底层实现。读完本文,你将掌握 Biome 对嵌套无序列表与任务列表的完整格式化决策链,并能通过 spec 测试机制自行验证任意 Markdown 列表写法。
测试用例概览:一个用例覆盖两类嵌套场景
关联文档位于crates/biome_markdown_formatter/tests/specs/prettier/markdown/list/nested-checkbox.md,共 11 行,构造了两个结构完全对称的嵌套列表块:
* parent list item parent list item parent list item parent list item parent list item parent list item * child list item child list item child list item child list item child list item child list item paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph * [x] parent task list item parent task list item parent task list item parent task list item * [x] child task list item child task list item child task list item child task list item paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph该用例刻意设计了三个"压力点",用于验证格式化器的鲁棒性:
- 父项后紧跟空行 + 缩进子项:测试"松散列表(loose list)"与"嵌套子列表"同时出现时的空行去留;
- 长文本:父项、子项与后续段落全部使用超长文本,用于检验行宽(80 字符)约束下格式化器是否强制换行;
[x]任务列表嵌套:父项与子项同时携带 GFM 复选框标记,验证任务列表在嵌套结构中的缩进与标记输出。
输入刻意使用*作为列表标记、5 空格缩进子项、4 空格缩进段落,而输出快照显示 Biome 会将其全部归一化为-标记与 2 空格缩进——这正是理解整个格式化流程的最佳样本。
输出快照解读:Biome 的四条归一化规则
同目录下的nested-checkbox.md.snap记录了该输入的格式化结果,# Output部分如下:
- parent list item parent list item parent list item parent list item parent list item parent list item - child list item child list item child list item child list item child list item child list item paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph - [x] parent task list item parent task list item parent list item parent task list item parent list item - [x] child task list item child list item child list item child list item paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph对比输入与输出,可提炼出 Biome 的 4 条核心归一化规则:
规则 1:列表标记统一为-
输入使用*作为无序列表标记,输出统一改写为-。这是 Biome Markdown 格式化器默认的 bullet 风格选择。同目录下其他用例(如unordered.md、nested.md)也遵循同一约定,说明该规则是全局性的,而非本用例特例。
规则 2:子项与后续段落统一缩进为 2 空格
- 子列表项:输入缩进 5 空格 → 输出缩进 2 空格(相对父项标记);
- 紧随列表块的段落:输入缩进 4 空格 → 输出缩进 2 空格。
Biome 对嵌套列表与"列表后续缩进段落"采用一致的 2 空格缩进基准,消除了手写 Markdown 中常见的缩进随意性(4 空格、5 空格、tab 混用等)。
规则 3:父项与子项之间的空行被移除
输入中父项与子项之间、父项与任务子项之间均有空行,输出中这些空行被删除,子项直接紧贴父项;而子项之后与段落之间、两个列表块之间的空行则被保留。这说明 Biome 能区分"块内结构空行"(可移除)与"块间分隔空行"(必须保留)。
规则 4:GFM 任务列表标记原样保留
[x]标记在嵌套结构中完整保留(父项与子项均如此),说明任务列表状态在缩进重排过程中不会被破坏,且输出为小写x形态(见下文源码部分关于normalize_task_state的说明)。
与 Prettier 的行为差异:一处刻意保留的分歧
快照中的# Prettier differences部分以 diff 形式记录了 Biome 与 Prettier 对同一输入的分歧:
--- Prettier +++ Biome @@ -1,11 +1,9 @@ - parent list item parent list item parent list item parent list item parent list item parent list item - - child list item child list item child list item child list item child list item child list item paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph - [x] parent task list item parent task list item parent task list item parent task list item - - [x] child task list item child task list item child task list item child task list item paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph差异点非常集中:Prettier 会在父列表项与其第一个嵌套子项之间保留一个空行,而 Biome 选择移除该空行,使父子列表项紧凑相连。这是该用例被归入prettier目录(表示"与 Prettier 对照但不要求完全一致")的原因——Biome 在列表紧凑性上采用了与 Prettier 不同的取舍,且通过 spec 快照将这一分歧显式固化,防止未来改动无意中抹平或扩大差异。
源码级剖析:归一化规则在何处实现
任务列表标记:normalize_task_state与小写化
GFM 任务列表的格式化实现在crates/biome_markdown_formatter/src/gfm/auxiliary/task_list_item.rs的FormatGfmTaskListItem中:
write!( f, [ fields.l_bracket_token.format(), state.format().with_options(FormatMdTextualOptions { normalize_task_state: true, ..FormatMdTextualOptions::default() }), fields.r_bracket_token.format(), ] )它通过FormatRuleWithOptions机制向FormatMdTextual传入normalize_task_state: true选项。真正的状态归一化逻辑位于crates/biome_markdown_formatter/src/markdown/auxiliary/textual.rs的fmt_fields中:
if self.normalize_task_state && value_token.text() == "X" { write!( f, [format_replaced( &value_token, &text("x", Some(value_token.text_range().start())) )] ) }即:当复选框状态为大写X时,会通过format_replaced生成一个替换 token,将X规范化为小写x,同时保留原始文本范围用于诊断定位。[ ](未选中)状态则不受影响,直接原样输出。这正是本用例输出中[x]稳定存在、且用户手写[X]也能被统一风格的实现依据。
列表分发机制:FmtAnyList与 debug_assert 护栏
bullet_list.rs中的FormatMdBulletList与bullet_list_item.rs中的FormatMdBulletListItem均以debug_assert!(false, "This node should be formatted via FmtAnyList...")开头——它们不是实际的格式化入口,而是用于拦截"直接格式化列表节点"的错误路径。真正的分发发生在FmtAnyList匹配AnyMdBlock并调用as_any_list_item之后,由生成的container_block.rs、block.rs等文件按节点类型逐层下派。这种"入口断言 + 统一分发"的结构保证嵌套列表的缩进与项间空行决策集中在同一路径处理,避免了多种列表写法各走各的逻辑。
缩进与空行:容器块的连续化输出
从快照输出可以看到,Biome 将"父项 → 子项 → 段落"在满足规则的前提下尽量连续输出。结合FormatAnyMdContainerBlock对MdBulletListItem、MdOrderedListItem、MdQuote的分派,可以推断:嵌套列表项作为容器块(container block)被递归格式化,子块的缩进通过continuation_indent等辅助节点按 2 空格基准计算,而空行处理则依据块级节点的松散(loose)属性决定去留。nested-checkbox.md恰好同时覆盖了"松散列表 + 嵌套"的组合,是验证这条决策链是否自洽的关键回归用例。
行宽约束:80 字符上限下的"不重排"策略
快照末尾的# Lines exceeding max width of 80 characters部分列出了输出中超过 80 字符的行:
1: - parent list item parent list item parent list item parent list item parent list item parent list item 2: - child list item child list item child list item child list item child list item child list item 4: paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph paragraph ...这揭示了一个重要事实:Biome 的 Markdown 格式化器目前采用"检测并报告超宽行、但不强制重排文本"的策略。这些构造的超长行在输出中原样保留,仅被快照机制标记为超出line_width(默认 80)。这与 Prettier 的"尽量折行"理念不同——Biome 在 Markdown 阶段优先保证结构归一化(标记、缩进、空行、任务状态),而把段落级文本换行视为更高风险、更低收益的操作,选择保守处理。同一list目录下的long-paragraph.md用例也印证了这一行为的一致性。
如何在本地复现与验证
本用例由biome_markdown_formatter的 spec 测试框架驱动,入口位于crates/biome_markdown_formatter/tests/spec_tests.rs,快照由crates/biome_formatter_test/src/snapshot_builder.rs生成(见.snap文件头部的source字段)。验证方式:
- 直接运行 CLI:将任意包含嵌套列表与任务列表的
.md文件交给biome format,观察*是否归一化为-、子项是否缩进 2 空格、[X]是否变为[x]; - 运行 spec 测试:在仓库根目录执行针对 markdown formatter 的 cargo 测试命令,测试框架会自动比对
nested-checkbox.md输入与.snap快照,任何格式化行为变化都会以快照 diff 形式暴露; - 对照 Prettier:同时查看
nested-checkbox.md.prettier-snap,即可快速定位 Biome 与 Prettier 在空行处理上的唯一分歧点。
修改或新增类似用例时,只需在tests/specs/prettier/markdown/list/目录下添加输入文件并运行测试,即可自动生成对应的.snap与.prettier-snap,实现"以测试固化格式化决策"的回归保障。
总结
nested-checkbox.md虽只有 11 行,却是理解 Biome Markdown 格式化器嵌套结构处理的一把钥匙:它同时验证了标记归一化(*→-)、缩进归一化(→ 2 空格)、松散列表空行处理、GFM 任务列表状态保留([x])、超宽行不重排策略,以及与 Prettier 在父子项空行上的刻意分歧。配合task_list_item.rs的normalize_task_state选项与textual.rs的大写X替换逻辑,开发者可以清楚回答"Biome 格式化一段嵌套任务列表时每一步做了什么"这个问题,并借助 spec 快照机制为自己的 Markdown 项目建立可验证的格式化预期。
【免费下载链接】biomeA toolchain for web projects, aimed to provide functionalities to maintain them. Biome offers formatter and linter, usable via CLI and LSP.项目地址: https://gitcode.com/gh_mirrors/bi/biome
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考