Oxlint 类型感知预览
本文章宣布类型感知检查的技术预览。 对于最新版本的 Alpha 版本(具有更好的稳定性、可配置性和规则覆盖范围),请参阅 类型感知检查 Alpha 版本公告。
我们非常激动地宣布 oxlint 中引入了类型感知检查!
备受期待的 no-floating-promises 及相关规则现已上线。
此预览版本旨在通过记录我们的决策过程和技术细节,与社区展开协作与讨论。
快速开始
如果 oxlint 已经配置好,请安装 oxlint-tsgolint 并使用 --type-aware 标志运行 oxlint:
pnpm add -D oxlint-tsgolint@latest
pnpm dlx oxlint --type-aware如果 oxlint 尚未配置,但你想立即体验 no-floating-promises 规则:
pnpm add -D oxlint-tsgolint@latest
pnpm dlx oxlint@latest --type-aware -A all -D typescript/no-floating-promises我们期望看到类似以下输出:
× typescript-eslint(no-floating-promises): Promises 必须被等待,以 .catch 调用结尾,以带有拒绝处理程序的 .then 调用结尾,或显式使用 `void` 操作符标记为忽略。
╭─[packages/rolldown/src/api/watch/watcher.ts:30:7]
29 │ await this.close();
30 │ originClose();
· ──────────────
31 │ };
╰────更多配置选项,请访问我们的 使用指南。
性能
我们的测试表明,此前使用 typescript-eslint 运行需耗时一分钟的仓库,现在可在 10 秒内完成。
这得益于我们利用了 typescript-go,即用 Go 编写的 10 倍更快的 TypeScript。
使用来自 oxc-ecosystem-ci 项目中的示例:
| 项目 | 文件数 | 时间 |
|---|---|---|
| napi-rs | 144 | 1.0s |
| preact | 245 | 2.7s |
| rolldown | 314 | 1.5s |
| bluesky | 1152 | 7.0s |
类型感知检查
请参考 基于 Rust 的 JavaScript 检查器:快速,但目前尚无类型感知检查 了解当前生态系统中类型感知检查的现状。
技术细节
这一新功能的核心是 oxc-project/tsgolint。
tsgolint 项目最初作为原型在 typescript-eslint/tsgolint 中实现。 然而,typescript-eslint 团队决定不为此原型投入开发资源,因为他们计划继续在 typescript-eslint 上推进基于 ESLint 的类型感知检查工作。
@boshen 向 @auvred 提出请求,希望获得一个专为 oxlint 定制的精简版分支。 该版本将仅包含类型感知规则,而不包含完整检查器所需的复杂配置解析机制。
@auvred 毅然同意在 Oxc 组织下继续该项目的开发。
架构设计
oxlint(用 Rust 编写)和 tsgolint(用 Go 编写)分别编译成独立的二进制文件。
oxlint 作为 tsgolint 的“前端”,负责处理 CLI、路径遍历、忽略逻辑和诊断信息的打印。
tsgolint 作为 oxlint 的后端,接收路径和配置作为输入,并输出结构化的诊断结果。
这形成了一条简洁的处理流程:
oxlint CLI(返回路径 + 规则 + 配置)
-> tsgolint(返回诊断信息)
-> oxlint CLI(打印诊断信息)tsgolint
tsgolint 并不通过公共 API 与 typescript-go 通信。
相反,它通过 打补丁 的方式,将 typescript-go 的内部接口暴露为公开接口。
所有类型感知规则都直接基于这些打补丁后的接口编写。
虽然这不是访问内部实现的推荐方式,但目前效果良好!
决策过程
自行编写类型检查器
过去尝试实现类型检查器的多个努力均告失败,包括:
- 我自己对 类型推断的尝试
- 集成 @kaleidawave 开发的 ezno 类型检查器
- @kdy1 开发的 stc
- 社区中还有许多未深入发展的尝试。
此外,正在开发中的 Biome 2.0 也拥有自己的类型推断实现。
我们判断,自行开发类型推断器或类型检查器不可行,因为需要持续跟进像 TypeScript 这样快速演进的目标,难度极高。
与 TypeScript 编译器的通信
在 typescript-go 之前,项目通过以下方式向 TypeScript 公共 API 添加插件接口:将其语法树映射到 estree,或直接遍历 TypeScript 语法树。例如:
我们也曾探索过 与 oxlint 的进程间通信,但最终放弃。
随着 typescript-go 的发展,TypeScript 团队正倾向于通过 进程间通信 将 TypeScript 语法树编码并解码到 JavaScript 端。
尽管这些方法有效,但仍存在以下问题:
- 性能问题程度不一,无法满足
oxlint的性能要求。 - 维护从 TypeScript 语法树到其他表示形式的映射成本高昂。
考虑事项
尽管 tsgolint 解决了性能问题,仍存在其他技术挑战亟待解决。
对不同 TypeScript 版本的需求
我们计划发布 typescript-go 的快照版本,并使其版本号与 TypeScript 对齐。 届时,你将能够安装正确版本的 oxlint-typescript。
该方法的缺点是,若 oxlint-tsgolint 需要更新,你可能需要升级 TypeScript。
tsgolint 的维护成本
打补丁方式会带来一定风险。不过,实际证明 TypeScript 语法树及其访问者结构非常稳定。 我们接受这种风险,并会在升级 typescript-go 时修复破坏性变更。
我们的 typescript-go 版本每天同步一次。
性能问题
目前 tsgolint 在包含数百个项目的大型多包仓库中表现不佳,或在大量项目引用场景下容易出现死锁,甚至导致内存溢出(OOM)。
我们正在积极解决这些问题,进行性能剖析并向 typescript-go 提交改进,惠及所有 typescript-go 用户。
核心团队成员 @camc314 已提交 多项 PR,显著提升了多个代码路径的性能。
v1.0 版本发布
对于 tsgolint v1.0,我们将解决以下问题:
- 大型多包仓库的性能问题
- 支持单个规则的配置
- 每个规则的正确性
- IDE 支持
- 整体稳定性
致谢
我们衷心感谢:
- TypeScript 团队创造了
typescript-go。 typescript-eslint团队给予的热情支持。- @auvred 创建了
tsgolint。 - @camchenry 实现了
oxlint与tsgolint的集成。 - @camc314 在性能问题上的贡献。
加入社区
我们非常期待听到你对 oxlint 以及类型感知检查的反馈,并热切期待看到它如何提升你的开发流程。
与我们连接:
- Discord:加入我们的 社区服务器 进行实时交流
- GitHub:在 GitHub Discussions 分享反馈
- 问题报告:将
oxlint的问题报告给 oxc,将类型感知检查的问题报告给 tsgolint。
下一步
安装 oxlint:
pnpm add -D oxlint@latest oxlint-tsgolint@latest
pnpm dlx oxlint --init # 生成 .oxlintrc.json或按照 安装指南 进行操作。
使用 --type-aware CLI 标志。
pnpm dlx oxlint --type-aware并在 .oxlintrc.json 中尝试任意一个类型感知规则:
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"rules": {
"typescript/await-thenable": "error",
"typescript/no-array-delete": "error",
"typescript/no-base-to-string": "error",
"typescript/no-confusing-void-expression": "error",
"typescript/no-duplicate-type-constituents": "error",
"typescript/no-floating-promises": "error",
"typescript/no-for-in-array": "error",
"typescript/no-implied-eval": "error",
"typescript/no-meaningless-void-operator": "error",
"typescript/no-misused-promises": "error",
"typescript/no-misused-spread": "error",
"typescript/no-mixed-enums": "error",
"typescript/no-redundant-type-constituents": "error",
"typescript/no-unnecessary-boolean-literal-compare": "error",
"typescript/no-unnecessary-template-expression": "error",
"typescript/no-unnecessary-type-arguments": "error",
"typescript/no-unnecessary-type-assertion": "error",
"typescript/no-unsafe-argument": "error",
"typescript/no-unsafe-assignment": "error",
"typescript/no-unsafe-call": "error",
"typescript/no-unsafe-enum-comparison": "error",
"typescript/no-unsafe-member-access": "error",
"typescript/no-unsafe-return": "error",
"typescript/no-unsafe-type-assertion": "error",
"typescript/no-unsafe-unary-minus": "error",
"typescript/non-nullable-type-assertion-style": "error",
"typescript/only-throw-error": "error",
"typescript/prefer-promise-reject-errors": "error",
"typescript/prefer-reduce-type-parameter": "error",
"typescript/prefer-return-this-type": "error",
"typescript/promise-function-async": "error",
"typescript/related-getter-setter-pairs": "error",
"typescript/require-array-sort-compare": "error",
"typescript/require-await": "error",
"typescript/restrict-plus-operands": "error",
"typescript/restrict-template-expressions": "error",
"typescript/return-await": "error",
"typescript/switch-exhaustiveness-check": "error",
"typescript/unbound-method": "error",
"typescript/use-unknown-in-catch-callback-variable": "error"
}
}



