markdownlint 表格空行规则 MD058/blanks-around-tables 全解析:原理、修复与边界场景
markdownlint 表格空行规则 MD058/blanks-around-tables 全解析:原理、修复与边界场景
markdownlint 是 Node.js 生态中针对 Markdown/CommonMark 文件的风格检查器与 lint 工具,本文聚焦其表格规则族中的核心成员 MD058(blanks-around-tables,表格前后必须留空行)。本指南以仓库中的场景测试文件 test/blanks-around-tables.md 为骨架,结合规则源码 lib/md058.mjs 与官方规则文档 doc/md058.md,系统讲解 MD058 的触发条件、自动修复行为、与 MD055/MD056 的联动,以及表格嵌入引用块、列表、指令块等复杂场景下的表现。读完本文,你将能精确预判任何文档中 MD058 的报错位置与修复结果,并学会在 CI 与自定义规则中正确使用这一规则。
一、规则总览:定位、别名与修复能力
在 markdownlint 中,每条规则都有一个编号、一个描述性别名和一组标签。MD058 的完整元信息如下(见 lib/md058.mjs):
| 属性 | 值 |
|---|---|
| 规则编号 | MD058 |
| 别名(Aliases) | blanks-around-tables |
| 描述 | Tables should be surrounded by blank lines |
| 标签(Tags) | table |
| 解析器(parser) | micromark |
| 是否可自动修复 | 是(部分违规可由工具修复) |
规则文档 doc/md058.md 中明确给出了官方定位:当表格的前方或后方缺少空行时,该规则被触发。其设计理由是:除了审美因素外,一些解析器无法正确处理前后没有空行的表格——表格与相邻段落文本黏连会导致解析歧义。
需要特别说明的是,MD058 属于"可修复规则"(fixable rule),它出现在 lib/constants.mjs 的 fixableRuleNames 列表中("MD058" 赫然在列)。这意味着使用 markdownlint 的自动修复能力(如 applyFixes)时,MD058 的违规可以被自动修正。
在 lib/configuration-strict.d.ts 中,MD058 与别名 blanks-around-tables 均作为可配置项出现,可配置为 boolean、"error"、"warning",或带 enabled / severity 字段的对象,与其他规则的配置形态完全一致。
二、触发条件:什么情况下会报 MD058
从 lib/md058.mjs 的源码可以看到,规则的检查逻辑非常直观:
- 通过
filterByTypesCached([ "table" ])获取文档中所有的table类型 micromark token; - 对每个表格,取
table.startLine作为首行行号,检查其上一行lines[firstLineNumber - 2]是否为空白行(isBlankLine); - 取
table.endLine作为末行行号,检查其下一行lines[lastLineNumber]是否为空白行。
只要任一侧不是空行,就会产生一条 MD058 错误。其中"空行"的判断由辅助函数 isBlankLine 完成(见 helpers/helpers.cjs),该函数不仅把"完全空白"的行视为空行,还认为仅含空白字符的行、以及仅含 HTML 注释的行都属于空行——这是 MD058 的一个实用细节:表格前一行即使写着 <!-- 注释 -->,也不会被判定为违规。
三、官方文档中的最小示例与正确写法
3.1 违规示例
以下写法会同时触发"上方无空行"与"下方无空行"两类错误(示例源自 doc/md058.md):
Some text
| Header | Header |
| ------ | ------ |
| Cell | Cell |
> Blockquote
3.2 正确写法
修复方式是在表格前后各加一个空行(示例同样源自 doc/md058.md):
Some text
| Header | Header |
| ------ | ------ |
| Cell | Cell |
> Blockquote
官方文档还补充了一条边界规则:文档开头的表格无需上方空行,文档结尾的表格无需下方空行——因为文档首行之前与末行之后本来就没有内容。
四、关键边界:紧跟表格的文字属于表格本身
MD058 最容易被误解的地方在于:紧跟在表格之后、且没有被空行隔开的文字,按规范属于表格的一部分,不会触发 MD058。官方文档 doc/md058.md 给出了这个示例:
| Header | Header |
| ------ | ------ |
| Cell | Cell |
This text is part of the table and the next line is blank
Some text
这里 This text is part of the table... 这一行没有触发任何"表格下方缺空行"的报错,因为 GFM(GitHub Flavored Markdown)解析器把它当作表格的延续内容。一旦它后面出现真正的空行、再接其他内容,表格就"结束"了。
这个边界特性在仓库的场景测试文件 test/blanks-around-tables.md 中体现得淋漓尽致——其中第 12~14 行的表格后紧跟一行 Part of table {MD055} {MD056},这一行本身不报 MD058,但它会触发 MD055 与 MD056 的报错(下文详述),这正是"表格延续文本"的典型示例。
五、场景测试文件逐段解读:从普通文本到引用块、列表与指令
关联文档 test/blanks-around-tables.md 是仓库场景测试(scenario test)的输入文件,全文以 {MD058} 等花括号标记标注了每条规则期望的报错行。这类文件的测试驱动逻辑位于 test/markdownlint-test-scenarios.mjs:先对文件执行 lint,再用正则 /\{(MD\d+)(?::([-+]?)(\d+))?\}/g 解析花括号标记得到"期望报错行号集合",最后与 lint 实际结果比对,并额外校验 applyFixes 修复后不再残留可修复错误。
下面按行号逐段解读该文件的布局与意图。
5.1 文档开头:合规基线(第 1~6 行)
# Blanks Around Tables
| Header | Header |
| ------ | ------ |
| Cell | Cell |
文档以一个一级标题开头,随后是文档中的第一个表格。由于该表格位于文档开头(紧接空行),上方无需空行,且其后紧跟一个空行,因此这里没有任何报错,是全文件的合规基线。
5.2 文本后紧跟表格:上方缺空行(第 7~8 行)
Text
| Header | {MD058} |
| ------ | ------- |
| Cell | Cell |
第 7 行的 Text 与第 8 行的表格之间没有空行,因此表格上方缺空行,第 8 行的 {MD058} 标记即指向该处报错。表格下方是空行,故只有一条错误。
5.3 表格延续文本:MD055 与 MD056 联动(第 12~15 行)
| Header | Header |
| ------ | ------ |
| Cell | Cell |
Part of table {MD055} {MD056}
这是全文件信息量最大的一段。第 12~14 行是标准表格,第 15 行 Part of table ... 紧贴表格末尾、无空行分隔,因此按规范它属于表格的一部分——不触发 MD058;但同时它破坏了表格结构,从而触发了两条其他规则:
- MD055(table-pipe-style,表格管道风格):这一行没有前导与尾随管道符
|,与表头行"前导+尾随"的风格不一致。快照 test/markdownlint-test-scenarios.mjs.snapshot 显示,第 15 行报了两条 MD055:Missing leading pipe与Missing trailing pipe,且errorRange分别为<a href="https://link.gitcode.com/i/599aaf8904bbe417fb1d3ff4824356a6" target="_blank">1,1]与[29,1](行首与行尾各一字符宽)。MD055 的完整规则说明见 [doc/md055.md,其style参数支持consistent(默认,以文档首个表格的表头行为准)及leading_and_trailing/leading_only/no_leading_or_trailing/trailing_only。 - MD056(table-column-count,表格列数一致性):该行只有 1 个"单元格",而表格应有 2 列。快照显示报错
Expected: 2; Actual: 1; Too few cells, row will be missing data。MD056 的规则说明见 doc/md056.md。
从源码看,MD055 对每一行(tableDelimiterRow / tableRow)分别检查首尾单元格的首尾 token 是否为 tableCellDivider(见 lib/md055.mjs),并据此推导 leading_and_trailing 等四种实际风格;MD056 则统计每行 tableData / tableDelimiter / tableHeader 子 token 的数量并与首行对齐比较(见 lib/md056.mjs)。这也解释了为什么"延续文本"行会同时撞上这两条规则。
5.4 引用块紧贴表格:下方缺空行(第 17~20 行)
| Header | Header |
| ------ | ------- |
| Cell | {MD058} |
> Blockquote
表格下方紧贴一个 > Blockquote,没有空行分隔,因此在表格末行(第 19 行)标记 {MD058},表示"表格下方缺空行"。快照中该错误的 fixInfo 为 {"lineNumber": 20, "insertText": "\n"}——即自动修复会在第 20 行(> Blockquote 之前)插入一个换行。
5.5 引用块内的表格(第 24~33 行)
> Text
> | Header | {MD058} |
> | ------ | ------- |
> | Cell | {MD058} |
Text
> | Header | Header |
> | ------ | ------- |
> | Cell | {MD058} |
> > Blockquote
这一段测试的是表格嵌在 blockquote(引用块)中的情况,共两个用例:
- 第 24~27 行:
> Text与> | Header ...之间没有>空行分隔,表格上方缺空行,第 25 行报 MD058;表格末行(第 27 行> | Cell ...)之后紧贴第 28 行的Text(不在引用块内),下方缺空行,因此第 27 行也报 MD058。 - 第 30~33 行:表格上方合规(
> Text与表格之间有空行),但表格末行(第 32 行> | Cell ...)之后紧贴> > Blockquote,下方缺空行,第 32 行报 MD058。
这里的修复文本带上了引用块前缀。快照中第 25 行与第 27 行错误的 fixInfo.insertText 都是 ">\n" 而非裸的 "\n"——这正是源码中 getBlockQuotePrefixText 的作用(见 helpers/micromark-helpers.cjs):它扫描 blockQuotePrefix / linePrefix 类型 token,取出该行对应的引用前缀文本(如 > ),追加换行并重复 count 次,从而保证自动修复后引用块结构不被破坏。md058.mjs 通过 filterByTypesCached(<a href="https://link.gitcode.com/i/1102831c6cc89418cf34f5dab7139e5e" target="_blank"> "blockQuotePrefix", "linePrefix" ]) 预先收集所有前缀 token,再传给该辅助函数使用(见 [lib/md058.mjs)。
5.6 无序列表中的缩进表格(第 35~39 行)
- List Item
| Header | {MD058} |
| ------ | ------- |
| Cell | {MD058} |
- List Item
表格以 2 个空格缩进嵌在列表项内。第 35 行的 - List Item 与表格之间无空行,表格上方缺空行,第 36 行报 MD058;表格末行(第 38 行)后紧贴第 39 行的下一个 - List Item,下方缺空行,第 38 行也报 MD058。快照中两处 fixInfo.insertText 均为 "\n"(列表内修复无需引用前缀)。
5.7 有序列表中的缩进表格(第 41~45 行)
1. List Item
| Header | {MD058} |
| ------ | ------- |
| Cell | {MD058} |
1. List Item
与无序列表用例结构完全对称:表格以 3 个空格缩进嵌在有序列表项内,第 42 行(上方缺空行)与第 44 行(下方缺空行)各报一条 MD058。快照中的修复文本同样为 "\n"。
5.8 指令容器(directive)中的表格(第 47~51 行)
:::directive
| Header | {MD058} |
| ------ | ------- |
| Cell | {MD058} |
:::
最后一段测试的是 directive 容器(micromark 扩展语法,见 package.json 中依赖 micromark-extension-directive)。表格紧贴 :::directive 容器的开闭标记,因此第 48 行(上方缺空行)与第 50 行(下方缺空行)各报一条 MD058,修复文本为 "\n"。
5.9 快照中的完整修复结果
将上述所有 fixInfo 应用到原文件后,快照 test/markdownlint-test-scenarios.mjs.snapshot 给出了修复后的完整文档。值得注意的修复后形态包括:
- 普通文本与表格之间插入空行:
Text\n\n| Header ...; - 引用块内插入带前缀的空行:
> Text\n>\n> | Header ...; - 列表项与表格之间插入空行:
- List Item\n\n | Header ...、1. List Item\n\n | Header ...; - directive 容器内插入空行:
:::directive\n\n| Header ...\n\n:::。
同时,修复后文档中 MD055 / MD056 的错误依然保留(如 Part of table {MD055} {MD056} 一行的管道与列数问题没有 fixInfo),说明 MD058 只负责空行,而管道风格与列数问题需要另行处理——三者职责正交,这正是 markdownlint 按规则拆分的典型设计。
六、在项目中使用 MD058
6.1 手动运行 lint
MD058 属于默认启用规则,直接用 markdownlint 的 Promise 风格 API 即可对文件或字符串执行检查。以仓库 test/harness.mjs 的用法为参考:
import { lint } from "markdownlint/promise";
const results = await lint({
"files": [ "path/to/your.md" ]
});
console.dir(results, { "depth": null });
场景测试使用的正是 markdownlint/promise 入口(见 test/markdownlint-test-scenarios.mjs),它是本仓库四个导出形态之一,其他还包括 markdownlint(含 applyFixes 等同步能力)、markdownlint/async 与 markdownlint/sync(见 package.json 的 exports 字段)。
6.2 配置项
MD058 支持标准配置形态(见 lib/configuration-strict.d.ts),可在 .markdownlint.json 中显式控制:
{
"MD058": true,
"blanks-around-tables": "warning"
}
也可以关闭该规则:
{
"MD058": false
}
如果希望把空行要求调整得更加宽松或严格,MD058 本身没有额外参数,其行为完全由"表格前后是否有空行"决定;相邻的 MD055(管道风格,含 style 参数)与 MD056(列数一致)则是表格族中带配置参数的两条规则,可在同文件配置。
6.3 自动修复与验证闭环
场景测试的第三步验证逻辑(见 test/markdownlint-test-scenarios.mjs)展示了标准的"修复闭环":对 applyFixes 输出再次 lint,断言其中不再残留任何 fixInfo 非空的可修复错误。你自己的工作流可以照搬这一模式:
import { applyFixes } from "markdownlint";
import { lint } from "markdownlint/promise";
const results = await lint({ "strings": { "input": content } });
const fixed = applyFixes(content, results.input);
const recheck = await lint({ "strings": { "input": fixed } });
// 断言 recheck.input 中不再存在 fixInfo 非空的错误
由于 MD058 在 lib/constants.mjs 的 fixableRuleNames 中,applyFixes 会正确应用其 insertText,一次修复即可让文档在"表格空行"维度上完全合规。
七、MD058 与 MD055 / MD056:表格规则族的职责划分
本文多处涉及 MD055 / MD056,这里将三者的边界做一次清晰归纳,方便你在排查错误时快速定位:
| 规则 | 别名 | 检查内容 | 参数 | 是否可修复 |
|---|---|---|---|---|
| MD055 | table-pipe-style |
表格行首/行尾管道符风格是否一致 | style(consistent / leading_and_trailing / leading_only / no_leading_or_trailing / trailing_only,默认 consistent) |
否 |
| MD056 | table-column-count |
每行列数是否与首行一致 | 无 | 否 |
| MD058 | blanks-around-tables |
表格前后是否有空行 | 无 | 是 |
三者共同的底层依赖是 micromark 解析器(规则的 parser 字段均为 micromark,且仓库依赖 micromark-extension-gfm-table,见 package.json):GFM 表格扩展先产出 table / tableRow / tableDelimiterRow / tableCellDivider 等 token,MD055 与 MD056 再基于这些 token 做结构与风格判断,MD058 则叠加 blockQuotePrefix / linePrefix token 实现带引用前缀的修复。理解了这条 token 数据链,就能把三条规则的报错行为串联起来预判。
八、总结
MD058 / blanks-around-tables 是 markdownlint 中保障表格可解析性与排版清晰度的基础规则:它要求每个表格前后各有一个空行(文档首尾除外),自动修复时能智能地在普通文本、引用块、列表与 directive 容器中插入正确的空行(引用块场景自动带 > 前缀)。通过 test/blanks-around-tables.md 这一场景文件的逐段分析,可以完整掌握其在各种容器嵌套下的报错行号与修复结果;而 lib/md058.mjs、helpers/micromark-helpers.cjs 与 helpers/helpers.cjs 则揭示了其底层实现原理。需要区分的是:紧贴表格的延续文本不触发 MD058,但会触发 MD055 / MD056——表格族三条规则各司其职,共同保证 GFM 表格的正确书写。