markdownlint 在引用块内缩进列表的完整实战指南:以 lists-in-blockquote-start-indented-no-list-items 测试场景为例
markdownlint 在引用块内缩进列表的完整实战指南:以 lists-in-blockquote-start-indented-no-list-items 测试场景为例
本指南以 markdownlint 仓库中的真实测试用例 lists-in-blockquote-start-indented-no-list-items.md 为蓝本,系统讲解如何在 Markdown 文档中让引用块(blockquote)内的无序列表与有序列表采用"起始缩进"(start-indented)排版风格,并保持通过 lint 检查。读完本文,你将掌握 ul-indent(MD007)与 no-multiple-space-blockquote(MD027)两条规则在引用块内的缩进计算规则、对应的三个配置参数、内联配置指令(markdownlint-disable / markdownlint-restore / markdownlint-configure-file)的实际用法,以及如何用快照测试验证 lint 结果与自动修复输出。
一、场景定位:这份测试文件在仓库中的角色
在 markdownlint 仓库中,所有以 .md 结尾的测试文件都会被 markdownlint-test-scenarios.mjs 自动收集并逐一执行 lint。该测试脚本的逻辑是:
- 读取
./test目录下每个.md文件; - 调用
lint({ "files": [ file ] })对该文件执行真实的规则检查(来自markdownlint/promise); - 用文件中形如
{MD027}、{MD007}的标记(marker)声明"该行应命中某条规则",与 lint 实际结果逐一比对; - 对命中结果执行
applyFixes自动修复,将修复后的文本与markdownlint-test-scenarios.mjs.snapshot快照比对,再对修复结果二次 lint,确保"所有可修复错误均已修复"。
因此,本文研究的 test/lists-in-blockquote-start-indented-no-list-items.md 不仅是一份测试样例,更是一份"必须零错误通过所有默认规则 + 引用块内列表采用起始缩进"的权威样例,其期望输出完整记录在快照文件 markdownlint-test-scenarios.mjs.snapshot 中(对应 lists-in-blockquote-start-indented-no-list-items.md 1 这个 snapshot 用例,期望 "errors": [],即零违规)。
二、文件整体结构与核心思路
测试文件的主体结构可以拆成三段对照布局:
| 区块 | 内容 | 说明 |
|---|---|---|
| 第一段(1–24 行) | 正文列表 | 顶层无序/有序列表使用 2 空格起始缩进,正文区域外的 4 空格缩进为代码块 |
| 第二段(26–49 行) | 引用块内列表 | 引用块内同样使用起始缩进,但被 markdownlint-disable / markdownlint-restore 包裹,用于绕开 MD027 对"引用符后多空格"的默认报错 |
| 第三段(74–104 行) | 引用块内有序列表 + 文件级配置 | 通过 markdownlint-configure-file 在文件内直接配置 ul-indent 的 start_indented: true,并关闭 MD027 的 list_items 检查 |
与仓库中同主题的另外两份样例对比可以更清晰地看出差异:
- lists-in-blockquote-normal-indent.md:列表顶格(0 缩进),引用块内列表标记符后只跟一个空格,全部规则零违规;
- lists-in-blockquote-start-indented.md:列表采用 2 空格起始缩进,但在引用块内出现了
{MD027}标记——即默认配置下"引用符后多个空格 + 列表项"会触发 MD027; - lists-in-blockquote-start-indented-no-list-items.md:本文主角,在上一份样例的基础上,通过禁用指令与文件级配置,把
{MD027}全部消除,实现"起始缩进 + 零违规"。
由此可以推断:这份文件在仓库中承担的是"起始缩进风格与 MD027 冲突的最终解决方案样例",即在追求起始缩进排版的同时,如何干净地处理引用块内多空格的问题。
三、起始缩进下正文列表的正确写法(文件 1–24 行)
文件开头(1–24 行)展示了在文档正文中,如何编写一个"起始缩进(start indented)"的无序列表并让它通过全部规则:
# Lists in Blockquote Start Indented
Text
+ Item
+ Item
more
item
+ Item
+ Item
+ Item
more
item
+ Item
+ Item
+ Item
+ Item
+ Item
Text
Code
Text
要点如下:
- 第一层列表项从第 3 列开始(前面有两个空格),而不是顶格从第 1 列开始;
- 嵌套层级每深一层增加 2 个空格:第二层为 4 空格、第三层为 6 空格,以此类推;
- 顶层列表项下方的续行内容(如
more、item)与列表标记后的内容对齐; - 正文中 4 空格缩进的内容是代码块(
Code),与列表缩进互不干扰。
这种"第一层也缩进"的风格之所以能通过默认规则,是因为 MD007(ul-indent)提供了 start_indented 参数。从 lib/md007.mjs 的源码可以看到其期望缩进的计算公式:
expectedIndent = baseIndent
+ (startIndented ? startIndent : 0)
+ (nesting * indent)
其中:
baseIndent:默认0,仅在gfmFootnoteDefinition(GFM 脚注定义)内为4;startIndent:start_indented生效时第一层的缩进宽度,默认2;indent:每加深一层列表额外增加的缩进宽度,默认2;nesting:当前列表的嵌套深度。
当 start_indented 为 false(默认值)时,第一层期望缩进为 0;当设置为 true 时,第一层期望缩进为 startIndent(默认 2),于是"顶层列表带两个前导空格"的写法就完全合法。此外 MD007 规则本身定义在 doc/md007.md,其参数说明为:
indent:每层缩进空格数(整数,默认2);start_indent:start_indented开启时第一层的缩进空格数(整数,默认2);start_indented:是否对列表第一层进行缩进(布尔值,默认false)。
注意:该规则仅当父级列表全部为无序列表时才生效;若父级中有有序列表,额外缩进会干扰规则判断,因此会跳过检测。
四、有序列表的起始缩进写法(文件 53–70 行)
紧随其后的 53–70 行展示了同样的起始缩进风格应用于有序列表:
Text
1. Item
1. Item
more
item
1. Item
1. Item
1. Item
more
item
1. Item
1. Item
1. Item
1. Item
1. Item
Text
Code
其缩进规律与无序列表完全一致:第一层从第 4 列开始( 1.,即两个空格 + 数字标记),第二层为 6 空格、第三层为 8 空格。由于 MD007 只作用于无序列表,有序列表主要依赖 MD030(列表标记后空格数)等规则,这里的写法在快照期望中同样为零违规。
五、引用块内列表的两难:MD027 与起始缩进的冲突
5.1 冲突的根源
当列表进入引用块后,写法变成(见文件 74–93 行):
> Text
>
> 1. Item
> 1. Item
> more
> item
> ...
这里为了在引用块内实现"起始缩进 + 嵌套递进",> 之后必须留出 2~3 个空格再写列表标记。然而 MD027(no-multiple-space-blockquote) 的默认行为是:引用符 > 之后出现多个空格即报错。这正是 lists-in-blockquote-start-indented.md 中 > + Item {MD027} 标记的来源——它在 30、31、34、43 行(无序)以及 76、77、80、89 行(有序)共标注了 8 处 MD027 违规。
从 lib/md027.mjs 源码看,该规则遍历 linePrefix token,当其前一个兄弟节点是 blockQuotePrefix(引用块前缀)且不属于 codeIndented(缩进代码块)时,就检查并报告"引用符后多个空格";同时它带有一个关键参数 list_items(布尔值,默认 true),决定是否对列表项也执行该检查。
5.2 两种解法
- 方式 A(局部禁用):用
markdownlint-disable/markdownlint-restore把引用块段落整体包裹,直接跳过该段的所有检查(本文文件 26–49 行采用的就是这种方案,禁用目标为ul-indent); - 方式 B(文件级配置):通过
markdownlint-configure-file将no-multiple-space-blockquote的list_items设为false,即"只对普通文本检查引用符后多空格,放行列表项";同时开启ul-indent的start_indented: true(本文文件末尾 97–104 行采用的就是这种方案)。
六、用 markdownlint-disable / markdownlint-restore 临时放行起始缩进列表
文件 26–49 行演示了"局部禁用 + 恢复"的完整写法:
<!-- markdownlint-disable ul-indent -->
> Text
>
> + Item
> + Item
> more
> item
> + Item
> + Item
> + Item
> more
> item
> + Item
> + Item
> + Item
> + Item
> + Item
>
> Text
>
> Code
<!-- markdownlint-restore -->
这里选用 ul-indent(MD007 的别名)作为禁用目标,是因为引用块内的"起始缩进"写法本身并不会触发 MD007 的默认行为,真正会报错的是 MD027;禁用指令的语义是"这部分内容我不希望被 lint 规则干涉"。markdownlint-restore 会把规则配置恢复到该区域之前的状态——仓库 README 在 内联配置说明 中明确:文档初始配置默认已被捕获,等价于每个文档都以 markdownlint-capture 开头,因此 disable ... restore 模式可以安全地只影响中间区域。
需要注意:若希望 disable/restore 不影响其他规则(如 MD032、MD030 对列表周围空行、标记后空格的检查),可以只按名称精确禁用,例如 <!-- markdownlint-disable MD027 -->,而不是笼统地 <!-- markdownlint-disable -->。
七、用 markdownlint-configure-file 实现文件级配置(文件 97–104 行)
文件末尾的 97–104 行是整个场景的"配置点睛":
<!-- markdownlint-configure-file {
"no-multiple-space-blockquote": {
"list_items": false
},
"ul-indent": {
"start_indented": true
}
} -->
这段 JSON 的含义是:
no-multiple-space-blockquote.list_items: false—— 让 MD027 不再检查列表项。源码中对应逻辑为const includeListItems = (listItems === undefined) ? true : !!listItems;,当配置为false时,规则对"位于列表内、或父级是列表"的linePrefix一律放行,从而允许> 1. Item、> + Item这种多空格写法;ul-indent.start_indented: true—— 显式声明本文档采用"起始缩进"风格,第一层列表缩进 2 空格(start_indent默认也是 2),保证 MD007 认可顶层带缩进的列表。
这正是 README 中 markdownlint-configure-file 语法 的标准用法:该注释对整个文件生效,与注释所在位置无关;多个此类注释按从上到下顺序应用。若需用多行 JSON 排版,也支持多行注释形式。默认按 JSON 解析,也可通过 options.configParsers 扩展其他格式。
八、快照验证:零错误与自动修复结果
仓库通过快照测试固化本场景的期望结果。在 markdownlint-test-scenarios.mjs.snapshot 的 lists-in-blockquote-start-indented-no-list-items.md 1 用例中,期望值为:
{
"errors": [],
"fixed": "# Lists in Blockquote Start Indented\n\nText\n\n + Item\n ...(与输入一致,未发生任何修改)...\n<!-- markdownlint-configure-file {\n \"no-multiple-space-blockquote\": {\n \"list_items\": false\n },\n \"ul-indent\": {\n \"start_indented\": true\n }\n} -->\n"
}
关键结论:
errors为空数组:说明整份文件在默认规则 + 文件级配置的组合下没有任何违规;fixed与原始内容一致:由于没有任何fixInfo,applyFixes不做任何改动,说明这份样例本身已经是"可修复性"意义上的完美状态;- 对比之下,lists-in-blockquote-start-indented.md 的快照则包含 8 条 MD027 错误(
errorRange: [3, 2],即第 3 列起删除 2 个空格),修复后> + Item会变成> + Item——这正是"未配置时起始缩进会被自动修复掉"的直接证据。
测试脚本 markdownlint-test-scenarios.mjs 还额外保证:修复后的文本二次 lint 时,所有带 fixInfo 的可修复错误必须清零,否则测试失败。这保证了仓库中每一份测试样例的"可修复闭环"。
九、配置速查表与实战要点
| 配置项 | 规则 | 类型/默认值 | 在本场景中的作用 |
|---|---|---|---|
ul-indent.indent |
MD007 | integer,默认 2 |
每层嵌套增加的缩进 |
ul-indent.start_indent |
MD007 | integer,默认 2 |
起始缩进时第一层的缩进宽度 |
ul-indent.start_indented |
MD007 | boolean,默认 false |
是否允许第一层列表缩进 |
no-multiple-space-blockquote.list_items |
MD027 | boolean,默认 true |
false 时放行列表项的引用符后多空格 |
实战要点汇总:
- 起始缩进必须在配置中显式声明:仅把列表写成"带 2 空格"并不够,需要
ul-indent.start_indented: true(文件级或options.config),否则 MD007 会按顶格期望报错; - 引用块内的起始缩进必然触发 MD027:要么用
markdownlint-disable MD027/markdownlint-restore局部放行,要么配置list_items: false文件级放行; - 配置注释位置不影响作用范围:
markdownlint-configure-file对整个文件生效,可以放心放在文件末尾; markdownlint-disable/markdownlint-restore只作用于中间区域:因为初始配置默认已被捕获(等价于隐式markdownlint-capture),恢复后即可回到文档开头的配置状态,见 README 内联配置说明;- 用快照测试守住行为边界:仓库的
test/markdownlint-test-scenarios.mjs+ 快照文件把"零错误 + 修复结果稳定"固化为回归测试,修改规则实现时若破坏该场景会立即失败。
十、扩展阅读:仓库中的相关证据
- 规则实现:lib/md007.mjs(
ul-indent缩进计算与blockQuotePrefix列偏移处理)、lib/md027.mjs(list_items参数过滤逻辑); - 规则文档:doc/md007.md、doc/md027.md;
- 同主题样例:test/lists-in-blockquote-normal-indent.md、test/lists-in-blockquote-start-indented.md、test/lists-in-blockquote.md;
- 测试驱动:test/markdownlint-test-scenarios.mjs 与快照 test/markdownlint-test-scenarios.mjs.snapshot;
- 内联指令与配置语法:README.md 的 Configuration 章节;
- 预置风格配置:如 style/cirosantilli.json 中
MD007.indent: 4(该风格指南采用 4 空格缩进)、style/relaxed.json 中直接关闭ul-indent、style/prettier.json 中同时关闭no-multiple-space-blockquote与ul-indent,可对照不同团队风格对这两条规则的取舍。
如果需要在自己的项目中复现这份样例,可先执行 npm install markdownlint --save-dev,再参考 README 的 Usage 示例 用 lint({ "files": <a href="https://link.gitcode.com/i/2e73c918274a48e16c5fb49f3cccf886" target="_blank">...] })(或 markdownlint/async、markdownlint/sync、markdownlint/promise 三种 API 形态)运行检查,配合 npm test(仓库测试脚本在 [package.json 中声明)即可获得与本指南一致的验证结果。