Biome 规则 useSingleTopLevelHeading 深度解析:以 deeper_levels 测试场景验证“无顶层标题不报错“的行为
2026/9/20 13:54:37 网站建设 项目流程
  • 开发工具
  • Lint
  • 格式化
  • 静态分析
  • 代码质量
  • 前端

【免费下载链接】biome

A 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 lint 规则useSingleTopLevelHeading展开,以规则测试套件中的 deeper_levels.md 用例为切入点,讲清该规则的核心语义、判定逻辑、level配置项以及完整的测试矩阵。读完本文,你将掌握如何在项目中配置该规则,理解其"何时报错、何时保持沉默"的边界条件,并能读懂对应测试用例与快照文件。

一、规则背景:为什么一个 Markdown 文档只能有一个顶层标题

useSingleTopLevelHeading是 Biome 内置的 Markdown lint 规则(language: "md"),其声明位于 use_single_top_level_heading.rs,规则要求:

一个 Markdown 文档应只有一个顶层标题(top-level heading),它充当文档的标题(title);默认情况下是h1,后续标题应使用更低层级(h2h3等)。

该规则受 markdownlint 的MD025 single-title规则启发(sources: &[RuleSource::MarkdownLint("md025", "single-title").inspired()]),目前归属于nursery分组,即尚未稳定、未来可能调整的规则,recommended: true

规则的诊断信息明确说明了约束理由:

  • 单一顶层标题充当文档标题,多余的顶层标题会破坏文档大纲(document outlines)、目录(tables of contents),以及转成 HTML 后的文档结构;
  • 修复建议是"将该标题降级为更低层级,或将其所在章节移入独立文档"。

二、deeper_levels 测试用例:只有深层级标题时,规则保持沉默

本篇文章的主角是测试输入文件 deeper_levels.md,其内容如下:

<!-- should not generate diagnostics --> ### Heading 3 #### Heading 4 ### Heading 3 again

文件的头注释明确了它的断言意图:不应当产生任何诊断should not generate diagnostics)。

这个用例验证的是一个容易被忽略的行为边界:当文档完全不存在顶层标题(既没有h1,也没有通过level配置指定的层级),只有更深层级的标题(h3h4)时,规则不会报告任何问题

为什么?结合规则的实现逻辑可以理解:

  1. 规则只关心"顶层标题的数量"——即标题层级等于配置level的标题;
  2. deeper_levels.md中的### Heading 3#### Heading 4层级分别为 3 和 4,都不等于默认的顶层层级 1;
  3. 因此没有候选标题参与判定,文档自然不会被报告。

对应的快照文件 deeper_levels.md.snap 也印证了这一点——快照中只有# Input部分,没有任何 Diagnostics 段落,说明规则对该输入保持了静默。

三、规则实现原理:源码视角的判定流程

规则的核心实现在use_single_top_level_heading.rs中,其查询类型为Ast<AnyMdHeader>,即每次匹配到一个 Markdown 标题节点时触发。判定流程可分为四步:

1. 层级过滤

let level = ctx.options().level() as usize; if header.level() != level { return None; }

规则只对层级等于配置值的标题感兴趣,其余层级直接忽略。默认level为 1。

2. front matter 提前短路

if root.frontmatter().is_some() { return None; }

当文档带有 front matter(仅在markdown.parser.frontmatter开启时才会被解析)时,规则直接保持沉默。原因是 front matter 可能已经承载了文档标题(如title: ...),正文中的标题不一定构成重复,为避免误报宁可放过。这一点在 yaml_front_matter.md 测试中得到验证:带 front matter 的文档即使包含两个#标题也不报错。

3. 必须是文档的直接子节点

if header.syntax().parent().as_ref() != Some(block_list.syntax()) { return None; }

标题必须直接挂在文档的块列表中(direct child of the document)。嵌套在 blockquote 或列表项里的标题永远不计为文档标题,也永远不会被报告为多余的顶层标题。测试用例 nested_headings.md 专门覆盖了这一点:

<!-- should not generate diagnostics --> > # Title - # Another top-level heading

尽管这里出现了两个#标题,但都被嵌套在引用和列表中,因此不产生诊断。

4. 确认首个顶层标题是文档标题

let title = header .syntax() .siblings(Direction::Prev) .skip(1) .filter_map(AnyMdHeader::cast) .filter(|header| header.level() == level) .last()?; if !is_document_title(&title) { return None; }

规则会向前查找同层级的最近一个标题,并通过辅助函数is_document_title判断它是否真的是文档标题:

fn is_document_title(header: &AnyMdHeader) -> bool { header .syntax() .siblings(Direction::Prev) .skip(1) .all(|sibling| match sibling.kind() { MD_NEWLINE => true, MD_HTML_BLOCK => { MdHtmlBlock::cast(sibling).is_some_and(|block| block.is_html_comment()) } _ => false, }) }

一个标题要成为"文档标题",其前面只能有空白行和 HTML 注释。如果标题之前存在段落、其他层级的标题等"非空白"内容,则它不算文档标题,规则保持沉默。测试用例 no_title_at_start.md 验证了这一行为:

<!-- should not generate diagnostics --> Some intro paragraph precedes the first top-level heading. # One # Two

文档先有一段引言段落,再出现两个#标题,由于第一个#标题前有段落,不满足"文档标题"条件,因此不报错。

只有确认存在一个"真正的文档标题"之后,后续的每个同级顶层标题才会被报告为多余标题(Some(title.range())),诊断信息会同时指向违规标题("This document has more than one top-level heading.")和原始标题("The other top-level heading is here.")。

四、报错场景:invalid 用例与快照对照

deeper_levels.md相对的是 invalid.md:

<!-- should generate diagnostics --> # One # Two # Three

文档以# One开头(前面只有 HTML 注释和空行,满足is_document_title),因此# Two# Three都会被报告。对应快照 invalid.md.snap 展示了完整的诊断输出:

  • 第 5 行# Two和 第 7 行# Three各产生一条lint/nursery/useSingleTopLevelHeading诊断;
  • 诊断主信息为 "This document has more than one top-level heading.";
  • 附加 detail 指向文档标题# One("The other top-level heading is here.");
  • 附带两条 note:一是说明顶层标题对文档大纲、目录和 HTML 结构的意义;二是给出修复建议"降级标题或拆分文档"。

同样会产生诊断的还有 setext 风格标题测试 setext.md:

<!-- should generate diagnostics --> Title ===== Another Title =============

两个用=====下划线构成的 setext 标题(h1)同样构成"多个顶层标题",规则对 ATX(#形式)和 setext(下划线形式)标题一视同仁。

混合场景 mixed_headings.md 则验证了 ATX 与 setext 混合时也会触发:

<!-- should generate diagnostics --> # Title Another Title =============

五、level 选项:改变"顶层"的判定层级

规则的选项定义在biome_rule_optionscrate 的use_single_top_level_heading模块中,核心选项只有一个:level

/// Use the `level` option to change which heading level is treated as the top-level one. /// This is useful when an external tool (a static site generator, for example) already /// injects an `h1` for the page title, so the Markdown source is expected to start at `h2`. /// The value must be between `1` and `6`. /// /// Default: `1`.
  • 取值范围:1 到 6;
  • 默认值:1。

适用场景:当外部工具(如静态站点生成器)已经为页面注入了h1作为页面标题时,Markdown 源码约定从h2开始,此时应将level配置为 2。

测试目录中的 level_two.options.json 给出了完整的配置示例:

{ "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", "linter": { "rules": { "nursery": { "useSingleTopLevelHeading": { "level": "error", "options": { "level": 2 } } } } } }

配套测试 level_two.md 验证了level=2时的行为:

<!-- should generate diagnostics --> ## Section A # An h1, ignored under level=2 Section B --------- Content More content

level=2下,## Section A是顶层标题;# An h1Section B下的 setext 标题(---------对应h2)中,第二个h2会被报告,而h1因为层级不等于 2 被忽略(注意测试注释 "An h1, ignored under level=2")。这正是"更深的标题层级不参与判定"这一核心语义在自定义层级下的再次体现,与deeper_levels.md验证的是同一条规则。

六、完整测试矩阵一览

useSingleTopLevelHeading测试目录(crates/biome_markdown_analyze/tests/specs/nursery/useSingleTopLevelHeading)中的用例覆盖了规则的全部行为边界:

测试文件是否产生诊断验证点
deeper_levels.md文档只有 h3/h4,无顶层标题时不报错
valid.md单个#标题 + 更低层级标题,符合规范
nested_headings.md引用/列表中的标题不计入顶层标题
no_title_at_start.md首个顶层标题前有段落,不算文档标题
yaml_front_matter.md存在 front matter 时规则静默
empty_list_before_title.md标题前的空列表不影响判定
empty_quote_before_title.md标题前的空引用不影响判定
html_comment_before_title.md标题前的 HTML 注释被允许
invalid.md多个 ATXh1全部报告
setext.md多个 setexth1同样报告
mixed_headings.mdATX 与 setext 混合时报告
level_two.md(level=2)自定义层级下报告多余h2
invalid_level.md(level=2)自定义层级场景的另一变体

每个用例都配套.snap快照文件,由 spec_tests.rs 驱动执行。快照中# Input段落展示输入内容;若产生诊断,则# Diagnostics段落展示完整输出,否则只有 Input 段落(如deeper_levels.md.snap所示)。

七、在项目中配置与运行

配置规则

biome.json中启用该规则并调整层级:

{ "linter": { "rules": { "nursery": { "useSingleTopLevelHeading": "error" } } } }

指定level选项:

{ "linter": { "rules": { "nursery": { "useSingleTopLevelHeading": { "level": "error", "options": { "level": 2 } } } } } }

需要注意:规则位于nursery分组,启用时需显式声明(recommended: true意味着在推荐配置中开启,但 nursery 规则的语义可能随版本演进发生变化)。

本地运行测试

如需在本地验证规则行为,可运行 Biome 的 Markdown 分析测试:

cargo test -p biome_markdown_analyze

测试会读取tests/specs/nursery/useSingleTopLevelHeading/目录下的用例,并将实际输出与.snap快照比对,从而验证deeper_levels.md这类"不应产生诊断"的用例确实保持静默。

八、小结

deeper_levels.md虽然只是一份简短的测试输入文件,但它精准锁定了useSingleTopLevelHeading规则的一条核心边界:规则只统计顶层标题的数量,文档里只有更深层级标题(h3、h4)时不属于"多个顶层标题",不产生任何诊断。结合 use_single_top_level_heading.rs 的实现源码与整个测试目录,可以完整还原该规则的判定链条——层级过滤、front matter 短路、直接子节点校验、文档标题确认——并据此在实际项目中正确配置level选项、避免误报,让 Markdown 文档保持单一标题的清晰大纲结构。

  • 开发工具
  • Lint
  • 格式化
  • 静态分析
  • 代码质量
  • 前端

【免费下载链接】biome

A 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
点击查看免费下载

相关推荐

上一篇:GrowingTextView 使用手册
下一篇:【亲测免费】 推荐项目:Datav-Vue3 - 动态数据可视化的新星

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询