- 开发工具
- 代码质量
- Lint
- 静态分析
【免费下载链接】markdownlint
A Node.js style checker and lint tool for Markdown/CommonMark files.
导读
本文以 markdownlint 仓库中的综合场景测试文件 test/MD011-MD021.md 为骨架,逐条剖析其中覆盖的 8 条核心规则——反向链接(MD011)、连续空行(MD012)、行长(MD013)、命令提示符(MD014)以及 ATX 标题空格系规则(MD018–MD021)。你将掌握每条规则的触发条件、参数配置、{MD###}行内标记的断言机制,以及 markdownlint 通过markdownlint-configure-file内联配置和applyFixes自动修复的完整工作流。
测试文件结构:用内联标记声明预期结果
test/MD011-MD021.md 是一个"自描述"的 Markdown 文件——它不仅是 lint 的输入样例,其内容本身也通过行内注释标明了每条规则期望报告的行号:
# Top level heading <!-- markdownlint-disable MD003 --> A (reversed)[link] example. {MD011} ... {MD012:7} {MD013:8} {MD013:10} <!-- markdownlint-configure-file { "line-length": { "heading_line_length": 40 } } -->其中的关键机制来自测试驱动 test/markdownlint-test-scenarios.mjs:测试框架用正则/\{(MD\d+)(?::([-+]?)(\d+))?\}/g扫描文件内容,将{MD011}解析为"第 5 行应报告 MD011",将{MD013:8}解析为"第 8 行应报告 MD013",支持{MD013:+2}这类相对行号写法(+/-前缀表示偏移)。随后框架对 lint 返回的ruleNames[0]与实际报告行号做深度比对,任何"多报"或"漏报"都会导致测试失败,这就是test/markdownlint-test-scenarios.mjs.snapshot中快照的来源。
值得注意的是,该文件第 3 行用<!-- markdownlint-disable MD003 -->关闭了标题风格一致性检查,避免标题用例干扰本文关注的规则;文件末尾的markdownlint-configure-file注释则在整文件范围内把line-length规则的heading_line_length调整为 40(详见下文 MD013 小节)。
MD011:反向链接语法(no-reversed-links)
触发条件与修复
MD011 在文本疑似链接、但方括号与圆括号顺序颠倒时触发,例如(reversed)[link]。规则文档 doc/md011.md 给出的正确写法是reversed,并特别说明 Markdown Extra 风格的脚注For (example)[^1]不触发该规则。
测试文件中第 5 行A (reversed)[link] example. {MD011}与第 26 行*Another* (reversed)[link] example. {MD011}两处命中,快照显示二者均被报告为errorDetail: "(reversed)[link]"、errorRange: [3,16]与[11,16],并携带自动修复信息insertText: "reversed"。
源码级实现原理
在 lib/md011.mjs 中,核心正则:
const reversedLinkRe = /(^|[^\\])\(([^()]+)\)\[([^\]^][^\]]*)\](?!\()/g;实现细节包括:
- 通过
filterByTypesCached(["codeFenced", "codeIndented", "mathFlow"])跳过围栏代码块、缩进代码块和数学公式块,避免误报代码内容; - 通过
filterByTypesCached(["codeText", "mathText"])排除行内代码与行内数学,并用hasOverlap做区间重叠判断; - 检测
linkText与linkDestination是否以反斜杠结尾,避免命中转义场景; - 命中后给出完整
fixInfo(editColumn、deleteCount、insertText),因此 MD011 位于 lib/constants.mjs 的fixableRuleNames列表中,可被applyFixes自动重排为reversed。
MD012:连续空行(no-multiple-blanks)
触发条件与参数
MD012 在文档中出现连续多个空行时触发。测试文件第 7 行附近存在两处连续空行,快照报告errorDetail: "Expected: 1; Actual: 2",且fixInfo.deleteCount: -1(表示在上一行末尾删掉一个换行)。
规则文档 doc/md012.md 给出参数:
| 参数 | 类型 | 默认值 | 含义 |
|---|---|---|---|
maximum | integer | 1 | 允许的最大连续空行数 |
代码块内部的连续空行不触发本规则。
源码级实现原理
lib/md012.mjs 的实现非常精简:先用filterByTypesCached(["codeFenced", "codeIndented"])收集代码块行号集合,然后逐行扫描,count = (inCode || line.trim().length > 0) ? 0 : count + 1——非空行或代码块内重置计数,连续空行累计,一旦count > maximum即报错并附带删除一个换行的修复信息。maximum通过Number(params.config.maximum || 1)读取,可直接在配置中调整。
MD013:行长(line-length)
参数表与三种检查模式
MD013 是本文档涉及参数最多的规则,完整参数见 doc/md013.md:
| 参数 | 类型 | 默认值 | 含义 |
|---|---|---|---|
line_length | integer | 80 | 普通行最大字符数 |
heading_line_length | integer | 80 | 标题行最大字符数 |
code_block_line_length | integer | 80 | 代码块行最大字符数 |
headings | boolean | true | 是否检查标题 |
code_blocks | boolean | true | 是否检查代码块 |
tables | boolean | true | 是否检查表格 |
strict | boolean | false | 严格模式:任何超长行都报错 |
stern | boolean | false | 严格但允许整行无空格 |
默认行为存在"无空格豁免":只要超出限制的部分没有空格(如长 URL),就不报错。strict关闭该豁免;stern则只对"可换行但未换行"的行报错。此外,链接/图片的引用定义行、以及仅包含单个链接/图片(可带强调)的独立行,即使在strict模式下也永远豁免,因为这类行无法在不破坏 URL 的前提下拆分。
测试文件中的应用
测试文件第 8 行是 99 个字符的长行(Expected: 80; Actual: 99),第 10 行是超长的 H2 标题。文件末尾的内联配置把标题行上限收窄:
<!-- markdownlint-configure-file { "line-length": { "heading_line_length": 40 } } -->因此快照中第 10 行报告Expected: 40; Actual: 62——同一规则对普通行与标题行使用了不同阈值,这正是heading_line_length独立生效的实证。
源码级实现原理
lib/md013.mjs 通过filterByTypesCached收集atxHeading/setextHeading、codeFenced/codeIndented、table、autolink/image/link/literalAutolink等 token 的行号区间,逐行按inCode → codeLineLength、isHeading → headingLineLength、否则lineLength选取阈值;并用line.replace(/\S*$/u, "#")实现"无空格豁免"(strict/stern模式下不替换)。definitionLineIndices(来自getReferenceLinkImageData())实现引用定义豁免,linkOnlyLineNumbers实现"纯链接独立行"豁免。MD013 本身不可自动修复(fixInfo: null),需要手动换行。
MD014:命令前的美元符号(commands-show-output)
触发条件
MD014 在代码块展示 shell 命令、且所有命令都以$开头、同时没有任何输出行时触发。规则文档 doc/md014.md 强调三个边界:
- 若命令后附带了输出(
$ ls后跟foo bar),不触发; - 若只有部分命令有输出,不触发;
- 只有"全部命令带
$且全部无输出"才触发。
测试文件第 12 行缩进代码块$ command with no output {MD014}命中,快照给出errorRange: [5,2](即$两字符)与fixInfo: { editColumn: 5, deleteCount: 2 }——自动修复会直接删掉美元符号与空格。
源码级实现原理
lib/md014.mjs 遍历codeFenced/codeIndented代码块,取出codeFlowValue子节点,用/^(\s*)(\$\s+)/匹配行首$。只有dollarMatches.length === codeFlowValues.length(全部行带$)时才报告所有命中行。这意味着只要代码块内出现一行未以$开头的文本(典型如命令输出),规则即整体放行。
MD018–MD021:ATX 标题空格四规则
这四条规则共同管理 ATX 风格标题(#开头)中"井号与标题文本之间"的空格数量,按"开式(不闭合)"与"闭式(行尾也加井号)"分为四组:
| 规则 | 别名 | 检查对象 | 判定 |
|---|---|---|---|
| MD018 | no-missing-space-atx | 开式标题 | 井号后缺少空格 |
| MD019 | no-multiple-space-atx | 开式标题 | 井号后多于一个空格 |
| MD020 | no-missing-space-closed-atx | 闭式标题 | 井号两侧缺少空格 |
| MD021 | no-multiple-space-closed-atx | 闭式标题 | 井号两侧多于一个空格 |
测试文件中的四组用例
测试文件 14–24 行密集放置了六条用例,恰好覆盖四种违规:
| 行号 | 原文 | 命中的规则 | 快照报告 |
|---|---|---|---|
| 14 | ##No space A {MD018} | MD018 | errorRange: [1,3],修复为在列 3 插入空格 |
| 16 | ## Multiple spaces B {MD019} | MD019 | errorRange: [4,1],删除第 4 列一个空格 |
| 18 | ##No space C {MD020} ## | MD020 | 左侧缺空格,整行重写为## No space C {MD020} ## |
| 20 | ## No space D {MD020}## | MD020 | 右侧缺空格,同样整行重写 |
| 22 | ## Multiple spaces E {MD021} ## | MD021 | 左侧多空格,删除第 4 列一个空格 |
| 24 | ## Multiple spaces F {MD021} ## | MD021 | 右侧多空格,删除第 30 列一个空格 |
从快照可见:MD018/019 的修复是"局部插入/删除",而 MD020 因可能同时涉及左右两侧,采用editColumn: 1, deleteCount: 行长度, insertText: 规范化整行的重写式修复——这也解释了 lib/md020.mjs 中同时判断leftSpaceLength与rightSpaceLength/rightEscape的原因。
源码级实现原理
MD018 在 lib/md018.mjs 中用正则/^#+[^# \t]/定位"井号后紧跟非井号非空格"的行,同时排除以#结尾的闭式标题、排除#️⃣emoji 序列,并跳过codeFenced/codeIndented/htmlFlow区间,修复方式是editColumn: hashCount + 1, insertText: " "。
MD019 与 MD021 共用同一辅助函数validateHeadingSpaces,实现在 lib/md019-md021.mjs:基于 micromark 解析出的atxHeadingtoken,通过getHeadingStyle(heading)区分"atx"与"atx_closed"两种风格;delta = 1正向扫描开式/闭式标题的起始空白,delta = -1仅对闭式标题扫描结尾空白。只有当whitespace.text.length > 1(多于一个空格)时才报告,修复为删除多余空格。这从实现层面印证:MD019 只检查开头,MD021 则开头、结尾都检查(闭式标题两侧皆可能违规)。
综合验证:快照与自动修复
test/markdownlint-test-scenarios.mjs.snapshot 中MD011-MD021.md条目完整记录了 13 个错误对象(2×MD011、1×MD012、2×MD013、1×MD014、1×MD018、1×MD019、2×MD020、2×MD021),并给出了applyFixes后的整文件内容:
# Top level heading <!-- markdownlint-disable MD003 --> A reversed example. {MD011} 123456789 ... ## 123456789 ... command with no output {MD014} ## No space A {MD018} ## Multiple spaces B {MD019} ## No space C {MD020} ## ## No space D {MD020} ## ## Multiple spaces E {MD021} ## ## Multiple spaces F {MD021} ##修复后再次 lint,markdownlint-test-scenarios.mjs会断言"可修复的错误均已清零"(fixableRuleNames中列出的规则必须全部修复),完成"报错 → 修复 → 复核"的闭环。你可以用同样的方式在本地验证:运行node test/markdownlint-test-scenarios.mjs(或直接node test/harness.mjs test/MD011-MD021.md查看 token 结构与 lint 结果),即可复现本文所有结论。
实战小结
通过这一份测试文件,可以提炼出四条可直接迁移到日常 Markdown 写作的准则:
- 链接语法:始终使用
文本,避免括号顺序颠倒(MD011),它可被自动修复; - 空行与行长:段落间最多保留一个空行(MD012),普通行控制在 80 字符内、标题行可通过
heading_line_length单独收紧(MD013),超长行需手动拆分; - 代码块命令:要么省略
$,要么展示命令输出,二者缺一不可(MD014),全无输出的$前缀命令会被自动删除提示符; - 标题空格:ATX 标题井号后必须有且仅有一个空格;使用闭式标题时两侧同样要求恰好一个空格(MD018–MD021),除 MD020 采用整行重写修复外,其余均为局部空格增删。
若需按项目自定义这些阈值,除了常规的options.config配置外,还可以像本测试文件一样使用<!-- markdownlint-configure-file { ... } -->内联注释按文件覆盖配置(语法见 README.md),实现"单文件级"的精细化规则调优。
- 开发工具
- 代码质量
- Lint
- 静态分析
【免费下载链接】markdownlint
A Node.js style checker and lint tool for Markdown/CommonMark files.
相关推荐
markdownlint 规则 MD021(no-multiple-space-closed-atx):闭合 ATX 标题井号内多余空格的检测与自动修复
markdownlint 规则 MD021(no multiple space closed atx):闭合 ATX 标题井号内多余空格的检测与自动修复 本篇文
开发工具代码质量Lint静态分析markdownlint 深入解析 MD011 规则:reversed link syntax(反转链接语法)的检测原理与自动修复
markdownlint 深入解析 MD011 规则:reversed link syntax(反转链接语法)的检测原理与自动修复 MD011 (别名 no r
开发工具代码质量Lint静态分析markdownlint MD039 规则详解:识别与自动修复链接文本内部的空格问题
markdownlint MD039 规则详解:识别与自动修复链接文本内部的空格问题 本文基于 markdownlint 仓库中的 MD039 规则文档 htt
开发工具代码质量Lint静态分析
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考