news 2026/9/20 15:35:17

Biome Markdown 格式化器对 blockquote(引用块)边界的处理:从 notext-end 测试用例看源码实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Biome Markdown 格式化器对 blockquote(引用块)边界的处理:从 notext-end 测试用例看源码实现

Biome Markdown 格式化器对 blockquote(引用块)边界的处理:从 notext-end 测试用例看源码实现

【免费下载链接】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

[!NOTE] 导读 本文以 Biome 仓库中的 Markdown 格式化测试用例crates/biome_markdown_formatter/tests/specs/prettier/markdown/blockquote/notext-end.md为切入点,剖析 Biome 的 Markdown 格式化器在遇到「以空引用行结尾」或「引用块内嵌套引用」时如何规范化>前缀与空行。读完本文,你将掌握该测试用例的输入/输出差异、QuoteBoundaryTrim边界裁剪算法、MdQuotePrefix前缀重建逻辑,以及这些行为与proseWrap配置项的联动关系。

1. 测试用例背景:它测的是什么

在 Biome 的 Markdown 格式化测试体系中,blockquote/目录存放着一组专门针对引用块(blockquote)的回归与兼容性用例,包含simple.mdnested.mdcode.mdlist.mdparagraph.mdinterrupt-others.mdissue-14228.md以及本文主角notext-end.md等。命名中的notext-end意为「结尾处没有文本」——即引用块最后一行是仅含>标记的空引用行,而非实际内容行。

该文件同时存在三个变体,构成完整的「输入—预期—实测」闭环:

文件作用
notext-end.md原始输入(未格式化)
notext-end.md.prettier-snapPrettier 的格式化输出,用于兼容性对比
notext-end.md.snapBiome 的快照,记录输入、与 Prettier 的 diff、Biome 实际输出

快照头部信息表明它由crates/biome_formatter_test/src/snapshot_builder.rs生成,info字段标注markdown/blockquote/notext-end.md,说明该用例会同时跑 Prettier 对比和 Biome 自身输出两条验证链路。

1.1 输入内容拆解

原始输入由五组引用块组成,覆盖了五类典型场景:

> [!NOTE] > `DOOM` > _b_ >> `A` >> `B` > *a* >> # foo >> `a` > `b` > This is a quote with an italic _across multuple lines > which should just work_. So make sure there is no > if we set > proseWrap to `never` > This is a quote with a link [across multuple lines > which should just work](). So make sure there is no > if we set > proseWrap to `never`
  • 第一组:GitHub 风格的[!NOTE]提示块(Alert 语法)+ 内联代码;
  • 第二组:外层引用内嵌双层引用(>>>混排),内容为行内代码AB
  • 第三组:外层引用内嵌「标题 + 行内代码比较表达式」(>a>b``),且引用块之后紧接两个连续空行
  • 第四组:跨行斜体(italic),文本在换行处跨过引用前缀;
  • 第五组:跨行链接(link),同样在换行处跨过引用前缀。

其中第四、五组的注释明确提示:当proseWrap设为never时,换行处不应被插入>前缀——这正是本用例要守护的行为边界。

2. Biome 输出与 Prettier 的差异:快照中的 diff 在说什么

Biome 对notext-end.md的实际输出为:

> [!NOTE] > `DOOM` > _b_ > > `A` > > `B` > _a_ > > # foo > > `a` > `b` > This is a quote with an italic _across multuple lines > which should just work_. So make sure there is no > if we set > proseWrap to `never` > This is a quote with a link [across multuple lines > which should just work](). So make sure there is no > if we set > proseWrap to `never`

对照notext-end.md.snap中记录的「Prettier differences」diff,可以精确还原 Biome 与 Prettier 的两处分歧:

  1. 嵌套引用之间的空引用行被移除:Prettier 在> _b_> >A之间保留了一行 `>`(仅一个引用标记的空行),Biome 则将其删除,直接让 `> _b_` 与下一层引用 `> > `A相邻;
  2. 嵌套引用内的标题前空行被移除:Prettier 在> > # foo之前保留了> >空引用行,Biome 同样删除,并将> >a>b`` 与标题直接衔接;
  3. 引用块之间的空行数量被归一化:第三组引用块与第四组之间原本有两个空行,Prettier 输出中空行被压缩,Biome 则保留了两个空行(即输入中的空行结构),并在 diff 中体现为新增的+空行。

这三处差异共同指向 Biome 的一个明确设计取向:默认(非 Preserve 模式)下对引用边界(仅含>前缀、不含任何内容的行)做「瘦身」处理,但保留引用块之间真实的空行分隔

3. 源码实现:边界裁剪从何而来

行为背后的核心逻辑位于crates/biome_markdown_formatter/src/markdown/lists/block_list.rs,其中定义了三种边界裁剪模式:

pub(crate) enum QuoteBoundaryTrim { /// Preserve quote-only boundary lines. #[default] None, /// Remove quote-only lines before blockquote content. Leading, /// Remove quote-only lines before and after blockquote content. LeadingAndTrailing, }

选择哪种模式由 FormatMdQuote 在格式化每个MdQuote节点时决定:

let quote_boundary_trim = if node.syntax().next_sibling().is_none() { QuoteBoundaryTrim::LeadingAndTrailing } else { QuoteBoundaryTrim::Leading };

即:若该引用块没有后续兄弟节点(是最后一个块),则同时裁剪首尾边界;否则只裁剪头部边界。这解释了为什么输入中位于文件中间、后面还有其他引用块的组别只处理前置空引用行,而不会误删其后的空行。

3.1 头部边界扫描算法

quote_boundary_trim_start展示了实现细节的微妙之处:纯引用行的 CST 形状并不统一——引用块自身前缀对应的首个空行表现为内容列表里的MdNewline,而后续的纯引用行则表现为MdQuotePrefix+MdNewline成对出现。算法因此只精确匹配这两种形态,一旦遇到「前缀后跟真实内容」就立即停止:

// The first empty line of a blockquote is represented by the quote node's // own prefix plus a leading newline in the content list. if iter.peek().is_some_and(|(_, block)| block.is_newline()) { iter.next(); start = 1; } // Additional quote-only leading lines are represented as // MdQuotePrefix + MdNewline pairs. while let Some((prefix_index, AnyMdBlock::MdQuotePrefix(_))) = iter.next() { if iter.peek().is_some_and(|(_, block)| block.is_newline()) { iter.next(); start = prefix_index + 2; } else { break; } }

3.2 尾部边界扫描算法

quote_boundary_trim_end采用反向扫描:只把「末尾的MdQuotePrefix」或「MdQuotePrefix+MdNewline」识别为尾部纯引用行。注释特别强调:单独的MdNewline不足以判定为边界,它只有在前一个条目是MdQuotePrefix时才属于纯引用行——避免误删真实内容行的换行。

被判定为边界的条目,在FormatMdBlockListQuoteBlockList中通过format_removed_quote_boundary输出为空,从而在最终渲染中消失(见 shared.rs 的对应辅助函数)。

4. 前缀重建:MdQuotePrefix的「补空格」行为

当引用内容被重新排版后,Biome 需要逐行重建>前缀。这一逻辑在 quote_prefix.rs:

if let Some(post_marker_space_token) = post_marker_space_token { write!(f, [post_marker_space_token.format()])?; } else { let marker = marker_token?; let next_has_text = marker .next_token() .is_some_and(|t| t.text().starts_with(|c: char| !c.is_whitespace())); if next_has_text { write!(f, [space()])?; } }
  • 若 CST 中保留了>后的空格 token,原样输出;
  • 若没有该 token,则检查紧随其后的 token 是否以非空白字符开头——是则补一个空格(保证> text的规范形态),否则不加(保持>空行形态)。

此外该文件还支持should_remove选项:当引用块以空行开头(starts_with_blank_line)且裁剪范围非空时,FormatMdQuote会让整个前缀以format_removed输出,从而把开头的空引用行整体抹掉(quote.rs)。

5. 与proseWrap的联动:跨行内容为什么不被插入>

输入第四、五组的注释点明了关键约束:跨行斜体与跨行链接在proseWrap: never下不应在续行补>。这与FormatMdQuote的分支结构对应:

let prose_wrap = f.options().prose_wrap(); if prose_wrap == ProseWrap::Preserve && should_format_quote_structurally(node)? { return Quote::new(node.clone()).fmt(f); }
  • proseWrap: preserve且引用内含「结构性续行」(跨行的段落文本等)时,走Quote::new(...)的专门实现(quote.rs),其中QuoteParagraph会针对跨行段落判断should_format_quote_continuation_after_newline,并配合QuoteLinePrefix按需重建行前缀;
  • 其余情况(含never/always)走通用路径,用align("> ", &content)对齐内容——该模式只会按块级结构输出前缀,不会在段落内部的软换行处硬塞>

ProseWrap枚举定义在 context.rs,取值preserve/always/never,并通过with_prose_wrap注入格式化上下文。因此本用例实际上覆盖了两种渲染策略下的边界稳定性:既验证默认对齐模式对纯引用行的裁剪,也验证 preserve 模式下跨行文本的前缀重建不会破坏语义。

6. 测试如何运行与验证

blockquote/用例属于 Prettier 兼容性测试套件。在crates/biome_markdown_formatter/tests/spec_tests.rs中,tests_macros::gen_tests!会扫描tests/specs/markdown/**/*.md生成测试函数;而prettier/子目录的用例由 prettier_tests.rs 驱动,逻辑上与crates/biome_formatter_test的快照构建器协作:读取输入 → 分别调用 Biome 与 Prettier 格式化 → 生成.snap快照记录 diff → 断言 Biome 输出与 Prettier 输出的一致性策略。

因此notext-end.md的价值是双重的:

  • 对格式化器开发者:它是边界行为的回归守卫,任何对QuoteBoundaryTrimMdQuotePrefix重建逻辑的改动都会在此用例的快照 diff 中原形毕露;
  • 对使用者:它直观展示了 Biome 在 blockquote 嵌套、空引用行、跨行行内元素等复杂组合下的输出约定——嵌套引用的空行会被收敛,引用块间的空行保留,跨行内容不补前缀。

7. 小结

通过notext-end.md这一个用例,可以串起 Biome Markdown 格式化器关于引用块的三条核心规则:

  1. 边界瘦身:默认模式下,仅含>的纯引用行在块首被裁剪(最后一块还会裁剪块尾),由QuoteBoundaryTrimquote_boundary_trim_range实现;
  2. 前缀重建>后的空格按「后随 token 是否为文本」动态决定,保证输出规范且稳定(quote_prefix.rs);
  3. proseWrap 联动preserve模式下跨行段落走专门的结构化路径(quote.rs),never/always下走对齐路径,跨行处均不额外插入>

如需进一步研究,可对比同目录下的 nested.md(嵌套引用)、code.md(代码块边界)与 issue-14228.md(历史回归问题),它们共同刻画了 Biome 对引用块这一 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 15:33:37

GPS静动态滤波卡尔曼滤波实验:Q/R整定与新息门限实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 15:30:25

Arduino UNO超声波避障小车:接线、决策状态机与实验数据

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 15:26:38

Claude.ai 远程 MCP 免安装,TaoToken 走通模型调用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 15:22:52

SAE J200标准解读:橡胶材料分类系统与选型实战指南

简介:这份PDF为SAE J200-2005《橡胶材料分类系统》的中文翻译版,面向汽车、航空航天、医疗等行业的橡胶制品设计、采购与质量工程师。标准将硫化橡胶按耐热老化性能与耐油溶胀性能划分为基本等级,并结合后缀数值形成完整命名体系,…

作者头像 李华