news 2026/10/16 2:16:48

markdownlint 有序列表前缀检查实战:从 MD029 测试用例读懂 ol-prefix 规则

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
markdownlint 有序列表前缀检查实战:从 MD029 测试用例读懂 ol-prefix 规则
  • 开发工具
  • 代码质量
  • Lint
  • 静态分析

【免费下载链接】markdownlint

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

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

本文以 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:

参数类型默认值可选值
stylestringone_or_orderedone/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
  1. Second list
`1. First list` 与 `1. Second list` 被中间的代码块分隔为两个独立的列表,第二个列表以 `1.` 开头本应合法,但视觉上读者会认为这是同一个列表的延续。修复方式是缩进代码块,使其成为前一个列表项的内容: ```text 1. First list ```text Code block
  1. 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.md

test/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.

项目地址:https://gitcode.com/gh_mirrors/ma/markdownlint
点击查看免费下载
上一篇:DoL-Lyra整合包技术评测:构建高效游戏体验的整合方案
下一篇:Illustrator智能填充脚本:提升矢量设计效率80%的自动化解决方案

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 2:15:29

RoboMaster装甲板识别:基于LED动态特征的轻量级实时方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 2:13:51

MTK 9.0平台gsensor调试:时序校准与四重节奏实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 2:13:27

时间统计法:不是效率手册,而是诚实地面对一生

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华