news 2026/10/16 3:00:47

markdownlint MD012 / no-multiple-blanks 规则深度解析:检测与自动修复 Markdown 连续空行

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
markdownlint MD012 / no-multiple-blanks 规则深度解析:检测与自动修复 Markdown 连续空行
  • 开发工具
  • 代码质量
  • Lint
  • 静态分析

【免费下载链接】markdownlint

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

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

本指南以 markdownlint 仓库中的规则测试样例 test/no-multiple-blanks.md 及其配套文档 doc/md012.md 为核心,完整讲解 MD012(别名no-multiple-blanks)规则的检测语义、maximum参数配置、代码块豁免逻辑与自动修复机制。读完本文,你将能独立阅读该规则的标记式测试文件、准确预判每一个报错行号,并在自己的项目中用maximum定制空行上限或通过applyFixes一键修复。

规则速览:标签、别名与参数

MD012 是 markdownlint 内置的"多个连续空行"检查规则,其正式定义位于 doc/md012.md(同时收录于规则总表 doc/Rules.md):

  • 名称 / 别名:MD012、no-multiple-blanks
  • 标签(Tags):blank_lines、whitespace
  • 参数(Parameters):maximum,类型integer,默认值1,表示允许的连续空行最大数量
  • 可修复性(Fixable):部分违规可由工具自动修复

该规则的触发条件是文档中出现连续多个空行。按默认配置,正文之间只允许保留 1 个空行,出现 2 个及以上连续空行即构成违规:

Some text here Some more text here

修复方式为删除多余空行:

Some text here Some more text here

规则设计者的理由(Rationale)写得很明确:除了代码块内部之外,空行不承载任何渲染语义,连续堆叠空行没有任何用途。这也是它在默认规则集中保持开启的核心理由。

阅读测试文件的标记约定

markdownlint 仓库的规则测试文件采用一种"内联期望标记"约定:每一处预期报错都用{MD012:行号}形式直接写在测试文本中,由测试框架 test/markdownlint-test-scenarios.mjs 解析(其标记正则位于该文件第 40 行),并与真实 lint 结果逐一比对;最终比对结果固化在快照文件 test/markdownlint-test-scenarios.mjs.snapshot 中。因此,测试文件里每一行{MD012:n}都是"该行号处应报 MD012"的声明,是我们理解规则行为的权威教材。

逐行拆解 no-multiple-blanks.md 测试样例

关联文档 test/no-multiple-blanks.md 全文 24 行,在默认配置(maximum = 1)下逐行分析如下:

行号内容分析
1# Heading非空行,计数清零
2(空行)计数 1,未超过上限
3Text非空行,计数清零
4(空行)计数 1,未超过上限
5Text非空行,计数清零
6–7连续 2 个空行第 7 行计数达到 2,报{MD012:7}
8Text计数清零
9–11连续 3 个空行第 10 行计数 2 报错,第 11 行计数 3 报错
12Text计数清零
13–16连续 4 个空行第 14、15、16 行分别以计数 2、3、4 各报一次
17Text计数清零
18(空行)计数 1,未超过上限
19Text计数清零
20–21连续 2 个空行第 21 行报{MD012:21}
22Text计数清零
23(空行)计数 1,未超过上限
24Text计数清零

由此可见,n个连续空行在默认配置下会报出n - 1条错误:空行计数从 1 开始,一旦超过maximum,超出的每一行都会触发一次违规。快照 test/markdownlint-test-scenarios.mjs.snapshot(对应no-multiple-blanks.md的用例,位于第 35832 行附近)证实了这一点,逐条列出了第 7、10、11、14、15、16、21 行共 7 条错误,且每条错误的errorDetail均为Expected: 1; Actual: 2/3/4——Expected对应配置的maximum,Actual对应该行处的累计空行计数。

报错行号的两个关键规则

从上面的拆解可以提炼出两个对任何 markdownlint 使用者都有价值的规律:

  1. 错误标记在"超出的空行"上:连续n个空行时,第 2 到第n个空行(即超出第一个的每一行)各自报告一次,错误行号即该空行的实际行号;
  2. 报错行号与该行Actual值一一对应:第k个连续空行报错时,Actual恰为k,所以错误行号完全可预测,这也是测试文件能用{MD012:行号}精确标注的原因。

从源码看检测与豁免逻辑

规则实现位于 lib/md012.mjs,核心算法非常简洁:

const maximum = Number(params.config.maximum || 1); const { lines } = params; const codeBlockLineNumbers = new Set(); for (const codeBlock of filterByTypesCached([ "codeFenced", "codeIndented" ])) { addRangeToSet(codeBlockLineNumbers, codeBlock.startLine, codeBlock.endLine); } let count = 0; for (const [ lineIndex, line ] of lines.entries()) { const inCode = codeBlockLineNumbers.has(lineIndex + 1); count = (inCode || (line.trim().length > 0)) ? 0 : count + 1; if (maximum < count) { addErrorDetailIf( onError, lineIndex + 1, maximum, count, undefined, undefined, undefined, { "deleteCount": -1 } ); } }

逐段理解其含义:

  • 参数读取:params.config.maximum || 1取出配置值,未配置时回落为默认值 1,Number()保证其为数值;
  • 代码块行号收集:通过 lib/cache.mjs 的filterByTypesCached缓存机制,从 micromark 解析出的 token 中筛选codeFenced(围栏代码块)与codeIndented(缩进代码块)两类 token,再借助 helpers/micromark-helpers.cjs 的addRangeToSet(其实现见该文件第 61 行)把每个代码块的起止行区间加入集合;
  • 逐行扫描:对每一行,若该行处于代码块内(inCode)或行内容去除空白后非空(line.trim().length > 0),则计数重置为 0;否则计数加 1。这就是文档中"代码块内的连续空行不会触发规则"的源码级证据;
  • 上报错误:当累计计数count超过maximum时,通过 helpers/helpers.cjs 的addErrorDetailIf(第 285 行)上报一条Expected: maximum; Actual: count的错误,同时附带{ "deleteCount": -1 }的修复信息,表示"整行删除"。

此外,filterByTypesCached基于规则参数做了按参数序列化的缓存(见 lib/cache.mjs 第 60 行),意味着同一份解析结果在同一次 lint 中被多条规则共享,这也是规则可以放心调用它的性能前提。

maximum 参数与自定义配置

maximum允许你放宽或收紧连续空行的上限。仓库提供了配套测试 test/no-multiple-blanks-maximum.md,其文件末尾通过内联配置指令将上限改为 3:

<!-- markdownlint-configure-file { "no-multiple-blanks": { "maximum": 3 } } -->

对比默认样例可以直观看到差异:该文件中连续 3 个空行不再报错,只有连续 4 个空行才会在第 4 行起逐行报错(如第 16 行、第 21–22 行、第 27–29 行、第 36 行,其中第 21–22 行的Actual分别为 4、5)。快照文件中对应用例同样逐条验证了这些Expected: 3; Actual: 4/5/6的错误。

在.markdownlint.json等全局配置文件中的写法等价为:

{ "no-multiple-blanks": { "maximum": 3 } }

如果希望完全关闭该规则,将配置值设为false即可,例如本仓库大量测试文件(如 test/blanks-around-headings.md 系列)在专门验证空行类规则时会用它来隔离干扰。设置maximum为大于 1 的值通常与"要求标题上下留多个空行"的场景配合使用:当 MD022 等规则需要多于一个空行时,文档 doc/Rules.md 明确提示应同步调整 MD012(见该文档 MD022 小节第 772–775 行的说明),否则会与 MD012 的默认上限互相冲突。

自动修复:整行删除的 deleteCount 机制

MD012 属于可自动修复的规则之一,这一点可以从 lib/constants.mjs 的fixableRuleNames列表(其中包含MD012)得到确认。当规则上报错误时携带的修复信息是:

{ "deleteCount": -1 }

在 markdownlint 的修复约定中,deleteCount为-1表示"删除整行"。修复引擎 lib/markdownlint.mjs 的applyFix函数(第 1207 行起)对deleteCount === -1的情况直接返回null表示该行被删除;applyFixes(第 1222 行起)则会先按"从下到上、整行删除优先、从右到左、长到短"的次序排序去重,再逐条应用,避免行号漂移导致误删。

快照中no-multiple-blanks.md用例的fixed字段展示了修复结果:所有连续空行被压缩到只剩 1 个,但每一处仍保留一个{MD012:n}标记——因为测试标记本身所在的行是"超出的空行"被删除后剩下的那行,这种保留保证了快照比对不会因修复后的文本行号错位而失真。

在自己项目中一键修复的用法(参见 README.md 的 Fixing 一节):

import { applyFixes } from "markdownlint"; import { lint as lintSync } from "markdownlint/sync"; const results = lintSync({ "strings": { "content": original } }); const fixed = applyFixes(original, results.content);

值得注意的是,修复信息在进入引擎前还要经过 lib/markdownlint.mjs 第 570 行起的校验逻辑:deleteCount必须是不小于-1且不超过该行长度的数值,超界会直接抛出fixInfo.deleteCount错误,这保证了规则作者上报的修复信息始终安全可执行。

与相邻空行规则的协同

MD012 只关心"空行本身是否连续堆叠",与它相邻的空白规则各司其职,理解这些边界有助于避免配置打架:

  • MD022(blanks-around-headings):要求标题上下保留指定数量的空行,默认 1。若把lines_above/lines_below配成大于 1,就必须同步调大 MD012 的maximum(见 doc/Rules.md 的明确提示);
  • MD031(blanks-around-fences):要求围栏代码块前后有空行,其测试文件(如 test/blanks-around-fences-in-lists.md)同样通过禁用no-multiple-blanks来隔离干扰;
  • MD009(no-trailing-spaces):处理行尾空白,与空行计数无直接关系,但列表项内的"空行"(实际含空格的行)会同时牵涉两条规则,仓库测试 test/trailing-spaces-in-lists-*.md 系列展示了这种组合场景。

小结

通过 test/no-multiple-blanks.md 这份样例,我们完整掌握了 MD012 规则的三层事实:语义上,默认只允许 1 个连续空行,n个空行报n-1条错误,错误行号即超出部分的空行行号;实现上,lib/md012.mjs 通过 micromark token 精确排除代码块区间,再逐行扫描计数上报;工程上,maximum参数可定制上限,{ "deleteCount": -1 }修复信息支持通过applyFixes自动清理。理解这些细节后,你不仅能读懂仓库中任何{MD012:n}标记的测试文件,也能在自己的 Markdown 项目中精准预测、定制并修复连续空行问题。

  • 开发工具
  • 代码质量
  • Lint
  • 静态分析

【免费下载链接】markdownlint

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

项目地址:https://gitcode.com/gh_mirrors/ma/markdownlint
点击查看免费下载
上一篇:智谱 GLM-5.3 技术解读:后训练驱动的复杂编程与长程 Agent 能力跃升及本地部署指南
下一篇:从零构建OpenSuperWhisper:CMake编译whisper.cpp、Cargo构建Rust自动纠错库到xcodebuild出包的完整流程

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

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

Go语言命名管道实战:从原理到高性能IPC实现

1. 为什么要在Go里折腾命名管道第一次接触命名管道是在做一个日志采集模块的时候。当时的需求很直接&#xff1a;一个常驻的采集进程负责从多个来源收数据&#xff0c;另一个分析进程需要实时拿到这些数据做统计。最直觉的方案是上消息队列&#xff0c;但部署环境里没有现成的中…

作者头像 李华