尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Prettier 如何对齐 Markdown 嵌套列表:深入 indent.md 格式化测试与源码实现

Prettier 如何对齐 Markdown 嵌套列表:深入 indent.md 格式化测试与源码实现 Prettier 如何对齐 Markdown 嵌套列表深入 indent.md 格式化测试与源码实现【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址: https://gitcode.com/gh_mirrors/pr/prettier本指南以 Prettier 仓库中的格式化测试用例 indent.md 为核心完整还原其输入结构、四组tabWidth配置下的快照输出并逐层剖析src/language-markdown中列表打印与对齐的源码实现。读完你不仅能看懂这份测试在验证什么还能掌握 Prettier 处理任务列表checkbox、嵌套有序/无序列表、超长续行段落以及缩进代码块边界时遵循的底层规则并学会在自己的环境里运行与扩展这类回归测试。一、这份测试文件到底在测什么tests/format/markdown/list/indent/indent.md表面上只是一段全部由超长行组成的 Markdown 列表文本它实际上是一个格式化回归测试的输入夹具fixture。Prettier 的格式测试体系约定tests/format/语言/主题/目录下的.md文件是输入同目录的format.test.js声明测试配置__snapshots__/format.test.js.snap记录期望输出。因此这份文件验证的是当列表项文字远超printWidth默认 80 列时Prettier 如何折行、如何计算续行缩进、如何在多层嵌套的混合列表中保持每一级的对齐一致。输入结构的五个层次仔细读 indent.md可以发现它刻意编排了 5 类元素并不断嵌套带复选框的无序列表项- [ ] a a a ...[ ]表示未勾选的任务项纯文字嵌套无序列表项- a a a ...纯文字嵌套有序列表项1. a a a ...超大数字编号的有序列表项12345678) a a a ...、12345678. [ ] a a a ...8 位数字标记用于探测对齐上限紧随列表项的续行段落b b b ...作为列表项的第二段验证“同一列表项内多段落”的缩进。整体结构为第一层是- [ ]复选框列表其下嵌套第二层无序-、带复选框的- [ ]、有序1.、带复选框的有序1. [ ]、8 位数字12345678)、带复选框的12345678. [ ]以及纯段落a a a随后第二层又嵌套出第三层第一层也换成了1. [ ]与12345678) [ ]形成 3 层混合嵌套。所有a/b行都被拉长到 80 列以上确保触发proseWrap: always的折行逻辑。配套测试脚本四组 tabWidth 压力测试format.test.js 只有 7 行却通过runFormatTest(import.meta, [markdown], {...})一次性跑了 4 组配置runFormatTest(import.meta, [markdown], { proseWrap: always }); runFormatTest(import.meta, [markdown], { proseWrap: always, tabWidth: 4 }); runFormatTest(import.meta, [markdown], { proseWrap: always, tabWidth: 999 }); runFormatTest(import.meta, [markdown], { proseWrap: always, tabWidth: 0 });proseWrap: always强制把超长段落按printWidth默认 80快照头部有printWidth: 80 (default)标注折行这是验证缩进对齐的前提——只有折了行续行缩进才有意义tabWidth取2默认、4、999、0四档2 与 4 是真实开发中的常用缩进宽度999 是极端值用来检验缩进空间是否存在硬上限0 用于验证边界行为。四组输出全部打进同一个快照文件任何一个输出发生变化测试都会失败从而捕获对齐回归。二、快照输出对比tabWidth 如何改变缩进形状snapshots/format.test.js.snap 中每组配置都包含完整的input/output区块。以下是从中提取的核心差异为便于阅读做了省略号截断实际快照为完整文本默认 tabWidth: 2- [ ] a a a a ... a a a a a a a a a ← 折行 a a a a ... a a a a a a a a a ← 续行对齐到内容列6 列 - a a a a ... a a a a a a a a ← 第二层缩进 2 空格 a a a a ... a a a a a a a a b b b b ... b b b b b b b b ← 第二层列表项内的续行段落对齐内容列4 列关键观察复选框[ ]被视为列表项前缀的一部分首行文字在[ ]之后开始折行后的续行对齐到- [ ]之后的内容列第 6 列嵌套子列表在父列表内容列基础上再推进 2 空格tabWidth宽度b段落与同一列表项的文字列对齐。tabWidth: 4- [ ] a a a a ... a a a a a a a a a a a a a ... a a a a a a a a a - a a a a ... a a a a a a a a ← 第二层缩进变为 4 空格 a a a a ... a a a a a a a a b b b b ... b b b b b b b b ← 续行段落缩进加深到 8 空格子列表缩进随tabWidth线性放大为 4 空格b段落从列 8 开始——说明缩进深度直接受tabWidth驱动。tabWidth: 999极端值- [ ] a a a a ... a a a a a a a a a a a a a ... a a a a a a a a a - a a a a ... a a a a a a a a ← 子列表只缩进 5 空格而非 999 a a a a ... a a a a a a a atabWidth被设为 999 时子列表缩进并没有变成上千空格而是停留在 5 空格。这是因为续行/嵌套的对齐空间被源码强制clamp到 03 的区间详见下一节印证了 Prettier 对“缩进代码块”边界的刻意防范。tabWidth: 0边界值tabWidth: 0的输出与默认值 2 几乎一致子列表同样为 2 空格、续行 4 空格说明当tabWidth过小时对齐逻辑回退到以实际前缀宽度为基准的最小可行缩进而不是产生零缩进或负缩进。对比方法提示这四份输出正是快照测试的价值所在——isAligned判断、alignListPrefix补空格、clamp上限等任何一处逻辑变化都会在这里留下可见痕迹。三、源码剖析列表打印与缩进计算链路Prettier 的 Markdown 打印核心位于 src/language-markdown/print/list.js缩进与对齐的完整链路可分为四条规则。1. 列表标记的交替生成getPrefixprintList中每个列表项的标记由getPrefix()生成无序列表nthSiblingIndex % 2 0 ? - : * ——同一列表内奇偶项交替使用-与*有序列表nthSiblingIndex % 2 0 ? . : ) ——数字后交替使用.与)编号本身首项用node.start其后按node.start path.index递增但被MAXIMUM_ORDERED_LIST_MARKER 999_999_999截断list.js 注释引用了 CommonMark 0.31.2 规范的有序列表标记上限nthSiblingIndex来自 utilities.js 的getNthListSiblingIndex它只统计连续的同类型列表兄弟有序/无序各自计数遇不同类型的列表项则重置这正是indent.md中-、1.、12345678)混排时各自独立交替的原因。indent.md顶层的- [ ]、12345678) [ ]等项还触发了getPrefix中的“最小缩进”逻辑prefix.length minIndent时直接使用否则先补尾随空格最多 4 个再补前导空格最多 3 个两处注释都写着// 4 will cause indented code block。2. 复选框前缀与续行对齐printListItemprintListItem把[x]/[ ]当作独立的 checkbox 前缀node.checked null ? : node.checked ? [x] : [ ] 。第一个非列表子节点通过align( .repeat(prefix.length), ...)与复选框内容列对齐对应indent.md中- [ ]后a行的续行对齐到第 6 列后续子节点如b段落与后续嵌套列表则使用const alignment .repeat( clamp(options.tabWidth - listPrefix.length, 0, 3), // 4 will cause indented code block );这就是上一节对比结果的直接来源tabWidth: 2时clamp(2 - 2, 0, 3) 0子列表在父列表内容列基础上不额外推进tabWidth: 4时clamp(4 - 2, 0, 3) 2子列表额外推进 2 空格连同父级align共 4 空格tabWidth: 999时被clamp到上限 3因此出现“5 空格缩进”而非天文数字tabWidth: 0时回退到 0输出与默认值一致。clamp定义在 list.js上界 3 是刻意的CommonMark 中一行缩进4 个及以上空格会被解析为缩进代码块Prettier 必须保证任何对齐空格都小于 4否则输出会被重新解析成代码块产生语义漂移。3. 复选框 缩进代码块的强制深度requiredIndentrequiredIndentlist.js处理一个更刁钻的场景复选框列表项后紧跟一个缩进代码块。此时若列表内容缩进不够深后续代码块会被误解。函数计算return ( 4 // base indent of the code block [...leadingSpaces].reduce( (count, char) count (char \t ? 4 : 1), 0, ) 1 // at least one space more than the code block );即“代码块基准缩进 4 代码块自身前导空格 至少 1 个额外空格”把列表内容压到比代码块更深的位置。这是indent.md中getPrefix里minIndent的取值来源之一。4. 有序列表的对齐判定与补位isAligned / alignListPrefix有序列表数字位数不同如1.与12345678)时内容列会错位。Prettier 在预处理阶段用markAlignedListpreprocess.js判断列表是否“已对齐”规则包括无序列表恒为对齐首项前导空格超过 1 个 → 视为已对齐单列表项起始列能被tabWidth整除才算对齐多列表项各项起始列必须一致且满足firstStart % options.tabWidth 0或第二项的leadingSpaces.length 1若某个祖先列表未对齐其后代列表一律视为未对齐parentStack检查列表后紧跟缩进代码块时无法安全对齐强制置为false。对齐的列表在打印时调用alignListPrefixlist.js在标记后补齐空格使内容列落到下一个tabWidth边界const restSpaces prefix.length % options.tabWidth; return restSpaces 0 ? 0 : options.tabWidth - restSpaces;同样地additionalSpaces 4 ? 0 : additionalSpaces再次守住 4 空格红线。indent.md中的12345678)/12345678.项正是为了覆盖isAligned的“前导空格 1”与“起始列取模”分支以及alignListPrefix的补位计算与 align.md 等专门用例互为补充。四、向周围扩散这个测试家族还验证了什么indent.md并非孤立用例tests/format/markdown/list/下的其他夹具共同构成列表格式化测试矩阵理解它们能帮你定位同一类问题git-diff-friendly.md配合hasGitDiffFriendlyOrderedListutilities.js当有序列表从 0 或 1 重新编号时保持编号为 1减少 diff 噪音huge-first-number.md覆盖超大起始编号与MAXIMUM_ORDERED_LIST_MARKER边界nested.md、nested-checkbox.md、checkbox.md纯嵌套、复选框嵌套与顶层复选框的专项用例tab.md、nested-tab.md、tab-width/Tab 字符与tabWidth的交互含indented-code-block.mdcombined-lists.md、interrupt.md、long-paragraph.md混合列表、段落打断与长段折行的组合场景。这些用例与indent.md共同说明Prettier 的 Markdown 列表缩进不是“数几个空格”的简单拼贴而是前缀宽度、tabWidth、printWidth与 CommonMark 缩进代码块规则四者之间的约束求解。五、如何在本地验证这份测试仓库采用 Jest snapshot 体系根目录 jest.config.js你可以只跑这一个目录# 在仓库根目录执行 yarn jest tests/format/markdown/list/indent无输出差异测试通过快照无需更新若你改动了 src/language-markdown/print/list.js 或预处理逻辑可用-u更新快照后人工 diff 四组output区块检查缩进是否符合预期yarn jest tests/format/markdown/list/indent -u若想观察更多组合可仿照 format.test.js 增加printWidth、singleQuote等配置行Markdown 语言支持的选项定义在 src/language-markdown/options.js目前仅导出proseWrap与singleQuote。六、小结从一份“只是很长”的 indent.md 出发我们还原了 Prettier 列表格式化的完整决策链getNthListSiblingIndex决定标记交替requiredIndent守住缩进代码块的最小深度printListItem的clamp(tabWidth - prefix.length, 0, 3)决定续行与嵌套的空间markAlignedList判定有序列表是否对齐alignListPrefix把内容列推进到tabWidth边界——而所有规则之上悬着一条共同的红线任何对齐空格不得超过 3 个因为 4 个空格会把内容变成缩进代码块。理解这条链路后你不仅能读懂这套快照测试也能在遇到自定义 Markdown 风格时准确判断哪些缩进是 Prettier 必须保留的语义约束哪些可以安全调整。【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址: https://gitcode.com/gh_mirrors/pr/prettier创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表