中 prettier-ignore 的行为解析:测试用例与源码原理深度解读)
开发工具格式化CLI【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址https://gitcode.com/gh_mirrors/pr/prettier点击查看免费下载在 Markdown 文档中!-- prettier-ignore --注释用于跳过下一段内容的格式化但当它出现在引文块blockquote、嵌套代码围栏fence、列表项等复杂结构中时其行为会变得微妙。本文以 Prettier 仓库中tests/format/markdown/blockquote/ignore-code.md测试用例为骨架结合src/language-markdown下的源码实现与快照测试结果系统解析 prettier-ignore 在 Markdown 引文块中的生效边界、跨语言代码块的忽略机制以及proseWrap三种取值下的行为差异帮助读者准确预判并掌控 被引用的代码块 的格式化结果。一、用例背景为什么需要专门测试 引文块中的 ignoreMarkdown 引文块前缀的行是文档中频繁出现的结构常被用来承载示例代码、引用段落或嵌套说明。当其中混入代码围栏和prettier-ignore注释时格式化器的行为涉及两层解析外层 Markdown 解析引文块中的每一行都带有前缀代码围栏的起始符、结束符与内部内容都必须正确剥离前缀后再解析内层代码语言解析被围栏包裹的代码如 JS是否跳过格式化取决于!-- prettier-ignore --是否被正确识别。tests/format/markdown/blockquote/ignore-code.md下称 该用例文件正是为此设计的回归测试输入它通过runFormatTest在三种proseWrap取值下分别运行格式化并产出快照tests/format/markdown/blockquote/__snapshots__/format.test.js.snap。用例文件本身仅包含 7 个输入片段却覆盖了引文块内嵌套代码围栏列表项内的引文块引文块包裹引文块无引文前缀的顶层代码围栏直接出现在引文块段落中的 ignore 注释等多种组合是理解 Prettier Markdown 格式化边界的最佳实验样本。二、测试驱动方式如何运行与验证该用例该用例由tests/format/markdown/blockquote/format.test.js驱动其核心只有三行runFormatTest(import.meta, [markdown], { proseWrap: always }); runFormatTest(import.meta, [markdown], { proseWrap: preserve }); runFormatTest(import.meta, [markdown], { proseWrap: never });这意味着同一个输入文件会分别在proseWrap: always、preserve、never三种配置下各格式化一次并与快照文件中的期望输出比对。通过输入与输出的差异即可精确锁定 ignore 注释在不同场景下的生效范围。运行方式为yarn jest tests/format/markdown/blockquote若需单独验证快照更新可使用-u更新快照。目录下的兄弟用例文件如 code.md用于对照引文块内普通 JSON 代码围栏的格式化行为从而反衬 ignore 用例的特殊性。三、七个测试片段逐段精读输入与输出的差异分析以下将用例文件中的 7 个片段逐一拆解结合快照中的输出format.test.js.snap说明每种结构下的实际行为。片段 1引文块内的四反引号 三反引号嵌套围栏JS 代码保持原样输入 md !-- prettier-ignore -- js ugly ( code ) ; 这是一个引文块中的 4 反引号围栏语言标记md内再嵌套 3 反引号 JS 围栏的结构。4 反引号用于让外层代码块内安全地展示内层 3 反引号围栏。输出与输入完全一致ugly ( code ) ;中的多余空格和分号前空格均被保留。这里出现了 Prettier 的一个关键行为外层md围栏中的内容本质是Markdown 代码而代码块内部的!-- prettier-ignore --只是普通文本并不会触发忽略机制。真正让 JS 保持原样的是Prettier 不会格式化代码块中的内容——Markdown 中的代码围栏内容默认按原文输出。因此即使没有 ignore 注释ugly ( code ) ;也不会被改变。该片段验证的是引文块 双层围栏不会导致代码内容被意外改写。片段 2引文块内嵌md代码围栏长段落保持原样输入 md !-- prettier-ignore -- - This is a long long long long long long long long paragraph. 输出同样与输入一致。这里的md围栏内放置了一个长列表段落若不忽略proseWrap: always下会被按 80 列自动换行。但由于整个内容处于代码围栏中Prettier 视其为不可格式化的代码块原样输出。该片段与片段 1 共同确认代码围栏是 prettier-ignore 之外的天然免疫区忽略注释在此更多是语义上的保险。片段 3列表项内的引文块 代码围栏输入 - test md !-- prettier-ignore -- - This is a long long long long long long long long paragraph. 这里是引文块 → 列表项 → 代码围栏的三级嵌套。注意输入中列表项内容行与围栏行使用了不同的缩进层级后 1 空格与 2 空格输出原样保留了这种缩进与内部结构。这表明 Prettier 在引文块内解析嵌套列表与代码围栏时能够准确保留每一层的前缀和缩进不会因格式化而改变代码围栏内部的空白布局。片段 4顶层无引文块的 4 反引号包裹的引文块输入md md !-- prettier-ignore -- - This is a long long long long long long long long paragraph. 这与片段 2 互为镜像片段 2 是引文块内嵌代码围栏片段 4 是代码围栏内嵌引文块。输出与输入一致说明 **代码围栏无论位于引文块内还是包裹引文块其内容均按原文输出**!-- prettier-ignore -- 在围栏内部只作为文本存在。 ### 片段 5引文块包裹引文块 代码围栏深层嵌套 **输入** md !-- prettier-ignore -- - This is a long long long long long long long long paragraph. 这是最深的一层嵌套外层引文块内是 4 反引号围栏围栏内再嵌套一层引文块其内才是 3 反引号代码围栏。每行都需要双重 前缀。输出与输入完全一致验证了多层前缀剥离与重建的稳定性。 ### 片段 6引文块段落中的 !-- prettier-ignore --真正触发忽略机制 **输入**This is a long long long long long long long long paragraph.这是 7 个片段中**唯一真正触发 ignore 机制**的用例注释不在代码围栏内部而是直接作为引文块中的一段独立 HTML 注释紧邻其后的是一段超长的列表段落。快照输出显示 - 在 proseWrap: always 下**列表段落保持原样**没有按 80 列自动换行对比同目录 [paragraph.md](https://link.gitcode.com/i/772f36bd1e3d36726fa4ff0438756c30) 中普通段落在 always 下会被强制折行 - 在 proseWrap: never 下输出同样保持原样 - 三份快照中输入里那个孤立的空引文块行在输出中被移除。 这说明 !-- prettier-ignore -- 在引文块内被正确识别为 HTML 注释节点且其忽略范围覆盖了**紧随其后的下一个块级节点**——这里即那个超长列表段落。这正是 Markdown 语言插件中 ignore 语义的核心。 ### 片段 7引文块内嵌 JS 代码围栏 // prettier-ignore跨语言忽略 **输入**// prettier-ignore const x 1, b 2**输出对比以 always 快照为例**// prettier-ignore const x 1, b 2;这是唯一一个**输出被修改**的片段b 2 末尾被追加了分号。其原理是外层围栏语言标记为 js此时围栏内容不再被视为不可格式化的 Markdown 代码而是被 **嵌入的 JS 格式化器**接管。// prettier-ignore 是 JS 语言层的忽略注释它只保护紧随其后的声明语句 const x 1,因此 b 2 仍被 JS 格式化器处理并补上分号而 const x 1, 保持原样未被拆成 const x 1;。 由此可以得出一个重要的实际结论**在引文块的 JS 围栏中必须使用 // prettier-ignore语言层注释而非 !-- prettier-ignore --HTML/Markdown 层注释**因为前者作用于被嵌入的 JS 代码后者仅作用于 Markdown 层。 ## 四、三种 proseWrap 模式下的行为汇总 三种模式下输入输出仅有两处差异详见快照文件其余结构完全一致 | 片段 | proseWrap: always | proseWrap: preserve | proseWrap: never | | --- | --- | --- | --- | | 片段 1~5代码围栏内 | 原样输出 | 原样输出 | 原样输出 | | 片段 6真 ignore | 列表段落不折行 | 列表段落不折行 | 列表段落不折行 | | 片段 7JS 围栏 | b 2 补分号 | b 2 补分号 | b 2 补分号 | 由此可见 1. **proseWrap 只影响可换行的散文/列表文本**对代码围栏、ignore 保护的内容均无作用 2. **引文块中的 ignore 保护在三种模式下都生效**说明 ignore 机制优先级高于 proseWrap 3. JS 嵌入格式化行为与 proseWrap 无关属于语言层恒定行为。 ## 五、源码级原理isPrettierIgnore 与 ignore 范围的计算 要理解片段 6、片段 7 的差异需要深入 src/language-markdown 的实现。 ### 5.1 忽略注释的识别规则 [utilities.js](https://link.gitcode.com/i/90110832c06122c6e32ab63383f33a4e) 中的 isPrettierIgnore(node) 定义了什么节点算 ignore 指令 js function isPrettierIgnore(node) { let match; if (node.type html) { match node.value.match(/^!--\s*prettier-ignore(?:-(start|end))?\s*--$/); } else { let comment; if (node.type esComment) { comment node; } else if ( node.type paragraph node.children.length 1 node.children[0].type esComment ) { comment node.children[0]; } if (comment) { match comment.value.match(/^prettier-ignore(?:-(start|end))?$/); } } return match ? match[1] || next : false; }关键点有三HTML 注释!-- prettier-ignore --、!-- prettier-ignore-start --、!-- prettier-ignore-end --在 Markdown 中对应 AST 节点类型html通过正则严格匹配允许注释内有多余空白如!-- prettier-ignore --也合法代码注释如 JS 的// prettier-ignore对应节点类型esComment可能是独立节点也可能被解析为只有一个esComment子节点的paragraph两种情况都会被识别返回值next表示忽略下一个节点start/end表示范围忽略的起止。5.2 忽略范围在根节点上的计算mdast.js 的printRoot会在遍历子节点前统一扫描所有 ignore 指令构建ignoreRanges数组遇到start记录起点遇到匹配的end记录终点而next类指令由 children.js 中的isPrevNodePrettierIgnore在打印兄弟节点时即时判断const isPrevNodePrettierIgnore isPrettierIgnore(previous) next;该判断参与needsBlankLine是否在节点间插入空行的计算从而影响换行布局同时hasPrettierIgnore(path)utilities.js被用于 printers.js 中作为上一节点被忽略的标记传递到具体打印逻辑。5.3 被忽略内容的原文透传对于start/end范围printRoot中的processor会直接使用原始文本切片透传mdast.jsreturn [ printIgnoreComment(children[ignoreRange.start.index]), options.originalText.slice(ignoreRange.start.offset, ignoreRange.end.offset), printIgnoreComment(children[ignoreRange.end.index]), ];即打印起始注释 → 原样复制中间文本 → 打印结束注释中间内容完全不经过格式化器。这从源码层面印证了片段 6 中长列表段落原样保留的行为。5.4 为什么 JS 围栏内必须用// prettier-ignoreMarkdown 打印器对代码围栏的处理是当语言标记如js能匹配到内置解析器时围栏内容会被委托给对应语言的嵌入打印逻辑embed而非作为文本原样输出。此时 Markdown 层的!-- prettier-ignore --已失去作用因为围栏内容已进入 JS 打印器的作用域JS 打印器只认// prettier-ignore等代码注释见 src/language-js/comments/is-prettier-ignore-comment.js。因此片段 7 中的// prettier-ignore保护了const x 1,却管不到同围栏内的b 2。5.5 兄弟目录对照不被 ignore 保护的引文块同目录 code.md 提供了一个极佳的反例引文块内的json围栏没有 ignore 注释快照中该 JSON 内容会被正常格式化。将它与片段 7 对比即可得出引文块本身不会让代码围栏免于格式化——决定格式化与否的是围栏语言标记与 ignore 注释的语言层级而非是否处于引文块中。六、实战建议在引文块中正确使用 prettier-ignore基于上述分析可归纳出在 Markdown 引文块中使用忽略机制的实用规则区分代码围栏内部与代码围栏外部围栏内的!-- prettier-ignore --只是文本不产生任何忽略效果若想保护围栏内的 Markdown 示例应保证外层围栏语言标记为md或普通文本围栏此时内容天然原样输出保护引文块中的列表/段落将!-- prettier-ignore --作为引文块中的独立一行置于待保护块级节点段落、列表之前即可在proseWrap: always下阻止自动折行三种 proseWrap 模式均生效保护引文块中的 JS/其他语言代码必须使用该语言的注释语法如// prettier-ignore并注意其只保护紧随的下一语句多行代码建议使用// prettier-ignore-start/// prettier-ignore-end成对包裹善用 4 反引号围栏当需要在引文块内展示包含代码围栏的 Markdown 示例时使用级别的围栏避免结束符冲突测试已覆盖引文块-围栏-引文块-围栏的四层嵌套场景回归验证修改涉及 Markdown 忽略逻辑时可运行yarn jest tests/format/markdown/blockquote结合 format.test.js.snap 快速确认各嵌套层级的行为未被破坏。七、延伸阅读测试输入文件ignore-code.md 与驱动文件 format.test.js期望输出快照format.test.js.snap含三种 proseWrap 模式的完整输入/输出对照Markdown 忽略指令识别utilities.js、printers.js忽略范围计算与原文透传mdast.js、children.jsJS 语言层的忽略注释实现is-prettier-ignore-comment.js相关测试目录tests/format/markdown/blockquote赞分享开发工具格式化CLI【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址https://gitcode.com/gh_mirrors/pr/prettier点击查看免费下载相关推荐Prettier 韩文HangulMarkdown 格式化解析splitCjkText/korean.md 测试用例深度解读Prettier 韩文HangulMarkdown 格式化解析 splitCjkText/korean.md 测试用例深度解读 本文聚焦 Prettier开发工具格式化CLIPrettier 处理 Markdown 链接中的 HTML 字符引用entity.md 测试用例深度解析Prettier 处理 Markdown 链接中的 HTML 字符引用entity.md 测试用例深度解析 本文围绕 Prettier 仓库中 tests/f开发工具格式化CLIPrettier Markdown 折行proseWrap深度解析从 break/wrap.md 测试用例到源码实现Prettier Markdown 折行proseWrap深度解析从 break/wrap.md 测试用例到源码实现 导读 本文以 Prettier 仓库开发工具格式化CLI创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考