☰
markdownlint 规则实战:MD022–MD030 测试样例全解析(标题、块引用与列表规范)
2026/10/15 3:47:41 网站建设 项目流程
  • 开发工具
  • 代码质量
  • Lint
  • 静态分析

【免费下载链接】markdownlint

A Node.js style checker and lint tool for Markdown/CommonMark files.

项目地址:https://gitcode.com/gh_mirrors/ma/markdownlint
点击查看免费下载

本文以 markdownlint 仓库中的 test/MD022-MD030.md 测试样例为线索,逐条拆解 MD022~MD030 这九条规则的触发条件、配置参数与自动修复行为,并结合仓库源码说明其底层实现。读完本文,你将能够读懂 markdownlint 的"违规标记"式测试用例,理解标题、块引用、有序/无序列表相关规则的实际判定逻辑,并能针对自己的文档直接定位、解释和修复同类问题。

一、测试样例与"违规标记"约定

test/MD022-MD030.md 是 markdownlint 仓库中一系列按规则编号命名的场景测试文件之一(其余还有 test/MD001-MD010.md、test/MD011-MD021.md、test/MD031-MD040.md 等)。它的核心机制是:在违规行内以{MD0XX}形式内联标注预期违规的规则号,测试框架据此校验 linter 的实际输出。

从 test/markdownlint-test-scenarios.mjs 的源码可以看到该机制的具体实现:

  • 测试启动时通过fs.readdir("./test")读取 test 目录下所有.md文件,并为每个文件生成一个测试用例;
  • 对每个文件,先用lint({ "files": [ file ] })执行完整扫描,再用正则/\{(MD\d+)(?::([-+]?)(\d+))?\}/g解析行内标记,把{MD022:1}、{MD028:9}这类写法转换成"规则 + 预期行号"的断言数据;
  • 最后将实际报错与预期结果做深度对比(t.assert.deepEqual(actual, expected, ...)),并校验"可修复的规则是否都在 constants 中声明过",同时用applyFixes验证自动修复后不再残留可修复错误。

也就是说,测试文件末尾的{MD022:1} {MD023:1} {MD024:4} {MD025:4} {MD028:9}并不是注释,而是断言:MD022 应在第 1 行报错、MD023 应在第 1 行报错、MD024 与 MD025 应在第 4 行报错、MD028 应在第 9 行报错。对应的真实输出固化在 test/markdownlint-test-scenarios.mjs.snapshot 的MD022-MD030.md 1快照中。

二、MD022:标题前后应留空行(blanks-around-headings)

样例第 1 行# Heading与紧随其后的Text之间没有空行,触发 MD022;末尾标记{MD022:1}断言错误发生在第 1 行。快照中对应的错误详情为Expected: 1; Actual: 0; Below,即标题下方应有 1 个空行、实际为 0,并给出fixInfo:在第 2 行插入一个换行。

规则参数

依据 doc/md022.md,MD022 支持三个参数:

参数类型默认值说明
include_front_matterbooleanfalse是否把 YAML front matter 计入标题前的空行检查
lines_aboveinteger \| integer[]1标题上方所需空行数(可为0,-1表示任意数量)
lines_belowinteger \| integer[]1标题下方所需空行数(可为0,-1表示任意数量)

当传入数组时,元素按下标依次对应标题层级 1~6(超出部分按默认值1处理);该规则只检查"至少"满足指定空行数,多余的空白行会被忽略。若把lines_above/lines_below配成大于 1 的数值,通常还需要同时调整 MD012/no-multiple-blanks,否则会引入"连续多个空行"的新违规。

源码实现

lib/md022.mjs 的实现值得关注:

  • 规则基于micromark解析器,遍历filterByTypesCached([ "atxHeading", "setextHeading" ])得到所有 ATX 与 Setext 标题 token;
  • getLinesFunction负责把lines_above/lines_below统一成"根据标题层级返回所需空行数"的函数:数组形式会先填充 6 个默认值再逐项覆盖,普通数值则直接Number(...)强转;
  • 检查上方时循环向上逐行调用isBlankLine统计实际空行数,少于预期则通过addErrorDetailIf报Above错误;检查下方时同理报Below错误,且fixInfo指向endLine + 1插入换行;
  • 标题出现在文档开头/末尾时,空行要求自然豁免(getLine越界返回空字符串)。

三、MD023:标题必须从行首开始(heading-start-left)

样例第 1 行# Heading的文本内容里包含 1 个前导空格(写为# Heading),触发 MD023;快照中该错误errorContext为" # Heading",errorRange为[1, 1],fixInfo为删除第 1 列的空格。

依据 doc/md023.md,该规则在标题被一个或多个空格缩进时触发,修复方式是把标题顶到行首。需要特别注意的是块引用场景:> # Heading in Block Quote中>符号本身就是行首内容,因此块引用内的标题不算违规——块引用符号"缩进"的是行的起点,而非标题本身。

lib/md023.mjs 的实现印证了这一点:它遍历atxHeading、setextHeading与linePrefixtoken,仅当某个linePrefix与其后紧邻的非linePrefixtoken 处于同一行时,才把该前缀的列区间作为错误范围并给出"删除前缀"的修复建议。linePrefix恰好就是用于表示行首空格/块引用符号占位的 token 类型。

四、MD024:标题内容不得重复(no-duplicate-heading)

样例第 1 行与第 4 行都是# Heading,内容相同,第 4 行的标题触发 MD024(重复的是后续那个),末尾{MD024:4}也断言错误在第 4 行。

依据 doc/md024.md,该规则只有一个参数:

参数类型默认值说明
siblings_onlybooleanfalse是否只检查"兄弟标题"之间的重复

默认false时,文档内任何层级出现相同文本的标题都会报错(如# Some text与## Some text);设为true后,父级不同的标题允许重复,这正是 CHANGELOG 中"每个版本下都有同名### Features小节"的常见写法。该规则没有自动修复(快照中fixInfo: null),因为"如何改写标题文本"无法由工具决定。

Rationale 来自锚点生成机制:不少 Markdown 解析器会依据标题文本生成锚点,重复标题会导致锚点冲突。

五、MD025:同一文档只能有一个顶级标题(single-h1 / single-title)

样例第 1 行# Heading与第 4 行# Heading构成两个 h1,第 4 行触发 MD025;{MD025:4}断言错误行号为 4,快照中规则别名同时列出single-title与single-h1。

依据 doc/md025.md,MD025 的判定前提是"文档首行即 h1"这一约定正在被使用,此时再出现第二个 h1 即违规,修复方式是把后续标题降级为 h2/h3 等。它支持两个参数:

参数类型默认值说明
front_matter_titlestring(RegExp 文本)^\s*title\s*[:=]用于在 front matter 中识别"标题"属性的正则
levelinteger1视为"顶级"的标题层级
  • 若 YAML front matter 中出现匹配title键的内容(博客场景常见),该规则会将其视为一个顶级标题,后续再出现顶级标题就报错;通过front_matter_title可以换用别的属性名,设成""则完全禁用 front matter 参与判定;
  • level参数用于"h1 由外部模板统一注入"的场景,例如把顶级层级调整为 h2。

六、MD026:标题末尾不得有标点(no-trailing-punctuation)

样例第 6 行## Another heading {MD026}.以句号结尾,触发 MD026;快照中错误详情为Punctuation: '.',errorRange为第 27 列起 1 个字符,fixInfo建议删除该句号。

依据 doc/md026.md,参数如下:

参数类型默认值说明
punctuationstring.,;:!。,;:!视为"标题末尾标点"的字符集合

几点值得注意:

  • 默认集合同时包含半角与全角标点(。,;:!等),适合中英文混排文档;
  • ?不在默认集合中,因为 FAQ 式标题以问号结尾非常常见;若想额外禁止?,可把punctuation改为".,;:?";
  • 把punctuation设为""等价于禁用该规则;
  • HTML 实体引用(如©、©、©)末尾的分号会被规则忽略,不会误判为标点。

七、MD027:块引用符号后不得有多个空格(no-multiple-space-blockquote)

样例第 8 行> Multiple spaces {MD027}中>后跟了两个空格,触发 MD027;第 15~19 行展示了四种不同的"嵌套/错位"写法,同样触发 MD027,且{MD027}直接标注在行内。快照中每个错误的errorRange都精确指向多出的空格列,fixInfo为删除该空格。

依据 doc/md027.md,参数如下:

参数类型默认值说明
list_itemsbooleantrue是否对块引用内的有序/无序列表项启用本规则

默认true时,列表项内部的块引用前缀空格同样受检;由于"块引用内列表的缩进意图"难以自动推断,当出现误报时可将list_items设为false关闭对列表项的检查。

lib/md027.mjs 的实现细节:遍历linePrefixtoken,仅当其前一兄弟 token 是blockQuotePrefix且自身不属于缩进代码块(codeIndented)时判为违规;list_items: false时还会额外排除"前/后兄弟是 list 类型"或"祖先中有 list 类型"的 token。这也解释了为什么样例第 19 行> > > ...这种"空格出现在第二个>之后"的写法也会被捕获——它对应的linePrefix同样紧跟在blockQuotePrefix之后。

八、MD028:块引用内不得出现空行分隔(no-blanks-blockquote)

样例第 9 行> Blank line above与其后第 11 行的空行构成"两个块引用块仅由空行分隔",触发 MD028;{MD028:9}断言错误行号为 9。该规则无参数、无自动修复(快照中fixInfo: null)。

依据 doc/md028.md,该规则在"两个块引用块之间只有空行"时触发,原因在于不同 Markdown 解析器对这种情况的处理不一致:有的会把两段合并为一个块引用,有的则视为两个独立块,导致渲染结果在不同平台上出现分歧。修复有两种途径:

  1. 若希望它们是两个独立的块引用,在中间插入正文段落或 HTML 注释,例如:
> This is a blockquote. This is paragraph text. > This is a second blockquote. <!-- This is an HTML comment --> > This is a third blockquote.
  1. 若它们本应是同一个块引用,则给空行也加上>前缀:
> This is a blockquote. > > This is the same blockquote.

九、MD029:有序列表编号前缀规则(ol-prefix)

样例第 12~13 行:

1. Alpha 3. Beta {MD029} {MD030}

第 13 行从1.跳到3.,触发 MD029;快照中错误详情为Expected: 2; Actual: 3; Style: 1/2/3,fixInfo建议把3改成2。同一行还因3.后跟了两个空格而同时触发 MD030(见下一节),这正是该测试文件"一行暴露多条规则"的设计意图。

依据 doc/md029.md,参数如下:

参数类型默认值可选值
stylestringone_or_orderedone/one_or_ordered/ordered/zero

四种样式的合法形态:

style合法编号示例
one1./1./1.(全部为 1)
ordered1./2./3.或0./1./2.(严格递增)
one_or_ordered上述三者均可
zero0./0./0.(全部为 0)

规则支持 0 前缀对齐写法(如08./09./10.),且默认行为是自动推断:当列表项多于一项且第二个编号不是1(或第一个编号是0)时按递增模式处理,否则按one处理——lib/md029.mjs 源码中的incrementing判定即为此逻辑。另外,若列表中间混入了一个缩进不当的代码块导致列表被"拆成两段",规则会把后一段视为新列表而误报,正确的修法是让代码块成为前一个列表项的子内容:

1. First list ```text Code block
  1. Still first list
## 十、MD030:列表标记后的空格数量(list-marker-space) 样例第 13 行 `3. Beta {MD029} {MD030}` 中列表标记 `3.` 后有两个空格,触发 MD030;快照中错误详情为 `Expected: 1; Actual: 2`,`fixInfo` 为把第 3 列起的 2 个空格替换成 1 个。 依据 [doc/md030.md](https://link.gitcode.com/i/d627bbf9f8cd2c6d4138aa3b412af768),该规则把"列表项内容是否只有单段"与"有序/无序"两个维度组合成四个参数: | 参数 | 类型 | 默认值 | 适用场景 | | --- | --- | --- | --- | | `ol_multi` | `integer` | `1` | 多段内容的有序列表项 | | `ol_single` | `integer` | `1` | 单段内容的有序列表项 | | `ul_multi` | `integer` | `1` | 多段内容的无序列表项 | | `ul_single` | `integer` | `1` | 单段内容的无序列表项 | [lib/md030.mjs](https://link.gitcode.com/i/7f704a0640e075a1b6185f580088bb1b) 通过判断"列表的行数是否恰好等于列表项个数"来区分单段/多段:行数等于项数说明每个列表项都只有一行,此时采用 `*_single` 参数,否则采用 `*_multi` 参数,并遍历 `listItemPrefixWhitespace` token 逐一核对实际空格数。以 Ciro Santilli 的 Markdown 风格指南为例:单段列表使用 1 个空格,多段内容(含子列表、代码块)时无序列表用 3 个空格、有序列表用 2 个空格: ```markdown * Foo Second paragraph * Bar
1. Foo Second paragraph 1. Bar

关于该规则与 Prettier 的兼容性说明,可参考 doc/Prettier.md。

十一、自动修复验证与快照

该测试文件最有价值的工程实践在于:所有"可修复"的违规都给出了精确的fixInfo,并且测试会验证修复后不再残留可修复错误。从 test/markdownlint-test-scenarios.mjs.snapshot 中MD022-MD030.md的fixed字段可以看到,执行applyFixes后样例文件被修正为:

  • MD022:第 1 行标题后、第 4 行标题后、第 6 行标题后都补上了空行;
  • MD023:删除了# Heading前导空格;
  • MD026:删除了标题末尾的句号;
  • MD027:块引用>后的多个空格全部收敛为 1 个;
  • MD029:3.被改写为2.;
  • MD030:列表标记后的双空格收敛为 1 个;
  • MD024、MD025、MD028 因无自动修复而保持不变,仍以{MD024:4}、{MD025:4}、{MD028:9}标注在文档末尾的"摘要行"中。

测试还断言:每个报错若有fixInfo,其规则必须被登记为可修复规则(fixableRuleNames校验);修复结果再次 lint 后不应再出现带fixInfo的错误。这套"内联标记 + 快照 + 二次 lint"的闭环,保证了 lib/ 下各规则实现(如 lib/md022.mjs、lib/md023.mjs、lib/md027.mjs、lib/md029.mjs、lib/md030.mjs)在可修复性声明与实际行为上始终一致。

十二、如何在你的文档中复现这套检查

如果你想在自己项目里验证或复现上述规则行为,可以直接以仓库中的 example/bad.md 或任意 test 目录下的.md文件为输入运行 markdownlint:

npm install npx markdownlint-cli2 test/MD022-MD030.md

也可以参考 example/standalone.mjs 用编程方式调用markdownlint/promise的lint接口并传入自定义配置。修改规则行为时,只需在配置对象中覆盖对应参数即可,例如:

{ "MD022": { "lines_above": 0, "lines_below": 1 }, "MD024": { "siblings_only": true }, "MD029": { "style": "ordered" }, "MD030": { "ul_multi": 3, "ol_multi": 2 } }

配置参数的完整 JSON Schema 定义位于 schema/markdownlint-config-schema.json,规则目录 doc/ 下每个规则都有独立文档(如 doc/md022.md~doc/md030.md),可供进一步查阅。通过对照 test/MD022-MD030.md 中的内联标记与 doc/ 中的规则说明,你就能快速掌握 markdownlint 对标题与列表类规则的精确判定标准,并据此打磨出风格统一、渲染稳定的 Markdown 文档。

  • 开发工具
  • 代码质量
  • Lint
  • 静态分析

【免费下载链接】markdownlint

A Node.js style checker and lint tool for Markdown/CommonMark files.

项目地址:https://gitcode.com/gh_mirrors/ma/markdownlint
点击查看免费下载

相关推荐

上一篇:Sass Embedded Protocol 演进全解析:从 1.0 到 3.2 的协议变更深度解读
下一篇:Polybar 系统托盘模块(internal/tray)完整指南:配置、原理与从旧版托盘的迁移

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

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

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

立即咨询