news 2026/9/19 19:06:36

Prettier Markdown 脚注定义格式化全解析:长段落折行、proseWrap 策略与源码实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Prettier Markdown 脚注定义格式化全解析:长段落折行、proseWrap 策略与源码实现

Prettier Markdown 脚注定义格式化全解析:长段落折行、proseWrap 策略与源码实现

【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址: https://gitcode.com/gh_mirrors/pr/prettier

本文以 Prettier 仓库中的脚注定义格式化测试夹具tests/format/markdown/footnoteDefinition/long.md为核心,深入剖析 Prettier 如何处理 Markdown 脚注定义(footnote definition)中的超长段落。读完本文,你将掌握proseWrap三种取值对脚注折行的实际影响、脚注内容固定 4 空格缩进的底层原理,以及如何通过源码与快照测试验证这些格式化行为。

一、脚注定义语法与测试夹具定位

Markdown 脚注(footnote)由两部分组成:正文中的脚注引用[^label]与文末的脚注定义[^label]: content。Prettier 在格式化 Markdown 时,会对脚注定义进行专门的排版处理。仓库中与之对应的测试目录是 tests/format/markdown/footnoteDefinition,包含四个输入夹具与配套快照:

文件覆盖场景
long.md超长段落脚注定义(含续行)
simple.md最短的单行脚注定义
multiline.md包含代码块、引用块的脚注定义
sibling.md大量相邻脚注定义与多行引用内容

这些夹具由 format.test.js 驱动,分别以proseWrap: "always"proseWrap: "never"proseWrap: "preserve"tabWidth: 3四组选项运行格式化,输出结果全部记录在 format.test.js.snap 快照中。该测试通过仓库统一的格式测试基架(见 format-test-setup.js)执行。

二、long.md 场景还原:三种 proseWrap 模式的行为对比

long.md的输入极其简短,仅三行,但覆盖了两个典型形态:

[^hello]: this is a long long long long long long long long long long long long long paragraph. [^world]: this is a long long long long long long long long long long long long long paragraph. this is a long long long long long long long long long long long long long paragraph.

其中[^hello]是单行定义;[^world]的第三行是前一行段落的续行(Markdown 语法中,同一段落可以跨多行源码),因此[^world]是一个跨两行的段落。这两种形态恰好触发了 Prettier 不同的格式化分支。

1.proseWrap: "always":一律块级排版并按 80 列折行

快照中long.md - {"proseWrap":"always"}的输出为:

[^hello]: this is a long long long long long long long long long long long long long paragraph. [^world]: this is a long long long long long long long long long long long long long paragraph. this is a long long long long long long long long long long long long long paragraph.

关键行为:

  • 标签与内容分行:两个脚注的[^label]:都单独成行,正文缩进 4 个空格。
  • 按 printWidth(默认 80)折行:正文在超过 80 列时自动换行,续行同样缩进 4 个空格。
  • 续行并入同一段落[^world]的续行与首行合并为同一个段落后统一重排,不再保留源码的换行位置。

2.proseWrap: "never":全部内联为单行

[^hello]: this is a long long long long long long long long long long long long long paragraph. [^world]: this is a long long long long long long long long long long long long long paragraph. this is a long long long long long long long long long long long long long paragraph.

此时无论内容多长都不折行:定义内容紧跟[^label]:后内联输出,[^world]的续行也被合并为一行,完全忽略 printWidth 限制。

3.proseWrap: "preserve":保留源码的换行形态

[^hello]: this is a long long long long long long long long long long long long long paragraph. [^world]: this is a long long long long long long long long long long long long long paragraph. this is a long long long long long long long long long long long long long paragraph.
  • [^hello]在源码中是单行,输出保持内联单行;
  • [^world]在源码中跨两行,输出转为块级形态:标签单独一行、正文按源码原有的行边界逐行保留,仅统一缩进 4 个空格,不按 80 列重新折行

行为对照表

proseWrap单行定义跨行定义是否按 80 列折行标签位置
always块级(标签换行)块级,段落合并重排独立一行
never内联单行内联单行,段落合并与内容同行
preserve内联单行块级,保留原行边界视源码形态而定

三、源码级原理:footnoteDefinition 的打印逻辑

上述行为并非黑盒魔法,其实现位于 src/language-markdown/print/mdast.js 的footnoteDefinition分支:

case "footnoteDefinition": { const shouldInlineFootnote = node.children.length === 1 && node.children[0].type === "paragraph" && (options.proseWrap === "never" || (options.proseWrap === "preserve" && node.children[0].position.start.line === node.children[0].position.end.line)); return [ printFootnoteReference(node), ": ", shouldInlineFootnote ? printChildren(path, options, print) : group([ align( " ".repeat(4), printChildren(path, options, print, { processor: ({ isFirst }) => isFirst ? group([softline, print()]) : print(), }), ), ]), ]; }

逐一拆解:

1. 内联判定的三重条件(shouldInlineFootnote

脚注定义只有在同时满足以下条件时才以内联形态输出:

  • 定义体只有一个子节点,且类型是paragraph(即纯段落,不含代码块、引用块等);
  • proseWrap === "never",或proseWrap === "preserve"且该段落在源码中起始行号与结束行号相同(单行)。

这解释了long.md中的现象:always模式下永远走块级分支;never模式下只要体是单段落就内联;preserve模式下单行定义内联、跨行定义转为块级。

2. 固定 4 空格缩进(align(" ".repeat(4), ...)

块级分支通过align将子节点整体缩进 4 个空格——这是硬编码的,不随tabWidth变化。这正是快照中tabWidth: 3的用例输出与preserve完全一致、缩进仍为 4 空格的原因。

3. 首行软换行(group([softline, ...])

块级分支对第一个子节点前置softline,并包在group中:若整体宽度放得下则softline折叠为空格(标签与内容同行,如multiline.md中较短的脚注),放不下则断行(标签单独成行,如long.md中的always输出)。

4. 段落折行引擎(fill

块级正文的按宽度折行由 src/language-markdown/print/sentence.js 中的printSentence实现——它遍历段落的单词与空白节点,用fill(parts)组装文档,fill在超出 printWidth 时自动插入换行。因此always模式下的脚注正文与普通段落享受同一套折行机制。

四、相邻定义与多行内容的格式化细节

sibling.mdmultiline.md补充了更复杂的场景:

相邻定义强制用空行分隔:无论哪种proseWrap模式,连续出现的多个脚注定义在输出中都会被空行隔开,避免定义块粘连(见 sibling.md 的快照输出)。

多行引用块[^a]: > 123\与续行> 456这类多行 blockquote 在块级分支下,引用内容整体缩进 4 空格:

[^a]: > 123\ > 456

含代码块的脚注体:当定义体不是单一段落(例如段落加代码块)时,shouldInlineFootnote恒为false,即使proseWrap: "never"也会走块级分支。此时短段落仍可与标签同行(group 不断行),而长段落会迫使 group 断行、标签独立成行,代码块内容按 Markdown 语法保持 4 空格缩进。完整输入输出可对照 multiline.md 与对应快照。

五、proseWrap 选项:定义、默认值与配置方式

proseWrap是影响脚注排版的核心开关,在 src/common/common-options.evaluate.js 中定义:

属性
类型choice(枚举)
默认值"preserve"
可选值"always"(超宽即折行)、"never"(不折行)、"preserve"(保持原样)

它是 Prettier 的通用选项(Markdown 语言插件在 src/language-markdown/options.js 中直接复用),可通过配置文件或 CLI 指定:

// prettier.config.js export default { proseWrap: "always", // 或 "never" / "preserve" };
prettier --prose-wrap always README.md

官方选项说明可进一步查阅 docs/options.md。需要留意:proseWrap只影响段落类内容(含脚注定义正文)的折行,不影响代码块、表格等非散文内容。

六、如何复现与验证

若想在本地复现long.md的全部格式化行为,可直接对夹具运行 Prettier:

npx prettier --parser markdown --prose-wrap always tests/format/markdown/footnoteDefinition/long.md npx prettier --parser markdown --prose-wrap never tests/format/markdown/footnoteDefinition/long.md npx prettier --parser markdown --prose-wrap preserve tests/format/markdown/footnoteDefinition/long.md npx prettier --parser markdown --tab-width 3 tests/format/markdown/footnoteDefinition/long.md

四组命令的输出应与 format.test.js.snap 中long.md的四个用例一一对应。该夹具由 format.test.js 通过runFormatTest执行,属于仓库统一的格式测试体系,任何对 mdast.js 中脚注打印逻辑的改动,都需要让这组快照保持通过,从而保证了脚注格式化行为的长期稳定。

总结

从三行测试输入到完整的格式化语义,long.md这一个夹具便覆盖了 Prettier Markdown 脚注定义格式化的核心规则:proseWrap决定内联还是块级、是否折行;纯段落且满足行数条件才可内联;块级内容固定缩进 4 空格且不随tabWidth变化;折行复用fill段落引擎。理解 mdast.js 中shouldInlineFootnote的判定与align/softline/group的组合方式,你就能准确预测任意脚注定义在 Prettier 下的输出形态。

【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址: https://gitcode.com/gh_mirrors/pr/prettier

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 19:05:05

Windows 提示 Internet 安全设置阻止文件?一文讲透 MotW 标记与 exe 排障

今天聊聊一个几乎每个用 Windows 的人都躲不过去的场景:从网盘下载了一个 zip 压缩包,解压以后双击里面的 exe,蹦出来一个提示,内容是“你的internet安全设置阻止打开一个或多个文件”;或者更安静一点——双击了、转圈…

作者头像 李华
网站建设 2026/9/19 19:04:37

ESP32+MAX30102心率检测实战:I2C通信与PPG信号处理全链路解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 19:03:17

340+精选AI提示词模板:论文写作与PDF翻译的完整实操指南

340精选AI提示词模板:论文写作与PDF翻译的完整实操指南 【免费下载链接】awesome-prompts Curated list of chatgpt prompts from the top-rated GPTs in the GPTs Store. Prompt Engineering, prompt attack & prompt protect. Advanced Prompt Engineering pa…

作者头像 李华