- 开发工具
- 代码质量
- Lint
- 静态分析
【免费下载链接】markdownlint
A Node.js style checker and lint tool for Markdown/CommonMark files.
本文以 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_matter | boolean | false | 是否把 YAML front matter 计入标题前的空行检查 |
lines_above | integer \| integer[] | 1 | 标题上方所需空行数(可为0,-1表示任意数量) |
lines_below | integer \| 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_only | boolean | false | 是否只检查"兄弟标题"之间的重复 |
默认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_title | string(RegExp 文本) | ^\s*title\s*[:=] | 用于在 front matter 中识别"标题"属性的正则 |
level | integer | 1 | 视为"顶级"的标题层级 |
- 若 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,参数如下:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
punctuation | string | .,;:!。,;:! | 视为"标题末尾标点"的字符集合 |
几点值得注意:
- 默认集合同时包含半角与全角标点(
。,;:!等),适合中英文混排文档; ?不在默认集合中,因为 FAQ 式标题以问号结尾非常常见;若想额外禁止?,可把punctuation改为".,;:?";- 把
punctuation设为""等价于禁用该规则; - HTML 实体引用(如
©、©、©)末尾的分号会被规则忽略,不会误判为标点。
七、MD027:块引用符号后不得有多个空格(no-multiple-space-blockquote)
样例第 8 行> Multiple spaces {MD027}中>后跟了两个空格,触发 MD027;第 15~19 行展示了四种不同的"嵌套/错位"写法,同样触发 MD027,且{MD027}直接标注在行内。快照中每个错误的errorRange都精确指向多出的空格列,fixInfo为删除该空格。
依据 doc/md027.md,参数如下:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
list_items | boolean | true | 是否对块引用内的有序/无序列表项启用本规则 |
默认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 解析器对这种情况的处理不一致:有的会把两段合并为一个块引用,有的则视为两个独立块,导致渲染结果在不同平台上出现分歧。修复有两种途径:
- 若希望它们是两个独立的块引用,在中间插入正文段落或 HTML 注释,例如:
> This is a blockquote. This is paragraph text. > This is a second blockquote. <!-- This is an HTML comment --> > This is a third blockquote.- 若它们本应是同一个块引用,则给空行也加上
>前缀:
> 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,参数如下:
| 参数 | 类型 | 默认值 | 可选值 |
|---|---|---|---|
style | string | one_or_ordered | one/one_or_ordered/ordered/zero |
四种样式的合法形态:
| style | 合法编号示例 |
|---|---|
one | 1./1./1.(全部为 1) |
ordered | 1./2./3.或0./1./2.(严格递增) |
one_or_ordered | 上述三者均可 |
zero | 0./0./0.(全部为 0) |
规则支持 0 前缀对齐写法(如08./09./10.),且默认行为是自动推断:当列表项多于一项且第二个编号不是1(或第一个编号是0)时按递增模式处理,否则按one处理——lib/md029.mjs 源码中的incrementing判定即为此逻辑。另外,若列表中间混入了一个缩进不当的代码块导致列表被"拆成两段",规则会把后一段视为新列表而误报,正确的修法是让代码块成为前一个列表项的子内容:
1. First list ```text Code block- 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 * Bar1. 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.
相关推荐
markdownlint MD030(list-marker-space)规则详解:列表标记后的空格数量检查与配置实战
markdownlint MD030(list marker space)规则详解:列表标记后的空格数量检查与配置实战 导读 MD030(别名 list mar
开发工具代码质量Lint静态分析markdownlint MD022(blanks-around-headings)规则详解:标题前后空行校验与配置实战
markdownlint MD022(blanks around headings)规则详解:标题前后空行校验与配置实战 本文基于 markdownlint 开
开发工具代码质量Lint静态分析NodeGui QGraphicsEffect 图形效果基类完全指南:API 参考、信号体系与实战应用
NodeGui QGraphicsEffect 图形效果基类完全指南:API 参考、信号体系与实战应用 QGraphicsEffect 是 NodeGui(基于
开发工具代码质量Lint静态分析
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考