- 开发工具
- 代码质量
- Lint
- 静态分析
【免费下载链接】markdownlint
A Node.js style checker and lint tool for Markdown/CommonMark files.
本文以 markdownlint 仓库中的真实测试用例 test/ordered-list-item-prefix-single-alternate.md 为切入点,完整剖析MD029(别名ol-prefix)有序列表编号前缀规则的触发条件、四种style配置的判定逻辑、自动修复机制与底层 micromark 实现。读完本文,你将掌握该规则的配置方法、源码运行原理,以及如何在本地用测试用例复现和验证检查结果。
从一行测试用例说起
整个测试文件只有三段内容,却精准地捕获了 MD029 最核心的检查场景:
# Ordered list examples 9. Item {MD029}去掉测试专用的{MD029}标记后,实际被 lint 的内容是:
# Ordered list examples 9. Item这里的{MD029}是 markdownlint 测试框架的"期望错误标记"。在 test/markdownlint-test-scenarios.mjs 中,测试代码用正则/\{(MD\d+)(?::([-+]?)(\d+))?\}/g扫描每个测试文件的行,把标记解释为"该行应产生一条 MD029 错误"(标记还支持{MD029:+2}、{MD029:5}这类显式行号偏移/绝对行号写法)。随后测试框架把 lint 得到的真实错误行号集合与标记推导的期望行号集合做deepEqual断言,任何多余或缺失的报告都会导致测试失败。
也就是说,这个文件断言了一个事实:在默认配置下,一个以9.开头、只有单个条目的有序列表,会被 MD029 判定为违规。
MD029 / ol-prefix 规则档案
从规则实现 lib/md029.mjs 的导出定义可以看到规则的元信息:
export default { "names": [ "MD029", "ol-prefix" ], "description": "Ordered list item prefix", "tags": [ "ol" ], "parser": "micromark", "function": function MD029(params, onError) { ... } };- 规则名:
MD029,别名ol-prefix - 标签:
ol(可在配置中用标签批量开关) - 解析器:
micromark(规则基于 token 流工作,而非字符串正则) - 可修复:是。
MD029被列在 lib/constants.mjs 的fixableRuleNames数组(第 8 行),意味着markdownlint-cli2 --fix或代码中的applyFixes可以自动改写违规前缀。
规则的官方参数只有一个,完整定义在 doc/md029.md 和 doc/Rules.md:
| 参数 | 类型 | 默认值 | 可选值 |
|---|---|---|---|
style | string | one_or_ordered | one/one_or_ordered/ordered/zero |
类型签名同样可以从 lib/configuration-strict.d.ts 中的"MD029" | "ol-prefix"配置项定义得到印证:style?: "one" | "ordered" | "one_or_ordered" | "zero"。JSON Schema 文件 schema/markdownlint-config-schema.json 也把默认值声明为one_or_ordered。
规则触发条件
规则文档对触发条件描述得很明确:有序列表不以1.开头,或编号前缀不按数值递增(具体取决于配置的 style)。另外,以0.开头的较少见模式(首项或全部为 0)同样受支持。
四种 style 的判定示例
style的四种取值对应四种不同的合规形态(以下示例全部来自 doc/md029.md):
one样式——所有条目前缀都必须是1.:
1. Do this. 1. Do that. 1. Done.ordered样式——前缀必须连续递增,可从1.开始:
1. Do this. 2. Do that. 3. Done.也可以从0.开始:
0. Do this. 1. Do that. 2. Done.one_or_ordered样式(默认)——上面三种写法全部合法:全1.、1/2/3递增、0/1/2递增都接受。
zero样式——所有条目前缀都必须是0.:
0. Do this. 0. Do that. 0. Done.对所有样式都不合法的反例:
1. Do this. 3. Done.这里3.既不等于1,也没有相对上一项的1递增关系,无论采用哪种 style 都会报告。
规则还支持为统一缩进使用 0 前缀编号的写法(例如 08/09/10/11),这类"带前导零对齐"的列表同样在ordered/one_or_ordered语义下工作。
源码视角:single-alternate 为何被判定违规
回到9. Item这个用例。规则实现 lib/md029.mjs 的处理分三步:
第一步,提取 token。规则通过filterByTypesCached([ "listOrdered" ])(来自 lib/cache.mjs 的带缓存过滤)遍历所有有序列表 token,再用getDescendantsByType(listOrdered, [ "listItemPrefix" ])获取每个列表项的listItemPrefix前缀 token。getOrderedListItemValue进一步从前缀中取出listItemValue子 token,得到起始列号startColumn和数值文本。
第二步,判定有效样式。源码中的核心逻辑是:
let expected = 1; let incrementing = false; // 列表项 >= 2 时,检查是否为递增模式 1/2/3 或 0/1/2 if (listItemPrefixes.length >= 2) { const first = getOrderedListItemValue(listItemPrefixes[0]); const second = getOrderedListItemValue(listItemPrefixes[1]); if ((second.value !== 1) || (first.value === 0)) { incrementing = true; if (first.value === 0) { expected = 0; } } } // 默认样式下:递增列表按 ordered 处理,否则按 one 处理 const listStyle = listStyles.includes(style) ? style : (incrementing ? "ordered" : "one"); if (listStyle === "zero") { expected = 0; } else if (listStyle === "one") { expected = 1; }9. Item所在列表只有一个listItemPrefix,listItemPrefixes.length >= 2不成立,incrementing保持false;又因为未显式配置style,listStyles.includes(style)为假,于是有效样式回落到"one",期望值expected固定为1。
第三步,逐项比对并生成修复信息。对唯一的9.前缀,actual = 9,走addErrorDetailIf(实现在 helpers/helpers.cjs):expected(1) !== actual(9),于是报告错误,并附带fixInfo:
const fixInfo = { "editColumn": orderedListItemValue.column, // 从第 1 列开始改 "deleteCount": orderedListItemValue.value.toString().length, // 删掉 1 个字符("9") "insertText": expected.toString() // 写入 "1" };这正是"可修复规则"的落地方式:MD029 知道如何把任意违规前缀改写为期望值,且ordered样式下每处理一项expected会自增,从而支持"重排编号"式修复。
测试快照中的实证
同一用例的预期输出固化在 test/markdownlint-test-scenarios.mjs.snapshot(ordered-list-item-prefix-single-alternate.md一节)中:
{ "lineNumber": 3, "ruleNames": [ "MD029", "ol-prefix" ], "ruleDescription": "Ordered list item prefix", "errorDetail": "Expected: 1; Actual: 9; Style: 1/1/1", "errorRange": [ 1, 3 ], "fixInfo": { "editColumn": 1, "deleteCount": 1, "insertText": "1" }, "severity": "error" }注意三处细节,它们直接回应了前面的源码分析:
errorDetail为Expected: 1; Actual: 9; Style: 1/1/1——Style: 1/1/1来自源码顶部listStyleExamples映射(one对应"1/1/1"),证明本次判定落入了one样式;errorRange: [1, 3]是前缀 token 在行内的列区间(从第 1 列起、长度 3);fixInfo.deleteCount: 1说明只删除数字字符9本身,.与空格保持不变。
快照还记录了修复后的整篇内容:# Ordered list examples\n\n1. Item {MD029}\n——9. Item被自动改写为1. Item。而markdownlint-test-scenarios.mjs在断言快照后还会对修复结果再次执行 lint,确认"修复后不再残留任何可修复错误",从机制上保证 fixInfo 的正确性。
相邻测试族:其他 style 与边界场景
single-alternate并非孤例,仓库里围绕 MD029 还有一整族针对性测试文件,可交叉验证不同分支:
- test/ordered-list-item-prefix-one.md:文件尾部通过内联指令
<!-- markdownlint-configure-file { "ol-prefix": { "style": "one" } } -->将 style 切到one。此时全1.列表合法,而1/2/3递增、0/1/2递增、嵌套子列表内的各种混合写法都会被逐行标记{MD029}。 - test/ordered-list-item-prefix-one_or_ordered.md:默认样式下,
1/2/3与0/1/2都合法,0.开头但第二项为1.的列表可合法延续;破坏递增模式的1.→2.→1.(回落)只在第三项报错。 - test/ordered-list-item-prefix-zero.md:
style: "zero"下全0.合法,出现1./2./3.一律违规。 - test/ordered-list-item-prefix-double-digits-ordered.md:验证两位数前缀场景——
ordered样式下1..11连续递增合法,而1. → 10. → 9.、11. → 10.、12. → 1.、0. → 10.等跳变全部违规。 - test/ordered-list-item-prefix-sublists-ordered.md:覆盖嵌套子列表在
ordered样式下的编号行为。
这套"每个 style 一个文件 +{MD029}标记 + snapshot 快照"的测试设计,让规则的每一个判定分支都有可回归的实证。
已知边界:代码块会"切断"列表
规则文档特别提醒了一个容易踩坑的场景:错误缩进的代码块(或类似元素)夹在两个列表项之间时,会把一个列表"断成"两个,从而触发误报。例如:
1. First list ```text Code block- Second list
`1. First list` 与 `1. Second list` 被中间的代码块分隔为两个独立的列表,第二个列表以 `1.` 开头本应合法,但视觉上读者会认为这是同一个列表的延续。修复方式是缩进代码块,使其成为前一个列表项的内容: ```text 1. First list ```text Code block- Still first list
缩进后两段内容属于同一个列表项,编号 `1.` → `2.` 的递增关系也顺理成章。规则文档用 `<!-- markdownlint-disable code-fence-style -->` 包裹了这段示例(因为示例中混用了反引号与波浪号围栏),展示如何用内联指令临时豁免无关规则的干扰。 ## 配置方式与实战建议 MD029 的 `style` 参数有三种配置途径: **1. 项目级配置文件**(`.markdownlint.json` 或 `package.json` 的 `markdownlintConfig` 字段): ```json { "MD029": { "style": "ordered" } }推荐在项目根目录采用ordered,它强制 Markdown 文档里的有序列表编号真实连续,适合技术文档中需要引用条目编号的场景。
2. 文件内联配置(测试文件即用此法):
<!-- markdownlint-configure-file { "ol-prefix": { "style": "zero" } } -->使用规则别名ol-prefix或规则名MD029均可。
3. 自定义规则/编程调用:通过lint的config参数传入同一结构。
几点选择建议:
- 追求最大兼容性:保持默认
one_or_ordered。它同时接受全1.和递增编号,且对单个列表项只要求从1.或0.开始——这正是single-alternate用例中9.被报告的原因(9.既不是1也构不成递增序列的起点)。 - 追求统一观感:文档内容较稳定时用
ordered,配合--fix可自动把1. 1. 1.重排为1. 2. 3.。 - 对齐列宽:需要
08./09./10.这类 0 前缀对齐编号时,ordered/one_or_ordered都支持,但规则要求序列整体以0.起始才能建立 0 前缀语境。 - 特殊排版:
zero常用于幻灯片式列表,所有条目都以0.开头。
本地复现与验证方法
在仓库根目录执行:
node test/harness.mjs test/ordered-list-item-prefix-single-alternate.mdtest/harness.mjs 会先打印该文件的 micromark token 树(可直观看到listOrdered→listItemPrefix→listItemValue的嵌套结构),再调用lint输出完整错误对象,包括errorDetail、errorRange与fixInfo。
跑全量测试验证规则行为:
node --test test/markdownlint-test-scenarios.mjs它会扫描test目录下所有.md测试文件,校验{MD029}标记与真实错误逐一对应,并核对 snapshot 与applyFixes的修复结果。如果修改或新增了 MD029 的测试用例,快照会通过node:test的快照更新机制自动刷新,形成"标记断言 + 快照固定 + 修复闭环"的三重保障。
小结
test/ordered-list-item-prefix-single-alternate.md用最简的一行9. Item,完整验证了 MD029 在默认样式下的核心行为:单条目列表必须以1.(或0.)起始。结合 lib/md029.mjs 的实现可以看到,规则真正的判定逻辑远比"必须是 1"复杂——它基于 micromark token 流动态推断one/ordered两种有效样式,支持 0 前缀递增与两位数编号,并通过fixInfo提供可靠的一键修复;而 doc/md029.md 与一整套ordered-list-item-prefix-*.md测试族,则为每种配置和边界场景提供了可复现、可回归的权威依据。
- 开发工具
- 代码质量
- Lint
- 静态分析
【免费下载链接】markdownlint
A Node.js style checker and lint tool for Markdown/CommonMark files.
相关推荐
CCX 前端工程 ESLint 配置全解:Vue 3 + TypeScript 的 Flat Config 实践
CCX 前端工程 ESLint 配置全解:Vue 3 + TypeScript 的 Flat Config 实践 CCX(Claude / Codex / Ge
开发工具代码质量Lint静态分析markdownlint MD029(ol-prefix)有序列表编号风格检查规则详解
markdownlint MD029(ol prefix)有序列表编号风格检查规则详解 MD029 是 markdownlint 提供的"有序列表项编号前缀"(
开发工具代码质量Lint静态分析markdownlint MD029 有序列表编号前缀规则(ol-prefix)深度解析与实战指南
markdownlint MD029 有序列表编号前缀规则(ol prefix)深度解析与实战指南 导读 MD029 是 markdownlint 中负责校验
开发工具代码质量Lint静态分析
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考