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.md与multiline.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的全部格式化行为可直接对夹具运行 Prettiernpx 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),仅供参考