本帖宣布类型感知 linting 的技术预览。 有关改进了稳定性、可配置性和规则覆盖的最新 alpha 版本,请参见 类型感知 Linting Alpha 公告。
我们很高兴地宣布在 oxlint 中支持类型感知的代码检查!
🌐 We're thrilled to announce type-aware linting in oxlint!
备受期待的 no-floating-promises 及相关规则已经发布。
🌐 The long-awaited no-floating-promises and related rules are here.
此预览版本旨在通过记录我们的决策过程和技术细节,与社区进行协作和讨论。
🌐 This preview release aims to engage with the community for collaboration and discussion by documenting our decision process and technical details.
快速开始
🌐 Quick Start
如果 oxlint 已经配置,安装 oxlint-tsgolint 并使用 --type-aware 标志运行 oxlint:
🌐 If oxlint is already configured, install oxlint-tsgolint and run oxlint with the --type-aware flag:
pnpm add -D oxlint-tsgolint@latest
pnpm dlx oxlint --type-aware如果未配置 oxlint,但你想查看 no-floating-promises 的效果:
🌐 If oxlint is not configured but you want to see no-floating-promises in action:
pnpm add -D oxlint-tsgolint@latest
pnpm dlx oxlint@latest --type-aware -A all -D typescript/no-floating-promises例如,我们预期会看到:
🌐 We expect to see, for example:
× typescript-eslint(no-floating-promises): Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator.
╭─[packages/rolldown/src/api/watch/watcher.ts:30:7]
29 │ await this.close();
30 │ originClose();
· ──────────────
31 │ };
╰────请访问我们的使用指南了解更多配置选项。
🌐 Please visit our usage guide for more configuration options.
性能
🌐 Performance
我们的测试显示,以前使用 typescript-eslint 运行需要一分钟的仓库,现在不到10秒就能完成。
🌐 Our testing shows that repositories which previously took a minute to run with typescript-eslint now complete in less than 10 seconds.
这是通过利用typescript-go实现的, 这是用 Go 编写的 快 10 倍的 TypeScript。
🌐 This is achieved by leveraging typescript-go, the 10x faster TypeScript written in Go.
使用来自 oxc-ecosystem-ci 的项目:
🌐 Using projects from oxc-ecosystem-ci:
| 项目 | 文件数 | 时间 |
|---|---|---|
| napi-rs | 144 | 1.0秒 |
| preact | 245 | 2.7秒 |
| rolldown | 314 | 1.5秒 |
| bluesky | 1152 | 7.0秒 |
类型感知的代码检查
🌐 Type-Aware Linting
请参考 基于 Rust 的 JavaScript Linter:快速,但目前没有类型检测 Lint 以了解生态系统中类型感知 Lint 的当前状态。
🌐 Please refer to Rust-Based JavaScript Linters: Fast, But No Typed Linting Right Now to understand the current status of type-aware linting in the ecosystem.
技术细节
🌐 Technical Details
这个新功能的核心是 oxc-project/tsgolint。
🌐 The core of this new functionality is oxc-project/tsgolint.
tsgolint 项目最初以 typescript-eslint/tsgolint 的形式进行原型开发。然而,typescript-eslint 团队决定不为这个原型分配开发资源,因为他们计划继续在 typescript-eslint 上进行 ESLint 类型化检查的工作。
🌐 The tsgolint project was initially prototyped as typescript-eslint/tsgolint. However, the typescript-eslint team decided not to allocate development resources to this prototype, as they plan to continue their work on typescript-eslint for typed linting with ESLint.
@boshen 联系了 @auvred,希望获得一个为 oxlint 调整的分叉、精简版本。这个版本只包含类型感知的规则,而不需要完整 linter 所需的复杂配置解析。
@auvred 慷慨地提出在 Oxc 组织下继续其开发工作。
架构
🌐 Architecture
oxlint(用 Rust 编写)和 tsgolint(用 Go 编写)被编译成各自的二进制文件。
oxlint 作为 tsgolint 的“前端”,负责处理命令行接口、路径遍历、忽略逻辑和诊断输出。
tsgolint 作为 oxlint 的后端,接受路径和配置作为输入,并输出结构化的诊断信息。
这创建了一个简单的管道:
🌐 This creates a simple pipeline:
oxlint CLI (returns paths + rules + configuration)
-> tsgolint (returns diagnostics)
-> oxlint CLI (prints diagnostics)tsgolint
tsgolint 不通过公共 API 与 typescript-go 进行通信。
相反,它通过 shimming 内部 API 使其公开,从而编译 typescript-go。
🌐 Instead, it compiles typescript-go by shimming its internal APIs to make them public.
所有类型感知的规则都是直接针对这些封装的 API 编写的。
🌐 All type-aware rules are written directly against these shimmed APIs.
虽然这不是访问内部功能的推荐方法,但它确实有效!
🌐 While this isn't the recommended approach for accessing internals, it works!
决策过程
🌐 Decision Process
编写我们自己的类型检查器
🌐 Write our own type checker
之前尝试实现类型检查器的失败尝试包括:
🌐 Previous abandoned attempts to implement a type-checker included:
- 我自己尝试的类型推断
- 集成 ezno 类型检查器,由 @kaleidawave 提供
- [stc] 由 [@kdy1] 制作
- 社区中也有许多未能取得进展的尝试。
此外,还有正在开发中的 Biome 2.0,它有自己的类型推断实现。
🌐 Additionally, there's the work-in-progress Biome 2.0 with its own type-inference implementation.
我们确定编写自己的类型推断器或类型检查器不可行,因为跟上像 TypeScript 这样快速发展的目标是一个挑战。
🌐 We determined that writing our own type-inferencer or type-checker was not feasible due to the challenge of keeping up with a fast-moving target like TypeScript.
与 TypeScript 编译器的通信
🌐 Communication with TypeScript Compiler
在 typescript-go 之前,项目通过将其 AST 映射到 estree 或直接遍历 TypeScript AST 来向 TypeScript 的公共 API 添加插件接口。示例包括:
🌐 Prior to typescript-go, projects added plugin interfaces to TypeScript's public API by either mapping its AST to estree or directly traversing the TypeScript AST. Examples include:
我们还探索了使用 oxlint 进行进程间通信,但最终放弃了这个想法。
🌐 We also explored inter-process communication with oxlint but abandoned the idea.
使用 typescript-go,TypeScript 团队倾向于通过进程间通信在 JavaScript 端对 TypeScript AST 进行编码和解码。
🌐 With typescript-go, the TypeScript team is leaning towards encoding the TypeScript AST and decoding it on the JavaScript side through inter-process communication.
虽然这些方法有效,但它们仍然会产生:
🌐 While these approaches work, they still incur:
- 不同程度的性能问题,不适合 oxlint 的性能特性。
- 维护 TypeScript AST 映射的成本。
注意事项
🌐 Considerations
虽然 tsgolint 解决了性能问题,但仍有其他技术挑战需要应对。
🌐 While tsgolint solves the performance issue, there are other technical challenges that need to be addressed.
需要不同版本的 TypeScript
🌐 Requirement for a Different TypeScript Version
我们计划发布 typescript-go 版本的快照,并将它们的版本号与 TypeScript 对齐。然后,你就可以使用正确的 TypeScript 版本安装 oxlint-typescript。
🌐 We plan to release snapshots of typescript-go versions and align their version numbers with TypeScript. You will then be able to install oxlint-typescript with the correct TypeScript version.
这种方法的缺点是,如果 oxlint-tsgolint 需要更改,你可能需要升级 TypeScript。
🌐 The downside of this approach is that you may need to upgrade TypeScript if oxlint-tsgolint requires changes.
tsgolint 的维护成本
🌐 Maintenance cost of tsgolint
对 TypeScript 的内部 API 进行 shim 有一定风险。不过,TypeScript 的 AST 及其访问器实际上非常稳定。我们接受这种风险,并将在升级 typescript-go 时修复破坏性更改。
🌐 Shimming TypeScript's internal APIs carries some risk. However, the TypeScript AST and its visitor are actually quite stable. We accept this risk and will fix breaking changes when upgrading typescript-go.
我们的 typescript-go 版本每天都会同步。
🌐 Our typescript-go version is synced every day.
性能问题
🌐 Performance Issues
tsgolint 目前在包含数百个项目或大量项目引用的大型单体仓库中表现不佳。
如果遇到错误,可能会死锁挂起或导致内存耗尽(OOM)。
🌐 It may hang with a deadlock or cause OOM (out-of-memory) if a bug is encountered.
我们正在积极处理这些问题,对 typescript-go 进行分析并提交改进,惠及所有 typescript-go 用户。
🌐 We are actively addressing these issues, profiling and submitting improvements to typescript-go, benefiting all typescript-go users.
我们的核心团队成员 @camc314 已经提交了 许多 PR,使得多个代码路径显著加快。
🌐 Our core team member @camc314 has already submitted many PRs that made several code paths significantly faster.
v1.0 版本发布
🌐 v1.0 Release
对于 tsgolint v1.0,我们将解决以下问题:
🌐 For tsgolint v1.0, we will address:
- 大规模单体代码库的性能问题
- 能够配置各个规则
- 每条规则的正确性
- 集成开发环境支持
- 整体稳定性
致谢
🌐 Acknowledgements
我们想向以下人员表示感谢:
🌐 We'd like to extend our gratitude to:
- TypeScript 团队负责创建
typescript-go。 - 感谢“typescript-eslint”团队的温馨支持。
- @auvred 创建了
tsgolint。 - @camchenry 用于
oxlint+tsgolint集成。 - @camc314 因在性能问题上的工作。
加入社区
🌐 Join the Community
我们非常希望听取你对 oxlint 和类型感知 linting 的反馈,并期待看到它如何帮助改善你的开发流程。
🌐 We'd love to hear your feedback on oxlint and type-aware linting and are excited to see how it helps improve your development workflow.
联系我们:
🌐 Connect with us:
下一步
🌐 Next steps
安装 oxlint:
🌐 Install oxlint:
pnpm add -D oxlint@latest oxlint-tsgolint@latest
pnpm dlx oxlint --init # generate .oxlintrc.json或遵循安装指南。
🌐 or follow the installation guide.
使用 --type-aware 命令行参数。
🌐 Use the --type-aware CLI flag.
pnpm dlx oxlint --type-aware并尝试使用 .oxlintrc.json 中的任何类型感知规则:
🌐 And play around with any of the type-aware rules in .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"
}
}