news 2026/10/21 17:21:36

ts-morph 类声明完全指南:ClassDeclaration 的查询、修改与结构提取

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ts-morph 类声明完全指南:ClassDeclaration 的查询、修改与结构提取
  • 开发工具

【免费下载链接】ts-morph

TypeScript Compiler API wrapper for static analysis and programmatic code changes.

项目地址:https://gitcode.com/gh_mirrors/ts/ts-morph
点击查看免费下载

本篇文章以 ts-morph 仓库中的 classes 官方文档 为主线,系统讲解ClassDeclaration(类声明)的获取、命名、增删改查、继承关系分析、成员操作与接口提取等完整能力。读完本文,你将能够用 ts-morph 以类型安全的方式对类声明进行静态分析与程序化代码变更,并理解这些 API 在源码中的底层实现。

获取类声明

类声明可以从源文件(SourceFile)、命名空间(Namespace)或函数体中获取。ts-morph 将这些容器统一封装为"语句容器",getClasses()等查询方法定义在 StatementedNode.ts 中,其实现是对getStatements()结果按节点类型过滤:

const classes = sourceFile.getClasses(); const class1 = sourceFile.getClass("Class1"); const firstClassWithConstructor = sourceFile.getClass(c => c.getConstructors().length > 0);

三种典型用法对应:

  • getClasses():返回全部ClassDeclaration[];
  • getClass("Class1"):按名称精确匹配,返回ClassDeclaration | undefined;
  • getClass(predicate):传入 find 函数做条件查询,返回第一个满足条件的声明或undefined。

类似地还有getClassOrThrow(),它会在找不到时抛出带有明确错误信息的异常。从源码看,getClass最终委托给getNodeByNameOrFindFunction(this.getClasses(), nameOrFindFunction)完成"按名或按函数"的查找。

注意:查询到的类声明(以及下文的成员、属性等)都支持"传字符串名字"或"传断言函数"两种参数形态,这是 ts-morph 的一贯设计。

名称(Name):允许匿名类

类声明可能没有名字,例如export default class {}这种匿名默认导出:

export default class { // etc... }

因此,ClassDeclaration上的getName()与getNameNode()都返回可空类型(nullable)。在做依赖类名才能执行的操作(如查找派生类、提取接口时使用默认名称)时,源码中也会显式处理getName() == null的情形——例如 ClassDeclaration.ts 中getDefaultExtractedName会依次回退到类名、源文件名(去扩展名并清洗非法字符)。

添加与插入(Add / Insert)

通过addClass()、addClasses()、insertClass()、insertClasses()可以在源文件、命名空间或函数体内添加或插入类声明:

const classDeclaration = sourceFile.addClass({ name: "ClassName", });

从 StatementedNode.ts 的实现可以看出,addClasses等价于"在语句数组末尾插入",insertClasses(index, ...)则支持指定子节点索引位置,插入逻辑统一走_insertChildren,并由structurePrinterFactory.forClassDeclaration(...)打印出最终文本。传入的结构体支持完整的ClassDeclarationStructure字段(name、extends、implements、ctors、methods、properties、getAccessors、setAccessors、staticBlocks、typeParameters、decorators、docs等),这些成员会在结构体设置阶段被逐一展开写入。

移除(Remove)

调用.remove()即可从源文件中删除该声明,ts-morph 会同步处理好格式化与前后空行:

classDeclaration.remove();

Extends 表达式

getExtends()返回ExpressionWithTypeArguments | undefined,即extends子句的类型节点(可带类型实参):

const extendsExpression = classDeclaration.getExtends();

设置 extends 表达式(支持字符串或WriterFunction):

classDeclaration.setExtends("BaseClass");

移除 extends:

classDeclaration.removeExtends();

在底层实现(ClassLikeDeclarationBase.ts)中,setExtends会优先定位已有的extendsHeritageClause 做原位替换;若不存在,则在implements子句之前或类体{之前插入extends <text>;而removeExtends本质是移除 extends 子句中的第一个类型节点。此外还有getExtendsOrThrow()供"必须存在"的场景使用。

Implements 表达式

getImplements()返回ExpressionWithTypeArguments[],即所有implements的类型节点数组:

const implementsExpressions = classDeclaration.getImplements();

添加或插入 implements 表达式(支持单个字符串、字符串数组):

classDeclaration.addImplements("Named"); classDeclaration.addImplements(["Named", "Aged"]); classDeclaration.insertImplements(1, "Named"); classDeclaration.insertImplements(1, ["Named", "Aged"]);

按索引或按节点移除某个表达式:

classDeclaration.removeImplements(0); // index classDeclaration.removeImplements(classDeclaration.getImplements()[0]); // node

insertImplements的index指的是 implements 表达式列表中的位置(从 0 开始)。

基类型(Base Types)

getBaseTypes()返回Type[],它基于类型检查器解析extends与implements声明的类型层面的父类型:

const baseTypes = classDeclaration.getBaseTypes(); // returns: Type[]

源码实现(ClassLikeDeclarationBase.ts)就是this.getType().getBaseTypes()。文档特别强调:当类可能继承自 mixin(多个基类型交叉合并)时,应优先使用getBaseTypes(),因为 type 层面能返回完整的父类型集合,而不只是单个基类。

基类(Base Class)

getBaseClass()返回ClassDeclaration | undefined,它只关心"真正的类":

const baseClass = classDeclaration.getBaseClass(); // returns: ClassDeclaration | undefined

实现上(ClassLikeDeclarationBase.ts),该方法先把基类型按交叉类型展开,收集其 symbol 的声明,然后只保留ClassDeclaration类型的声明;当恰好只有一个类声明时返回它,否则返回undefined。因此:

若基类可能是 mixin,请使用.getBaseTypes(),而不是.getBaseClass()。

派生类(Derived Classes)

getDerivedClasses()返回所有继承自当前类的类声明(含间接派生,避免循环引用):

const derivedClasses = classDeclaration.getDerivedClasses();

其实现(ClassLikeDeclarationBase.ts)先通过getImmediateDerivedClasses找到直接派生类:对类名做findReferencesAsNodes()引用查找,过滤出位于extendsHeritageClause 中、且父级为ClassDeclaration的节点;然后 BFS 式地逐层扩展所有间接派生类,同时用classes.indexOf(derivedClass) === -1防止循环继承造成死循环。注意:由于依赖findReferencesAsNodes,此方法要求类名节点存在(匿名类无法使用)。

构造函数(Constructor)

getConstructors()返回类中的构造函数声明数组:

const constructors = classDeclaration.getConstructors();

关键语义(源码见 ClassLikeDeclarationBase.ts 与getAllMembers的过滤逻辑):

  • ambient(环境声明)上下文:返回所有构造函数(包括重载签名);
  • 非 ambient 上下文:仅返回实现构造函数(overload 签名会被过滤掉)。

添加或插入构造函数,使用addConstructor()、addConstructors()、insertConstructor()、insertConstructors():

const ctor = classDeclaration.addConstructor({ /* options like parameters may go here */ });

构造函数结构体支持parameters、scope、returnType、docs、decorators等字段。例如:

classDeclaration.addConstructor({ parameters: [{ name: "value", type: "number", scope: Scope.Public }], });

set()方法在设置ctors结构时,会先getConstructors().forEach(c => c.remove())清空再重新添加(ClassDeclaration.ts),实现"整体替换"。

方法(Methods)

获取实例方法(支持按名或按 find 函数):

const instanceMethods = classDeclaration.getInstanceMethods(); const myMethod = classDeclaration.getInstanceMethod("myMethod"); const firstMethodWith2Params = classDeclaration.getInstanceMethod(m => m.getParameters().length === 2);

获取静态方法:

const staticMethods = classDeclaration.getStaticMethods(); const myStaticMethod = classDeclaration.getStaticMethod("myMethod"); const firstStaticMethodWith2Params = classDeclaration.getStaticMethod(m => m.getParameters().length === 2);

添加 / 插入

const method = classDeclaration.addMethod({ isStatic: true, name: "myMethod", returnType: "string" });

insertMethods(index, ...)、insertMethod、addMethod、addMethods四件套齐备。MethodDeclarationStructure支持isStatic、name、parameters、returnType、typeParameters、statements(函数体)、scope、isAbstract、isAsync、isGenerator、decorators、docs等字段。

移除

method.remove();

属性(Properties)

获取实例属性——包含参数属性(parameter properties),即构造函数的public/private/readonly参数提升为的属性:

const instanceProperties = classDeclaration.getInstanceProperties(); const myProperty = classDeclaration.getInstanceProperty("myProperty"); const myStringProperty = classDeclaration.getInstanceProperty(p => Node.isPropertyDeclaration(p) && p.getType().getText() === "string");

获取静态属性:

const staticProperties = classDeclaration.getStaticProperties(); const myStaticProperty = classDeclaration.getStaticProperty("myStaticProperty"); const myStaticStringProperty = classDeclaration.getStaticProperty(p => Node.isPropertyDeclaration(p) && p.getType().getText() === "string");

添加 / 插入

添加或插入属性:

const property = classDeclaration.addProperty({ isStatic: true, name: "prop", type: "string", });

添加 get 访问器:

const getAccessor = classDeclaration.addGetAccessor({ name: "someNumber", returnType: "number", statements: ["return 5;"], });

添加 set 访问器:

const setAccessor = classDeclaration.addSetAccessor({ name: "someNumber", parameters: [{ name: "value", type: "number" }], statements: ["_someNumber = value;"], });

以上均有对应的insertGetAccessors()/insertGetAccessor/addGetAccessors/addGetAccessor与insertSetAccessors()/insertSetAccessor/addSetAccessors/addSetAccessor变体(定义见 ClassLikeDeclarationBase.ts)。

移除

propertyDeclaration.remove();

获取成员(Get members)

getMembers()返回全部成员,不区分静态或实例,包含构造函数与静态块(ClassStaticBlockDeclaration):

const allMembers = classDeclaration.getMembers();

getInstanceMembers()返回实例成员(包含参数属性,不含构造函数):

const instanceMembers = classDeclaration.getInstanceMembers();

getStaticMembers()返回静态成员(含静态块,不含构造函数与参数属性):

const staticMembers = classDeclaration.getStaticMembers();

底层实现值得留意:getMembers()会过滤掉无函数体的重载方法/构造函数(ambient 上下文除外);getInstanceMembers()通过getMembersWithParameterProperties()把实现构造函数中的参数属性"提升"为成员(ClassLikeDeclarationBase.ts)。此外还有getMember(name)、getMemberOrThrow()、getMembersWithComments()(含注释类元素)等更细粒度的 API。

提取接口(Extracting an Interface)

extractInterface()可从类中提取出等价的接口声明结构体(InterfaceDeclarationStructure),用于重构"类 → 接口"或生成类型约束:

// the passed in name is optional and defaults to the class name const interfaceStructure = classDeclaration.extractInterface(`I${classDeclaration.getName()}`);

提取静态部分的接口结构体:

const interfaceStructure = classDeclaration.extractStaticInterface(`${classDeclaration.getName()}Static`);

从 ClassDeclaration.ts 的实现看,提取规则为:

  • extractInterface:仅收集public的实例成员——包括构造函数的 public 参数属性(转为PropertySignatureStructure)、public 属性、public 访问器(get/set 合并为只读或可写属性签名)、public 方法(重载展开为MethodSignatureStructure),并保留类的 JSDoc 与类型参数;
  • extractStaticInterface:收集public 静态成员,并把构造函数转为ConstructSignature(returnType指向实例接口名);
  • 参数属性的 JSDoc 注释会从构造函数的@param标签中提取并迁移到对应属性签名上;
  • 名字缺省时依次回退:传入名 → 类名 → 源文件基础名(清洗非法字符)。

抽象(Abstract)

类的节点(方法、访问器等成员)可以为抽象。AbstractableNodemixin(AbstractableNode.ts)为类及类成员提供了统一能力:

method.isAbstract(); // returns: boolean

获取抽象关键字节点:

method.getAbstractKeyword(); // returns: node | undefined

设置是否为抽象:

method.setIsAbstract(true); // set as abstract method.setIsAbstract(false); // set as not abstract

实现上isAbstract()即getAbstractKeyword() != null;getAbstractKeyword()查找abstract修饰符;setIsAbstract通过toggleModifier("abstract", isAbstract)增删修饰符,并同步处理格式化。

构造函数 / 方法 / 属性:更深入的能力

文档提示:ConstructorDeclaration、MethodDeclaration、PropertyDeclaration各自实现了"函数类声明"(FunctionLikeDeclaration)上的通用能力,并叠加了各自专属能力:

  • Constructors:ConstructorDeclaration除具备函数式声明的参数、返回类型、重载、JSDoc、装饰器能力外,还带有scope(作用域:public / protected / private),支持isOverload()判断是否重载签名、isImplementation()判断是否实现体、isParameterProperty()判断参数属性等;
  • Methods:MethodDeclaration支持isStatic、isAbstract、isAsync、isGenerator、作用域与重载等;
  • Properties:PropertyDeclaration支持isStatic、isReadonly、hasQuestionToken(可选属性)、isAbstract、作用域、getType()等。

这些成员的具体方法非常丰富,实践中可通过 IDE 的自动补全(auto-complete)直接探索全部能力。

访问器互相查找(Get / Set Accessors)

如果存在对应的访问器,get 访问器可以反向找到配对的 set 访问器:

const setAccessor = getAccessor.getSetAccessor();

set 访问器也可以反向找到配对的 get 访问器:

const getAccessor = setAccessor.getGetAccessor();

这为"读写属性对"的联动分析(如检查某个属性是否只读、是否存在非对称访问器)提供了便捷入口。

小结

结合本文与源码可以确认,ts-morph 对类声明的支持是全方位的:查询(getClasses/getClass)、增删(addClass/insertClass/remove)、继承分析(getExtends/getImplements/getBaseTypes/getBaseClass/getDerivedClasses)、成员操作(方法、属性、访问器、构造函数、静态块)、抽象修饰以及接口提取(extractInterface/extractStaticInterface)。这些 API 的绝大多数能力集中在 ClassDeclaration.ts 与 ClassLikeDeclarationBase.ts 中,对应的行为验证可参考 classDeclarationTests.ts。如果你想系统掌握 ts-morph 中ExpressionWithTypeArguments(表达式与类型实参)、接口声明等相邻概念,可继续阅读 expressions 与 interfaces 文档。

  • 开发工具

【免费下载链接】ts-morph

TypeScript Compiler API wrapper for static analysis and programmatic code changes.

项目地址:https://gitcode.com/gh_mirrors/ts/ts-morph
点击查看免费下载

相关推荐

上一篇:JetBrains IDE试用期重置终极指南:如何轻松获得30天免费使用时间
下一篇:RePKG深度解析:Wallpaper Engine资源解包与TEX格式转换的专业工具

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 11:01:23

自己动手降AI:毕业论文AI疑似度六步修改法

2026年本科毕业论文季&#xff0c;最让我意外的是&#xff0c;身边很多同学不是在改查重率&#xff0c;而是在焦虑"AI疑似度"。有的被导师警告初稿AI相似度过高&#xff0c;有的花了三四百块钱买"降AI服务"&#xff0c;结果发回来的文件改得一塌糊涂&#…

作者头像 李华
网站建设 2026/10/19 20:03:52

平均值填充与逻辑回归:客户流失二分类实战全流程

手头拿到一批含缺失值的数据集&#xff0c;任务是做二分类&#xff0c;很多人的第一反应是直接上模型。但我的习惯是先把逻辑回归&#xff08;Logistic Regression&#xff09;作为基线跑通&#xff0c;因为它是分类任务里最透明、最不容易踩坑的模型之一。这篇文章就把完整的流…

作者头像 李华
网站建设 2026/10/21 3:30:57

nteract 中的 Prompt 组件:构建 Notebook 单元格执行状态指示器

开发工具数据科学 【免费下载链接】archived-desktop-app The old electron based nteract notebook 项目地址&#xff1a; https://gitcode.com/gh_mirrors/nt/archived-desktop-app 点击查看 免费下载 本指南以 nteract 仓库中 packages/presentational-components/src/comp…

作者头像 李华
网站建设 2026/10/19 20:52:37

Win11登录PIN码无法输入字母?从输入法到文本服务的排查与解决

Win11登录界面PIN码无法输入字母&#xff0c;光标明明就在输入框里闪&#xff0c;按数字键正常显示&#xff0c;一碰字母键屏幕却死活没反应——这个问题近两年我被朋友和读者问了不下二十次。每次听到我都先说一句&#xff1a;别急着换键盘&#xff0c;也别怀疑电脑中招&#…

作者头像 李华
网站建设 2026/10/20 15:42:22

基于WebRTC的SFU多人通话实践:mediasoup优化、监控与断线处理

做一个基于WebRTC的多人通话练习项目&#xff0c;最折磨人的不是把“两个人连上”&#xff0c;而是把“五个人连上”之后&#xff1a;画面忽好忽坏、有个人一掉线其他人跟着遭殃、新用户死活挤不进来。我断断续续用了个把月的时间&#xff0c;在某个音视频练习项目里把基于medi…

作者头像 李华