12k
All articles

使用 ESLint 对 TypeScript 进行 Lint 检查

TypeScript 的 ESLint 10 flat config:用 typescript-eslint 配置,开启 projectService 类型感知 lint,并正确接入 Prettier。

OpenReplay Team
OpenReplay Team
使用 ESLint 对 TypeScript 进行 Lint 检查

截至 2026 年 7 月,对 TypeScript 进行 lint 检查的现行做法是:ESLint 10 搭配 typescript-eslint 包,并使用扁平配置(flat config,即 eslint.config.mjs)——而不是绝大多数搜索结果里仍在展示的旧式 .eslintrc 方案。

如果你曾经从 2022 年的教程里复制过一份配置,却发现 ESLint 完全无视它,原因就在这里:那份配置是为一套已经不存在的配置系统编写的。替代方案很简洁,不过其中涉及类型感知(type-aware)的部分需要一个额外选项,而这个选项很容易被忽略。

ESLint 10 彻底移除了 eslintrc 配置系统,这在该项目推进扁平配置的规划中早有预告。仅此一项变更就足以让几乎所有 2024 年之前的教程失效,因为 ESLint 已经完全不再读取 .eslintrc.eslintignore 文件。本文将为你提供一份正确、可直接复制使用的 TypeScript 扁平配置,演示如何启用类型感知规则,并将 lint 检查接入你的 npm 脚本、编辑器和 CI 流程。

关键要点

  • 现代技术栈是 ESLint 10 加上扁平配置下的 typescript-eslint v8;自 ESLint 10 起,.eslintrc/.eslintignore 已经彻底退场。
  • 一份最小可用配置只需在名为 eslint.config.js/.mjs 的文件中,把 js.configs.recommendedtseslint.configs.recommended 传给来自 eslint/configdefineConfig()
  • no-floating-promises 这类类型感知规则需要 parserOptions: { projectService: true }。空的 parserOptions 并不会启用它们。
  • 类型化 lint 需要 TypeScript 在检查前先构建项目,因此速度较慢;建议在 CI 中运行,编辑器里则依赖 IDE 的缓存。
  • 在扁平配置中,--ext 标志已经过时:文件匹配范围由各配置块的 files glob 决定,所以 lint 脚本只需写成 eslint .

ESLint 和 TypeScript 做的是同一件事吗?

ESLint 和 TypeScript 是互补关系,而非竞争关系。确实有少数 typescript-eslint 规则会深入调用 TypeScript 的类型检查器来更透彻地理解代码,但这两个工具回答的是不同的问题:TypeScript 编译器检查类型是否匹配,而 ESLint 在整个代码库中强制执行代码风格并捕获潜在缺陷(未使用的变量、未处理的 Promise、不安全的写法)。两者都要用。

如果你正在从 TSLint 迁移过来,请注意它早已停止维护多年。其支持方在 2019 年宣布将弃用 TSLint,转而支持 typescript-eslint,此后 ESLint 生态成为 TypeScript lint 检查的事实标准。在新项目中没有任何理由再选择 TSLint。

安装前还有一个前提条件:ESLint 10 放弃了对旧版本 Node 的支持。它现在要求 Node.js v20.19.0 及以上、v22.13.0 及以上,或 v24 及以上,v21.x 和 v23.x 不再受支持。

如何为 TypeScript 配置 ESLint?

安装你真正需要的四个包:

npm i -D eslint @eslint/js typescript typescript-eslint

typescript-eslint 这个辅助包已经打包了 parser 和 plugin,因此你无需手动接入 @typescript-eslint/parser@typescript-eslint/eslint-plugin。它支持当前主版本:typescript-eslint 文档中列出的 ESLint 版本范围涵盖 ^8.57.0 || ^9.0.0 || ^10.0.0,因此 typescript-eslint@latest(v8.x)可以在 ESLint 10 上正常运行。

创建 eslint.config.mjs(扁平配置,不是 .eslintrc):

// eslint.config.mjs
import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';

export default defineConfig(
  js.configs.recommended,
  tseslint.configs.recommended,
);

这就是一份可用的基线配置:ESLint 核心推荐规则,加上 typescript-eslint 的推荐规则集(后者会自动为你指定 typescript-eslint 的 parser 和 plugin)。defineConfig() 来自 ESLint 核心,是目前应当采用的辅助函数,因为 typescript-eslint 已经弃用了自家的 tseslint.config() 并转而推荐它。旧的辅助函数仍能运行,因此已经跑通的配置不会被破坏,但新项目应当使用 defineConfig()。无论采用哪种方式,都仍需继续导入 tseslint,因为你还要用到 tseslint.configs.* 以及相关的 glob 辅助工具。

提高严格度,再逐条微调规则

recommended 只是起点;另有两套可选预设能进一步抬高标准。tseslint.configs.strict 增加了更具倾向性的正确性规则,tseslint.configs.stylistic 则增加了无需类型信息的一致性规则。把它们与 recommended 一起加入配置数组即可。

任何规则都可以在 rules 块中覆盖。规则严重级别分为三档:off(或 0)完全关闭规则,warn(或 1)报告问题但不影响退出码,error(或 2)报告问题并让 ESLint 以退出码 1 结束。对于希望可见但不阻断流程的问题使用 warn;对于绝不允许进入仓库的问题使用 error,因为它会返回非零退出码并导致 CI 失败。

rules: {
  '@typescript-eslint/no-explicit-any': 'warn',
  '@typescript-eslint/no-unused-vars': 'error',
}

在现代配置中,建议使用字符串形式的严重级别('warn'/'error'),而非数字形式。前者可读性更好,而只用数字的写法是过时 .eslintrc 教程的典型特征。

类型感知 lint:那些需要类型信息的规则

一些最有价值的规则——no-floating-promisesno-misused-promises 就在其中——需要类型信息,你可以通过添加 parserOptions: { projectService: true } 来启用它。自 typescript-eslint v8 起,这一直是官方推荐的启用类型化 lint 的方式,它取代了较旧的 project 选项,因为配置更少、运行更快。同时,还要把预设切换为其类型检查版本(recommendedTypeCheckedstrictTypeCheckedstylisticTypeChecked)。空的 parserOptions: {} 不会开启类型感知 lint,这是复制粘贴配置时常见的错误。

{
  files: ['**/*.ts', '**/*.tsx'],
  extends: [tseslint.configs.recommendedTypeChecked],
  languageOptions: {
    parserOptions: {
      projectService: true,
      tsconfigRootDir: import.meta.dirname,
    },
  },
}

类型化 lint 是有实际代价的。开启它意味着 TypeScript 必须先构建你的项目,ESLint 才能开始检查;在小型代码库上这不过一两秒,在大型代码库上则会明显更久。typescript-eslint 官方给出的建议正是基于这种不对称性:编辑器插件会缓存类型信息,基本能规避这份开销,因此可以在 CI 和 pre-commit 中运行完整的类型化 lint,日常开发则交给编辑器覆盖。projectService 还免去了以往维护单独 tsconfig.eslint.json 的变通做法,因为它使用的正是编辑器所用的同一个项目配置。

把 JS 与 TS 分开,并设置忽略规则

类型检查规则只对 TypeScript 能理解的文件有意义,因此应将其作用范围限定在 **/*.ts/**/*.tsx,并对纯 JavaScript 文件关闭。typescript-eslint 恰好为此提供了一套预设。其官方文档tseslint.configs.disableTypeChecked 应用于 **/*.js 配置块,以剥离 TypeScript 专有的设置。在扁平配置中,忽略规则就是一个只包含 ignores 键的配置块,它正是 .eslintignore 的替代品。

// eslint.config.mjs
import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
import prettier from 'eslint-config-prettier';

export default defineConfig(
  { ignores: ['dist/', 'node_modules/', 'coverage/', '**/*.d.ts'] },
  js.configs.recommended,
  {
    files: ['**/*.ts', '**/*.tsx'],
    extends: [tseslint.configs.recommendedTypeChecked],
    languageOptions: {
      parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname },
    },
    rules: { '@typescript-eslint/no-explicit-any': 'warn' },
  },
  { files: ['**/*.js', '**/*.mjs'], extends: [tseslint.configs.disableTypeChecked] },
  prettier, // 必须放在最后
);

把格式化交给 Prettier,再打通整个工作流

不要让 ESLint 承担格式化职责。将 eslint-config-prettier 放在最后,用它关闭那些会与 Prettier 冲突的 ESLint 风格规则,并将版本锁定在 ^10.1.8 或更高。版本很关键:2025 年 7 月,一次针对维护者 npm 凭据的钓鱼攻击导致四个被篡改的版本被发布,该事件记录为 CVE-2025-54313。8.10.1、9.1.1、10.1.6 和 10.1.7 这几个版本携带了一个 postinstall 脚本,会在 Windows 机器上运行内置的 DLL 载荷,修复版本为 8.10.2、9.1.2 和 10.1.8。只有那四个版本受影响,且载荷仅在 Windows 上执行,因此 10.1.5 等更早的干净构建从未被污染。通过 eslint-plugin-prettier 把 Prettier 作为 ESLint 规则来运行也是可行的,但并非必需;许多团队会跳过这种做法,因为它会让 lint 变慢,并产生更多噪声。

添加一个 lint 脚本。不需要 --ext 标志,因为文件匹配范围已由各配置块的 files glob 决定:

{
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix"
  }
}

在此基础上,可以借助 Husky 和 lint-staged 在每次提交前对暂存文件运行 eslint --fix;在 VS Code 中通过 codeActionsOnSave 里的 "source.fixAll.eslint": "explicit" 启用保存时自动修复;并把 eslint . 作为 CI 步骤运行,让规则失败直接阻断合并。

最后还有一件值得立即处理的事:ESLint 9 已于 2026-08-06 结束生命周期,不再接收任何更新。如果你还停留在 ESLint 9,上面的配置在 ESLint 10 上无需改动即可运行,所以直接升级运行时然后继续前进即可。从最小的两行配置起步,当你需要 Promise 安全相关规则时,再加上带 projectServicerecommendedTypeChecked,并把 eslint-config-prettier 放在最后。

常见问题

我应该启用类型感知 lint 吗?代价是什么?

如果你想使用 no-floating-promises 和 no-misused-promises 这类价值最高的正确性规则,就应该启用,因为它们离开类型信息就无法工作。代价在于 ESLint 会要求 TypeScript 在 lint 之前先构建项目,这在小型项目上可以忽略不计,但在大型项目上会比较明显。大多数团队会在 CI 和 pre-commit 中运行完整的类型化 lint,而在编辑器中依赖 IDE 缓存,从而规避这部分开销。

在类型化 lint 中,projectService 和 project 有什么区别?

两者都能启用类型化 lint,但自 v8 起 typescript-eslint 推荐使用 projectService,因为它配置更简单、lint 更快,并且复用编辑器已经在使用的那个 tsconfig.json。较旧的 project 选项要求你通过路径指定一个或多个 TSConfig 文件,常常迫使团队额外维护一个 tsconfig.eslint.json。除非有特殊理由,否则请使用 projectService: true。

在 ESLint 扁平配置中,--ext 标志还有效吗?

无效,扁平配置中已不再需要 --ext。文件匹配范围位于各配置块的 files glob 中,例如 files: ['**/*.ts', '**/*.tsx'],因此 ESLint 已经知道要检查哪些文件。你的 lint 脚本简化为 eslint .,无需扩展名标志。那些仍在传 --ext 的脚本,都是从为已被移除的 eslintrc 系统编写的、扁平配置之前的教程中复制来的。

我应该用 eslint-config-prettier 还是 eslint-plugin-prettier?

大多数项目应使用 eslint-config-prettier。它会关闭与 Prettier 冲突的 ESLint 风格规则,且不带来运行时开销;把它放在配置数组的最后即可。eslint-plugin-prettier 的做法是把 Prettier 当作真正的 lint 规则来运行,这是可选的且速度更慢,并且会把每一处格式差异都报告为 lint 错误。请将 eslint-config-prettier 锁定在 10.1.8 或更高版本,以避开 2025 年 7 月的供应链安全事件。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.