
Prettier Markdown 格式化行为深度解析以 kitchen-sink 测试用例 test-case.md 为样本【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址: https://gitcode.com/gh_mirrors/pr/prettierPrettier 作为有主见的代码格式化器其 Markdown 支持同样遵循解析 AST 后按规则重排的核心哲学。本文以 tests/format/markdown/markdown/test-case.md 这份覆盖 Markdown 几乎所有语法元素的测试样本为主线逐项拆解 Prettier 对段落、标题、列表、引用块、代码块、链接、强调等结构的实际格式化规则并结合快照输出与 src/language-markdown 下的打印源码验证其底层实现。读完本文你将能准确预判 Prettier 对任意 Markdown 片段的格式化结果并理解仓库内 Markdown 回归测试的运作方式。一、测试用例的定位一份 Markdown 语法大全样本test-case.md源自著名的 github-markdown-kitchen-sink 示例文档一份刻意收集了 Markdown 各种写法的语法大全被 Prettier 仓库收录为 Markdown 打印器的回归测试输入。文件首行保留了对原始文档的链接说明其后依次排布了如下语法要素普通段落与缩进段落4 空格缩进的代码块Setext 标题Header 1与 ATX 标题#######含带闭合井号的写法# Header 1 #引用块普通文本、嵌套标题与列表、含缩进代码块的引用无序列表-、、*三种标记与有序列表1.主题分隔线* * *、***、*****、- - -、长-线行内链接、带 title 的链接、快捷引用式链接与引用定义强调*single*、_single_、**double**、__double__行内代码、缩进代码块、语言标注为 markdown 的围栏代码块图片语法与 HTML 注释。它与同目录下的 real-world-case.md一份模拟真实项目 README 的长文档样本互为补充前者覆盖语法宽度后者覆盖真实文档深度共同构成 markdown 目录下的两条核心测试基线。二、测试如何运行runFormatTest 与两种选项组合驱动该用例的测试文件 format.test.js 内容极简只有两行runFormatTest调用runFormatTest(import.meta, [markdown], { proseWrap: always }); runFormatTest(import.meta, [markdown], { proseWrap: always, singleQuote: true, });它揭示了两个关键信息测试固定开启proseWrap: always。这是影响 Markdown 输出的最重要的选项它决定段落prose文本在超过printWidth默认 80时是否强制换行重排。Prettier 的proseWrap选项共有always、preserve、never三档仓库对该用例选择最激进的always从而可以观察到完整的重排行为。singleQuote在 Markdown 中并非直接作用于正文而是作用于链接/图片的 title 属性引号详见下文第六节测试专门跑了两遍以覆盖双引号与单引号两种偏好下的输出差异。runFormatTest是仓库统一的快照测试框架测试执行时先格式化输入文件再将输入与输出按固定格式写入snapshots/format.test.js.snap后续运行逐字比对任何打印行为的变动都会导致快照 diff 从而暴露回归。在仓库根目录执行yarn jest tests/format/markdown/markdown即可单独运行本组测试修改了 Markdown 打印逻辑后这正是验证行为是否意外改变的第一道防线。三、Markdown 打印器的选项面为何只有两个选项从源码看Markdown 语言模块刻意保持极简的选项面。 src/language-markdown/options.js 全文如下import commonOptions from ../common/common-options.evaluate.js; const options { proseWrap: commonOptions.proseWrap, singleQuote: commonOptions.singleQuote, }; export default options;也就是说Markdown 打印器只从公共选项中暴露proseWrap与singleQuote两项其余选项缩进宽度、分号等对 Markdown 均不适用——这解释了为什么同一份test-case.md在两种选项组合下输出差异仅限于链接 title 的引号风格而段落换行、列表标记等行为完全一致。四、标题ATX 与 Setext 两种风格的取舍test-case.md同时包含了 ATX 标题# Header 1与 Setext 标题Header 1 下划线快照输出揭示了 Prettier 对二者的差异化处理。ATX 标题#级别保留但发生两处规范化——其一# Header 1 #这类带闭合井号的写法闭合井号会被移除统一为无闭合标记的 ATX 风格其二相邻标题之间会自动补足一个空行快照中# Header 1与## Header 2之间由输入的无空行变为输出的一空行分隔。Setext 标题Header 1的下划线标注被原样保留不会被改写为#风格。其实现见 src/language-markdown/print/heading.jsprintSetextHeading会回溯原始文本截取标题行下方的/-下划线并原样打印而 ATX 分支则是#.repeat(path.node.depth) 后拼接子节点内容heading.js。一个值得注意的例外是所有缩进 4 空格即缩进代码块中的标题无论 ATX 还是 Setext、无论是否带闭合井号都被原样保留——因为缩进代码块在解析阶段就被视为代码不参与标题语义。五、段落、引用块与 proseWrap 重排proseWrap: always最直观的效果体现在长文本段落上。test-case.md中块引用内那句超长的 Lorem ipsum 文本输入为一行输出则被按 80 列拆成三行且每一行都保留前缀 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.这正是prose 换行机制的体现。底层实现位于 src/language-markdown/print/paragraph.js段落打印先将子节点逐个打印再通过flattenFill把嵌套的fill文档展平后重新组装交给文档打印器的fill指令做能放得下就放、放不下就换行的度量式排版——这与代码打印器处理长函数调用链的思路同源都是 Wadler prettier 打印算法的典型应用。对比之下缩进 4 空格的代码块包括 Lorem ipsum...这类被整体缩进的内容不做任何重排长行原样保留。可见是否换行完全取决于内容在解析后属于 prose 还是 code。块引用还体现了另一个细节当块引用内同时包含标题与列表时 ## This is a header.之后会被补上一行独立的空行将标题与 1. This is the first list item.隔开保证块内结构清晰可读。六、列表无序标记交替、有序编号保留test-case.md连续给出了-、、*三种无序列表标记快照输出显示它们被统一为两种标记交替出现- Red - Green - Blue * Red * Green * Blue - Red - Green - Blue即第一组用-、第二组用*、第三组又用-。这一规律直接对应 src/language-markdown/print/list.js 中的前缀生成逻辑nthSiblingIndex % 2 0 ? - : * ——同级兄弟列表按索引奇偶交替使用-与*既保证了标记一致又在视觉上形成层次差异。有序列表则不同1. Buy flour and salt / 1. Mix together with water / 1. Bake三个条目在输入输出中均保持1.编号原样。这正是 Markdown 打印器git-diff-friendly的处理策略list.js 中的hasGitDiffFriendlyOrderedList逻辑不把1.展开为1. 2. 3.避免新增/删除条目时整段列表产生 diff 噪音若源码中的起始编号非 1则保留实际数值同样可见于 list.js 对node.start的处理并在超过 CommonMark 上限999_999_999时截断。七、主题分隔线五种写法统一为---用例给出了* * *、***、*****、- - -与一行超长的-共五种分隔线写法快照输出显示它们全部归一为---。这是 Markdown 打印器对 thematicBreak 节点的标准化处理语义等价的分隔线写法不保留原始花样统一输出最短的标准形式。同样缩进代码块内的分隔线写法* * *、***等因属于代码块而被原样保留。八、链接、引用定义与图片title 引号随 singleQuote 变化test-case.md覆盖了行内链接、带 title 链接、快捷引用式链接与引用定义四种形式。在proseWrap: alwayssingleQuote: true的组合下行内链接[an example](http://example.com Example)的 title 由双引号变为单引号Example引用定义[id]: http://example.com Optional Title同样变为Optional Title图片的 title 同步变为Image Title无 title 的链接[This link](http://example.com)不受影响。这解释了 format.test.js 为何要跑两组配置对比两组快照format.test.js.snap可见singleQuote只影响 title 引号这一处其余输出完全一致。同样地所有缩进代码块中的链接写法含双引号 title均原样保留。九、强调与行内代码*归一为___归一为**强调是 Markdown 中写法最自由的语法之一Prettier 在此同样执行语义等价则统一风格的策略输入输出*single asterisks*_single asterisks__single underscores__single underscores_**double asterisks****double asterisks**__double underscores__**double underscores**即单层强调统一使用_双层强调统一使用**__被改写为**。行内代码code则不做任何改动原样保留因为行内代码属于代码语义而非 prose。十、代码块缩进代码块保留、围栏代码块递归格式化test-case.md中代码块分为两类行为截然不同缩进代码块行首 4 空格整体原样保留。包括Paragraph:\n\n Code这种块内再缩进的嵌套结构以及缩进块内的标题、列表、链接、分隔线等一切内容都不被格式化。围栏代码块包裹这里是本用例最有意思的地方。快照显示语言标注为markdown的围栏块内部内容同样被格式化了——输入块内的 Red列表在输出中变为* Red* Red变为- Red与正文列表的交替规则完全一致。其原因是 src/language-markdown/embed.js 中的嵌入逻辑围栏代码块只要语言可被推断出对应 parsermarkdown恰好能被inferParser识别为 Markdown 自身就会通过textToDoc递归调用对应解析器重新格式化再以printCodeFences包回围栏外壳只有当语言缺失或无法推断如markdown之外的未知语言时才原样保留。而块内有序列表1. Buy flour and salt保持1.不变再次印证了有序编号的 git-diff-friendly 策略在递归格式化中同样生效。十一、快照回归测试的实践价值将 test-case.md 与snapshots/format.test.js.snap 中的输入、输出对照阅读可以在一个文件内完整推演出 Prettier 的 Markdown 格式化决策树内容属于 prose段落、块引用文本→ 按printWidth换行重排内容属于代码缩进块、行内代码、不可推断语言的围栏块→ 原样保留语法语义等价但写法多样分隔线、强调标记、列表标记、标题闭合井号、链接 title 引号→ 统一为规范形式有序列表编号 → 保持 diff 友好不重排编号。这套输入 快照的测试组织方式是 Prettier 全仓库通用的模式每个语法主题如 heading、list、link、blockquote、fenced-code-block 等目录都拥有独立的输入用例与对应快照任何打印规则的调整都必须同时通过全部用例。如果你正在调试某个 Markdown 边界行为最快的路径就是新增或修改一个tests/format/markdown/下的.md输入文件运行yarn jest观察快照 diff。十二、延伸阅读src/language-markdown/index.js 与 src/language-markdown/parsers.jsMarkdown 语言注册与解析器接入src/language-markdown/print/index.js各节点类型到打印函数的分发入口src/language-markdown/embed.js围栏代码块、MDX 导入/导出/JSX 的嵌入格式化src/language-markdown/print/code.js围栏代码块外壳的打印docs/options.mdproseWrap、singleQuote等选项的完整说明tests/format/markdown/markdown/real-world-case.md同一测试目录下的真实文档长样本可与test-case.md对照观察真实场景中的格式化效果。【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址: https://gitcode.com/gh_mirrors/pr/prettier创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考