☰
markdownlint 的 MD013 行长度规则:测试用例拆解、参数详解与源码实现
2026/10/16 6:20:54 网站建设 项目流程
  • 开发工具
  • 代码质量
  • Lint
  • 静态分析

【免费下载链接】markdownlint

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

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

行长度(Line Length)是 Markdown 文档中最常见的风格约束之一:过长的行在编辑器、Diff 与代码审查中都会带来阅读负担。本文以 markdownlint 的MD013 / line-length规则为核心,从仓库中的官方规则文档、真实测试用例(test/long_lines.md)与实现源码(lib/md013.mjs)三个维度出发,系统讲解该规则的 8 个配置参数、默认 /strict/stern三种检查模式、各类内建豁免场景以及底层解析原理。读完本文,你将能精确配置 MD013,理解它"该报不报"的每一种边界情况,并能读懂测试文件中的{MD013}断言标记,把规则用对、用透。

一、为什么需要行长度规则

极长的行在某些编辑器中难以处理:代码折叠失效、逐行浏览困难、并排 Diff 时需要横向滚动。md013 的官方文档(doc/md013.md)给出了规则存在的 Rationale:"Extremely long lines can be difficult to work with in some editors."

因此 markdownlint 将行长度作为一组内置规则之一提供,规则编号MD013,别名line-length,标签line_length。它默认启用,对所有 Markdown 行做 80 字符的长度检查,同时为链接、代码块、表格、标题等结构提供灵活的差异化配置,避免"一刀切"误伤真实场景。

二、规则总览与参数说明

MD013 的完整参数如下(全部来自 doc/md013.md):

参数类型默认值含义
line_lengthinteger80普通文本行的最大字符数
heading_line_lengthinteger80标题行的最大字符数
code_block_line_lengthinteger80代码块内每行的最大字符数
code_blocksbooleantrue是否对代码块启用检查
headingsbooleantrue是否对标题启用检查
tablesbooleantrue是否对表格启用检查
strictbooleanfalse严格模式:任何超过限制的行都报错
sternbooleanfalse审慎模式:可修复的过长行报错,无空格长行豁免

规则触发条件:当存在超过配置的line_length(默认 80 字符)的行时触发。修复方法:把行拆成多行。如需为标题单独设置上限,使用heading_line_length;为代码块单独设置上限,使用code_block_line_length。

在实现层面(lib/md013.mjs),各参数的默认值处理如下:

const lineLength = Number(params.config.line_length || 80); const headingLineLength = Number(params.config.heading_line_length || lineLength); const codeLineLength = Number(params.config.code_block_line_length || lineLength); const strict = !!params.config.strict; const stern = !!params.config.stern; const codeBlocks = params.config.code_blocks; const includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks; const tables = params.config.tables; const includeTables = (tables === undefined) ? true : !!tables; const headings = params.config.headings; const includeHeadings = (headings === undefined) ? true : !!headings;

可见heading_line_length与code_block_line_length都回退到line_length,未显式配置时三者统一为 80;三个开关型参数在未配置时默认视为true。

三、三种检查模式:默认、strict 与 stern

MD013 的检查行为由strict与stern两个布尔参数组合决定,这是该规则最核心的语义。规则文档给出了如下示例(假设使用默认line_length):

IF THIS LINE IS THE MAXIMUM LENGTH This line is okay because there are-no-spaces-beyond-that-length This line is a violation because there are spaces beyond that length This-line-is-okay-because-there-are-no-spaces-anywhere-within
  • 默认模式:只有第三行(在超长区间内出现空格、可以自然断行)报错;第二行虽然超过长度,但超出部分没有空格(可视为一个完整 URL 之类的 token),第四行整体没有空格,两者都豁免。
  • strict 模式:后三行全部报错——任何超过line_length的行都会被报告,包括无空格的长行。
  • stern 模式:中间两行报错,最后一行豁免——即"能断没断"的行报错,"根本没法断"的行放过。规则文档的原话是:stern用于"warn for lines that are too long and could be fixed but allow long lines without spaces"。

仓库中的三个测试文件恰好一一对应这三种模式:

  • 默认模式:test/long_lines.md
  • strict 模式:test/long-lines-strict.md
  • stern 模式:test/long-lines-stern.md

以 stern 测试(test/long-lines-stern.md)为例,其行内配置为:

{ "line-length": { "stern": true, "heading_line_length": 30, "code_block_line_length": 20 } }

该文件中ThisLineIsOkaytext...(第 15 行)、ThisTooLongHeadingIsOkaytext、ThisTooLongCodeIsOkay.等无空格长行均不报错,而普通带空格的长行(第 5、13、17、26、32、35、38、42 行)都被{MD013:行号}标注为违规。strict 测试(test/long-lines-strict.md)则在同样内容下多报了两处(第 9 行"barely too long"与第 22 行代码行),体现了"任何超过限制都报"的差异。

实现层面的模式差异

strict/stern的差别在源码中体现得非常直接(lib/md013.mjs):

// If not strict/stern, the last run of non-whitespace is allowed to go // beyond the limit as long as it begins within the limit const text = (strict || stern) ? line : line.replace(/\S*$/u, "#");

默认模式下,代码先把行尾最后一个非空白"词"用#替换后再比较长度——这就实现了"最后一个无空格 token 允许超出长度限制"的豁免。strict/stern模式下不做这个替换,所以这类行也会被纳入检查。

两种模式对"整行无空格"的处理则不同,核心是这条正则:

// Regular expression for a line that is not wrappable const notWrappableRe = /^(?:[#>\s]*\s)?\S*$/;
  • strict:无条件检查所有行,只要text.length > maxLength就报错;
  • stern:仅当行不匹配notWrappableRe(即行内有可断行的空格)时才报错;
  • 默认模式:既不检查行尾无空格 token 超限,也通过linkOnlyLineNumbers豁免整行只有链接的行(下文详述)。

四、内建豁免机制:哪些长行"应该"不报错

规则文档明确说明:带链接/图片引用定义的行,以及仅包含一个链接/图片(可带强调)的独立行,即使在strict模式下也始终豁免,因为拆分这类行往往会破坏 URL,没有合理的断行位置。

这些豁免在测试用例 test/long_lines.md 中被逐条验证,下面按场景拆解。

1. 普通过长行:报错

This is a very very very very very very very very very very very very very very long line {MD013}

第 3 行超过 80 字符且包含空格,测试以{MD013}标注期望违规。这是规则最基本的触发场景。

2. 无空格长行(URL 场景):默认豁免

This line however, while very long, doesn't have whitespace after the 80th columnwhichallowsforURLsandotherlongthings.

第 5 行虽然整体很长,但从第 80 列之后没有空格(形如 URL 的连续 token),默认模式不报错。这是规则文档描述的"exception when there is no whitespace beyond the configured line length"。

3. 整行仅含链接:豁免

以下三类"整行就是链接"的行(第 7、9、23、31、33、37 行等)默认一律豁免:

[This long line is comprised entirely of a link](https://example.com "This is the long link's title") > [This long line is comprised entirely of a link](https://example.com "This is the long link's title") [This long line is comprised entirely of a reference link and is long enough to violate the rule][label] [](https://example.com "This long line is comprised entirely of a link with empty text and a non-empty title") *[This long line is comprised of an emphasized link](https://example.com "This is the long link's title")* **[This long line is comprised of a bolded link](https://example.com "This is the long link's title")**

即使链接被强调(*...*)、加粗(**...**)或同时强调加粗(**_..._**),甚至链接文本为空,只要该行独立且只包含这一个链接结构,就豁免。这在源码中对应linkOnlyLineNumbers集合的构建逻辑:先收集所有autolink、image、link、literalAutolinktoken 所在的行,再排除掉"这些行同时也包含普通段落文本数据(datatoken)"的情况,剩下的就是"纯链接行"。

4. 链接之外还有文本:报错

This [long line is comprised mostly of a link](https://example.com "This is the long link's title") {MD013} [This long line is comprised mostly of a link](https://example.com "This is the long link's title") text {MD013} This long line includes a simple [reference][label] link and is long enough to violate the rule. {MD013}

第 17、19、21 行:链接前后还有普通文字,整行并非"纯链接",因此照常报错——豁免只保护"无法拆分"的纯链接行,不保护"可以换行"的混合行。

5. 链接/图片引用定义:豁免

[label]: https://example.org "Title for a link reference that is itself long enough to violate the rule"

第 25 行的引用定义本身超长,但引用定义行始终豁免。同样,第 61 行[image]: ...图片引用定义也豁免。源码依据是getReferenceLinkImageData()(定义于 helpers/helpers.cjs)返回结果中的definitionLineIndices:

const definitionLineIndices = new Set(getReferenceLinkImageData().definitionLineIndices);

这个集合记录了所有链接/图片引用定义所在的行,检查时直接跳过:

!definitionLineIndices.has(lineIndex) && ...

专门针对引用定义的测试见 test/long-lines-long-reference-definitions.md,其中长 URL 的引用定义、以及把长 URL 拆到第二行的"折行定义"([long-reference-definition-split]:换行后缩进续写)都在豁免之列,甚至在strict/stern模式(test/long-lines-long-reference-definitions-strict.md、test/long-lines-long-reference-definitions-stern.md)下依然豁免。

6. 非法链接标签:按普通文本处理

[Link to broken label][notlabel] {MD052} [notlabel\]: notlink "Invalid syntax for a link label because the right bracket is backslash-escaped {MD013}"

第 27 行引用了不存在的标签,同时触发 MD052(引用链接和图片)与 MD013 双重违规标注;第 29 行因为右方括号被反斜杠转义,标签语法不成立,整行按普通文本处理,照样超长报错——也就是说,豁免只授予"语法成立且整行独立的链接"。

7. 图片(内联与引用式):豁免

![Alternate text for long line image example](https://example.com "Title text for long line image example") ![Reference style for long line image which is itself an example of a long line with content][image] *![Alternate text for long line image example](https://example.com "Title text for long line image example")*

第 49、51、53、55、57、59 行:无论内联图片还是引用式图片,无论是否被强调或加粗,只要整行独立就豁免。图片在 micromark 解析中同样是imagetoken,自然进入linkLineNumbers集合。

8. HTML 注释:不豁免

<!-- Long lines inside HTML comments should also produce a violation of the line-length rule. {MD013} --> <!-- Long lines inside HTML comments should also produce a violation of the line-length rule. {MD013} --> Long lines inside HTML comments should also <!-- produce a violation of the line-length rule. {MD013} -->

第 63~74 行:多行 HTML 注释、单行 HTML 注释、以及行内嵌注释的文本,只要超过长度就报错。HTML 注释内容不是链接 token,也不享受任何豁免——这是很多初用者容易踩的坑:注释里的长行不会被放过。

9. Autolink 与裸链接:整行豁免、混合报错

<https://example.com/long-line-comprised-entirely-of-an-autolink-long-line-comprised-entirely-of-an-autolink> https://example.com/long-line-comprised-entirely-of-a-bare-link-long-line-comprised-entirely-of-a-bare-link Long <https://example.com/line-comprised-mostly-of-an-autolink-long-line-comprised-mostly-of-an-autolink> {MD013} Long https://example.com/long-line-comprised-mostly-of-a-bare-link-long-line-comprised-mostly-of-a-bare-link {MD013}

第 76、78 行:整行只有 autolink 或裸链接(bare URL)时豁免。第 80、82 行:链接前还有普通文字时照常报错。测试文件末尾通过行内配置注释关闭了no-bare-urls规则,避免裸链接本身触发 MD034 干扰测试:

<!-- markdownlint-configure-file { "no-bare-urls": false } -->

五、代码块、标题与表格的差异化配置

MD013 允许对三种结构单独设置长度或直接关闭检查,这在规则文档中被明确列为"optional to exclude this rule for code blocks, tables, or headings"。

代码块

{ "line-length": { "code_block_line_length": 30 } }

测试 test/long-lines-short-code.md 用code_block_line_length: 30验证:缩进代码块与围栏代码块中超过 30 字符的行报{MD013},而普通文本仍按 80 字符检查。规则文档特别说明:代码块默认纳入检查,因为"文档可读性通常是硬需求,且与代码风格规则大体兼容",但也承认"有些语言不适合短行"——这正是code_blocks: false存在的意义:

{ "line-length": { "code_blocks": false, "tables": false } }

test/long_lines_code.md 验证了在code_blocks: false且tables: false时,代码块与表格内的长行不再报错,而普通文本长行依旧报错。对照测试 test/long_lines_code-default.md 则展示默认配置下代码块和表格中的长行都会被报告。另外,表格中的每一行(包括分隔行)都在tabletoken 的起止行范围内,因此默认全表纳入长度检查。

标题

{ "heading-style": false, "line-length": { "heading_line_length": 30 } }

test/long-lines-short-headings.md 验证:ATX 标题(## ...)、闭合式标题(## ... ##)以及 setext 标题(下方-----下划线)超过heading_line_length: 30时都报{MD013},短标题则通过。源码中标题行通过filterByTypesCached([ "atxHeading", "setextHeading" ])收集行号范围,代码块行通过filterByTypesCached([ "codeFenced", "codeIndented" ])收集,表格行通过filterByTypesCached([ "table" ])收集,然后逐行选择对应的maxLength:

const maxLength = inCode ? codeLineLength : (isHeading ? headingLineLength : lineLength);

阈值联动

test/long-lines-thresholds.md 用一个配置同时验证三层阈值:

{ "code-block-style": false, "line-length": { "line_length": 40, "heading_line_length": 33, "code_block_line_length": 20 } }
  • 普通段落:40 字符上限,超长行报{MD013};
  • 标题:33 字符上限;
  • 缩进与围栏代码块:20 字符上限。

测试末尾的{MD013:-2} {MD013:-4} {MD013:-6}等负号标记表示"相对当前锚点上方第 2 / 4 / 6 行"的期望违规位置,这是 markdownlint 测试框架的定位语法(见测试运行入口 test/markdownlint-test.mjs 中的断言逻辑与 test/harness.mjs 的解析辅助)。

六、源码实现原理:MD013 的完整判定流程

把 lib/md013.mjs 的MD013函数整体梳理一遍,规则判定流程如下:

  1. 收集结构行号集合:遍历 micromark token 树,用addRangeToSet(定义于 helpers/micromark-helpers.cjs)把标题、代码块、表格的起止行区间分别写入headingLineNumbers、codeBlockLineNumbers、tableLineNumbers三个 Set。
  2. 收集链接行号:遍历autolink、image、link、literalAutolinktoken 得到linkLineNumbers;再遍历段落中的data文本 token 得到paragraphDataLineNumbers,两集合求差得到"整行只有链接"的linkOnlyLineNumbers。
  3. 收集引用定义行号:getReferenceLinkImageData()(helpers/helpers.cjs)返回definitionLineIndices。
  4. 逐行检查:对每一行,先确定maxLength(代码块 > 标题 > 普通文本的优先级),再判断是否被includeCodeBlocks/includeTables/includeHeadings排除、是否在引用定义行中、是否属于strict或stern的豁免集合,最后比较text.length与maxLength。
  5. 上报错误:命中则调用addErrorDetailIf(helpers/helpers.cjs)生成"Expected: 80; Actual: 123"形式的错误详情,并给出从maxLength + 1开始的错误范围([ maxLength + 1, line.length - maxLength ])。

值得注意的细节:默认模式对行尾无空格 token 的替换是line.replace(/\S*$/u, "#"),只替换最后一个连续非空白片段;而notWrappableRe判断的是整行是否"不可断行"(允许行首的标题符号、引用符号和空格)。两者结合,构成了默认与stern模式对"URL 长行"的完整保护。

七、边界情况与向后兼容:line_length 为负值

现实中存在line_length: -1这类"野生配置"(例如某些旧项目的 lint 配置)。test/long-lines-negative-line-length.md 专门覆盖了这一情况:负值无法作为合法长度,为了向后兼容,规则被整体禁用——maxLength > 0的检查条件让负数长度直接跳过所有报告。该文件也因此被validateJsonUsingConfigSchemaStrict测试列入忽略清单(见 test/markdownlint-test.mjs 中的ignoreFiles),避免用严格 Schema 校验这个"故意不合法"的配置。

八、与其他规则的协作

长行场景常常与多条规则同时触发,测试文件中清晰地展示了这种联动:

  • {MD046:13}:围栏代码块的起始围栏(第 13 行```markdown)同时命中 MD046(代码块样式);
  • {MD049}/{MD050}:下划线强调(_..._)与下划线加粗(__...__)在长链接测试行中被标注,因为 test/long_lines.md 使用了风格上应统一为星号的强调标记;
  • {MD052}:引用到不存在的链接标签(第 27 行);
  • 规则文档还提示,line_length与 Ciro Santilli 的 Markdown 风格指南一致(见 doc/md013.md 末尾的 "More information" 说明),这也是 markdownlint 内置cirosantilli风格配置(style/cirosantilli.json)的一部分。

九、实践建议:如何配置 MD013

根据文档与测试用例,可以总结出如下配置实践:

{ "MD013": { "line_length": 100, "heading_line_length": 80, "code_block_line_length": 80, "code_blocks": true, "tables": false, "headings": true, "strict": false, "stern": false } }

选择建议:

  • 团队普遍使用 80/100 字符编辑器宽度:仅调line_length即可,例如 test/long_lines_100.md 演示的line_length: 100;
  • 文档里大量出现长 URL:保持默认模式或stern: true,让无空格长行豁免;
  • 希望杜绝一切超长行:strict: true;
  • 代码示例中有些语言天然行很长:适当调大code_block_line_length或设code_blocks: false;
  • 表格列多导致行必然超长:设tables: false,或使用heading_line_length单独放宽标题。

需要修改配置时,可以通过 markdownlint 支持的 JSON / YAML / TOML / JS 配置文件设置,也可以在 Markdown 文件中用行内注释<!-- markdownlint-configure-file { ... } -->进行局部覆盖——本文引用的所有测试文件都是这样在文件末尾嵌入测试配置的,这也是复现各场景的最快方式。

十、如何亲手复现这些场景

在仓库中运行测试即可验证上述所有行为。markdownlint 的测试入口见 test/markdownlint-test.mjs,常规测试命令定义在 package.json 的test脚本中。若只想观察某个文件的 lint 结果,可以使用仓库提供的解析辅助脚本 test/harness.mjs:

node test/harness.mjs test/long_lines.md

该脚本会调用lint(来自markdownlint/promise)输出完整结果对象,包含每条违规的规则名、行号、错误详情与范围;配合--profile参数还可以做 1000 次循环的性能采样。修改测试文件中的{MD013}断言标记并重跑,即可验证你对豁免规则的理解是否正确。

结语

MD013 看似只做"数一数行有多长"的简单检查,实则围绕"哪些长行无法合理断行"这一核心问题,用strict/stern两种模式和链接、图片、引用定义等一套豁免体系,把误报降到了最低。理解 test/long_lines.md 中 86 行测试样例背后的每一处豁免与报错,就等于掌握了这条规则的全部语义——无论是调配置、读报告还是贡献规则,都能做到心中有数。

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

【免费下载链接】markdownlint

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

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

相关推荐

上一篇:CKEditor 5 Remove Format 功能完整指南:清除文本格式的原理、配置与自定义扩展
下一篇:Rerun DepthMeter 组件深度解析:深度图单位与世界坐标的换算原理

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询