news 2026/9/20 8:45:57

Biome Markdown 格式化器如何保护围栏代码块中的 CSS 内容:以 mdn-font-face-1 测试用例为线索的源码级解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Biome Markdown 格式化器如何保护围栏代码块中的 CSS 内容:以 mdn-font-face-1 测试用例为线索的源码级解析

Biome Markdown 格式化器如何保护围栏代码块中的 CSS 内容:以 mdn-font-face-1 测试用例为线索的源码级解析

【免费下载链接】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 仓库中的测试规格文件crates/biome_markdown_formatter/tests/specs/prettier/markdown/code/mdn-font-face-1.md为切入点,深入解析 Biome Markdown 格式化器(formatter)对围栏代码块(fenced code block)内部代码内容的处理策略,包括围栏长度归一化、代码块缩进保护、CommonMark 兼容规则,以及对应的测试基础设施与配置方法。读完本文,你将理解为什么格式混乱的 CSS 代码块在 Markdown 中会被原样保留,并掌握如何运行、验证与配置 Biome 的 Markdown 格式化能力。

一、这个测试用例在测什么:一段“故意凌乱”的 CSS 代码块

在 Biome 仓库中,crates/biome_markdown_formatter/tests/specs/prettier/markdown/code/mdn-font-face-1.md是一个从 Prettier 官方测试套件中迁移过来的 Markdown 规格测试输入文件,其完整内容如下:

```css @font-face { font-family: "HeydingsControlsRegular"; src: url("fonts/heydings_controls-webfont.eot"); src: url("fonts/heydings_controls-webfont.eot?#iefix") format("embedded-opentype"), url("fonts/heydings_controls-webfont.woff") format("woff"), url("fonts/heydings_controls-webfont.ttf") format("truetype"); font-weight: normal; font-style: normal; }
这段输入刻意模拟了开发者从 MDN(Mozilla Developer Network)教程中复制过来的真实代码:CSS 规则内部缩进极其混乱——`src` 属性前有 8 个空格、第二条 `url(...)` 完全没有缩进、`font-weight` 与 `font-style` 的缩进也不一致。与之配套的期望输出文件 `mdn-font-face-1.md.prettier-snap` 表明:**Prettier 与 Biome 对该文件格式化后的结果与输入完全一致,代码块内部的 CSS 内容被逐字保留**。 这正是该测试用例要验证的核心行为:**Markdown 格式化器不应重排围栏代码块内部的代码**。无论内部 CSS 缩进多么混乱,只要它位于反引号围栏内,就属于“字面量内容”(verbatim content),格式化器只负责处理 Markdown 语法层面的结构(围栏本身、外层缩进、空行),而把内部代码原样交给渲染器。 同目录下还有姊妹用例 `mdn-font-face-2.md`,其中包含了 `tech(color-COLVr1)` 这类现代 CSS 字体技术语法,同样验证了含 `tech()`/`format()` 参数的复杂 `@font-face` 声明在围栏内不会被重排。 ## 二、围栏代码块格式化核心:FormatMdFencedCodeBlock 的源码实现 围栏代码块的格式化逻辑位于 [fenced_code_block.rs](https://link.gitcode.com/i/9b3bcfc6417f8b5cd092e422ff98eec1),核心类型是 `FormatMdFencedCodeBlock`,它实现了 `FormatNodeRule<MdFencedCodeBlock>`。在 `fmt_fields` 方法中,代码块被拆解为 `l_fence`(开围栏)、`r_fence`(闭围栏)、`r_fence_indent`(闭围栏缩进)、`content`(内容)、`code_list`(语言标识)、`indent`(开围栏缩进)等字段分别处理。 ### 2.1 围栏长度归一化:CommonMark §4.5 规则 `fenced_code_block.rs` 中最关键的一段逻辑是围栏长度的计算(第 26–33 行): ```rust // Compute the minimum fence length needed (CommonMark §4.5). // The fence must be strictly longer than any same-character sequence // in the content, otherwise the inner sequence would be parsed as a // closing fence. E.g. if the content contains ``` (3 backticks), // the outer fence needs at least 4. let max_inner = longest_fence_char_sequence(node, '`'); let fence_len = (max_inner + 1).max(3); let normalized_fence: String = std::iter::repeat_n('`', fence_len).collect();

其原理遵循 CommonMark 规范 §4.5:闭围栏是一行至少与开围栏等长(或更长)的连续反引号序列。如果代码内容中恰好存在与围栏等长的连续反引号,解析器会把内容中的那一段误判为闭围栏。因此 Biome 会先通过辅助函数longest_fence_char_sequence扫描内容中最长的连续反引号序列max_inner,再将围栏长度设为max_inner + 1(且至少 3 个反引号),从而保证围栏永远“严格长于”内容中的任何反引号串。

随后,开围栏l_fence与闭围栏r_fence都会被替换为统一计算出的normalized_fence。也就是说,即使源码中使用了 4 个、5 个甚至更多反引号,只要内容中不需要更长的围栏,输出都会被归一化为恰好够用的长度。这正是“Markdown 语法层格式化”与“代码内容不动”之间边界的一个典型体现。

2.2 围栏缩进的移除与标准化

除了围栏长度,代码块还涉及缩进处理:

  • 开围栏前的缩进indent字段):用于将代码块嵌入列表等嵌套结构,Biome 会遍历这些缩进 token,通过format_removed将其从输出中移除,再依赖外层(如列表项)的格式化器统一重新生成缩进(第 44–50 行)。
  • 闭围栏前的缩进r_fence_indent字段):同样被format_removed移除并重新标准化(第 96–102 行),随后写出归一化后的闭围栏(第 104–117 行)。

此外,代码还统计了开围栏的缩进宽度opening_fence_indent(第 39–42 行),供后续内容处理时计算“应被剥离的公共缩进”使用。

2.3 语言标识与内容的分流处理

code_list(如 ```css 中的css)与围栏一并输出。而代码块内容(content)的处理分为两种情况:

  • 若内容中不存在MdCodeContent(即文档级代码块通常以单个字面量节点存储),则走普通内容格式化分支;
  • 若存在MdCodeContent节点,则对每个代码内容节点调用FormatMdCodeContentOptions,并传入opening_fence_indent,见 fenced_code_block.rs。

三、代码内容保护:FormatMdCodeContent 如何逐行剥离缩进

代码块内部的逐行处理由 code_content.rs 中的FormatMdCodeContent完成,其行为在源码注释中有明确说明:“Trivia is excluded on both sides”(两侧排除 trivia),即开围栏信息字符串后的空白不会作为多余内容行输出。

关键算法在fmt_fields中(第 36–82 行):

  1. 跳过行首换行符value_token以开围栏行末的换行符开头(\r\n\n),先从line_start中跳过,避免产生空内容行。
  2. 剥离公共缩进:对每一行,从行首开始,在不超过opening_fence_indent(开围栏缩进宽度)的范围内剥离连续空格(第 43–50 行)。这意味着,如果代码块位于列表项中(开围栏有 2 空格缩进),内容行的前 2 个空格会被视为 Markdown 语法缩进而去除,剩余部分才是真正的代码。
  3. 逐行按原样输出:剥离缩进后的每一行代码,通过syntax_token_cow_slice(...).with_literal_line_breaks()字面换行的方式写出(第 27–34 行)。with_literal_line_breaks保证换行符被忠实保留,不会参与自动换行(line wrapping)或重排。

对于 CRLF(\r\n)行尾,代码还做了细致的兼容处理:遇到\r时,若后面紧跟\n则一起作为行尾输出,否则单独生成一个不依赖父级的字面换行(第 62–74 行)。

这就是mdn-font-face-1.md中那些“缩进错乱”的 CSS 行得以原样保留的根本原因:格式化器只剥离与围栏对齐的 Markdown 语法缩进,而 CSS 内部每个属性、每条url(...)之前的空格都是代码内容的组成部分,会被逐字输出。整段@font-face对格式化器而言是“不透明”的文本,从而保证了 MDN 示例这种真实代码在文档中不会被破坏。

四、测试基础设施:该用例如何被自动执行与验证

4.1 从 Prettier 测试套件迁移

这些mdn-*.md文件来自 prepare_tests.js,该脚本以 Prettier 仓库的tests/format目录为输入,遍历其中所有测试文件:

  • 将输入文件复制到 Biome 的tests/specs/prettier/对应目录;
  • 从 Prettier 的快照中提取“输出”部分,并用 Prettier 自身重新格式化后写入.prettier-snap文件(第 122–124 行);
  • 若 Prettier 重格式化前后不一致,还会额外生成.prettier-snap-original文件用于比对。

也就是说,.md是输入、.prettier-snap是期望输出,二者成对出现,构成一份可对照的格式化测试规格。

4.2 Prettier 兼容性快照测试

prettier_tests.rs 通过宏批量生成测试:

tests_macros::gen_tests! {"tests/specs/prettier/markdown/**/*.{md}", crate::test_snapshot, ""}

每个.md输入都会触发test_snapshot:它使用PrettierTestFile读取测试文件,以MdFormatOptions::default()IndentStyle::Space、缩进宽度 2)和 GFM(GitHub Flavored Markdown)方言构造MarkdownTestFormatLanguage,最终交给PrettierSnapshot::new(...)执行格式化并与.prettier-snap期望输出对比。这保证了 Biome 的 Markdown 输出与 Prettier 保持兼容——这正是mdn-font-face-1.md这类用例存在的意义:任何破坏代码块内容的行为都会导致快照不一致,从而让测试失败

4.3 通用规格测试与运行方式

除了 Prettier 兼容性测试,Biome 还有一套面向自身语法的规格测试 spec_tests.rs,它扫描tests/specs/markdown/**/*.md下的所有用例,并以启用 Markdown formatter 的配置执行SpecSnapshot测试。两套测试体系共同守护 Markdown 格式化行为。

在仓库根目录下运行以下命令即可执行 Markdown 格式化器的全部测试:

cargo test -p biome_markdown_formatter

若要只跑某个特定用例(例如本主题的mdn-font-face-1),可用:

cargo test -p biome_markdown_formatter -- mdn_font_face

五、如何在真实项目中启用与配置 Markdown 格式化

需要特别说明的是:在 Biome 当前配置源码 markdown.rs 中,Markdown 格式化器默认处于禁用状态pub type MarkdownFormatterEnabled = Bool<false>;注释明确指出“Keep it disabled by default while experimental”,即实验功能默认关闭)。因此若要在项目中使用,需在biome.json中显式开启:

{ "formatter": { "indentStyle": "space", "indentWidth": 2, "lineWidth": 80 }, "markdown": { "formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2, "lineWidth": 80, "proseWrap": "preserve", "lineEnding": "lf", "trailingNewline": true }, "parser": { "frontmatter": false, "gfm": true } } }

各配置项的含义与默认值(来自 markdown.rs 的结构体定义):

配置项默认值作用
markdown.formatter.enabledfalse(实验性,默认关闭)是否启用 Markdown 格式化
indentStyle跟随全局(测试默认spaceMarkdown 文件的缩进风格
indentWidth2缩进宽度
lineWidth80单行最大宽度
proseWrappreserve段落换行策略:preserve保持原样、always按行宽重排、never合并为单行;手动换行(行尾两个空格或反斜杠)始终保留,见 context.rs 中ProseWrap枚举定义
lineEndinglf行尾风格,auto在 Windows 用 CRLF、其他平台用 LF
trailingNewlinetrue文件末尾是否保留换行符
parser.frontmatterfalse是否解析文件开头的 frontmatter
parser.gfmtrue是否启用 GitHub Flavored Markdown 扩展

启用后,可对单个文件执行格式化验证本文描述的行为:

biome format crates/biome_markdown_formatter/tests/specs/prettier/markdown/code/mdn-font-face-1.md

也可以搭配--write参数直接写入格式化结果,或使用biome check做整体检查。

六、关键要点总结

  • 代码块是字面量:无论代码块内部 CSS 的缩进多么混乱,Biome 的 Markdown 格式化器都会将其逐字保留——这正是 code_content.rs 中with_literal_line_breaks逐行原样输出的结果。
  • 格式化边界清晰:格式化器只处理 Markdown 语法层(围栏长度归一化、围栏前后缩进标准化、空行),并遵循 CommonMark §4.5 规则保证围栏严格长于内容中的反引号序列,见 fenced_code_block.rs。
  • 兼容性有测试兜底mdn-font-face-1.md及其.prettier-snap期望输出由 prepare_tests.js 从 Prettier 套件迁移而来,经 prettier_tests.rs 的快照机制持续验证。
  • 默认关闭、需显式开启:Markdown 格式化器在 markdown.rs 中默认禁用,属于实验性功能,需在biome.json中设置markdown.formatter.enabled: true后才会生效。

对于希望在文档中嵌入 CSS、JavaScript 等代码示例的开发者而言,理解“代码块内容受保护、Markdown 结构被规范化”这一设计,能够帮助你放心地把凌乱的示例代码放进 Markdown——Biome 会替你把围栏整理干净,同时绝不擅自动你的代码。

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

OpenToonz:免费的开源 2D 动画软件,从草图到成片一站完成

OpenToonz&#xff1a;免费的开源 2D 动画软件&#xff0c;从草图到成片一站完成 【免费下载链接】opentoonz OpenToonz - An open-source full-featured 2D animation creation software 项目地址: https://gitcode.com/GitHub_Trending/op/opentoonz 商业级 2D 动画软…

作者头像 李华
网站建设 2026/9/20 8:44:40

从零自研短链系统:从短码生成到高并发跳转的完整实战

1. 内容整体设计与思路拆解短链这东西&#xff0c;听起来是个再小不过的工程。做之前我也觉得&#xff0c;不就是把一串长 URL 变成一个小短码嘛&#xff0c;能有多复杂。可真到动手写的时候才发现&#xff0c;一个能上线的短链系统&#xff0c;几乎把后端常见的套路全部串起来…

作者头像 李华
网站建设 2026/9/20 8:44:35

AIGC检测与学术写作:深度改写与混合创作方法论

1. 论文AIGC率问题的现状与挑战2026年的学术环境正在面临一个前所未有的挑战——随着生成式AI技术的普及&#xff0c;论文中AI生成内容&#xff08;AIGC&#xff09;的比例正在急剧攀升。最近一项针对全球TOP100高校的调研显示&#xff0c;超过67%的导师表示他们无法准确判断学…

作者头像 李华
网站建设 2026/9/20 8:42:21

Coursor:提升开发者终端效率的智能命令行工具

1. 项目概述Coursor是一款专为开发者设计的命令行工具&#xff0c;它能够显著提升终端操作效率。作为一个长期与终端打交道的开发者&#xff0c;我深刻理解在复杂项目环境中频繁切换目录、记忆冗长路径的痛苦。Coursor通过智能索引和快速跳转功能&#xff0c;让终端导航变得像使…

作者头像 李华
网站建设 2026/9/20 8:42:10

Context Pruning技术解析:提升RAG系统效率的关键方法

1. 为什么我们需要Context Pruning&#xff1f;在检索增强生成&#xff08;RAG&#xff09;系统中&#xff0c;我们经常会遇到一个典型问题&#xff1a;当检索到的上下文文档过长或包含大量无关信息时&#xff0c;生成模型的表现会显著下降。这个问题就像让一个学生在考试时同时…

作者头像 李华