Biome Markdown 格式化器深度解析:嵌套列表与 GFM 任务列表的格式化规则
2026/9/21 3:03:17 网站建设 项目流程

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

该用例刻意设计了三个"压力点",用于验证格式化器的鲁棒性:

  1. 父项后紧跟空行 + 缩进子项:测试"松散列表(loose list)"与"嵌套子列表"同时出现时的空行去留;
  2. 长文本:父项、子项与后续段落全部使用超长文本,用于检验行宽(80 字符)约束下格式化器是否强制换行;
  3. [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.mdnested.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.rsFormatGfmTaskListItem中:

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.rsfmt_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中的FormatMdBulletListbullet_list_item.rs中的FormatMdBulletListItem均以debug_assert!(false, "This node should be formatted via FmtAnyList...")开头——它们不是实际的格式化入口,而是用于拦截"直接格式化列表节点"的错误路径。真正的分发发生在FmtAnyList匹配AnyMdBlock并调用as_any_list_item之后,由生成的container_block.rsblock.rs等文件按节点类型逐层下派。这种"入口断言 + 统一分发"的结构保证嵌套列表的缩进与项间空行决策集中在同一路径处理,避免了多种列表写法各走各的逻辑。

缩进与空行:容器块的连续化输出

从快照输出可以看到,Biome 将"父项 → 子项 → 段落"在满足规则的前提下尽量连续输出。结合FormatAnyMdContainerBlockMdBulletListItemMdOrderedListItemMdQuote的分派,可以推断:嵌套列表项作为容器块(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字段)。验证方式:

  1. 直接运行 CLI:将任意包含嵌套列表与任务列表的.md文件交给biome format,观察*是否归一化为-、子项是否缩进 2 空格、[X]是否变为[x]
  2. 运行 spec 测试:在仓库根目录执行针对 markdown formatter 的 cargo 测试命令,测试框架会自动比对nested-checkbox.md输入与.snap快照,任何格式化行为变化都会以快照 diff 形式暴露;
  3. 对照 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.rsnormalize_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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询