核心代码逻辑 - whiter001/vue-tsc-files GitHub Wiki

基于版本 1.3.4,提交 9372517d 的源码静态阅读。

核心定位

vue-tsc-files 本身不实现类型检查,而是围绕 vue-tsc 的轻量 CLI 包装器。它解决两个问题:

  1. 保留项目 tsconfig 配置,同时缩小类型检查的入口文件范围。
  2. 可选择只让目标文件自身的错误影响退出码,依赖文件的错误仍完整展示。

执行主线

解析命令行参数,合并显式文件和 Git 收集结果
  → 读取项目 tsconfig,补充项目范围内的 .d.ts
  → 在原配置旁生成临时 tsconfig
  → 定位 vue-tsc 的 JS 入口
  → 使用当前 Node 调用 vue-tsc,强制 --noEmit
  → 处理输出与退出码
  → 退出时清理临时配置

运行时代码只有两个文件:

  • src/cli.ts:入口,串联流程、启动子进程、处理输出和退出码。
  • src/util.ts:参数解析、Git 文件收集、临时配置生成与清理、诊断归属判断。

参数解析:检查谁,如何检查

setupArgs() 将参数分为三类:

  • 文件参数:按扩展名识别 .vue/.ts/.tsx/.mts/.cts,大小写不敏感;相对路径按当前工作目录解析,也接受绝对路径。
  • 工具参数:消费 -p/--project、Git 收集开关、错误过滤开关、帮助参数。支持 --project=<path> 和 -p=<path>。
  • 其他参数:原样转发给 vue-tsc,例如 --pretty。

显式文件与 Git 收集模式可以混用,结果合并去重:

  • specifiedFiles:用户显式指定或 Git 收集的目标文件,用于错误归属。
  • files:目标列表的副本;生成配置时再拼接自动收集的 .d.ts,作为临时配置的入口列表。

最终没有目标文件时:启用了任意 Git 收集开关,提示并退出 0;否则是用法错误,退出 1。--help 在文件收集和配置读取之前生效。

Git 收集:只决定文件名单

实现见 getChangedFiles()/getStagedFiles()/getUnstagedFiles()。

参数 数据来源 收集范围
--changed git status --porcelain=v1 -z -uall -- . 暂存、未暂存、未跟踪等变更
--staged git diff --cached --name-only -z --diff-filter=ACMRT -- . 暂存区变更
--unstaged git diff --name-only -z --diff-filter=ACMRT -- . 已跟踪文件的未暂存变更,不含未跟踪文件

使用 NUL 分隔处理含空格路径;git status 重命名记录保留新路径、跳过旧路径。收集结果转为 cwd 相对路径,并过滤不支持的扩展名、node_modules 和已经不存在的文件。命令带 -- .,在仓库子目录执行时只收集该子目录范围的变更。

Git 子进程设有 30 秒超时和 64 MB 输出缓冲区。

--staged 按 index 选文件,但检查磁盘上的当前内容,不读取暂存快照。

临时 tsconfig:核心设计

入口见 createAndSetupTsConfig(),构造见 createTmpTsConfig()。

  1. 未传 -p 时使用 cwd 的 tsconfig.json,不向上搜索;传目录时读取目录内的 tsconfig.json。
  2. 使用 TypeScript 的 readConfigFile() 读取配置,支持注释和尾逗号。
  3. 使用 getParsedCommandLineOfConfigFile() 解析 extends 链及原项目 files/include/exclude 范围,从文件列表中补充 .d.ts。
  4. 展开保留原配置的其他字段,包括 extends、exclude、references 和编译选项;替换入口 files,并设置 include: []。
  5. files 写绝对路径,避免 cwd 与 -p 配置目录不同导致路径错位。
  6. skipLibCheck 默认 true,但根配置或 extends 链显式设置过时尊重用户值。

临时配置放在原 tsconfig 旁边,保证相对 extends、baseUrl、paths、typeRoots 等按原目录解析。

显式列入 files 的源码不受原 include/exclude 过滤。缩小入口不等于隔离依赖:目标文件通过 import 引入的其他文件仍可能进入类型检查。

检测到 references 时会提示;工具不跟随引用执行多项目检查,不能将 solution tsconfig 的调用视为完整项目检查。

依赖定位与底层调用

createResolverRequires() 提供 cwd 和工具自身两个解析锚点。TypeScript 优先从当前项目加载,找不到再回退到工具位置。

resolveVueTscEntry() 首先按 cwd、工具位置的顺序解析 vue-tsc/package.json,拼出 bin/vue-tsc.js;若均失败,再按这两个锚点直接解析 bin 子路径。

这样通常优先使用项目版本,并兼容 pnpm 的符号链接布局。最终调用:

当前 Node + vue-tsc JS入口 + -p 临时配置 + 转发参数 + --noEmit

输出与退出码

实现见 src/cli.ts:59–114。

普通模式使用 stdio: "inherit" 直接透传输出,退出码使用 status ?? 1,启动失败不会静默通过。

--errors-in-changed-only(别名 --changed-only)模式捕获输出,缓冲区为 64 MB;正常获得结果时完整打印 stdout/stderr,再判断是否阻断:

情况 结果
子进程启动或运行返回 error 退出 1
vue-tsc 成功 退出 0
非零状态,指定文件自身有错误 保留非零退出码
非零状态,有无文件位置的全局诊断 保留非零退出码
非零状态,输出无可识别的 TS 错误,例如崩溃 保留非零退出码
非零状态,错误全部属于非指定文件 展示错误和提示,但退出 0

filterErrorsInFiles() 去除 ANSI 色码,兼容普通与 --pretty 诊断格式,将文件路径规范化后与 specifiedFiles 比较。无位置的 error TSxxxx: 诊断单独归为全局错误。

过滤依据是文件归属,不比较 Git 基线或修改行。 指定文件中的旧错误仍阻断,非指定文件中的新错误也可能被豁免。自动补充的 .d.ts 不参与指定文件归属,除非它也被显式传入或由 Git 收集。

捕获模式设置 process.exitCode 而非立即 process.exit(),让输出自然排空,避免截断管道尾部。

清理、构建与测试

registerCleanupHandler() 用集合记录临时路径,监听器只注册一次;退出或收到 SIGHUP/SIGINT/SIGTERM 时同步删除临时配置,信号退出码分别是 129/130/143。

rolldown.config.mjs 将 src/cli.ts 打包为根目录的 vue-tsc-files,使用 ESM、不压缩,TypeScript 保持外部依赖。根目录文件是生成的发布产物,不应直接编辑。

  • pnpm build:生成 CLI bundle。
  • pnpm check:oxlint、oxfmt 检查与 TypeScript 检查。
  • pnpm test:pretest 先构建,再执行 Node 测试。

test/util.test.ts 覆盖参数解析、Git 输出解析、诊断过滤和配置生成;test/cli.test.ts 覆盖真实 CLI 的退出码、依赖错误过滤、Git 收集、配置路径、依赖定位和临时文件清理。

⚠️ **GitHub.com Fallback** ⚠️