☰
markdownlint 的 front matter title 解析:让 Jekyll 博客的 YAML 标题成为文档的 h1
2026/10/15 5:43:41 网站建设 项目流程
  • 开发工具
  • 代码质量
  • Lint
  • 静态分析

【免费下载链接】markdownlint

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

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

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 } -->

这个文件由三部分构成:

  1. YAML front matter 块:包含layout: post、title: "Welcome to Jekyll!"、date与categories,是 Jekyll 典型文章头部;
  2. 正文起始的## Secondary heading:一个 h2 级标题;
  3. 内联配置注释: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 的文档时,可遵循以下实践:

  1. 博客文章标准写法:front matter 中写好title,正文从##次级标题开始,无需也不应重复 h1——这是零违规且语义最干净的结构,即 test/front-matter-title-h2.md 所示范的形态;
  2. 迁移旧文档时:若既有正文以#开头,优先删除正文 h1 或把它降级为 h2,而不是依赖配置豁免,以保持 MD025 的语义完整性;
  3. 标题属性改名时:使用front_matter_title传入自定义正则文本,可同时作用于 MD025、MD041、MD001 三个规则(三者的配置键均为front_matter_title);
  4. 禁用该机制时:对确实不应把 front matter 标题当作文档标题的项目,统一配置front_matter_title: "";
  5. 外部模板注入 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.

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

相关推荐

上一篇:小米Miloco家庭AI管家:打造智能家庭的终极指南
下一篇:如何为Corne键盘选择最佳热插拔插座:Kailh与Gateron深度对比指南

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

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

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

立即咨询