- 开发工具
- 代码质量
- Lint
- 静态分析
【免费下载链接】markdownlint
A Node.js style checker and lint tool for Markdown/CommonMark files.
Front matter(前置元数据)是 Jekyll、Hugo、Hexo 等静态站点生成器写作的标配:文章顶部用---包裹的 YAML 块存放title、date、categories等元信息。这类文档在正文中往往不再重复书写# 标题,而是直接从## 次级标题开始。markdownlint 为此设计了一套完整的 front matter 处理机制——默认剥离 front matter 区域、把其中的title属性视为文档的顶级标题(h1),并据此联动MD025(文档内多个顶级标题)、MD041(首行应为顶级标题)、MD001(标题层级递增)等规则。读完本文,你将理解 markdownlint 如何识别、剥离 front matter,title属性如何参与标题判定,以及如何用front_matter_title与level参数应对不同元数据格式与外部注入 h1 的场景。
从测试场景看合规写法:front matter 标题 + h2 正文
仓库中的 test/front-matter-title-h2.md 是一个典型的 Jekyll 博客文章场景,全文如下:
--- layout: post title: "Welcome to Jekyll!" date: 2015-11-17 16:16:01 -0600 categories: jekyll update --- ## Secondary heading Front matter from [Jekyll documentation](https://jekyllrb.com/docs/posts/#a-typical-post). <!-- markdownlint-configure-file { "line-length": false } -->这个文件由三部分构成:
- YAML front matter 块:包含
layout: post、title: "Welcome to Jekyll!"、date与categories,是 Jekyll 典型文章头部; - 正文起始的
## Secondary heading:一个 h2 级标题; - 内联配置注释:
markdownlint-configure-file把line-length(MD013)关闭,因为引用 Jekyll 文档的 URL 较长。
该文件对应的测试快照(见 test/markdownlint-test-scenarios.mjs.snapshot)结果为"errors": []——即整个文档没有任何规则违规。其合规性来自一个关键事实:front matter 中的title属性被 markdownlint 视为一个 h1 顶级标题,因此正文中不再需要(也不应该)出现第二个 h1;从##开始的次级标题完全合法,也不会触发 MD025(多个顶级标题)或 MD041(首行应为顶级标题)。
front matter 的识别与剥离:从正则到removeFrontMatter
默认的 front matter 匹配正则
markdownlint 对 front matter 的识别定义在 helpers/helpers.cjs 的frontMatterRe:
/((^---[^\S\r\n\u2028\u2029]*$[\s\S]+?^---\s*)|(^\+\+\+[^\S\r\n\u2028\u2029]*$[\s\S]+?^(\+\+\+|\.\.\.)\s*)|(^\{[^\S\r\n\u2028\u2029]*$[\s\S]+?^\}\s*))(\r\n|\r|\n|$)/m这个正则同时覆盖三种常见元数据格式:
- YAML:以
---开头并以---结尾(如本文的 Jekyll 场景); - TOML:以
+++开头并以+++或...结尾(Pandoc 风格); - JSON:以
{开头并以}结尾。
README 中的options.frontMatter一节(见 README.md)说明:该选项类型为RegExp,默认值即上述正则,用于匹配文件开头的 front matter;调用lintAPI 时可以传入自定义正则,或传null完全关闭此功能。注意:匹配必须发生在文件起始位置。
剥离流程:removeFrontMatter
文件内容读入后,lib/markdownlint.mjs 的removeFrontMatter函数会做两件事:
- 用
frontMatter正则匹配内容头部,若匹配成功且匹配位置在文件开头(!frontMatterMatch.index),就把匹配到的整块从正文中切掉; - 把切下的块按行拆分存入
frontMatterLines数组(去掉末尾空行),与剩余正文一起传给各规则。
随后 getEnabledRulesPerLineNumber 与各规则都能通过params.frontMatterLines访问到这些行——这就是frontMatterHasTitle得以工作的数据基础。同时,规则报告的违规行号也会统一加上 front matter 行数(见 lib/markdownlint.mjs),确保错误定位到真实文件行。
frontMatterHasTitle:title属性如何被"翻译"成顶级标题
识别 front matter 中是否存在标题属性的核心逻辑在 helpers/helpers.cjs:
module.exports.frontMatterHasTitle = function frontMatterHasTitle(frontMatterLines, frontMatterTitlePattern) { const ignoreFrontMatter = (frontMatterTitlePattern !== undefined) && !frontMatterTitlePattern; const frontMatterTitleRe = new RegExp( String(frontMatterTitlePattern || "^\\s*\"?title\"?\\s*[:=]"), "i" ); return !ignoreFrontMatter && frontMatterLines.some((line) => frontMatterTitleRe.test(line)); };几个值得注意的实现细节:
- 默认正则:
^\s*"?title"?\s*[:=],带i标志(不区分大小写),同时兼容title:(YAML/Pandoc 冒号形式)与title =(TOML 等号形式),并容忍行首缩进与两侧引号; - 关闭机制:当
frontMatterTitlePattern显式传入空字符串""时(!frontMatterTitlePattern为真),函数直接返回false——即完全忽略 front matter 标题; - 判定方式:只要 front matter 的任意一行命中正则即认为存在标题,因此
title在块中的位置无关紧要。
三个核心标题规则都调用该函数:
- MD025(single-title / single-h1):lib/md025.mjs 中
frontMatterHasTitle返回true时,hasTopLevelHeading直接置真,并把匹配到的所有顶级标题(默认level === 1)都视为违规(L18-L35)。也就是说:front matter 有title时,正文再出现任何 h1 都会被报告; - MD041(first-line-heading / first-line-h1):lib/md041.mjs 在
frontMatterHasTitle为真时直接跳过检查,因为标题已经由 front matter 承担,正文首行不必是 h1; - MD001(heading-increment):lib/md001.mjs 将
prevLevel初始化为hasTitle ? 1 : Number.MAX_SAFE_INTEGER,即把 front matter 标题当作已存在的 h1,后续正文从 h2 递增(h2 → h3 → …)完全合法。
对照实验:同场景不同写法的违规判定
仓库的测试目录围绕同一 Jekyll 场景设计了多组对照文件,可以直观看出title与正文标题的四种组合:
| 测试文件 | front matter 是否含title | 正文首个标题 | 判定结果 |
|---|---|---|---|
| front-matter-title-h2.md | 是 | ## Secondary heading(h2) | 无违规 |
| front-matter-title-h1.md | 是 | # Top level heading {MD025}(h1) | MD025 违规(第 7 行) |
| front-matter-title-no-h1.md | 是 | 无标题 | 无违规 |
| front-matter-no-title-no-h1.md | 否(notitle:) | 无标题 | MD041 违规(第 7 行) |
| front-matter-no-title-h1.md | 否(notitle:) | # Top level heading(h1) | 无违规 |
对照结论清晰:
- 有
title+ 正文 h1 →两个 h1 冲突,触发 MD025(快照见 test/markdownlint-test-scenarios.mjs.snapshot,违规行号 7、errorContext 为标题文本); - 有
title+ 正文从 h2 开始 →推荐的博客文章写法,零违规; - 无
title+ 正文无标题 → 文档缺标题,触发 MD041。
测试中的{MD025}、{MD041}标记由 test/markdownlint-test-scenarios.mjs 的测试脚手架解析:它读取每个.md文件,用正则/\{(MD\d+)(?::([-+]?)(\d+))?\}/g提取期望违规的行号与规则,再与lint实际输出比对(L40-L86),同时用applyFixes验证修复后的结果不再有可修复错误。这也是理解这些测试文件标记约定的入口。
配置参数:front_matter_title与level
front_matter_title:自定义标题属性的匹配模式
若 front matter 中的标题属性不叫title,可以通过front_matter_title参数传入正则文本。官方文档 doc/md025.md 与 doc/md041.md 均给出说明:默认值为^\s*title\s*[:=],传入""可完全禁用该机制。
仓库中的 test/front-matter-alt-title-h1.md 演示了自定义写法——front matter 使用alternate="Welcome to Jekyll!"属性,正文是# Top level heading {MD025},通过内联配置将single-title的front_matter_title改为^\s*alternate=:
<!-- markdownlint-configure-file { "line-length": false, "single-title": { "front_matter_title": "^\\s*alternate=" } } -->配置生效后,alternate被视为标题属性,正文的 h1 与之一并构成"多个顶级标题",从而正确触发 MD025。front_matter_title的 TypeScript 定义也出现在 lib/configuration-strict.d.ts 中(注释为 "RegExp for matching title in front matter")。
level:外部注入 h1 时的顶级标题调整
level参数(默认1)用于改变"顶级标题"的判定层级。典型场景是:文档的 h1 由站点模板、脚手架等外部机制统一注入,正文只从 h2 开始书写。此时将MD025的level设为2,正文中的 h2 即被当作顶级标题对待。同样,MD041也支持level参数(见 doc/md041.md),用于"首行应为顶级标题"的对应调整。
配置注入方式
以上参数既可以在lintAPI 的config对象中声明式配置,也可以通过文档内的markdownlint-configure-fileHTML 注释按文件覆盖(前文 alt-title 示例即此方式)。README 的 Configuration 一节(README.md)说明:传入lint的文本会被解析为 Markdown 并逐条检查,其中 HTML 注释与 front matter 两种内容会被多数规则忽略。
多格式变体:YAML、JSON、TOML 与 Pandoc
title标题机制不限于 YAML。仓库还覆盖了另外几种常见元数据格式,全部依赖frontMatterRe的多分支匹配与frontMatterHasTitle正则中[:=]对冒号/等号的兼容:
- JSON front matter:test/front-matter-title-json.md 以
{开头、}结尾,"title": "My document title and heading"; - TOML front matter:test/front-matter-title-toml.md 以
+++包裹,使用title = "..."等号形式; - Pandoc 风格:test/front-matter-title-pandoc.md 以
+++开头、...结尾,属性使用title:冒号形式。
这些变体的快照均为"errors": [],证明无论元数据采用哪种语法,只要包含标题属性,正文从普通文本或次级标题开始都不会被误报。
实战建议
结合上述机制,在 markdownlint 下编写带 front matter 的文档时,可遵循以下实践:
- 博客文章标准写法:front matter 中写好
title,正文从##次级标题开始,无需也不应重复 h1——这是零违规且语义最干净的结构,即 test/front-matter-title-h2.md 所示范的形态; - 迁移旧文档时:若既有正文以
#开头,优先删除正文 h1 或把它降级为 h2,而不是依赖配置豁免,以保持 MD025 的语义完整性; - 标题属性改名时:使用
front_matter_title传入自定义正则文本,可同时作用于 MD025、MD041、MD001 三个规则(三者的配置键均为front_matter_title); - 禁用该机制时:对确实不应把 front matter 标题当作文档标题的项目,统一配置
front_matter_title: ""; - 外部模板注入 h1 时:将 MD025/MD041 的
level调整为2,与正文实际使用的最高标题层级对齐。
通过理解frontMatterRe的多格式识别、removeFrontMatter的剥离流程与frontMatterHasTitle的正则判定,你可以精确预测 markdownlint 对任意带元数据文档的标题类规则行为,并借助front_matter_title与level参数将这套机制适配到自己的写作工作流中。
- 开发工具
- 代码质量
- Lint
- 静态分析
【免费下载链接】markdownlint
A Node.js style checker and lint tool for Markdown/CommonMark files.
相关推荐
markdownlint 实战:用 `front_matter_title` 让 TOML Front Matter 的 `alternate` 字段成为文档标题依据
markdownlint 实战:用 front_matter_title 让 TOML Front Matter 的 alternate 字段成为文档标题依据
开发工具代码质量Lint静态分析markdownlint 规则实战:Front Matter 无 title 时的首行 H1 校验(MD041)与单标题约束(MD025)
markdownlint 规则实战:Front Matter 无 title 时的首行 H1 校验(MD041)与单标题约束(MD025) 导读 在 Jekyl
开发工具代码质量Lint静态分析markdownlint 的 MD041 规则与 Front Matter 标题判定:从无标题无 H1 场景到源码级解析
markdownlint 的 MD041 规则与 Front Matter 标题判定:从无标题无 H1 场景到源码级解析 本篇指南聚焦 markdownlint
开发工具代码质量Lint静态分析
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考