- 开发工具
- 代码质量
- Lint
- 静态分析
【免费下载链接】markdownlint
A Node.js style checker and lint tool for Markdown/CommonMark files.
markdownlint 是 Node.js 生态中最常用的 Markdown/CommonMark 风格检查工具。本文以仓库中的测试用例 test/links-with-markup.md 为切入点,完整讲解链接文本(Link Text)内部空格检测规则MD039(no-space-in-links)的判定标准、错误报告结构与自动修复行为,并深入 lib/md039.mjs 源码剖析其基于 Micromark 词法分析的底层实现。读完本文,你将能准确识别哪些"链接文本空格"会被报告、哪些合法写法会被放行,并能在自己的项目中复现运行这条规则与对应测试。
一、links-with-markup.md 是什么
test/links-with-markup.md是 markdownlint 仓库中的一个场景测试用例文件(Scenario Test),它不属于业务文档,而是被测试框架直接读取并执行 lint 的 Markdown 输入。文件全文如下(共 17 行):
# links-with-markup This link is plain This link has `code` This link has *some* emphasis This link has **more** emphasis This link has `code` and right space {MD039} This link has *emphasis* and left space {MD039} This line has multiple links. This does too and one has a space {MD039}文件名中的 "links-with-markup" 概括了它的测试意图:验证当链接文本内部包含行内标记(inline markup,如代码跨度code、斜体*emphasis*、粗体**more emphasis**)时,markdownlint 的链接相关规则——尤其是 MD039——仍能正确定位和报告"链接文本首尾多余空格"问题。同时它覆盖了"一行内出现多个链接""链接与普通文本混排"等常见场景。
测试文件里出现的{MD039}不是 Markdown 语法,而是 markdownlint 测试框架专用的预期违规标记,它告诉测试框架:该行应当被报告一条 MD039 规则错误。具体匹配逻辑见 test/markdownlint-test.mjs 中的正则/\{(MD\d+)(?::([-+]?)(\d+))?\}/g,它允许通过{MD039:5}或{MD039:-1}这类写法精确指定错误所在行号(偏移量)。
二、MD039 规则的定义:链接文本周围不得有空格
MD039 的正式名称是Spaces inside link text(链接文本内部空格),别名no-space-in-links,标签为links与whitespace。规则文档见 doc/md039.md,其被 doc/Rules.md 收录为全量规则说明的一部分。
规则触发条件非常明确:链接文本(方括号内的内容)首尾出现空格。例如:
[ a link ](https://www.example.com/)会被报告;修正方式是移除链接文本两侧的空格:
[a link](https://www.example.com/)规则给出的设计理由(Rationale)是:一致的格式让文档更易理解。值得强调的是,MD039 只关心紧贴方括号内侧的首尾空格,位于链接文本内部的标记字符与文字之间的空格(如*some*前后的普通文字间距)不受影响,这也正是 links-with-markup.md 能同时容纳"含标记的合法链接"与"含空格的违规链接"两类样例的原因。
规则属性速查
| 属性 | 值 | 来源 |
|---|---|---|
| 规则编号 | MD039 | doc/md039.md |
| 别名 | no-space-in-links | 同上 |
| 标签 | links、whitespace | 同上 |
| 是否可自动修复 | 是(列入 fixable 列表) | lib/constants.mjs |
| 解析器 | micromark | lib/md039.mjs |
在 lib/constants.mjs 的fixableRuleNames数组中,MD039与MD009、MD037、MD038等同列,这意味着--fix工具链会把 MD039 的错误当作可自动修复项处理。
三、逐行解读:哪些写法合法,哪些触发 MD039
对照 MD039 的判定规则,可将 links-with-markup.md 中的八条链接用例分为三类:
1. 合法:链接文本干净或含行内标记
This link is plain — 无多余空格,合法 This link has `code` — 代码跨度,合法 This link has *some* emphasis — 斜体标记,合法 This link has **more** emphasis — 粗体标记,合法这四行验证了核心结论:链接文本内部的标记字符本身不产生空格问题。code 中的反引号、*emphasis*中的星号都不是空白字符,只要不触碰方括号内侧边界,MD039 一律放行。这与 doc/md037.md 中"强调标记两侧不得有空格"的规则(MD037)形成互补:MD037 约束标记字符与强调文本之间,MD039 约束方括号与整个链接文本之间。
2. 违规:链接文本首尾出现空格
This link has `code` and right space {MD039} This link has *emphasis* and left space {MD039}第一行是"尾部空格"(right space):]紧贴右方括号内侧有一个空格;第二行是"首部空格"(left space): 紧贴左方括号内侧有一个空格。两者都会命中 MD039。注意违规链接的文本内部同样含有行内标记(code、*emphasis*),说明 MD039 的空格定位逻辑并不因标记存在而失效——它作用于链接文本整体的首尾,而非单个标记字符。
3. 混合行:一行多个链接时逐个独立判定
[This line has multiple links. This does too and one has a space {MD039}这两行验证了 MD039 的逐链接独立判定能力:同一行内每个链接分别检查自己的文本。第一行两个链接都干净,不报错;第二行中[too ]存在尾部空格而被报告,同行的另外两个链接([This]、[space])不受牵连。这从测试角度印证了实现中对labeltoken 的逐个遍历逻辑。
四、测试快照:错误报告结构与自动修复结果
markdownlint 对每个场景测试文件都会生成一份快照(snapshot),记录两次关键信息:lint 产生的错误对象数组与应用全部修复后的文档内容。links-with-markup.md 的完整快照位于 test/markdownlint-test-scenarios.mjs.snapshot(搜索links-with-markup.md即可定位)。实际执行结果共报告3 处 MD039 错误,分别位于第 11、13、17 行,与文件中的三个{MD039}标记一一对应。
以第 11 行的错误为例,其对象结构如下:
{ "lineNumber": 11, "ruleNames": ["MD039", "no-space-in-links"], "ruleDescription": "Spaces inside link text", "errorDetail": null, "errorContext": "...k has `code` and right space ]", "errorRange": [38, 1], "fixInfo": { "editColumn": 38, "deleteCount": 1 }, "severity": "error" }关键字段的工程含义:
- errorContext:被截断的链接文本上下文(
...表示省略),用于在报告中快速定位问题区域; - errorRange:
[起始列, 长度],此处表示第 38 列起长度为 1 的字符是多余空格,这是编辑器"波浪线标红"的坐标依据; - fixInfo:
editColumn: 38, deleteCount: 1,即"从第 38 列开始删除 1 个字符",正是自动修复器要执行的操作; - severity:默认
error,可通过配置把 MD039 降级为warning或disabled。
快照末尾还给出了fixed字段——应用全部修复后的文档。将修复后的This link hascodeand right space与原文对比即可看到,三处多余空格被精确删除,而含行内标记的文本本身原样保留。修复后再次 lint 不再有任何 fixable 错误,这一"修复闭环"由 test/markdownlint-test.mjs 中"对 fixed 内容重新 lint 并断言无 fixable 错误"的逻辑保证。
五、源码实现:基于 Micromark 的 label 文本扫描
MD039 的实现位于 lib/md039.mjs,全文不到 60 行,核心思路是让 Micromark 解析器先完成词法切分,再对切分结果做精确的空格边界检查。规则主体:
export default { "names": [ "MD039", "no-space-in-links" ], "description": "Spaces inside link text", "tags": [ "whitespace", "links" ], "parser": "micromark", "function": function MD039(params, onError) { const labels = filterByTypesCached([ "label" ]) .filter((label) => label.parent?.type === "link"); for (const label of labels) { const labelTexts = label.children.filter((child) => child.type === "labelText"); for (const labelText of labelTexts) { if (labelText.text.trimStart().length !== labelText.text.length) { addLabelSpaceError(onError, label, labelText, true); } if (labelText.text.trimEnd().length !== labelText.text.length) { addLabelSpaceError(onError, label, labelText, false); } } } } };逐步拆解这段逻辑:
parser: "micromark":声明该规则依赖 Micromark 解析管线。仓库的解析入口在 lib/micromark-parse.mjs,它对标准 Micromark 增加了 GFM 表格、脚注、数学公式、autolink 等扩展,并输出带父子关系的 token 树;- 筛选 label token:
filterByTypesCached(["label"])从扁平 token 列表中取出所有label类型 token(带缓存加速),再通过label.parent?.type === "link"过滤出父节点是链接的 label——这一步排除了图片、引用定义等结构,确保只检查真正的链接文本。这也解释了为什么测试文件中"方括号里套图片"之类的写法不会误报; - 提取 labelText 子 token:每个链接 label 下可能有多个
labelText子 token。在含行内标记的链接中,code、*emphasis*等会被切分为独立的标记 token,而labelText只承载纯文本片段;links-with-markup.md 中[This link hascodeand right space ]这类混合内容会拆成多个 labelText,每个都独立执行首尾空格检查,因此标记内容不影响边界判断; - trimStart / trimEnd 对比:将
labelText.text与其trimStart()、trimEnd()的结果比较长度,长度不一致即说明该文本片段的首部或尾部存在空白,随即调用addLabelSpaceError报告错误。
addLabelSpaceError(lib/md039.mjs)负责计算精确的错误范围与修复信息:它用正则^[^\S\r\n]+(行首非空白)或[^\S\r\n]+$(行尾非空白)匹配多余空格,再依据labelText.startColumn/endColumn计算出errorRange,并生成{ editColumn, deleteCount }形式的fixInfo。错误上下文的最终格式化由 helpers/helpers.cjs 的addErrorContext完成——它会先把换行符归一化,再对过长的上下文做省略号截断(这正是快照中errorContext出现...的原因)。
从实现可以看出:MD039 的空格定位完全依赖 Micromark token 的精确行列信息,这与旧式 markdown-it 解析器的"正则盲扫"方案有本质区别——它天然知道哪些方括号是链接、哪些只是普通文本中的括号。测试文件 test/spaces_inside_link_text.md 第 105 行Not a link, just [ text in ] brackets正是为此设计:普通文本中的方括号即使含空格也不会被报告。
六、如何运行这条规则与测试
单独 lint 该测试文件
仓库根目录下执行(需要先npm install安装依赖):
npx markdownlint-cli2 "test/links-with-markup.md"更贴近仓库内部机制的方式是使用 test/harness.mjs 这个调试工具,它既能打印文件的 Micromark token 树,也能调用lint输出完整结果:
node test/harness.mjs test/links-with-markup.md运行完整测试套件
该文件属于"场景测试",由 test/markdownlint-test-scenarios.mjs 驱动,完整测试命令定义在 package.json 的testscript 中:
npm test测试框架会对test/下所有.md文件执行 lint,将结果与文件中{MD039}等标记比对,并与快照文件进行快照比对。若你修改了测试用例本身,可通过以下命令更新快照:
npm run update-snapshots七、延伸:更多边界场景参考
若想深入了解 MD039 的全貌,推荐阅读仓库中更全面的测试文件 test/spaces_inside_link_text.md,它额外覆盖了本用例未涉及的多类边界情况:
- 空链接文本:
[ ](https://bar/)也会报 MD039([ ]中有一个空格); - 引用式链接:
[link ][ref]、[ ref]、[ref ]等引用/快捷引用形式同样受检查; - 换行包裹的链接:
\n link...这类跨行写法同时触发 MD009(尾随空格)与 MD039,两者按{MD009:-1} {MD039:-1}标记在快照中共同出现,可用于观察多规则叠加的报告顺序; - 非链接的方括号:普通文本
[ text in ] brackets、图片not links不被报告,验证了parent?.type === "link"过滤逻辑。
结合 doc/md039.md 的规则说明、links-with-markup.md 的标记混合场景、spaces_inside_link_text.md 的边界矩阵,以及 lib/md039.mjs 的实现,你便掌握了 MD039 从"规则定义 → 词法解析 → 错误报告 → 自动修复"的完整链路。在接入 markdownlint 的 CI 或编辑器插件时,遇到"链接文本空格"类告警,即可依据本文所述判定标准快速判断并修复。
- 开发工具
- 代码质量
- Lint
- 静态分析
【免费下载链接】markdownlint
A Node.js style checker and lint tool for Markdown/CommonMark files.
相关推荐
markdownlint MD039 规则详解:链接文本中的空格检测与自动修复
markdownlint MD039 规则详解:链接文本中的空格检测与自动修复 本文围绕 markdownlint 规则 MD039(Spaces inside
开发工具代码质量Lint静态分析markdownlint MD039 规则详解:识别与自动修复链接文本内部的空格问题
markdownlint MD039 规则详解:识别与自动修复链接文本内部的空格问题 本文基于 markdownlint 仓库中的 MD039 规则文档 htt
开发工具代码质量Lint静态分析markdownlint 规则 MD011(no-reversed-links):反转链接语法检测原理与实战修复
markdownlint 规则 MD011(no reversed links):反转链接语法检测原理与实战修复 MD011 (别名 no reversed l
开发工具代码质量Lint静态分析
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考