
开发工具【免费下载链接】ts-morphTypeScript Compiler API wrapper for static analysis and programmatic code changes.项目地址https://gitcode.com/gh_mirrors/ts/ts-morph点击查看免费下载导读Structures结构对象是 ts-morph 中一套简化的 AST 表示形式它把ClassDeclaration、InterfaceDeclaration、EnumDeclaration等成千上万种编译器节点压缩成纯 JSON 对象既可以从节点读取getStructure()也可以反向写回set()与addX/insertX还可以脱离Project直接打印成代码字符串printStructure。阅读本文后你将掌握如何在静态分析、程序化改码、代码生成与结构遍历forEachStructureChild四类场景中熟练使用 Structures并结合仓库源码理解其底层实现与边界行为。什么是 StructuresAST 的简化 JSON 视图ts-morph 的核心能力之一是把 Node 这种庞杂的编译器节点映射为简单、可序列化、可批量操作的普通对象。这类对象统称为 structures。它们简化了 AST你不需要关心 token、语法列表、空格等底层细节只关心这是什么声明、它有哪些成员、成员带什么修饰符这类语义层面的信息。Structures的类型骨架定义在 Structure.generated.ts其中Structure是所有结构对象的基础接口提供可选的leadingTrivia与trailingTrivia前导/尾部注释或空白可以是字符串、WriterFunction或二者的数组见 Structure.generated.tsKindedStructureTKind表示带kind标记的结构每个具体结构ClassDeclarationStructure、InterfaceDeclarationStructure、SourceFileStructure等通过 mixin 方式组合各个能力接口如ExportableNodeStructure、TypedNodeStructure、ParameteredNodeStructure。结构的kind由枚举 StructureKind 定义覆盖了类、接口、枚举、函数、类型别名、变量语句、导入导出、JSX、JSDoc、装饰器等 40 余种声明类别是printStructure分派打印与forEachStructureChild判断子节点类型的依据。获取结构node.getStructure()对任意支持结构化的节点调用getStructure()即可得到它的结构对象。以下代码以类声明为例接口、枚举及大量其他节点同样支持// 例类声明但同样适用于接口、枚举以及许多其他节点 const classStructure classDeclaration.getStructure(); // 返回: ClassDeclarationStructure例如对如下类声明export class MyClass { myProp 5; }调用getStructure()将得到与下面类似的结构对象{ isAbstract: false, isExported: true, name: MyClass, typeParameters: [], constructors: [], properties: [{ name: myProp, initializer: 5, type: undefined, isReadonly: false, isStatic: false }], methods: [] }注意几个典型特征默认值会被显式写出如isExported: true未推导出的字段为undefined如type成员数组保持声明顺序且只包含有意义成员getStructure()默认过滤掉函数重载overload只保留实际签名见 ClassDeclaration.tsextends字段取getExtends()?.getText()即继承子句的文本形式。实现原理mixin 链式拼装getStructure()并非魔法而是由各个 base mixin 逐层调用callBaseGetStructure累积出来的。以 ClassDeclaration.ts 的getStructure()为例它先把自己的独有字段ctors、methods、properties、extends、getAccessors等收集起来再调用callBaseGetStructure(ClassDeclarationBase.prototype, this, {...})向上合并ExportableNode、AmbientableNode、ClassLikeDeclarationBase等 mixin 提供的字段如isExported、isAmbient、isAbstract。底层实现见 callBaseGetStructure.ts它递归调用父级getStructure若父级无实现则从空对象开始随后用Object.assign覆盖当前层字段。正是因为这种 mixin 拼装机制一个节点的结构对象几乎穷尽了该节点所有可配置语义可作为节点快照保存、比较或传输。设置结构set()、addX与insertX结构对象同样是set()的输入。你可以用已有结构整体覆盖节点classDeclaration.set(classStructure);也可以只设置部分字段未提供的字段保持不变// 设置名字 classDeclaration.set({ name: NewName }); // 设置属性覆盖现有属性 classDeclaration.set({ properties: [{ name: newProperty }] });从 ClassDeclaration.ts 的set()实现可以看到其行为语义先调用callBaseSet处理 mixin 层共有的字段名称、修饰符、装饰器、JSDoc 等对集合型字段ctors、staticBlocks、properties、getAccessors、setAccessors、methods采用先删除全部旧成员再按结构数组重建的策略保证结构与代码严格一致对extends采用为 null 时移除、有值时设置的差异化逻辑支持传undefined清除继承。因此set({ properties: [...] })会替换所有旧属性而不是追加——这是使用set与addX的关键区别。addX / insertX追加式写入若只想追加而保留已有内容应使用addX或insertX方法结构对象同样可以作为入参sourceFile.addClass({ name: NewClass, ...classDeclaration.getStructure() });这里先getStructure()拿到现有类声明的完整结构再展开合并进新的结构并追加到源文件中——读取-改造-写回的经典循环由此闭合你可以从一个节点读出结构、改造后再创建出相似的新节点。打印为字符串printStructure结构对象可以直接打印成代码字符串且无需创建Project实例——这是它用于轻量级代码生成的核心优势。import { printStructure, StructureKind } from ts-morph; const code printStructure({ kind: StructureKind.Class, name: MyClass, isExported: true, properties: [{ name: myProp, type: string }], methods: [{ name: myMethod, parameters: [{ name: param, type: number }], returnType: void, }], });输出export class MyClass { myProp: string; myMethod(param: number): void { } }几乎所有带kind属性的结构都可打印包括SourceFile、Interface、Enum、Function、TypeAlias等。可打印种类与分派逻辑完整列举在 printStructure.ts语句级类、接口、枚举、函数、类型别名、变量语句、导入导出声明、模块、SourceFile、类成员构造函数、方法、属性、get/set 访问器、静态块、接口成员调用签名、构造签名、索引签名、方法签名、属性签名、枚举成员、导入导出说明符、对象字面量成员、参数、类型参数、装饰器、JSDoc 以及 JSX 系列结构全部由StructurePrinterFactory中对应的 printer 输出。不支持独立打印的结构三种 overload 结构不能单独打印调用会抛出NotSupportedError见 printStructure.ts、printStructure.ts 与 printStructure.tsFunctionOverload请打印父级函数结构ConstructorOverload请打印父级构造函数结构MethodOverload请打印父级方法结构。测试 printStructureTests.ts 对这三种抛错场景有专门用例覆盖。Options格式化选项可通过可选的第二个参数定制输出格式const code printStructure(structure, { indentNumberOfSpaces: 2, useTabs: false, newLine: \n, useSingleQuote: true, insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, });PrintStructureOptions的完整定义与默认值见 printStructure.ts选项默认值说明indentNumberOfSpaces4缩进空格数当useTabs为 true 时被忽略useTabsfalse是否使用制表符缩进newLine\n换行符仅支持\n或\r\nuseSingleQuotefalse是否使用单引号insertSpaceAfterOpeningAndBeforeClosingNonemptyBracestrue非空花括号内部是否插入空格如import { Item }与import {Item}的区别在实现上printStructure.ts 将这些选项同时喂给StructurePrinterFactory决定缩进换算与花括号空格和CodeBlockWriter决定缩进方式、换行与引号因此这些选项会影响包括成员级结构在内的整体输出。仓库测试 printStructureTests.ts 验证了indentNumberOfSpaces: 2与useTabs: true的实际输出效果。打印预期输出一览同一测试文件给出了多种结构的精确输出可直接作为行为参考结构输出Interface含value: number属性interface MyInterface {\n value: number;\n}Function参数x: number返回stringfunction myFunc(x: number): string {\n}Enum成员 A0, B1enum MyEnum {\n A 0,\n B 1\n}TypeAliasstring \| numbertype MyType string \| number;VariableStatementx 5let x 5;ImportDeclaration./foo具名导入 Bar、Bazimport { Bar, Baz } from ./foo;ExportDeclaration./foo导出 Barexport { Bar } from ./foo;DecoratorInjectableInjectable以上期望值均可在 printStructureTests.ts 中核实。遍历结构类型守卫与forEachStructureChildStructure类型守卫类似Node上的静态方法Structure导出对象提供了一组结构类型守卫用于在运行时判断结构是否具备某类能力。例如import { Structure } from ts-morph; // ...等等... if (Structure.isExportable(structure)) structure.isExported false;Structure守卫完整定义在 Structure.generated.ts 及后续行包括Structure.hasName(structure)结构是否有name: string字段Structure.isCallSignature、Structure.isClass、Structure.isEnum、Structure.isFunction、Structure.isInterface、Structure.isMethod、Structure.isProperty等按kind判定的具体类型守卫Structure.isJSDocableT、Structure.isSignaturedT、Structure.isParameteredT、Structure.isTypeParameteredT、Structure.isTypedT、Structure.isExportableT等按kind白名单判定的能力守卫返回structure is T XXXStructure的窄化类型配合if即可安全访问对应字段。它们基于structure?.kind StructureKind.XXX实现可直接用于forEachStructureChild回调中以缩小类型。forEachStructureChild结构级遍历与编译器 API 的forEachChild类似ts-morph 提供forEachStructureChild用于遍历结构的子结构。例如import { forEachStructureChild, SourceFileStructure, Structure } from ts-morph; const structure: SourceFileStructure { kind: StructureKind.SourceFile, statements: [{ kind: StructureKind.Function, name: myFunction, parameters: [{ name: myParam }], }], }; forEachStructureChild(structure, child { if (Structure.hasName(child)) console.log(child.name); });输出myFunction从实现看forEachStructureChild.ts 对每种kind分派到对应的forXXX辅助函数例如ClassDeclarationStructure会依次遍历decorators、typeParameters、docs、ctors、staticBlocks、properties、getAccessors、setAccessors、methods见 forEachStructureChild.ts各成员数组的元素顺序即遍历顺序。文件中标注generated的代码由deno task code-generate生成保持了与结构定义同步。没有 kind 的结构自动补全部分结构的kind是可选的。例如上面parameters: [{ name: myParam }]中再写kind: StructureKind.Parameter是多余且啰嗦的。但使用forEachStructureChild时你很可能需要知道子结构的kind才能执行特定操作。为此forEachStructureChild会自动为缺失 kind 的结构补上正确的kind属性——具体由ensureKindforEachStructureChild.ts完成if (structure.kind null) structure.kind kind;就地修改同一对象引用。测试 forEachStructureChildTests.ts 明确验证了返回的子结构与structure.properties[1]是同一引用因此在回调中做的修改会直接反映到原结构上。顺带一提OptionalKindT类型types.ts正是用来表达kind 可省的结构它保留除kind外全部字段并把kind变为可选允许书写{ name: myParam }这样简洁的成员结构。查找子结构返回值即结果注意与 ts-morph 的forEachChild不同forEachStructureChild的行为与编译器 API 的forEachChild一致——回调返回的任何真值都会被当作遍历结果提前返回const firstClassDecStructure forEachStructureChild(structure, child Structure.isClass(child) ? child : undefined);从签名forEachStructureChild.ts可看到两个重载传入单个结构遍历其全部子结构callback返回真值则立即返回该值传入结构数组依次对每个元素执行callback同样真值即返回。内部辅助函数forAll/forAllIfStructure均遵循命中即返回的短路逻辑见 forEachStructureChild.ts配合数组重载可以方便地在一组结构中定位目标。测试 forEachStructureChildTests.ts 覆盖了从 SourceFile 结构中提取接口从无 kind 的属性数组中定位指定属性以及直接传入结构数组三种典型用法可作为实战参考。实战场景小结综合来看Structures 在 ts-morph 中打通了三条链路读取getStructure()将节点序列化为纯对象便于保存、比较、传输或快照写入set()整体覆盖、addX/insertX追加、getStructure() 展开克隆新节点构成读-改-写闭环生成与遍历printStructure()脱离Project直接输出代码字符串forEachStructureChild与Structure类型守卫则支持对结构树做无节点的纯数据级遍历、查找与改造。三者配合即可在不直接触碰编译器节点细节的前提下完成从解析、改造到生成的完整代码处理流水线。若需深入了解各具体结构的字段定义可继续阅读 structures 目录想查看节点级set/getStructure的通用拼装机制可查阅 callBaseGetStructure.ts 与 callBaseSet.ts。赞分享开发工具【免费下载链接】ts-morphTypeScript Compiler API wrapper for static analysis and programmatic code changes.项目地址https://gitcode.com/gh_mirrors/ts/ts-morph点击查看免费下载相关推荐TypeScript代码操作终极指南ts-morph实战解析TypeScript代码操作终极指南ts morph实战解析 TypeScript代码操作与静态分析是现代前端开发中的核心技能而ts morph作为Type开发工具Qwen3.8-27B-NVFP4架构探秘Gated DeltaNet混合注意力与MTP多Token预测Qwen3.8 27B NVFP4架构探秘Gated DeltaNet混合注意力与MTP多Token预测 Qwen3.8 27B是Qwen开源家族最新一代27大模型基础模型模型量化多模态ts-morph 最佳实践从项目结构到代码组织的完整指南ts morph 最佳实践从项目结构到代码组织的完整指南 TypeScript 开发者们你是否曾经在操作 TypeScript AST抽象语法树时感到困开发工具上一篇5分钟解放你的华硕笔记本G-Helper轻量控制工具完全指南下一篇如何快速掌握20,000条心理咨询对话数据Emotional First Aid Dataset完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考