news 2026/10/10 2:45:30

ts-morph 获取 Source Files 完全指南:getSourceFiles / getSourceFile 的匹配语义与实战用法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ts-morph 获取 Source Files 完全指南:getSourceFiles / getSourceFile 的匹配语义与实战用法
  • 开发工具

【免费下载链接】ts-morph

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

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

导读

本篇指南围绕 ts-morph 中「如何在 Source File 已添加到 Project 之后获取它们」这一核心环节展开,系统讲解getSourceFiles(全部与 glob 过滤)、getSourceFile(按路径/按条件)以及getSourceFileOrThrow三种 API 的使用方式、匹配语义与底层实现。读完本文,你将能准确回答「在什么情况下会命中哪些文件」这一最关键的问题,避免因当前工作目录(CWD)、相对路径与 glob 排除模式使用不当而取错文件,并理解这些行为背后对应仓库中的哪一段实现代码。

为什么需要单独讨论「获取」Source Files

在 ts-morph 中,Project 是管理所有已加载源码文件的中心对象。无论通过addSourceFilesAtPaths、addSourceFilesFromTsConfig添加文件,还是通过createSourceFile创建内存中的文件(对应 Project.ts),这些文件都会被登记到 Project 内部。而「获取」则是一个独立环节:正如原文档(getting-source-files.md)所指出的——「After source files are added, you will need to get them in order to navigate or make changes」,即只有先稳定地取回SourceFile对象,才能继续做节点遍历(Navigation)或程序化修改(Manipulation)。

因此本文所有 API 都建立在文件已添加的前提之上,讨论重点不是「怎么添加」,而是「如何精确、高效地把目标文件取回来」。

获取全部 Source Files

最简单的情形是获取当前 Project 中的全部源码文件,直接调用无参的getSourceFiles():

const sourceFiles = project.getSourceFiles();

返回类型为SourceFile[]。从源码看,该无参重载(Project.ts)内部会调用#getProjectSourceFilesByDirectoryDepth()生成器:它遍历编译器工厂中的所有源文件,并通过inProjectCoordinator.isSourceFileInProject(sourceFile)过滤,只返回真正属于当前 Project 的文件。这一过滤非常重要——那些仅被 TypeScript 编译器解析进来、但并未被显式标记为「属于项目」的依赖文件不会混入结果。

从源码结构看,遍历顺序并非随机:测试 projectTests.ts 验证了结果按目录深度排序(同一目录下大写字母优先,随后按目录层级由浅入深)。这意味着当你需要稳定的文件顺序做批处理时,可以依赖该顺序。

使用 glob 模式过滤获取

getSourceFiles还支持传入一个或多个 glob 模式进行过滤,这在只关心某类文件(例如测试文件)时非常实用:

// 单个 glob const testSourceFiles = project.getSourceFiles("src/test/**/*.ts"); // 多个 glob(含排除模式) const nonTestSourceFiles = project.getSourceFiles([ "src/**/*.ts", "!src/test/**/*.ts", ]);

单模式形式直接按模式筛选;数组形式中,!前缀表示「否定/排除」模式。底层实现位于 matchGlobs.ts:它会依次遍历每个文件路径与每个模式,先用FileUtils.toAbsoluteGlob把(相对)模式转换为基于当前目录的绝对 glob,再用FileUtils.isNegatedGlob识别!前缀,命中否定模式时从结果中移除该路径。因此多个模式之间是叠加的过滤过程:先加入匹配项,再用排除项剔除。

仓库测试给出了一个多模式叠加的完整示例(projectTests.ts):

project.getSourceFiles(["**/src/**/*.ts", "!**/src/test/**/*.ts", "!**/*.d.ts"]) // 结果仅剩 /src/file.ts

在创建了file.ts、src/file.ts、src/test/file1.ts、src/test/file1.d.ts、src/test/file2.ts、src/test/file3.ts、src/test/file3.js、src/test/folder/file.ts等文件的前提下:

  • **/src/**/*.ts先命中所有 src 下的 ts/d.ts 文件;
  • !**/src/test/**/*.ts剔除 src/test 目录下的全部 .ts/.d.ts;
  • !**/*.d.ts进一步剔除声明文件;
  • 最终只剩src/file.ts。

这一组合写法是「保留大部分、剔除特定子集」的标准模式,可直接套用到实际项目。

路径解析基于当前工作目录(重点)

原文档特别强调了一个易错点:路径解析与模式匹配都是相对于当前工作目录(current working directory,CWD)进行的。

const project = new Project({ tsConfigFilePath: `/someDirectory/notCurrent/tsconfig.json`, });

如果 tsconfig 位于当前目录之外的/someDirectory/notCurrent/,那么想要匹配该目录下的文件,就不能写基于 tsconfig 所在目录的相对 glob,而必须写出「相对于当前工作目录或绝对路径」的模式:

const sourceFile = project.getSourceFiles(`/someDirectory/notCurrent/**/config/index.ts`);

从源码印证:matchGlobs在匹配前会以fileSystemWrapper.getCurrentDirectory()作为cwd调用FileUtils.toAbsoluteGlob将模式标准化为绝对 glob(见 matchGlobs.ts 与 FileUtils.ts)。也就是说,模式中的相对路径全部以 CWD 为基准解析,而与 Project 内部保存的 tsconfig 路径没有直接关系。跨目录加载项目时,最稳妥的做法是直接使用绝对路径模式,避免一切歧义。

按文件路径获取:返回第一个「路径结尾匹配」的文件

getSourceFile(filePath)用于按路径获取单个源文件,返回SourceFile | undefined。其语义是原文档描述的关键:返回第一个「文件路径以给定路径结尾」匹配的文件:

const personFile = project.getSourceFile("Models/Person.ts");

底层行为(Project.ts)分为两种情况:

  1. 传入纯文件名(不含/):例如getSourceFile("file.ts"),会被包装成一个谓词def => FileUtils.pathEndsWith(def.getFilePath(), fileNameOrPath),即对每个源文件逐一做「路径结尾匹配」,返回第一个命中的文件。FileUtils.pathEndsWith(FileUtils.ts)会按/切分路径片段后从尾部逐一比对,因此"Models/Person.ts"、"Person.ts"这类片段式写法都能命中".../Models/Person.ts"。
  2. 传入含/的路径或绝对路径:会被FileUtils.standardizeSlashes规范化并通过getStandardizedAbsolutePath解析为绝对路径,随后直接从编译器缓存中按完整路径精确查找。值得注意的是源码注释明确说明「when a file path is specified, return even source files not in the project」——按完整路径查找时,即使该文件未标记为项目内文件也会返回。

需要特别留意「返回第一个」的含义:如果 Project 中存在dir/file.ts与file.ts两个文件,getSourceFile("file.ts")会返回哪个?测试 projectTests.ts 给出了答案——取决于两个文件被创建的先后顺序(即目录深度遍历顺序),并验证了./file.ts、dir/../dir/file.ts、/file.ts等多种写法都能被正确处理。因此当同名文件可能冲突时,建议使用更完整的路径(含目录或绝对路径)来消除歧义。

按条件获取:谓词搜索

当「按路径」无法表达你的查找意图时,可以传入一个谓词函数,getSourceFile将返回第一个满足条件的源文件:

const fileWithFiveClasses = project.getSourceFile(f => f.getClasses().length === 5);

该重载的实现在getSourceFile(fileNameOrSearchFunction: string | ((file: SourceFile) => boolean))中:若参数是函数,则直接作为谓词交给IterableUtils.find对源文件集合进行线性查找,找到第一个返回true的文件(Project.ts)。谓词可以组合任意 SourceFile 能力,例如「文件名包含某个关键词」「是否处于 ambient 环境」「包含特定数量的接口」等。因为只返回第一个命中项,若你需要收集所有符合条件的文件,应改用getSourceFiles+ glob,或自行遍历getSourceFiles()后过滤。

找不到时的强约束版本:getSourceFileOrThrow

getSourceFile在找不到文件时返回undefined,需要你自行判空。若希望「找不到就立即抛错」,可以使用强约束版本getSourceFileOrThrow:

const mainFile = project.getSourceFileOrThrow("main.ts");

其行为(Project.ts)为:内部先调用getSourceFile,若结果为null则抛出InvalidOperationError,并且错误信息会根据输入形式区分三类:

  • 传入纯文件名:报错Could not find source file in project with the provided file name: <name>;
  • 传入相对/绝对路径:先标准化为绝对路径再报错Could not find source file in project at the provided path: <path>;
  • 传入谓词函数:报错Could not find source file in project based on the provided condition.。

测试 projectTests.ts 对这四种错误路径均有断言。在编写批处理脚本时,getSourceFileOrThrow能让「文件缺失」这类配置问题在第一时间暴露,而不是等到下游访问undefined时才抛出难以定位的异常。

从源码理解整体匹配流程

综合上述源码,一次「获取源文件」请求的完整链路可归纳为:

  1. 无参getSourceFiles():从compilerFactory.getSourceFilesByDirectoryDepth()取得全部源文件,再经InProjectCoordinator过滤出项目内文件(Project.ts);
  2. 带 glob:先收集项目内文件的路径,再由matchGlobs以 CWD 为基准将模式转绝对 glob 并做「加入/排除」过滤,最后按匹配路径从缓存取回SourceFile(Project.ts、matchGlobs.ts);
  3. 按路径/按条件:路径形式经标准化后走缓存精确查找(含项目外文件),纯文件名形式走pathEndsWith结尾匹配,函数形式走谓词线性查找。

这套设计意味着:匹配始终以「Project 当前持有哪些源文件」为候选集(路径精确查找除外)。如果你先后addSourceFilesAtPaths后又removeSourceFile或调用了forget,候选集也随之变化——测试 projectTests.ts 专门验证了「不属于项目的文件不会被getSourceFiles返回」。因此在使用这些 API 前,先确认候选文件确实已被添加进 Project,是最常见的排错起点。

实战小结与最佳实践

  • 拿全部:无参getSourceFiles(),返回按目录深度排序的项目内全部源文件。
  • 拿子集:getSourceFiles(["src/**/*.ts", "!src/test/**/*.ts"]),用排除模式剔除目标目录;多模式按「先加入、后剔除」的顺序生效。
  • 拿单个:getSourceFile("Models/Person.ts")做路径结尾匹配并返回第一个命中;同名冲突时改用含目录的完整路径或绝对路径。
  • 拿不到就报错:改用getSourceFileOrThrow(...),让文件缺失在第一时间暴露,且错误信息区分文件名/路径/条件三种形态。
  • 按逻辑拿:getSourceFile(f => ...)用谓词表达无法用路径描述的条件。
  • 跨目录项目:glob 始终相对 CWD 解析,涉及 tsconfig 在其他目录的 Project 时,使用绝对路径模式最安全。

如果你还需要按目录层级组织地获取文件(例如拿取某个目录下的全部源文件、处理目录级批量操作),可以继续阅读仓库中的 directories.md;而若需要基于语言服务做跨文件分析(如查找引用、语义诊断),可参考 program.md 与 type-checker.md。掌握了「添加—获取—遍历—修改」这条主线,你就能用 ts-morph 编写出稳定、可预测的程序化代码改造工具。

  • 开发工具

【免费下载链接】ts-morph

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

项目地址:https://gitcode.com/gh_mirrors/ts/ts-morph
点击查看免费下载
上一篇:Radar RBAC权限可见性深度解析:ServiceAccount爆炸半径排查与OIDC认证安全实战
下一篇:treg health 凭据健康检查完整教程:OAuth 刷新、工具探测与 webhook 告警

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

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

Polars GPU 加速引擎实战指南:在 NVIDIA GPU 上运行 Lazy API 查询

数据分析大数据 【免费下载链接】polars Extremely fast Query Engine for DataFrames, written in Rust 项目地址&#xff1a; https://gitcode.com/GitHub_Trending/po/polars 点击查看 免费下载 导读 本文围绕 Polars 官方用户指南中的 gpu-support.md 展开&#xff0c;系…

作者头像 李华
网站建设 2026/10/10 2:45:20

整本小说批量转 AI 漫剧实操教程:知漫剧一键自动拆分多集

长篇网文做连载漫剧&#xff0c;最耗费精力的就是手动分割章节、逐集撰写分镜脚本。实测知漫剧&#xff08;zz.jiaxunai.cn&#xff09;支持整本小说导入&#xff0c;AI 自动识别剧情节奏&#xff0c;一键拆分成多集漫剧脚本。整套创作流程都在平台内完成&#xff0c;无需来回切…

作者头像 李华
网站建设 2026/10/10 2:43:45

从 Java 后端到大模型应用:我把公司知识库问答从 0 到 1 搭了起来

去年公司要做智能客服知识库问答&#xff0c;这个活儿落到了我这个纯 Java 后端头上。从 “没碰过大模型” 到上线一套可用的 RAG 问答系统&#xff0c;过程记录在这里 —— 架构怎么设计、代码怎么写、坑怎么踩&#xff0c;Java 工程师可以直接参考。 一、需求与架构&#xff…

作者头像 李华