Prettier 对 Markdown Wiki 链接的换行处理:proseWrap 行为与源码解析 Prettier 对 Markdown Wiki 链接的换行处理proseWrap 行为与源码解析【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址: https://gitcode.com/gh_mirrors/pr/prettierPrettier 在格式化 Markdown 时会将[[wiki-style link]]这类双括号内链视为不可拆分的整体。本文以仓库中 exceeds-line-length-in-prose.md 测试用例为切入点讲解超长 wiki 链接出现在正文段落中时 Prettier 的换行规则、三种proseWrap模式的差异以及底层源码中链接不可内部断行、只能整块挪到下一行的实现原理。读完本文你将能在实际项目中准确预判并配置 Prettier 对 wiki 链接与正文的排版行为。测试用例正文中超长的 wiki 链接仓库中的格式化测试用例 exceeds-line-length-in-prose.md 全文只有一行原始内容不含换行符I have some markdown prose here, with a horrible run-on sentence that [[makes little sense at all as I continue it into an obscenely long wiki-style link thingy]].这段输入的结构非常典型前缀是普通 Markdown 正文文本I have some markdown prose here, with a horrible run-on sentence that中间嵌入一个[[...]]形式的 wiki 链接其内部文本本身非常长约 70 个字符整行长度远超 Prettier 默认的printWidth: 80换行宽度。它的姊妹用例 exceeds-line-length.md 则是 wiki 链接独占一行、同样超长的情况而 exceeds-line-length-in-prose-broken.md 展示了链接内部已被手动拆成两行的已断行输入。三者共同覆盖了链接单独超长链接在正文中超长链接内部已断行三类边界场景。关键行为换行发生在链接外部链接本体保持完整该用例在proseWrap: always下的格式化输出见快照 format.test.js.snap 中exceeds-line-length-in-prose.md一节为I have some markdown prose here, with a horrible run-on sentence that [[makes little sense at all as I continue it into an obscenely long wiki-style link thingy]].从中可以提炼出 Prettier 处理 wiki 链接换行的两条核心规则链接整体不可拆分即使[[...]]内部的文本远超printWidthPrettier 也不会在链接内部断行链接保持为单一整体。换行发生在链接边界之外由于整行放不下Prettier 在链接开始[[之前的最后一个可断行位置进行换行把前缀文本与整个 wiki 链接分成两行链接被整体挪到下一行。对应地快照exceeds-line-length.md一节显示当超长 wiki 链接独占一行时proseWrap: always下输出与输入完全一致——链接本身不会被拆开即使它超出换行宽度。这就是wiki 链接是单行原子单元这一规则的直接体现。为什么不在链接内部断行isBreakable源码依据这一行为由 whitespace.js 中的isBreakable函数实现。该文件第 20 行定义了一个单行节点类型集合const SINGLE_LINE_NODE_TYPES new Set([tableCell, link, wikiLink]);在isBreakable中第 182-193 行当满足以下任一条件时返回false即不允许在该位置断行proseWrap ! always || path.hasAncestor( (node) SINGLE_LINE_NODE_TYPES.has(node.type) || (node.type heading ...) )也就是说只要当前内容处于wikiLink或link、tableCell节点内部无论proseWrap如何设置Prettier 都不会在此处插入换行。这从源码层面印证了快照中的行为换行只能发生在链接外部的普通文本节点上链接内部永远保持单行实体。链接内部的空白被归一化print/mdast.js依据虽然链接内部不能断行但若输入中链接内容里包含制表符或换行Prettier 会在打印阶段将其归一化为单个空格。见 mdast.js 中wikiLink节点的打印逻辑case wikiLink: { let contents; if (options.proseWrap preserve) { contents node.value; } else { contents node.value.replaceAll(/[\t\n]/g, ); } return [[[, contents, ]]]; }当proseWrap为always或never时链接内容中的换行与制表符会被替换为空格当proseWrap为preserve时链接内容原样保留。这一点在 multi-line.md 的[[test1 [[wiki link]] ...]]等用例中可以看到具体效果。而 additional-spacing.md 则验证了链接内部普通空格不会被压缩Additional spacing within the link should be preserved原样保留归一化只针对\t和\n。三种 proseWrap 模式的完整对比该测试目录下的 format.test.js 以四种配置组合运行同一批用例proseWrap: always、proseWrap: always singleQuote: true、proseWrap: never、proseWrap: preserve。对exceeds-line-length-in-prose.md而言三种模式的输出对比取自快照如下模式格式化结果always前缀文本在[[之前换行超长 wiki 链接整体移至下一行并保持自身完整never整段正文保持单行与输入完全一致输入即单行preserve保持原始换行原样与输入完全一致输入即单行注意never与preserve在本用例中输出相同的表象但语义完全不同never是把所有 prose 压缩到一行的强制行为而preserve是保留现有换行的保守行为。区别在 end-of-line.md 与 exceeds-line-length-in-prose-broken.md 中体现得更明显exceeds-line-length-in-prose-broken.md输入中链接内部已被断成两行。always下输出保留了链接内部的原始换行因为链接不可拆分preprocess 阶段会保护既有断行而preserve下同样保留——两者的差异主要作用于普通正文的换行end-of-line.md在always下普通正文被按 80 列重新折行而[[wrapped as a single entity]]整体不被拆开在never与preserve下正文换行保持/合并。此外singleQuote: true对 wiki 链接的输出没有影响快照中该配置组合的输出与默认一致因为 wiki 链接语法本身不含字符串引号。官方选项定义根据 docs/options.md 的 Prose Wrap 章节三种模式的定义为always—— 将 prose 按printWidth折行never—— 每个 prose 块放在单一行上依赖编辑器/查看器软换行preserve—— 保持已有换行不变自 v1.9.0 起可用。默认值为preserve。这是因为部分服务如 GitHub 评论、BitBucket使用对换行敏感的渲染器Prettier 默认不改变 Markdown 文本的换行。可通过 CLI 参数--prose-wrap always|never|preserve或 API 配置proseWrap: always | never | preserve覆盖。解析层wiki 链接如何被识别为独立节点要理解链接不可拆分还需知道[[...]]在解析阶段就被识别为独立节点。Markdown 解析存在两条路径标准 Markdown 路径parse-markdown.js使用braindb/micromark-extension-wiki-link的syntax扩展与braindb/mdast-util-wiki-link的fromMarkdown扩展将[[...]]解析为wikiLink类型的 mdast 节点MDX 路径parse-mdx.js使用自研的 unified 插件 unified-plugins/wiki-link.js。后者以正则^\[\[(?linkContents.?)\]\]/s匹配链接将其插入到link内联 tokenizer 之前并把链接内容trim()后作为节点valueconst wikiLinkRegex /^\[\[(?linkContents.?)\]\]/s; function tokenizer(eat, value) { const match wikiLinkRegex.exec(value); if (match) { const linkContents match.groups.linkContents.trim(); return eat(match[0])({ type: entityType, // wikiLink value: linkContents, }); } }得到wikiLink节点后Prettier 便能在 mdast.js 中按上面所述规则输出[[ 内容 ]]。同时visitor-keys.evaluate.js 中wikiLink: []表示该节点没有子节点需要递归遍历进一步确保其内部文本不会被当作可重排的普通 prose。断行保护避免换行制造出意外链接仓库中还有一个值得注意的细节[[与]]是 Markdown 中的危险标记如果在普通正文中断行后恰好把[与[拼到相邻位置就可能意外产生 wiki 链接。Prettier 在 preprocess.js 的splitTextIntoSentences中对此做了保护对已存在的wikiLink节点标记其所有祖先段落为风险段落避免换行把[[foo\n[[wiki link]]这类相邻内容意外合并对普通文本节点若raw中包含[[将该文本所在段落标记为可能开启意外 wiki 链接若包含]]则同样标记祖先。这些标记参与后续的断句与换行决策从而保证proseWrap: always的折行不会改变文档语义。这也是 wiki 链接相关测试在 multi-line.md 中覆盖大量嵌套、转义、注释等边界输入的原因——例如\\[[test3 text\n]]这类被反斜杠转义的输入必须原样保留。实战建议结合以上源码与测试证据可以给出针对 wiki 链接的格式化实践建议想让正文按 80 列折行、同时保证链接完整使用proseWrap: always或 CLI--prose-wrap always。超长 wiki 链接不会被拆开而是在[[之前整体换行。链接内容内部的换行/制表符会被归一化为空格always/never模式多行手写 wiki 链接会被压平若希望完全保留链接内部换行只能使用proseWrap: preserve。Wiki 链接的识别覆盖标准 Markdown 与 MDX 两条解析路径但实现机制不同micromark 扩展 vs 自研 unified 插件在 MDX 中格式化为 wiki 链接时建议用对应用例验证。不必担心折行破坏链接语义preprocess中的风险段落标记机制会阻止换行意外拼出[[/]]。若要在本地复现这些行为可直接运行仓库的格式化测试测试入口为 wiki-link/format.test.js其runFormatTest(import.meta, [markdown], { proseWrap: always })会以markdown解析器对目录内所有.md用例跑一遍格式快照测试。修改任一输入文件后更新快照即可直观观察各种proseWrap模式下超长 wiki 链接与正文的换行结果。【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址: https://gitcode.com/gh_mirrors/pr/prettier创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考