ts-morph 对象字面量表达式(Object Literal Expressions)操作指南:属性、访问器与方法的新增与删除

发布时间:2026/10/10 8:34:44
ts-morph 对象字面量表达式(Object Literal Expressions)操作指南:属性、访问器与方法的新增与删除
开发工具【免费下载链接】ts-morphTypeScript Compiler API wrapper for static analysis and programmatic code changes.项目地址https://gitcode.com/gh_mirrors/ts/ts-morph点击查看免费下载导读对象字面量Object Literal是 JavaScript / TypeScript 中最常用的数据结构表达形式而 ts-morph 作为 TypeScript 编译器 API 的封装库为它提供了完整的“读取—修改—写出”能力。本文以 ts-morph 官方文档中 object-literal-expressions.md 为骨架结合 ObjectLiteralExpression 源码 与 单元测试系统讲解如何获取对象字面量的属性、如何通过结构Structure以编程方式新增各类成员属性赋值、简写属性、展开赋值、get/set 访问器、方法以及如何删除成员并深入剖析其底层的插入与格式化原理帮助读者在静态分析与代码生成场景中直接落地使用。什么是对象字面量表达式在 ts-morph 中对象字面量表达式对应 TypeScript AST 中的ObjectLiteralExpression节点其外观就是我们在代码中常见的{ ... }字面量。一个对象字面量可以混合包含多种成员其典型形态如下const obj { propertyAssignment: 5, shorthandPropertyAssignment, ...spreadAssignment, get getAccessor() { return 5; }, set setAccessor(value: number) { // do something }, method() { return some string; }, };上述成员依次对应 ts-morph 中以下节点类型与结构成员形态节点类对应结构key: valuePropertyAssignmentPropertyAssignmentStructurekey简写属性ShorthandPropertyAssignmentShorthandPropertyAssignmentStructure...exprSpreadAssignmentSpreadAssignmentStructureget key()/set key()GetAccessorDeclaration/SetAccessorDeclarationGetAccessorDeclarationStructure/SetAccessorDeclarationStructuremethod()MethodDeclarationMethodDeclarationStructure从源码结构看所有可出现在对象字面量内部的成员都被统一为ObjectLiteralElementLike别名见 aliases.tsgetProperties()的返回值即以此为类型。获取 ObjectLiteralExpression 节点在使用任何属性操作方法之前首先需要拿到ObjectLiteralExpression节点本身。可以通过 AST 遍历接口来定位例如从一个源码文件中查找import { SyntaxKind } from ts-morph; const objectLiteralExpression sourceFile.getFirstDescendantByKindOrThrow(SyntaxKind.ObjectLiteralExpression);测试代码中正是采用这种方式获取节点见 objectLiteralExpressionTests.ts。也可以从某个初始化的变量声明出发先取到初始化表达式再向下获取const variableDeclaration sourceFile.getVariableDeclarationOrThrow(obj); const objectLiteralExpression variableDeclaration.getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);更一般地initializers 文档描述了如何从带初始化器的声明中定位表达式节点对象字面量最常见的出现位置正是变量声明的初始化器。读取对象的属性拿到ObjectLiteralExpression节点后读取属性有以下几种方式对应 ObjectLiteralExpression.ts 中的实现// 获取全部属性返回 ObjectLiteralElementLike[] const properties objectLiteralExpression.getProperties(); // 按名称获取第一个匹配的属性找不到返回 undefined const property objectLiteralExpression.getProperty(propertyAssignment); // 用查找函数过滤适合名称不便直接书写如含扩展运算符的情况 const spreadAssignment objectLiteralExpression.getProperty( p p.getText() ...spreadAssignment, ); // 按名称获取找不到时抛出异常 const method objectLiteralExpression.getPropertyOrThrow(method);getProperty的内部实现值得关注源码 L56-L69当传入字符串名称时它会先检查成员上是否存在getName()方法用于排除展开赋值等无名称成员再逐一比较当传入函数时则直接作为find谓词使用。这意味着getProperty既支持精确名称匹配也支持任意复杂的自定义条件例如按getKind()过滤const firstPropertyAssignment objectLiteralExpression.getProperty( p p.getKind() SyntaxKind.PropertyAssignment, );getPropertyOrThrow则在找不到时通过errors.throwIfNullOrUndefined抛出带名称的报错信息源码 L37-L42。测试用例验证了这些行为名称匹配成功时返回节点文本、找不到时getProperty返回undefined而getPropertyOrThrow抛出异常测试 L63-L100。关于注释的处理需要注意的是getProperties()只返回真实的 AST 成员不包含注释。如果希望把对象内部的注释也作为可操作元素例如在删除时保留或一并处理应使用getPropertiesWithComments()const propertiesWithComments objectLiteralExpression.getPropertiesWithComments();从实现看该方法通过ExtendedParser.getContainerArray读取语法列表容器内的全部元素包括注释节点源码 L81-L84返回类型为(ObjectLiteralElementLike | CommentObjectLiteralElement)[]。对应测试L52-L61展示了const t {\n //a\n /*b*/\n};场景下getProperties()返回空数组而getPropertiesWithComments()返回两条注释文本。对注释语义感兴趣的读者还可进一步参考 comments 与 comment-ranges 文档。新增对象成员操作 API 总览ts-morph 为每种成员类型都提供了add与insert两族方法命名模式完全一致add 系列追加到对象字面量的末尾返回新节点insert 系列在指定索引处插入返回新节点。每种成员的方法清单如下成员类型add 方法insert 方法属性赋值addPropertyAssignment/addPropertyAssignmentsinsertPropertyAssignment/insertPropertyAssignments简写属性addShorthandPropertyAssignment/addShorthandPropertyAssignmentsinsertShorthandPropertyAssignment/insertShorthandPropertyAssignments展开赋值addSpreadAssignment/addSpreadAssignmentsinsertSpreadAssignment/insertSpreadAssignmentsget 访问器addGetAccessor/addGetAccessorsinsertGetAccessor/insertGetAccessorsset 访问器addSetAccessor/addSetAccessorsinsertSetAccessor/insertSetAccessors方法addMethod/addMethodsinsertMethod/insertMethods此外还有一组通用方法addProperty/addProperties/insertProperty/insertProperties它们接受统一的联合类型ObjectLiteralExpressionPropertyStructures或字符串、WriterFunction可混合插入任意成员。该联合类型定义于 aliases.tsexport type ObjectLiteralExpressionPropertyStructures | PropertyAssignmentStructure | ShorthandPropertyAssignmentStructure | SpreadAssignmentStructure | GetAccessorDeclarationStructure | SetAccessorDeclarationStructure | MethodDeclarationStructure;add系列内部本质上是insert(index 当前成员总数)的语法糖源码中的#getAddIndex()返回当前容器成员数量L87-L90而addPropertyAssignment等均调用对应的insert族方法。新增属性赋值Property Assignment通过addPropertyAssignment传入PropertyAssignmentStructure即可const propertyAssignment objectLiteralExpression.addPropertyAssignment({ name: propertyAssignment, initializer: 5, });PropertyAssignmentStructure的核心字段见 PropertyAssignmentStructure.ts为name属性名继承自PropertyNamedNodeStructureinitializer初始化器文本类型为string | WriterFunction可传字符串或写入函数如writer writer.write(5)。initializer支持字符串与WriterFunction两种形态测试用例中就有{ name: prop2, initializer: writer writer.write(5) }的用法测试 L273。在索引位置插入的写法为objectLiteralExpression.insertPropertyAssignment(0, { name: prop1, initializer: 4 });对应测试验证了在开头、中间、末尾插入的文本结果L269-L308。新增简写属性Shorthand Property Assignment简写属性只需提供name无需初始化器这也是它与属性赋值在结构上的唯一差别见 ShorthandPropertyAssignmentStructure.tsconst shorthandPropertyAssignment objectLiteralExpression.addShorthandPropertyAssignment({ name: shorthandPropertyAssignment, });多个简写属性可通过addShorthandPropertyAssignments/insertShorthandPropertyAssignments批量处理测试中展示了中间插入两个简写属性的格式化效果L372-L379。新增展开赋值Spread Assignment展开赋值通过addSpreadAssignment添加其结构继承自ExpressionedNodeStructure核心字段是expression展开目标表达式文本const spreadAssignment objectLiteralExpression.addSpreadAssignment({ expression: spreadAssignment });expression同样支持字符串或WriterFunction例如测试中的{ expression: writer writer.write(prop3) }L448。批量或按索引插入分别使用addSpreadAssignments/insertSpreadAssignments。新增 get / set 访问器get 与 set 访问器在结构上属于访问器声明GetAccessorDeclarationStructure/SetAccessorDeclarationStructure与类中的访问器共用结构类型因此可以携带returnType、parameters、statements等字段const getAccessor objectLiteralExpression.addGetAccessor({ name: someNumber, returnType: number, statements: [return someNumber;], }); const setAccessor objectLiteralExpression.addSetAccessor({ name: someNumber, parameters: [{ name: value, type: number }], statements: [someNumber value;], });statements是写入函数体内的语句数组逐行渲染到{ }中。对应测试验证了插入后的文本形如get prop1() {\n }L588-L648。注意这里isAmbient: false被固定写入结构打印器选项见下文源码分析因为对象字面量内的访问器不可能处于 ambient 环境。新增方法Method方法与访问器一样复用类成员的MethodDeclarationStructureconst method objectLiteralExpression.addMethod({ name: method, statements: [return some string;], });同样支持insertMethod/insertMethods/addMethods测试验证了批量插入多个方法的输出L552-L558。方法结构还可携带parameters、returnType、scope对象字面量内无实际意义但结构兼容、decorators等通用声明字段。混合插入与注释当需要一次性插入不同类型成员时可以使用通用insertProperties/addProperties甚至混入注释文本const result objectLiteralExpression.addProperties([ { kind: StructureKind.PropertyAssignment, name: p1, initializer: 5 }, { kind: StructureKind.ShorthandPropertyAssignment, name: p2 }, { kind: StructureKind.SpreadAssignment, expression: p3 }, { kind: StructureKind.Method, name: m1, statements: [return 1;] }, // 这是一条注释, writer writer.write(// 通过 WriterFunction 写入的注释), ]);通用方法接受string | WriterFunction | ObjectLiteralExpressionPropertyStructures或其数组字符串与写入函数会作为原始文本直接输出。测试用例 L113-L146 验证了这种混合插入的完整输出格式。删除对象成员删除操作极为简洁对获取到的成员节点调用.remove()即可。const obj: ObjectLiteralExpression; // 假设已获取节点 obj.getPropertyOrThrow(prop1).remove();.remove()是所有 ts-morph 节点通用的删除接口其底层由RemoveChildrenTextManipulator/RemoveChildrenWithFormattingTextManipulator处理文本替换相关实现见 manipulations/removal.ts并自动处理逗号与换行的清理。测试验证了三种典型删除场景L235-L257属性独占一行时删除const t {\n prop1: 5\n};变为const t {\n};属性与花括号同行时删除const t { prop1: 5 };变为const t { };删除夹在两个属性之间的方法其余成员保持正确逗号。若需同时处理对象内部的注释可先通过getPropertiesWithComments()拿到包含注释元素的列表再逐个.remove()。源码级原理一次插入发生了什么理解内部机制有助于预判格式化结果、调试复杂场景。以属性插入为例insertPropertyAssignments最终调用私有方法#insertProperty源码 L388-L406其流程为索引校验verifyAndGetIndex确保索引位于合法范围[0, 当前成员数]文本生成使用_getWriterWithChildIndentation()创建带缩进的写入器再由CommaNewLineSeparatedStructuresPrinter按成员结构逐项打印代码文本插入调用insertIntoCommaSeparatedNodes将生成的文本插入到SyntaxList语法列表中的指定位置节点重建getNodesToReturn依据旧列表与新列表的差异重建包装节点返回新成员对应的节点数组。关键格式化行为由 manipulation settings 控制useTrailingCommas是否在新成员后生成尾逗号由project.manipulationSettings.getUseTrailingCommas()读取。测试 L148-L172 展示了开启尾逗号后p1,\n};的输出效果缩进新成员统一使用子级缩进层级并与现有成员的换行风格对齐。而各类型成员的打印则通过 ObjectLiteralExpressionPropertyStructurePrinter 按kind分发PropertyAssignment、ShorthandPropertyAssignment、SpreadAssignment、Method、GetAccessor、SetAccessor分别交给对应的结构打印器其中访问器与方法打印器固定使用{ isAmbient: false }选项。这也是为什么文档示例中只需给出name、initializer、expression等最小字段即可生成正确代码——其余细节由打印器按统一约定补齐。完整实战动态构造并修改一个对象将上述 API 组合起来可以完成一次完整的“读取—修改”闭环。以下示例基于Project读取内存中的源码并执行修改import { Project, StructureKind, SyntaxKind, ObjectLiteralExpression, } from ts-morph; const project new Project({ useInMemoryFileSystem: true }); const sourceFile project.createSourceFile(test.ts, const config { name: app, version, };); const objectLiteralExpression sourceFile.getFirstDescendantByKindOrThrow( SyntaxKind.ObjectLiteralExpression, ) as ObjectLiteralExpression; // 1. 读取 const nameProperty objectLiteralExpression.getProperty(name); console.log(nameProperty?.getText()); // name: app // 2. 修改已有属性初始化器 if (nameProperty?.isKind(SyntaxKind.PropertyAssignment)) nameProperty.setInitializer(my-app); // 3. 追加新成员 objectLiteralExpression.addPropertyAssignment({ name: debug, initializer: false, }); objectLiteralExpression.addShorthandPropertyAssignment({ name: nodeEnv }); objectLiteralExpression.addSpreadAssignment({ expression: defaults }); objectLiteralExpression.addGetAccessor({ name: label, returnType: string, statements: [return this.name;], }); objectLiteralExpression.addMethod({ name: print, statements: [console.log(this.label);], }); // 4. 删除旧成员 objectLiteralExpression.getPropertyOrThrow(version).remove(); console.log(sourceFile.getFullText());输出结果大致为const config { name: my-app, nodeEnv, ...defaults, get label() { return this.name; }, print() { console.log(this.label); }, debug: false };通过getText()可以随时读取修改后的源码文本再配合 emitting.md 中的emit与文件系统写入流程即可将改动落盘。小结与进一步阅读对象字面量表达式操作是 ts-morph 表达式体系中最常用的一组能力getProperties/getProperty/getPropertyOrThrow覆盖读取按成员类型配对的add/insert族方法覆盖新增通用的.remove()覆盖删除而ObjectLiteralExpressionPropertyStructures联合类型让混合插入变得简洁。底层的结构打印器与insertIntoCommaSeparatedNodes机制保证了缩进、逗号与换行的正确性且可通过manipulationSettings微调输出风格。相关主题可继续深入expressions表达式节点体系概览initializers如何从变量声明等带初始化器的节点定位表达式object literal 相关节点源码PropertyAssignment、ShorthandPropertyAssignment、SpreadAssignment等节点的完整实现structures 文档结构对象Structure的通用设计规范settings 文档useTrailingCommas等全局操作设置对象字面量结构打印器各成员类型的打印分发逻辑ObjectLiteralExpression 测试套件覆盖本文所述全部 API 的行为验证。赞分享开发工具【免费下载链接】ts-morphTypeScript Compiler API wrapper for static analysis and programmatic code changes.项目地址https://gitcode.com/gh_mirrors/ts/ts-morph点击查看免费下载相关推荐ts-morph 函数操作完整指南Function Declaration 的检索、增删、重载与函数表达式处理ts morph 函数操作完整指南Function Declaration 的检索、增删、重载与函数表达式处理 本文是 ts morph 文档体系中关于函数开发工具Apollo Save ToolPS4存档管理的终极免费解决方案Apollo Save ToolPS4存档管理的终极免费解决方案 还在为PS4游戏存档丢失而烦恼吗Apollo Save Tool正是你需要的专业存档管理工游戏开发Penrose 的 Substance 字面量表达式Literal Expressions完全指南Number/String 内置类型与索引语句的数值展开Penrose 的 Substance 字面量表达式Literal Expressions完全指南Number/String 内置类型与索引语句的数值展开开发工具数据可视化上一篇LunaTranslator3 步跑通的游戏翻译工具下一篇Table CalendarFlutter日历组件终极配置指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考