markdownlint 在引用块内缩进列表的完整实战指南:以 lists-in-blockquote-start-indented-no-list-items 测试场景为例

原创2026-10-15 09:53:051,462 阅读
文章标签:开发工具Lint静态分析代码质量

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。该测试脚本的逻辑是:

  1. 读取 ./test 目录下每个 .md 文件;
  2. 调用 lint({ "files": [ file ] }) 对该文件执行真实的规则检查(来自 markdownlint/promise);
  3. 用文件中形如 {MD027}、{MD007} 的标记(marker)声明"该行应命中某条规则",与 lint 实际结果逐一比对;
  4. 对命中结果执行 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 检查

与仓库中同主题的另外两份样例对比可以更清晰地看出差异:

由此可以推断:这份文件在仓库中承担的是"起始缩进风格与 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 时放行列表项的引用符后多空格

实战要点汇总:

  1. 起始缩进必须在配置中显式声明:仅把列表写成"带 2 空格"并不够,需要 ul-indent.start_indented: true(文件级或 options.config),否则 MD007 会按顶格期望报错;
  2. 引用块内的起始缩进必然触发 MD027:要么用 markdownlint-disable MD027 / markdownlint-restore 局部放行,要么配置 list_items: false 文件级放行;
  3. 配置注释位置不影响作用范围:markdownlint-configure-file 对整个文件生效,可以放心放在文件末尾;
  4. markdownlint-disable / markdownlint-restore 只作用于中间区域:因为初始配置默认已被捕获(等价于隐式 markdownlint-capture),恢复后即可回到文档开头的配置状态,见 README 内联配置说明;
  5. 用快照测试守住行为边界:仓库的 test/markdownlint-test-scenarios.mjs + 快照文件把"零错误 + 修复结果稳定"固化为回归测试,修改规则实现时若破坏该场景会立即失败。

十、扩展阅读:仓库中的相关证据

如果需要在自己的项目中复现这份样例,可先执行 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 中声明)即可获得与本指南一致的验证结果。

登录后查看全文
markdownlint