Prettier 3.10:保留解构模式后的类型注释位置(TypeScript 格式化变更 #19905)
【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址: https://gitcode.com/gh_mirrors/pr/prettier
本篇技术指南基于 Prettier 仓库中未发布的变更条目 changelog_unreleased/typescript/19905.md,解析 Prettier 对 TypeScript 类型断言语句的一个格式修复:当解构赋值/参数后面跟随: Type类型标注时,位于模式右花括号与冒号之间的行内注释(如/* comment */)不再被移动进解构模式内部,而是保留在其原始位置。读完本文,你能理解该变更的前后行为差异、注释在 Prettier 格式化管线中"附着—打印"的底层机制,以及如何在仓库中复现和验证这类格式化行为。
一、变更内容:注释留在模式之后
变更条目的原文示例如下(来自 19905.md,由 @lazerg 贡献):
// Input const { foo } /* comment */ : Foo = bar; // Prettier stable const { foo /* comment */ }: Foo = bar; // Prettier main const { foo } /* comment */ : Foo = bar;三段输出的含义:
- Input:多行解构对象模式
const { foo },紧接着一个行内注释/* comment */,再换行后是: Foo类型标注和默认值= bar。 - Prettier stable(旧版稳定行为):压缩为单行时,注释被移动到了模式内部,成为
}前面的内容——const { foo /* comment */ }。这改变了注释与源码中代码实体的视觉绑定关系:原本它标注的是"模式结束之后的类型标注",现在却看起来像是模式内最后一个属性foo的尾随注释。 - Prettier main(本变更后的行为):注释保留在模式关闭括号之外、类型冒号之前——
const { foo } /* comment */ : Foo = bar。
这个变更属于 Prettier 变更日志流程中typescript/目录下的标准条目。按照 changelog_unreleased/TEMPLATE.md 的约定,每个 PR 对应一个XXXX.md文件,内容为"输入 + 旧版输出 + 新版输出"的对比示例;TypeScript 特定语法(如类型标注、as断言)归属typescript/目录。
二、涉及的代码:类型标注语句的打印方式
const { foo }: Foo = bar;这类语句在类型标注的打印路径中,与as/satisfies断言共享同一套"表达式 + 运算符 + 类型"的三段式输出逻辑。在 src/language-js/print/typescript.js 中:
case "TSAsExpression": case "TSSatisfiesExpression": return printBinaryCastExpression(path, options, print);对应的打印实现位于 src/language-js/print/binary-cast-expression.js:
function printBinaryCastExpression(path, options, print) { const { parent, node, key } = path; const isFlowAsConstExpression = node.type === "AsConstExpression"; const typeAnnotationDoc = isFlowAsConstExpression ? "const" : print("typeAnnotation"); const parts = [ print("expression"), // 左操作数:表达式(此处为解构模式) " ", isSatisfiesExpression(node) ? "satisfies" : "as", ]; if ( // Union type already indented !isUnionType(node.typeAnnotation) && hasLeadingOwnLineComment(options.originalText, node.typeAnnotation) ) { parts.push(indent([hardline, typeAnnotationDoc])); } else { parts.push(" ", typeAnnotationDoc); } // ...(callee/object 场景的缩进分支) }从源码结构看,这类语句的输出是"先打印表达式子树,再打印类型标注子树"。注释并不参与表达式子树内部的排版——它们在解析后通过独立的注释附着阶段被分配到各个 AST 节点上,最终由节点的打印函数决定其相对位置。hasLeadingOwnLineComment分支也体现了这一点:如果类型标注自带独占行的前导注释,会触发换行缩进,而不是把注释塞进左侧表达式。这正是修复"注释被吸入模式内部"这类问题的落点——注释最终锚定在"模式结束符之后",而不是}之前。
三、注释附着:为什么修复点不在打印函数本身
Prettier 的注释处理是一个独立于语言打印的通用阶段。核心判断逻辑在 src/language-js/comments/can-attach-comment.js,它根据节点及其祖先链决定某条注释是否允许附着到某个 AST 节点上,例如其中针对TSAsExpression场景的专门判断(can-attach-comment.js#L87-L107):
// `foo as const` // ^^^^^^^^^^^^ `TSAsExpression` // ^^^^^ `TSTypeReference` (`TSAsExpression.typeAnnotation`) // ^^^^^ `Identifier` (`TSTypeReference.typeName`) const isTsAsConstTypeReference = (node, [parent]) => // @ts-expect-error -- Safe parent?.typeAnnotation === node && isTsAsConstExpression(parent);可以推断,#19905 的修复正是作用于这一附着判定/输出位置:旧逻辑把紧跟模式}的注释视为模式内部最后一个元素的尾随注释(因此被排进花括号内);修复后,由于该注释实际位于类型标注之前,它被保留为模式之后的独立片段,即{ foo } /* comment */ : Foo。
同一套机制在satisfies场景下已有对应的行为测试。仓库中的测试输入 tests/format/typescript/satisfies-operators/comments.ts 包含:
const t2 = {} /* comment */ satisfies {};其快照(satisfies-operators 快照)确认注释稳定地保留在{}与satisfies之间。这说明"注释留在断言表达式与类型之间"是 Prettier 已确立的排版原则,#19905 把这一原则扩展到了对象/数组解构模式带: Type标注的场景。
四、关联测试:类型标注注释的一致性验证
仓库中有一组专门覆盖"模式后类型标注与注释"行为的格式化测试:tests/format/typescript/comments/consistent-with-flow/15707.ts。该文件以typescript与flow双解析器运行(见 format.test.js):
runFormatTest(import.meta, ["typescript", "flow"]);其输入覆盖了对象模式、数组模式、函数参数等多种形态:
const { foo11, // bar // baz }: Foo = expr; const [ foo31, // bar // baz ]: Foo = expr; function method({ foo, // bar = "bar", // bazz = "bazz", }: Foo) {} const { // foo }: Foo = expr;对应快照 15707.ts 格式化快照 记录了这些用例在两种解析器下的精确输出。结合 #19905,可以推断这条测试线覆盖的就是"注释不得在模式与类型标注之间被错误迁移"这一类问题——15707 处理的是模式内部注释,19905 处理的是模式之后注释,二者共同构成该语法区域注释行为的回归防护。
五、如何验证这一行为
Prettier 仓库的格式化行为验证基于 Jest 快照测试:
- 测试入口使用
runFormatTest(配置见 tests/format/format-test 目录),它会读取测试目录下的输入文件、按指定解析器格式化,并与__snapshots__下的快照逐字比对。 - 以本文主题为例,
tests/format/typescript/comments/consistent-with-flow/目录下的每个输入文件都有对应的format.test.js驱动,双解析器(typescript、flow)的输出都会写入 format.test.js.snap。 - 若想观察变更条目的完整格式约定(标题、PR 号、作者、示例代码),可参考 changelog_unreleased/TEMPLATE.md:示例需给出
// Input、// Prettier stable(旧行为)、// Prettier main(新行为)三段,并用<!-- prettier-ignore -->保持原文排版。
六、影响范围与适用前提
- 语言范围:该变更针对 TypeScript 特定语法(对象/数组解构模式 +
: Type类型标注 + 默认值的变量声明),归属变更日志的typescript/目录;从源码结构看,类似的"表达式 + 类型"断言输出(as/satisfies)走 binary-cast-expression.js 同一机制,行为已有一致性。 - 适用前提:变更条目位于
changelog_unreleased/目录,说明该行为尚未进入已发布的稳定版本线;"Prettier stable" 输出代表旧行为,"Prettier main" 输出代表本变更落地后的行为。 - 对用户的实际影响:如果你的代码依赖"模式后注释"的精确位置(例如用
/* TODO */标注类型标注前的待办),升级后注释将保持在冒号之前的原始位置,而不会被移入花括号内部;格式化结果更符合源码的原始意图。
参考路径
- 变更条目:changelog_unreleased/typescript/19905.md
- 变更日志模板:changelog_unreleased/TEMPLATE.md
- 断言表达式打印:src/language-js/print/binary-cast-expression.js
- TS 节点分发:src/language-js/print/typescript.js
- 注释附着判定:src/language-js/comments/can-attach-comment.js
- 关联测试:tests/format/typescript/comments/consistent-with-flow/15707.ts、tests/format/typescript/satisfies-operators/comments.ts
【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址: https://gitcode.com/gh_mirrors/pr/prettier
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考