ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Biome Markdown 格式化器围栏代码块缩进规范化解析:以 0-indent-js 测试用例为入口

Biome Markdown 格式化器围栏代码块缩进规范化解析:以 0-indent-js 测试用例为入口 Biome Markdown 格式化器围栏代码块缩进规范化解析以 0-indent-js 测试用例为入口【免费下载链接】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本文以 0-indent-js.md 这一 Prettier 兼容性测试用例为线索深入剖析 Biome Markdown 格式化器biome_markdown_formatter在处理嵌套列表内js围栏代码块时的根缩进root indent规范化行为。读完本文你将理解代码块内容为何不会被重新排版、开围栏缩进如何逐行剥离、围栏长度如何按 CommonMark 规则自动归一化以及如何在本仓库中运行同类用例、通过biome.json配置 Markdown 格式化能力。一、测试用例本体一个输入即输出的缩进边界场景1.1 原始输入文件该用例位于 0-indent-js.md完整内容如下行号仅为阅读辅助1 | - 1 2 | - 2 3 | - 3 4 | js 5 | md 6 | # this is the root indent 7 | 8 | # this is the root indent 9 | 10 | # this is the root indent 11 | 12 | 13 | something 14 | asd 15 | 16 | asd 17 | 18 | asd 19 | 20 | 这是一个三层嵌套的无序列表- 1缩进 0、- 2缩进 2、- 3缩进 4在第三层列表项内部放置了一个缩进 6 个空格的 js 围栏代码块。代码块内容是一段 JavaScript 模板字符串字面量第一段为md开头的模板字符串内含三行重复的# this is the root indent第二段为something开头的模板字符串内含多行asd文本。从用例命名0-indent-js可以推断其意图验证缩进为 0即相对根位置的 js 代码块内容在格式化后保持不变。这一用例源自 Prettier 官方测试套件Biome 将其纳入自己的兼容性测试目录用于对齐 Prettier 的格式化行为。1.2 预期输出快照格式化结果与输入完全一致配套的预期快照 0-indent-js.md.prettier-snap 与输入逐字节一致列表层级、6 空格缩进、代码块内所有行包括空行均原样保留。这一输入即输出的结果揭示了 Biome Markdown 格式化器对围栏代码块的两条核心语义代码块内容是 verbatim逐字保留的无论内容里的 JS 写得多么乱如空行、重复文本格式化器都不会对其做任何词法/语法级重排代码块内容行的缩进只做以开围栏为基准的规范化每行行首最多剥离与开围栏缩进等长的空格剥离后剩余部分原样输出本例中内容行与围栏同处 6 空格缩进因此结果不变。二、测试如何被执行Prettier 兼容性测试基建2.1 prettier_tests.rs 的宏驱动该用例位于tests/specs/prettier/markdown/目录下与普通快照测试目录tests/specs/markdown/分开管理。执行入口是 prettier_tests.rstests_macros::gen_tests! {tests/specs/prettier/markdown/**/*.{md}, crate::test_snapshot, }gen_tests!宏会为prettier/markdown目录下的每一个.md文件生成一个测试函数全部调用test_snapshot。该函数的关键配置在 prettier_tests.rs 中fn test_snapshot(input: static str, _: str, _: str, _: str) { countme::enable(true); let root_path Utf8Path::new(concat!( env!(CARGO_MANIFEST_DIR), /tests/specs/prettier/ )); let test_file PrettierTestFile::new(input, root_path); let options MdFormatOptions::default() .with_indent_style(IndentStyle::Space) .with_indent_width(IndentWidth::default()); let language language::MarkdownTestFormatLanguage::gfm(); let snapshot PrettierSnapshot::new(test_file, language, MdFormatLanguage::new(options)); snapshot.test() }几个值得注意的细节测试使用空格缩进IndentStyle::Space与默认缩进宽度Biome 默认 2 空格这与0-indent-js.md中每层列表缩进 2 空格的编排吻合语言实例通过 language.rs 中的MarkdownTestFormatLanguage::gfm()构造解析时走parse_markdown_with_cache(..., MarkdownParserOptions::default().with_gfm(true))即启用 GitHub Flavored Markdown 扩展PrettierSnapshot将格式化结果与同目录下的.prettier-snap文件对比——这正是本用例没有普通.snap文件、只带一个.prettier-snap的原因它属于与 Prettier 对齐的兼容性测试而非 Biome 独立行为的快照测试。2.2 与 spec_tests.rs 的分工对照 spec_tests.rs 可以看到Biome 自己的快照测试走的是另一条宏路径tests_macros::gen_tests! { tests/specs/markdown/**/*.md, crate::spec_test::run, }它只覆盖tests/specs/markdown/目录并且在 spec_test.rs 中显式构造配置MarkdownFormatterConfiguration { enabled: Some(true.into()), .. }来驱动格式化。因此可以推断tests/specs/prettier/markdown/是专为 Prettier 兼容性对照而设的测试域0-indent-js.md正是这一对照体系中的一员。三、源码级原理围栏代码块的两大格式化机制理解了测试如何运行后我们进入实现层。围栏代码块的格式化逻辑分散在两个文件中外层结构处理在fenced_code_block.rs内容行的逐行输出在code_content.rs。3.1 开围栏缩进opening fence indent的计算与剥离code_content.rs 负责将MdCodeContent的字面量文本按行输出其核心是一个按行扫描 有条件剥离行首空格的循环let mut line_start match bytes { [b\r, b\n, ..] 2, [b\r | b\n, ..] 1, _ 0, }; while line_start bytes.len() { let mut content_start line_start; let max_content_start line_start self.opening_fence_indent; while content_start bytes.len() content_start max_content_start bytes[content_start] b { content_start 1; } // ... 找到行尾并输出 content_start..line_end 的切片 }这段逻辑的含义非常明确每一行的行首最多剥离opening_fence_indent个空格max_content_start line_start opening_fence_indent一旦遇到非空格字符或达到上限立即停止。也就是说内容行与围栏对齐的缩进会被吸收掉输出时统一从列表实际内容位置开始超过围栏缩进的额外空格不会被误删——这保证了模板字符串、缩进敏感的代码如本例中的md\ 与something 字面量在剥离基准缩进后保持相对结构不变。同时源码注释还指出一个细节MdCodeContent的字面量以结束开围栏行的换行开头因此循环起始会先跳过开头的\r\n或\n开围栏行 info-string 后的空白会被解析器作为该 token 的前导 trivia 挂载格式化时特意排除format_replaced避免多打印出一行多余的空行。3.2 opening_fence_indent 的来源与围栏长度归一化opening_fence_indent由外层 fenced_code_block.rs 计算let opening_fence_indent indent .iter() .map(|token| token.md_indent_char_token().map(|token| token.text().len())) .sum::Resultusize, _()?;它累加语法树中indent节点列表项/引用块等容器的缩进 token的字符长度。对0-indent-js.md而言三层列表累积的缩进恰为 6 个空格与围栏自身的 6 空格对齐于是内容行 6 空格缩进被完整剥离后再原样写出——最终结果与输入一致。同一个文件中还实现了围栏长度的归一化fenced_code_block.rs// 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();辅助函数longest_fence_char_sequencefenced_code_block.rs遍历MdCodeContent/MdTextual节点统计内容中连续反引号的最长长度。最终围栏长度取max(最长连续反引号 1, 3)——这正对应 CommonMark 规范 §4.5 的要求闭围栏必须严格长于内容中任何相同字符序列。闭围栏r_fence同样会被替换为normalized_fence。3.3 列表 / 引用块上下文的分支处理fenced_code_block.rs 根据上下文走三条输出路径内容含引用前缀时整段内容连同前缀一起dedent_to_root后原样打印keep_fences_in_italics: true无MdCodeContent、纯文本行时按TextPrintMode::Clean/Fill取决于是否在列表内走行内项列表格式化存在MdCodeContent即本例场景时对MdCodeContent项单独调用.with_options(FormatMdCodeContentOptions { opening_fence_indent })其余行内项原样输出——即内容按行剥离基准缩进的路径。此外开围栏/闭围栏前的缩进 token 在非引用场景下会被显式移除format_removed因为列表本身的缩进已由外层列表规则负责只有在引用块内才保留原缩进 token。四、同类用例家族从 sibling 文件看格式化边界0-indent-js.md并非孤例tests/specs/prettier/markdown/code/目录下还有一批针对代码块行为的对照用例共同勾勒出 Biome Markdown 代码块格式化的完整边界用例输入要点预期行为indent.md5 空格缩进的缩进式代码块 列表内围栏代码块 有序列表缩进代码块保留1.后补一个空格变为1.列表标记间距 1→2backtick.md10 个反引号的开/闭围栏包住 3 反引号内层代码块外层围栏被归一化为 4 个反引号max(31, 3) 4simple.md无语言标注的代码块内容原样保留lang.mdjs 信息字符串语言标注与内容均不变format.mdjs 内包含明显乱排的 JS多空格、乱换行内容完全不被重排——印证 verbatim 语义leading-trailing-newlines.md代码块首尾多个空行空行原样保留mdn-auth-api.md / mdn-import.mdMDN 文档中真实摘录的javascript /css 大段代码逐字保留含内部乱缩进additional-space.md有序列表项内嵌代码块、段落间多余空行空行/围栏间距规范化编号自动重排代码块内容保留ts-trailing-comma.md / tsx-trailing-comma.mdts /typescript / tsx 内T,尖括号尾逗号写法内容不触碰原样输出从这些用例可以归纳出 Biome Markdown 格式化器对围栏代码块的一贯策略围栏与容器负责排版内容负责保留——容器列表、引用、段落间距、围栏长度由格式化器统一治理而代码块内部文本永远作为不透明字面量输出。五、配置与运行让 Markdown 格式化器跑起来5.1 配置项总览Biome 的 Markdown 格式化器目前是实验性特性默认关闭。在 markdown.rs 中可见其默认值定义pub type MarkdownFormatterEnabled Boolfalse; // Keep it disabled by default while experimental.MarkdownFormatterConfiguration支持以下字段见 markdown.rs字段说明默认值enabled是否对 Markdown 及其超语言文件启用格式化false实验性阶段默认关闭indentStyle缩进风格tab/space继承全局测试中固定为spaceindentWidth缩进宽度2lineWidth行最大宽度80trailingNewline文件末尾是否保留换行关闭可能引发其他工具问题官方强烈不建议truelineEnding行尾符lf/crlf/autoauto在 Windows 用 CRLF其余平台用 LFautoproseWrap段落换行策略preserve/always/never手工换行行尾两空格或反斜杠始终保留preserve对应的MarkdownParserConfiguration还提供两个解析级开关markdown.rsfrontmatter是否解析文件开头的 frontmatter默认falseMarkdownParseFrontmatter Boolfalsegfm是否启用 GitHub Flavored Markdown 扩展默认trueMarkdownParseGfm Booltrue。5.2 一个可用的 biome.json 示例{ $schema: ./node_modules/biomejs/biome/configuration_schema.json, markdown: { parser: { frontmatter: true, gfm: true }, formatter: { enabled: true, indentStyle: space, indentWidth: 2, lineWidth: 80, lineEnding: lf, trailingNewline: true, proseWrap: preserve } } }在formatter.enabled: true的前提下biome format/biome check即会对.md文件按上述规则格式化。5.3 本地复现本用例本仓库为纯 Rust 项目当前环境未安装 cargo以下命令按仓库结构给出执行方式。要运行本文讨论的 Prettier 兼容性用例可在仓库根目录执行cargo test -p biome_markdown_formatter --test prettier_tests若只关注本用例可用测试名过滤宏生成测试名基于文件路径下划线分隔cargo test -p biome_markdown_formatter --test prettier_tests 0_indent_js断言失败时PrettierSnapshot会输出格式化结果与.prettier-snap的差异便于对照检查缩进规范化是否符合预期。六、小结0-indent-js.md虽然只是一个十几行的测试夹具但它精确锁定了 Biome Markdown 格式化器的一项关键行为契约嵌套列表内围栏代码块的内容行只按开围栏缩进做基准剥离不做任何内容重排。配合 fenced_code_block.rs 的围栏长度归一化CommonMark §4.5与 code_content.rs 的逐行剥离算法以及prettier/markdown/code目录下十余个 sibling 用例我们可以完整还原 Biome 在容器排版、内容 verbatim这一设计原则下的具体实现。如果你正在为项目接入 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),仅供参考
返回列表