news 2026/10/15 20:26:30

markdownlint 自定义标题空行数量配置实战:解读 MD022 `blanks-around-headings` 的 0-2 测试用例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
markdownlint 自定义标题空行数量配置实战:解读 MD022 `blanks-around-headings` 的 0-2 测试用例
  • 开发工具
  • 代码质量
  • Lint
  • 静态分析

【免费下载链接】markdownlint

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

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

导读

在 Markdown 文档中,标题前后是否留空行不仅影响排版美观,还直接决定kramdown等解析器能否正确识别标题。markdownlint 的 MD022(别名blanks-around-headings)规则默认要求标题上下各留 1 个空行,但通过lines_above与lines_below两个参数,可以按需自定义为空行、任意空行甚至按标题级别分别设值。本文以仓库中的测试用例 test/blanks-around-headings-0-2.md 为骨架,结合 规则官方文档 与 规则源码实现,完整拆解该规则的配置语法、预期行为、自动修复机制与底层实现原理,帮助你掌握在真实项目里精确控制标题空行数量的完整方案。

一、MD022 规则概述:标题必须被空行包围

MD022(别名blanks-around-headings)属于headings与blank_lines标签组,核心约束是:任何样式的标题(ATX 标题#、闭合 ATX 标题# ##、Setext 标题===/---)前后都必须存在至少一个空行。触发场景与修复方式如下(来自 规则文档):

# Heading 1 Some text Some more text ## Heading 2

以上文档中# Heading 1与## Heading 2均未满足"前后各有空行"的要求。修复后应为:

# Heading 1 Some text Some more text ## Heading 2

该规则是**可自动修复(Fixable)**的,即部分违规可以通过工具自动插入缺失的空行。

1.1 三个配置参数

参数类型默认值说明
include_front_matterbooleanfalse是否将 YAML front matter 内容计入标题上方空行检查
lines_aboveinteger \| integer[]1标题上方应存在的空行数量
lines_belowinteger \| integer[]1标题下方应存在的空行数量

其中lines_above/lines_below的取值规则为:

  • 0:标题上下方不需要空行;
  • 正整数 N:至少需要 N 个空行(超出部分忽略,规则只检查"不少于"指定数量);
  • -1:允许任意数量的空行(包含 0);
  • integer[]数组:按顺序对应标题级别 1~6 分别设值,数组长度可不足 6,未覆盖的级别回落到默认值1(此规则在源码getLinesFunction中以new Array(6).fill(defaultLines)体现,见下文源码分析)。

1.2 与 MD012 的联动

如果lines_above或lines_below被配置为大于 1,则 MD012 / no-multiple-blanks(禁止连续多个空行)也应同时自定义,否则两个规则会互相冲突。此外 MD022 只检查"至少指定数量"的空行,额外的空行不会被报错——这正是测试用例中同时关闭 MD012 的原因。

1.3 规则背后的解析器动机

除美学因素外,kramdown等部分 Markdown 解析器不会解析前面没有空行的标题,而是将其当作普通文本处理。因此该规则本质上是保证标题语义正确性的防御性检查。

二、测试用例本体:blanks-around-headings-0-2.md逐行解析

本文的核心关联文档是 test/blanks-around-headings-0-2.md,它是一份针对 MD022 规则的回归测试样例,专门验证lines_above: 0, lines_below: 2组合下的检查行为。全文如下:

# Blanks Around Headings ## Apple Text ## Banana {MD022} Text ## Cherry Text ## Durian ## Text --- Elderberry {MD022} ------------------ Text ## Fig <!-- markdownlint-configure-file { "heading-style": false, "no-multiple-blanks": false, "blanks-around-headings": { "lines_above": 0, "lines_below": 2 } } -->

文件中出现了两个关键标记{MD022},它们是 markdownlint 测试框架约定俗成的预期错误标记(见 测试框架源码 中/\{(MD\d+)(?::([-+]?)(\d+))?\}/g正则解析逻辑):声明该行(或通过MD022:+/-N偏移定位的行)应当被 MD022 报告为违规。该文件还通过内联配置注释<!-- markdownlint-configure-file { ... } -->声明了本次测试的有效配置:

  • heading-style: false:关闭标题样式一致性规则,避免 ATX / 闭合 ATX / Setext 混用被干扰;
  • no-multiple-blanks: false:关闭 MD012,允许测试文档中出现连续空行,从而让"标题下方要求 2 个空行"的检查不被 MD012 抢先报错;
  • blanks-around-headings: { "lines_above": 0, "lines_below": 2 }:将标题上方空行需求清零、标题下方空行需求提升为 2。

2.1 预期违规结果解读(对照快照)

根据 场景测试快照 中blanks-around-headings-0-2.md的条目,在lines_above: 0, lines_below: 2配置下,该文档应精确报告 2 条错误:

  1. 第 8 行## Banana {MD022}:errorDetail为Expected: 2; Actual: 1; Below。即 Banana 标题下方只有 1 个空行,少于要求的 2 个。其fixInfo为在下一行插入\n(补足 1 个空行)。
  2. 第 21 行Elderberry {MD022}:errorDetail为Expected: 2; Actual: 0; Below。即 Setext 标题Elderberry / ------------------下方完全没有空行。其fixInfo为在标题结束行下一行插入\n\n(补足 2 个空行)。

这个结果精准印证了lines_below: 2的语义:标题下方(含 Setext 下划线所在行之后)必须至少有 2 个空行,否则报错。与此同时:

  • lines_above: 0意味着标题上方即使没有空行(如Text直接紧跟## Banana、## Durian ##等)也不算违规;
  • 第一个标题# Blanks Around Headings位于文档开头,上方无空行且被允许;最后一个标题## Fig位于文档末尾,下方是配置注释行,也未被报错——符合"标题位于文档开头或结尾时无需空行"的默认豁免。

快照中的fixed字段还展示了自动修复后的完整文档:Apple、Cherry、Durian 标题下方被补足为 2 个空行,Banana 补 1 个、Elderberry 补 2 个,最终文档经 applyFixes 后重新 lint 不应再有任何可修复错误。

三、参数取值的实战对照:仓库中的兄弟测试用例

blanks-around-headings-0-2.md只是该规则测试矩阵中的一员。仓库test/目录下还提供了覆盖各种取值组合的兄弟用例,可对照理解每个参数行为:

测试文件配置要点验证语义
blanks-around-headings--1-1.mdlines_above: -1, lines_below: 1上方允许任意空行(仅 1 个 Setext 标题因上方无空行被报错)
blanks-around-headings-1--1.mdlines_above: 1, lines_below: -1下方允许任意空行(仅 1 个标题因上方缺空行被报错)
blanks-around-headings-3-0.mdlines_above: 3, lines_below: 0上方要求 3 个空行、下方清零(报 2 条 Above 违规)
blanks-around-headings-arrays.mdlines_above: [ 1, 2, 3, -1 ]、lines_below: [ -1, 2, 1, 3, -1, 4, 10 ]按标题级别 1~6 分别设值
blanks-around-headings-one-true.md 等include_front_matter: true/false标题上方空行检查是否计入 front matter 行

其中 blanks-around-headings-arrays.md 对数组语义的覆盖尤为完整:lines_above: [ 1, 2, 3, -1 ]表示 1 级标题上方需 1 个空行、2 级需 2 个、3 级需 3 个、4 级及以上任意空行;lines_below: [ -1, 2, 1, 3, -1, 4, 10 ]中的第 7 项(10)会被忽略,因为数组只截取前 6 项对应标题级别 1~6。

四、源码实现原理:lib/md022.mjs深度拆解

规则的真实行为由 lib/md022.mjs 实现,它基于micromark解析器("parser": "micromark")。核心逻辑可归纳为以下三层:

4.1 配置归一化:getLinesFunction

const defaultLines = 1; const getLinesFunction = (linesParam) => { if (Array.isArray(linesParam)) { const linesArray = new Array(6).fill(defaultLines); for (const [ index, value ] of [ ...linesParam.entries() ].slice(0, 6)) { linesArray[index] = value; } return (heading) => linesArray[getHeadingLevel(heading) - 1]; } const lines = (linesParam === undefined) ? defaultLines : Number(linesParam); return () => lines; };
  • 传入数组时:初始化 6 个元素、全部填充默认值1,再按索引覆盖前 6 项;返回的函数根据 getHeadingLevel 得到的级别(1~6)取对应值;
  • 传入数字(含undefined和-1)时:undefined回落为默认值1,其余经Number()强转后作为常量返回。

4.2 空行统计与违规判定

主函数遍历filterByTypesCached([ "atxHeading", "setextHeading" ])得到的所有标题 token:

  • 上方检查:从startLine - 2起向上逐行调用isBlankLine,统计实际空行数actualAbove,最多统计linesAbove次;
  • 下方检查:从endLine + 1起向下逐行统计actualBelow;
  • 当linesAbove / linesBelow >= 0时检查才生效(-1表示跳过检查);
  • 最终通过 addErrorDetailIf(仅当expected !== actual时上报)生成Expected: N; Actual: M; Above/Below这样的错误详情——与快照中Expected: 2; Actual: 1; Below的格式完全对应。

值得注意的是isBlankLine(helpers/helpers.cjs)对"空行"的定义很宽:仅含空白字符的行,以及仅含 HTML 注释(<!-- ... -->,允许未闭合)的行都被视为空行。这解释了为何本测试用例文件末尾的<!-- markdownlint-configure-file { ... } -->配置注释行不会触发违规——它被视作空行。

4.3 自动修复与 blockquote 前缀保留

addErrorDetailIf的fixInfo携带两段关键信息:

  • insertText:由 getBlockQuotePrefixText 生成,若标题位于 blockquote(引用块)内,会在插入的空行前保留>前缀,避免修复破坏引用块结构;
  • 下方检查的fixInfo.lineNumber指向endLine + 1,即标题结束行的下一行,确保空行插入位置准确。

快照中 Banana 的修复为insertText: "\n"(差 1 个空行补 1 行),Elderberry 为insertText: "\n\n"(差 2 个补 2 行),与源码逻辑完全吻合。

五、front matter 交互:include_front_matter的边界行为

默认情况下(include_front_matter: false),YAML front matter 被忽略,以下文档不会报告 MD022 违规(来自 规则文档):

--- title: Title --- ## Heading

在源码层面,params.frontMatterLines由主库 removeFrontMatter 解析产生:文档开头若匹配 front matter 正则,则将其按行拆分并剥离出正文。MD022 的getLine辅助函数中,仅上方检查在includeFrontMatter为true且索引越界到 front matter 区域时才会回读frontMatterLines;下方检查固定以false调用(见 lib/md022.mjs 中getLine(lines, endLine + i, frontMatterLines, false)),即标题下方空行从不与 front matter 发生关系。

对应测试用例:默认行为可看 blanks-around-headings-one-yaml-false.md;include_front_matter: true后,front matter 与首个标题之间必须出现要求的空行数,blanks-around-headings-zero-yaml-true.md 中的Text {MD022:-2}标记正是断言这种新引入的违规(行号偏移定位到标题行)。

六、如何运行与验证该测试用例

该测试文件由 test/markdownlint-test-scenarios.mjs 驱动:它遍历./test目录下所有.md文件,逐一执行lint({ "files": [ file ] }),用文档中的{MD022}标记与真实报错行号做assert.deepEqual比对,并将错误详情与修复结果写入 快照文件。这种"标记即断言"的测试范式让每个.md文件本身就是可读的规格说明书。

在本地克隆仓库后,可执行以下命令运行全部场景测试:

npm install npm test

(运行环境要求 Node.js 支持 ESM 与node:test,仓库根目录的 package.json 中声明了依赖与脚本;也可单独运行node --test test/markdownlint-test-scenarios.mjs聚焦本类用例。)

七、工程实践建议

综合文档、测试与源码,使用 MD022 自定义空行数量时建议遵循以下要点:

  1. 优先用0收紧、用-1放宽:0表示"不允许空行"(如追求紧凑排版),-1表示"不检查"(如标题处于 blockquote 密集结构时);
  2. 数组按级别精细化:若 2 级标题与 1 级标题排版风格不同,使用lines_above: [ 1, 2 ]之类的数组,未列出的级别自动回落默认值1;
  3. 与 MD012 协同配置:一旦某方向要求 ≥2 个空行,务必同时将 MD012 / no-multiple-blanks 的maximum调大或直接关闭,否则两规则互相冲突(本测试用例即关闭了 MD012);
  4. 善用{MD022}标记驱动开发:仿照 blanks-around-headings-0-2.md 的写法,在配置变更时用测试标记快速确认预期报错行号与可修复性。

总结

blanks-around-headings-0-2.md虽然只是一个测试文件,却浓缩了 MD022 规则最核心的定制能力:通过lines_above: 0与lines_below: 2分别收紧与放宽标题上下空行要求,并以{MD022}标记与快照双向验证行为。结合 规则文档 的参数说明与 源码实现 的归一化逻辑,你可以准确预测任意lines_above/lines_below组合(数字、-1、按级别数组)下的 lint 结果与自动修复行为,从而在自己的 Markdown 工程中精确落地标题空行规范。

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

【免费下载链接】markdownlint

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

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

相关推荐

上一篇:OpenEBS 统一 Kubectl 插件 kubectl-openebs:一站式管理 Mayastor 与 LocalPV 存储引擎
下一篇:PyWxDump 微信版本支持完整指南:快速判断版本兼容与不支持时的自救方法

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

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

JMeter 5.6.2压力测试实战:从线程组到报告解读

简介&#xff1a;Apache JMeter 5.6.2 是由 Apache 组织维护的开源纯 Java 压力测试工具&#xff0c;具备跨平台特性&#xff0c;可运行于 Windows、Linux、macOS。其用途在于模拟大量用户并发访问&#xff0c;评估 Web 应用、数据库、FTP 服务器等静态与动态资源的高并发稳定性…

作者头像 李华
网站建设 2026/10/15 20:23:36

Path of Building 完整指南:3 分钟规划好你的流放之路 Build

Path of Building 完整指南&#xff1a;3 分钟规划好你的流放之路 Build 【免费下载链接】PathOfBuilding Offline build planner for Path of Exile. 项目地址: https://gitcode.com/GitHub_Trending/pa/PathOfBuilding 还在凭感觉估算一件装备、一个天赋点到底带来多少…

作者头像 李华
网站建设 2026/10/15 20:22:41

Faster R-CNN源码解析:从RPN到RoI Head的PyTorch实现细节

最近重看了一遍 simple-faster-rcnn-pytorch 的源码&#xff0c;好几处当年囫囵吞枣跳过去的细节&#xff0c;这次总算想明白了。这个库可太适合拿来啃了——它没有把网络结构塞进一个几千行的 mega 文件里&#xff0c;RPN、RoI Head、Proposal 筛选这些都是单独模块&#xff0…

作者头像 李华
网站建设 2026/10/15 20:21:55

大麦网自动抢票脚本:6 个参数 + 4 步部署,开售瞬间自动下单

大麦网自动抢票脚本&#xff1a;6 个参数 4 步部署&#xff0c;开售瞬间自动下单 【免费下载链接】Automatic_ticket_purchase 大麦网抢票脚本 项目地址: https://gitcode.com/GitHub_Trending/au/Automatic_ticket_purchase Automatic_ticket_purchase 是一个大麦网自…

作者头像 李华
网站建设 2026/10/15 20:18:51

软考 系统架构设计师历年真题集萃(64)

接前一篇文章:软考 系统架构设计师系列知识点之杂项集萃(63) 第103题 使用McCabe方法可以计算程序流图的环形复杂度,下图的环形复杂度为()。 A. 3 B. 4 C. 5 D. 6 正确答案:B。 试题解析: 本题考查环路复杂度计算。 方法一 图G的环形复杂度V(G)=E-N+2,其中,E…

作者头像 李华