markdownlint 规则 MD021 深度解析:closed atx 标题内多余的井号间距

发布时间:2026/10/15 21:00:06

markdownlint 规则 MD021 深度解析:closed atx 标题内多余的井号间距 开发工具代码质量Lint静态分析【免费下载链接】markdownlintA Node.js style checker and lint tool for Markdown/CommonMark files.项目地址https://gitcode.com/gh_mirrors/ma/markdownlint点击查看免费下载本文围绕 markdownlint 仓库中的 MD021 规则文档 展开系统讲解该规则如何识别并修复「闭合式 ATX 标题closed atx style heading」中标题文本与井号之间的多余空格并结合 规则实现源码、micromark 解析辅助函数 与测试快照说明其判定逻辑、双向扫描机制与自动修复能力。读完本文你将能准确理解 MD021 的触发边界、与 MD018/MD019/MD020 的规则家族关系并能在实际项目中正确配置、排除或利用其自动修复能力。规则速览MD021 是 markdownlint 中一条针对标题书写的风格检查规则其完整元信息定义于 doc/md021.md同时汇总在 doc/Rules.md 与 README.md 的规则清单中规则编号MD021别名Aliasno-multiple-space-closed-atx标签Tagsatx_closed、headings、spaces可自动修复部分违规可通过工具自动修复规则描述闭合式 ATX 标题中井号#与标题文本之间存在多个空格这里的「closed atx style heading」指同时具备开头井号与结尾闭合井号的 ATX 标题例如# Heading 1 #、## Heading 2 ##。它与仅以开头井号标记的普通 ATX 标题如# Heading 1以及 Setext 标题用/---下划线标记是三种不同的标题风格具体区分逻辑见下文源码分析。触发条件什么时候会报 MD021按照 doc/md021.md 的定义当闭合式 ATX 标题中任一侧开头井号之后、或结尾闭合井号之前出现多于一个空格时该规则即被触发# Heading 1 # ## Heading 2 ##上面两行中#与Heading 1之间、Heading 1与结尾#之间均插入了两个空格这两行都会产生 MD021 违规。需要注意的要点两侧独立判定规则会同时检查开头与结尾两侧只要任意一侧有多余空格就会报告两侧同时违规时报告多次当标题两侧同时包含多个空格时同一行会报告两条独立错误分别对应开头侧与结尾侧单个空格是唯一合法形式检查基准是「恰好一个空格」零个空格或两个及以上空格都不符合要求。零空格与多个空格分别由不同规则覆盖缺少空格对应 MD020 规则no-missing-space-closed-atx多余空格对应本文的 MD021。正确写法与修复方式将标题文本与井号之间统一为单个空格即可消除违规# Heading 1 # ## Heading 2 ##在 markdownlint 中MD021 属于「Fixable: Some violations can be fixed by tooling」的规则。这意味着通过官方提供的applyFix/applyFixes工具函数可以自动删除多余的空格详见下文「自动修复机制」一节无需手工逐个清理。边界情况多空格数量的差异从规则实现看MD021 对多余空格的数量不做上限区分——只要空白 token 长度大于 1 就会触发2 个空格、3 个空格乃至更多均属违规。这一行为可以从测试用例 test/atx_closed_heading_spacing.md 中得到印证## Heading 4 {MD021} ## ← 开头侧 2 个空格 ## Heading 5 {MD021} ## ← 结尾侧 2 个空格 ## Heading 6 {MD021} ## ← 两侧各 2 个空格报告两条错误 ## Heading 7 {MD021} ## ← 两侧各 3 个空格报告两条错误而同一测试文件中的这些写法不会触发 MD021## Heading 8\# ← 结尾井号被转义不是闭合井号 ## Heading 9 \# ## Heading 10 \#可以看到只有真正的闭合井号未转义、与开头井号成对之前的多余空格才会被检查带反斜杠转义的\#属于标题文本的一部分不受 MD021 约束。源码实现双向扫描的空白校验MD021 的实现位于 lib/md019-md021.mjs它与 MD019普通 ATX 标题开头的多余空格共用同一个校验函数validateHeadingSpaces区别仅在于扫描方向与过滤条件function validateHeadingSpaces(onError, heading, delta) { const { children, startLine, text } heading; let index (delta 0) ? 0 : (children.length - 1); while ( children[index] (children[index].type ! atxHeadingSequence) ) { index delta; } const headingSequence children[index]; const whitespace children[index delta]; if ( (headingSequence?.type atxHeadingSequence) (whitespace?.type whitespace) (whitespace.text.length 1) ) { // ... 计算列号与长度报告错误并附加 fixInfo } }关键逻辑可以拆解为三步定位井号序列在 heading token 的 children 中沿delta方向1表示从开头向结尾、-1表示从结尾向开头找到类型为atxHeadingSequence的节点即该侧的井号串检查相邻空白取井号序列紧邻的相邻 token若其类型为whitespace且文本长度大于 1判定为违规计算错误位置以空白 token 的startColumn/endColumn计算出range列号与长度并通过addErrorContext上报。MD021 的规则函数对每个闭合式 ATX 标题调用两次validateHeadingSpaces一次delta 1检查开头侧、一次delta -1检查结尾侧这正对应了文档中「either side」的说明function MD021(params, onError) { const atxClosedHeadings filterByTypesCached([ atxHeading ]) .filter((heading) getHeadingStyle(heading) atx_closed); for (const atxClosedHeading of atxClosedHeadings) { validateHeadingSpaces(onError, atxClosedHeading, 1); validateHeadingSpaces(onError, atxClosedHeading, -1); } }如何判定「closed atx」getHeadingStyleMD021 只作用于闭合式 ATX 标题风格的判定复用 helpers/micromark-helpers.cjs 中的getHeadingStyle函数function getHeadingStyle(heading) { if (heading.type setextHeading) { return setext; } const atxHeadingSequenceLength heading.children.filter( (child) child.type atxHeadingSequence ).length; if (atxHeadingSequenceLength 1) { return atx; } return atx_closed; }判定规则很直观Setext 标题返回setext其余 ATX 类标题中atxHeadingSequence节点井号串的数量为 1 的是普通 ATX 标题数量为 2开头 结尾各一组的才是atx_closed。MD019 与 MD021 正是通过这一函数分流前者过滤出atx风格、只做单侧开头扫描后者过滤出atx_closed风格、做双侧扫描。自动修复机制MD021 属于可修复规则validateHeadingSpaces在报告错误时携带了fixInfoaddErrorContext( onError, startLine, text.trim(), delta 0, // 标记错误位于开头侧 delta 0, // 标记错误位于结尾侧 [ column, length ], { editColumn: column, deleteCount: length } );editColumn指向多余空白的起始列deleteCount为需删除的空白长度。从 test/markdownlint-test-scenarios.mjs.snapshot 中atx_closed_heading_spacing.md的期望结果可以看到具体的修复参数第 7 行## Heading 4 {MD021} ##errorRange: [4, 1]fixInfo: { editColumn: 4, deleteCount: 1 }第 9 行## Heading 5 {MD021} ##errorRange: [22, 1]fixInfo: { editColumn: 22, deleteCount: 1 }第 11 行## Heading 6 {MD021} ##同时报告两条错误errorRange分别为[4, 1]与[23, 1]第 13 行## Heading 7 {MD021} ##同样两条错误errorRange分别为[4, 2]与[24, 2]3 个空格需删除 2 个。在同一快照的fixed输出中上述标题全部被规整为## Heading 4 {MD021} ##这类单空格形式。这说明自动修复是增量删除保留第一个空格仅删除多余部分而不是整行重写。实际应用时可参照 README.md 的「Fixing」章节用applyFixes把 lint 结果直接写回内容import { applyFixes } from markdownlint; import { lint as lintSync } from markdownlint/sync; const results lintSync({ strings: { content: original } }); const fixed applyFixes(original, results.content);与其他相关规则的边界MD021 并非孤立存在它与 MD018、MD019、MD020 共同构成「ATX 标题与井号间距」的规则家族容易混淆这里给出对照规则别名标题风格检查位置违规形态MD018no-missing-space-atx普通 ATX开头井号后缺少空格#HeadingMD019no-multiple-space-atx普通 ATX开头井号后多个空格# HeadingMD020no-missing-space-closed-atx闭合式 ATX两侧缺少空格#Heading 1#MD021no-multiple-space-closed-atx闭合式 ATX两侧多个空格# Heading 1 #其中 MD019 与 MD021 共享同一实现文件 lib/md019-md021.mjs该文件同时导出两条规则在 lib/rules.mjs 中解构注册MD018 与 MD020 则分别在 lib/md018.mjs、lib/md020.mjs 独立实现。从仓库的模块组织方式可以看出作者按「间距方向」分组共享逻辑md019-md021.mjs处理「多个空格」md020.mjs处理「缺少空格」。测试验证与回归保障仓库为 MD021 准备了多层测试验证专项场景文件test/atx_closed_heading_spacing.md集中构造了开头侧多空格、结尾侧多空格、两侧同时多空格、以及转义井号等边界用例文件头部通过!-- markdownlint-configure-file { first-line-heading: false, heading-style: false } --内联配置排除了无关规则的干扰快照断言test/markdownlint-test-scenarios.mjs.snapshot由markdownlint-test-scenarios.mjs生成精确记录了每条错误的lineNumber、errorRange与fixInfo并对修复后的全文做逐字符比对防止修复逻辑回归综合用例test/MD011-MD021.md 与 test/atx-heading-spacing-trailing-spaces.md将 MD021 与 MD018/MD019/MD020 混排在同一文件中验证各规则互不误报全规则回归test/break-all-the-rules.md 配合 test/markdownlint-test.mjs 中的styleAll测试使用 style/all.json 配置跑全量规则确认 MD021 在其第 31 行正常触发、无遗漏。配置与使用建议MD021 默认在 markdownlint 的完整配置中启用它在 lib/rules.mjs 的rules数组中注册运行时由库统一加载。可按需进行以下操作按编号或别名启用/禁用在.markdownlint.json等配置文件中将MD021: false或no-multiple-space-closed-atx: false即可关闭置为true或error恢复行内临时控制借助 markdownlint 的行内指令例如!-- markdownlint-disable MD021 --禁用、!-- markdownlint-enable MD021 --恢复或使用!-- markdownlint-disable-next-line MD021 --仅豁免下一行测试文件中的markdownlint-configure-file注释即为行内配置的实例自动修复流程在 CI 或提交钩子中接入lintSyncapplyFixes见上文示例让多余空格问题在保存或提交阶段自动消失无需人工介入。实践上的核心建议是保持标题两侧与井号之间恰好一个空格。这一约定不仅让源码更整洁也让标题文本在复制、搜索、以及嵌入其他工具链时行为可预期。由于多余的空白不影响渲染这正是 doc/md021.md 中 Rationale 的表述Extra space has no purpose and does not affect the rendering of contentMD021 本质上是一条纯风格规范是否启用完全取决于团队约定若你的文档风格允许对齐式的多空格标题直接关闭该规则即可不会产生功能影响。赞分享开发工具代码质量Lint静态分析【免费下载链接】markdownlintA Node.js style checker and lint tool for Markdown/CommonMark files.项目地址https://gitcode.com/gh_mirrors/ma/markdownlint点击查看免费下载相关推荐markdownlint 规则 MD021no-multiple-space-closed-atx闭合 ATX 标题井号内多余空格的检测与自动修复markdownlint 规则 MD021no multiple space closed atx闭合 ATX 标题井号内多余空格的检测与自动修复 本篇文开发工具代码质量Lint静态分析markdownlint MD019no-multiple-space-atx规则详解ATX 标题井号后多余空格检测与修复markdownlint MD019no multiple space atx规则详解ATX 标题井号后多余空格检测与修复 MD019 是 markdow开发工具代码质量Lint静态分析markdownlint 规则 MD020no-missing-space-closed-atx详解闭合 ATX 标题井号内缺失空格检测与自动修复markdownlint 规则 MD020no missing space closed atx详解闭合 ATX 标题井号内缺失空格检测与自动修复 导读开发工具代码质量Lint静态分析上一篇Lemmy基于 ActivityPub 的 Rust 去中心化讨论平台——架构、特性与部署指南下一篇终极自动化指南无需代码轻松实现电脑操作自动化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/15 20:55:05

Samba文件服务器安全(1)

在企业 IT 基础设施中,Samba 文件服务器往往扮演着"沉默的枢纽"角色——它让 Windows 客户端能够无缝访问 Linux 上的共享目录,让 Active Directory 域中的用户凭同一套身份凭证读写文件。然而,正是这种跨平台桥接能力,使 Samba 服务器成为网络安全链条上最容易被…

2026/10/15 20:55:05

大模型人才报告解读:岗位图谱、技能栈与能力培养路径

简介:由中国软件行业协会发布的《2024人工智能大模型的技术岗位与能力培养研究报告》,围绕大模型引发的岗位变革与人才需求展开,适合想进入AI大模型方向的开发者、在校学生以及负责技术团队建设与课程规划的管理者阅读。资料为单个PDF文件&am…

2026/10/15 20:55:05

深海数据中心测试:从故障注入到代码生存的实操指南

接到“深海数据中心测试”这个任务时,团队里一度以为是在整活。把装了几十台服务器的标准机柜塞进耐压舱体,沉到几十米深的海床上,让它以接近工业级可靠性的标准连续运行——这个听起来有点科幻片味道的项目,其实催生了一连串极其…

2026/10/15 22:10:47

Flutter网络层请求校验与数据封装实践:用户中心My模块改造

做开源项目最怕什么?我自己的答案很直接:网络层写成一坨。这期笔记继续记录我在 Flutter 3.2.x 上维护的开源项目 OChina 的迭代过程,系列走到第 04 篇,聚焦主界面“My”模块里最容易被忽略、却直接影响体验的两件事——网络请求校…

2026/10/15 22:10:47

Unity编辑器快捷键实战:场景视口与窗口切换的效率提升指南

干了这么多年 Unity 开发,我见过太多人拿着鼠标在编辑器里点来点去:切个窗口要点四下,复制个物体要右键菜单,想从 Scene 切到 Game 还要抬头找标签。Unity 操作快捷键这件事,很多人觉得“知道就行”,但它其…

2026/10/15 22:10:47

Qt桌面端Excel操作方案选型与实战:QtXlsx、libxlsxwriter、COM对比

1. 为什么桌面端项目总绕不开Excel这关做桌面端开发的人,迟早会撞上Excel这个需求。不管是工业上位机导出检测报表、医疗软件生成患者数据汇总、还是内部工具批量导入配置表,只要用户群体是传统行业,Excel就是他们最熟悉的“数据库”。我做过…

2026/10/15 22:10:47

Unity自动化打包实战:构建确定性、可观测性与可追溯性

1. 这不是“配个Jenkins就完事”的活儿:Unity自动化打包的真实水深在哪你是不是也经历过这样的凌晨三点?项目提测前最后一版包,美术刚塞进来的200MB贴图还没压缩,策划临时改的UI动效脚本还在热更目录里没清理,而你手抖…

2026/10/15 22:10:47

编辑宅家怎么玩出内容?五样东西变成选题库

说来有点不好意思。最近被几个同行私信问:“你们这些编辑在家过的日子,平时都在玩儿啥?”我想了半天,发现真实答案不是“改稿、开会、做表”,而是“玩AI对话工具、搭笔记迷宫、种薄荷、翻旧照片”。居家办公这段时间&a…

2026/10/15 22:05:47

Ubuntu安装Docker完整指南:从环境预检到排错实战

从零开始:Ubuntu 装机路上的一次 Docker 安装记录作为一个长期折腾 Linux 桌面和服务器的用户,我几乎每次重装系统都要和 Docker 打交道。这篇文章是我前段时间在一台全新的 Ubuntu 设备上安装 Docker 的完整记录,包含了每一步操作的背后逻辑…

2026/10/14 11:31:03

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/14 17:41:07

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/15 13:42:16

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/15 0:02:22

防关联浏览器哪个好?三款产品实测差异解析

新手做多店铺,最纠结的就是防关联浏览器选哪个。飞跨、网易、站斧这三款被问得最多,先给结论:要一体化省心、长期便宜,选站斧;要网易招牌和稳,选网易;预算紧做东南亚 Lazada,选飞跨。…

2026/10/15 0:02:22

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成 一、技术背景 大模型(LLM)在通用场景下表现强大,但当我们将 GPT-4 或 Qwen 直接用于生成影刀RPA操作指令和 XPath 定位时,准确率通常只有 40%-60%。原因…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑