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),仅供参考

相关新闻

拆解桌面版 Claude Code 系统提示词:Agent 架构与安全设计

拆解桌面版 Claude Code 系统提示词:Agent 架构与安全设计

拆解桌面版 Claude Code 系统提示词:Agent 架构与安全设计 【免费下载链接】system_prompts_leaks Documented system prompts from Anthropic - Claude Fable 5.1, Opus 5.5, Claude Design, Claude Code. OpenAI - ChatGPT GPT-6-Astra, Codex. Google - Gemini 3…

2026/10/17 5:24:19 阅读更多 →
SpringBoot嵌入SparkSQL直查Hive实战指南

SpringBoot嵌入SparkSQL直查Hive实战指南

1. 项目概述:为什么在 SpringBoot 里硬要“塞进” SparkSQL 和 Hive?最近有好几个后端团队的朋友私下问我:“我们系统用 SpringBoot 做业务中台,数据层走的是 MySQL MyBatis,现在突然要查 Hive 里的 PB 级日志表&…

2026/10/17 5:23:39 阅读更多 →
Flutter跨端开发鸿蒙应用:星座运势App实战教程

Flutter跨端开发鸿蒙应用:星座运势App实战教程

1. 为什么选Flutter做鸿蒙应用,而不是等官方框架这段时间后台一直有人问我:现在入局鸿蒙开发,是不是应该直接学ArkTS,Flutter还有没有价值?我的答案是,Flutter不仅能做鸿蒙应用,而且适合像我这种…

2026/10/15 20:59:02 阅读更多 →

最新新闻

Claude Code模板化实战:把项目约定固化,让AI编程助手真正懂你

Claude Code模板化实战:把项目约定固化,让AI编程助手真正懂你

项目标题往那摆,说白了就是一句话的事儿:Claude Code 的真正效率,不在于你问得多细,而在于你给它的模板有多靠谱。今年 Claude Code 这类命令行 AI 编程工具火了之后,最容易被忽略的就是“模板化”这件事。很多人拿到 …

2026/10/17 5:23:24 阅读更多 →
金智维入选WISE2025商业之王:企业级RPA数字员工如何实现精度与效率双提升

金智维入选WISE2025商业之王:企业级RPA数字员工如何实现精度与效率双提升

金智维入选“WISE2025商业之王”年度企业,这件事在自动化圈子里这几天的讨论度确实不低。很多人问我怎么看,我的标准一直很简单:不只看它拿了什么奖,而是看这个奖背后的评判维度,是不是真的踩中了企业自动化的命门。按…

2026/10/17 5:23:24 阅读更多 →
让Claude Code懂你的项目:CLAUDE.md、斜杠命令与工作流全解析

让Claude Code懂你的项目:CLAUDE.md、斜杠命令与工作流全解析

用过Claude Code的朋友应该都有体会:刚上手那阵子特别爽,随手问一句它就能翻代码、改文件、提提交,感觉像开了挂。但用上一周、两周之后,问题就来了——它总是重复问你已经回答过的问题,总是不按你仓库里的目录习惯找文…

2026/10/17 5:23:24 阅读更多 →
Claude Code模板库实战:打造可复用的AI编程工作流

Claude Code模板库实战:打造可复用的AI编程工作流

一个多月前,我把分散在多个仓库里的 Claude Code 配置、斜杠命令和项目记忆文件整理成了一个 claude-code-templates 模板库。原本只是想解决自己来回复制粘贴的问题,没想到它对团队协作的帮助更大。这篇文章就把这套模板的设计思路、具体内容和落地过程…

2026/10/17 5:23:24 阅读更多 →
分类模型与评估指标实战:从混淆矩阵到阈值选择

分类模型与评估指标实战:从混淆矩阵到阈值选择

做算法这几年,我招过不少人,也带过不少新人。大家第一次接触项目时,问得最多的往往不是某个公式怎么推,而是“这批数据到底能上什么模型”。每次听到这个问题,我都会先把话题引到另一边:你想用它判断什么&a…

2026/10/17 5:23:24 阅读更多 →
Claude Code模板实战:CLAUDE.md、自定义命令与hooks全解析

Claude Code模板实战:CLAUDE.md、自定义命令与hooks全解析

1. 先把这个标题拆明白看到“claude-code-templates”这个项目名,估计大部分人都能猜个大概:这是给Claude Code用的模板集合。但真正用过Claude Code的人会知道,模板这两个字背后藏的东西远比想象中多——它不是简单的几段提示词,…

2026/10/17 5:22:24 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →