diff --git a/docs/getting-started/Quickstart.mdx b/docs/getting-started/Quickstart.mdx index 232fe15966d5..c362a5978ffd 100644 --- a/docs/getting-started/Quickstart.mdx +++ b/docs/getting-started/Quickstart.mdx @@ -35,9 +35,10 @@ Next, create an `eslint.config.mjs` config file in the root of your project, and // @ts-check import eslint from '@eslint/js'; +import { defineConfig } from 'eslint/config'; import tseslint from 'typescript-eslint'; -export default tseslint.config( +export default defineConfig( eslint.configs.recommended, tseslint.configs.recommended, ); @@ -47,21 +48,10 @@ This code will enable our [recommended configuration](../users/Shared_Configurat #### Details -- `tseslint.config(...)` is an **_optional_** helper function — see [`typescript-eslint`'s `config(...)`](../packages/TypeScript_ESLint.mdx#config). +- `defineConfig(...)` is an optional helper function built in to current versions of ESLint. See [the ESLint configuration docs](https://eslint.org/docs/latest/use/configure/configuration-files) for more detail. - `'@eslint/js'` / `eslint.configs.recommended` turns on [eslint's recommended config](https://www.npmjs.com/package/@eslint/js). - `tseslint.configs.recommended` turns on [our recommended config](../users/Shared_Configurations.mdx#recommended). -
-Aside on ESLint's `defineConfig()` - -ESLint also provides a `defineConfig()` helper similar to `tseslint.config()`. -However, there is a types incompatibility issue that causes type errors to incorrectly be reported when mixing typescript-eslint's configs and `defineConfig()`. -For now we recommend using `tseslint.config()` for use with typescript-eslint's configs. - -See [typescript-eslint#10899](https://github.com/typescript-eslint/typescript-eslint/issues/10899) for more details. - -
-
Aside on file extensions @@ -111,7 +101,7 @@ We recommend you consider enabling the following two configs: - [`stylistic`](../users/Shared_Configurations.mdx#stylistic): additional rules that enforce consistent styling without significantly catching bugs or changing logic. ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( eslint.configs.recommended, // Remove this line tseslint.configs.recommended, diff --git a/docs/getting-started/Typed_Linting.mdx b/docs/getting-started/Typed_Linting.mdx index 95f225038dd3..bc36f5589735 100644 --- a/docs/getting-started/Typed_Linting.mdx +++ b/docs/getting-started/Typed_Linting.mdx @@ -20,9 +20,10 @@ To enable typed linting, there are two small changes you need to make to your co ```js title="eslint.config.mjs" import eslint from '@eslint/js'; +import { defineConfig } from 'eslint/config'; import tseslint from 'typescript-eslint'; -export default tseslint.config( +export default defineConfig( eslint.configs.recommended, // Remove this line tseslint.configs.recommended, @@ -99,7 +100,7 @@ If you enabled the [`strict` shared config](../users/Shared_Configurations.mdx#s ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( eslint.configs.recommended, // Removed lines start tseslint.configs.strict, diff --git a/docs/packages/TypeScript_ESLint.mdx b/docs/packages/TypeScript_ESLint.mdx index 9fc886d872bd..e2c283adf146 100644 --- a/docs/packages/TypeScript_ESLint.mdx +++ b/docs/packages/TypeScript_ESLint.mdx @@ -16,12 +16,12 @@ This package is the main entrypoint that you can use to consume our tooling with This package exports the following: -| Name | Description | -| --------- | -------------------------------------------------------------------------------------- | -| `config` | A utility function for creating type-safe flat configs -- see [`config(...)`](#config) | -| `configs` | [Shared ESLint (flat) configs](../users/Shared_Configurations.mdx) | -| `parser` | A re-export of [`@typescript-eslint/parser`](./Parser.mdx) | -| `plugin` | A re-export of [`@typescript-eslint/eslint-plugin`](./ESLint_Plugin.mdx) | +| Name | Description | +| --------------------- | ------------------------------------------------------------------------------------------------- | +| `config` (deprecated) | A utility function for creating type-safe flat configs -- see [`config(...)`](#config-deprecated) | +| `configs` | [Shared ESLint (flat) configs](../users/Shared_Configurations.mdx) | +| `parser` | A re-export of [`@typescript-eslint/parser`](./Parser.mdx) | +| `plugin` | A re-export of [`@typescript-eslint/eslint-plugin`](./ESLint_Plugin.mdx) | ## Installation @@ -31,15 +31,16 @@ npm i typescript-eslint ## Usage -We recommend getting started by using the `tseslint.config()` helper function in your ESLint config: +We recommend getting started by using the default ESLint setup with our shared configs. ```js title="eslint.config.mjs" // @ts-check import eslint from '@eslint/js'; +import { defineConfig } from 'eslint/config'; import tseslint from 'typescript-eslint'; -export default tseslint.config( +export default defineConfig( eslint.configs.recommended, tseslint.configs.recommended, ); @@ -47,16 +48,16 @@ export default tseslint.config( This config file exports a flat config that enables both the [core ESLint recommended config](https://www.npmjs.com/package/@eslint/js) and [our recommended config](../users/Shared_Configurations.mdx#recommended). -:::note -ESLint also provides a `defineConfig()` helper similar to `tseslint.config()`. -However, there is a types incompatibility issue that causes type errors to incorrectly be reported when mixing typescript-eslint's configs and `defineConfig()`. -For now we recommend using `tseslint.config()` for use with typescript-eslint configs. +### `config(...)` (deprecated) -See [typescript-eslint#10899](https://github.com/typescript-eslint/typescript-eslint/issues/10899) for more details. +:::danger -::: +The `config(...)` utility function was deprecated in favor of ESLint core's [`defineConfig(...)`]() in [#10935](https://github.com/typescript-eslint/typescript-eslint/issues/10935). +See [the `defineConfig` migration guide later](#migrating-to-defineconfig) for more details. + +The documentation here is preserved for historical reference and migration purposes. -### `config(...)` +::: `tseslint.config(...)` takes in any number of ESLint config objects, each of which may additionally include an `extends` array of configs to extend. `tseslint.config(...)` returns the equivalent ESLint config of applying the rest of the settings for each extension. @@ -114,7 +115,7 @@ Otherwise it _will not_ impact your ability to use our tooling. #### Flat config `extends` -The `tseslint.config()` utility function also adds handling for the `extends` property on flat config objects. +The `tseslint.config(...)` utility function also adds handling for the `extends` property on flat config objects. This allows you to more easily extend shared configs for specific file patterns whilst also overriding rules/options provided by those configs: ```js @@ -168,6 +169,79 @@ export default tseslint.config({ }); ``` +#### Migrating to `defineConfig(...)` + +The core `defineConfig(...)` helper is a nearly exact clone of `tseslint.config(...)` that was [first released in ESLint v9.22.0](https://eslint.org/blog/2025/03/eslint-v9.22.0-released/). +See [the ESLint blog post](https://eslint.org/blog/2025/03/flat-config-extends-define-config-global-ignores/#support-for-older-eslint-versions) for info on how to use `defineConfig(...)` with older versions of ESLint. + +At the time of writing there are a small number of known edge cases in which the two have different functionality. + +{/* https://github.com/prettier/prettier/issues/17816 -- prettier has trouble with the code fences in the custom elements */} + +{/* prettier-ignore */} +1. Overriding `files` in `extends`. + When `files` is provided in both a base object and an extension, `tseslint.config(...)` _overrides_ the `files` property in the extension, whereas `defineConfig(...)` semantically intersects the two provided `files` specifiers. + + + + ```ts title="eslint.config.mjs" + import tseslint from 'typescript-eslint'; + + export default tseslint.config({ + files: ['a.ts'], + extends: [ + { + files: ['b.ts'], + rules: { + 'some-rule': 'error', + }, + }, + ], + }); + + // is equivalent to + + export default { + files: ['a.ts'], + rules: { 'some-rule': 'error' }, + }; + ``` + + + + + ```ts title="eslint.config.mjs" + import { defineConfig } from 'eslint/config'; + + export default defineConfig({ + files: ['a.ts'], + extends: [ + { + files: ['b.ts'], + rules: { + 'some-rule': 'error', + }, + }, + ], + }); + + // is equivalent to + + // The base config technically ensures that 'a.ts' is still included in + // the lint run, but otherwise the config has no effect, due to the + // intersection of 'a.ts' and 'b.ts' being empty. + export default { + files: ['a.ts'], + }; + ``` + + + + +2. Type declarations (only applies to users who typecheck their eslint configs). + There are slight differences in the way types are declared between the two functions, which may cause typechecking errors when you switch from `tseslint.config(...)` to `defineConfig(...)` in some cases (see [#10899](https://github.com/typescript-eslint/typescript-eslint/issues/10899) for an example that used to impact typescript-eslint's own configs). + Type errors such as these do not indicate a runtime problem and can safely be ignored. + ### Manual usage [typescript-eslint's recommended and stylistic configurations](../users/configs) specify typescript-eslint `parser` and `plugin` options for you, so there is no need to manually provide those. @@ -181,10 +255,11 @@ You can declare our plugin and parser in your config via this package, for examp // @ts-check import eslint from '@eslint/js'; +import { defineConfig } from 'eslint/config'; import jestPlugin from 'eslint-plugin-jest'; import tseslint from 'typescript-eslint'; -export default tseslint.config({ +export default defineConfig({ plugins: { // highlight-next-line '@typescript-eslint': tseslint.plugin, @@ -226,10 +301,11 @@ This config: // @ts-check import eslint from '@eslint/js'; +import { defineConfig } from 'eslint/config'; import jestPlugin from 'eslint-plugin-jest'; import tseslint from 'typescript-eslint'; -export default tseslint.config( +export default defineConfig( { // config with just ignores is the replacement for `.eslintignore` ignores: ['**/build/**', '**/dist/**', 'src/some/file/to/ignore.ts'], diff --git a/docs/troubleshooting/faqs/ESLint.mdx b/docs/troubleshooting/faqs/ESLint.mdx index d1a5cdf0d809..a07e16a2f798 100644 --- a/docs/troubleshooting/faqs/ESLint.mdx +++ b/docs/troubleshooting/faqs/ESLint.mdx @@ -49,9 +49,9 @@ Note, that for a mixed project including JavaScript and TypeScript, the `no-unde ```js title="eslint.config.mjs" -import tseslint from 'typescript-eslint'; +import { defineConfig } from 'eslint/config'; -export default tseslint.config( +export default defineConfig( // ... the rest of your config ... { files: ['**/*.{ts,tsx,mts,cts}'], diff --git a/docs/troubleshooting/faqs/Frameworks.mdx b/docs/troubleshooting/faqs/Frameworks.mdx index fc0e559b56fa..f80be6a84296 100644 --- a/docs/troubleshooting/faqs/Frameworks.mdx +++ b/docs/troubleshooting/faqs/Frameworks.mdx @@ -19,7 +19,7 @@ See [Changes to `extraFileExtensions` with `projectService`](../typed-linting/Pe ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( // ... the rest of your config ... { languageOptions: { @@ -61,10 +61,11 @@ If you are running into issues parsing .vue files, it might be because parsers l ```js title="eslint.config.mjs" import tseslint from 'typescript-eslint'; +import { defineConfig } from 'eslint/config'; // Add this line import vueParser from 'vue-eslint-parser'; -export default tseslint.config( +export default defineConfig( // ... the rest of your config ... { languageOptions: { diff --git a/docs/troubleshooting/faqs/General.mdx b/docs/troubleshooting/faqs/General.mdx index 53b752ab9b80..aa04254f59b9 100644 --- a/docs/troubleshooting/faqs/General.mdx +++ b/docs/troubleshooting/faqs/General.mdx @@ -36,7 +36,7 @@ Some examples ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( // ... the rest of your config ... { rules: { @@ -242,9 +242,10 @@ For example, the following config enables only the recommended config's type-che {/* prettier-ignore */} ```js title="eslint.config.mjs" import eslint from '@eslint/js'; +import { defineConfig } from 'eslint/config'; import tseslint from 'typescript-eslint'; -export default tseslint.config( +export default defineConfig( tseslint.configs.recommendedTypeCheckedOnly, { languageOptions: { diff --git a/docs/troubleshooting/typed-linting/Monorepos.mdx b/docs/troubleshooting/typed-linting/Monorepos.mdx index 85cc6ef264db..2364b9defe24 100644 --- a/docs/troubleshooting/typed-linting/Monorepos.mdx +++ b/docs/troubleshooting/typed-linting/Monorepos.mdx @@ -56,7 +56,7 @@ For each file being linted, the first matching project path will be used as its ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( eslint.configs.recommended, tseslint.configs.recommendedTypeChecked, { @@ -108,7 +108,7 @@ Instead of globs that use `**` to recursively check all folders, prefer paths th ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( eslint.configs.recommended, tseslint.configs.recommendedTypeChecked, { diff --git a/docs/troubleshooting/typed-linting/Performance.mdx b/docs/troubleshooting/typed-linting/Performance.mdx index bc6ac0fa4769..66b9e2bfbe13 100644 --- a/docs/troubleshooting/typed-linting/Performance.mdx +++ b/docs/troubleshooting/typed-linting/Performance.mdx @@ -180,7 +180,7 @@ Instead of globs that use `**` to recursively check all folders, prefer paths th import eslint from '@eslint/js'; import tseslint from 'typescript-eslint'; -export default tseslint.config( +export default defineConfig( eslint.configs.recommended, tseslint.configs.recommendedRequiringTypeChecking, { diff --git a/docs/troubleshooting/typed-linting/index.mdx b/docs/troubleshooting/typed-linting/index.mdx index 6681b1069b5d..5b76f5c53ad3 100644 --- a/docs/troubleshooting/typed-linting/index.mdx +++ b/docs/troubleshooting/typed-linting/index.mdx @@ -30,9 +30,10 @@ For example, to disable type-checked linting on all `.js` files: ```js title="eslint.config.mjs" +import defineConfig from 'eslint/config'; import tseslint from 'typescript-eslint'; -export default tseslint.config( +export default defineConfig( // ... the rest of your config ... { files: ['**/*.js'], @@ -69,7 +70,7 @@ You can combine ESLint's [overrides](https://eslint.org/docs/latest/use/configur ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( eslint.configs.recommended, tseslint.configs.recommendedTypeChecked, tseslint.configs.stylisticTypeChecked, @@ -297,7 +298,7 @@ For example, if you use a specific `tsconfig.eslint.json` for linting, you'd spe ```js title="eslint.config.mjs" -export default tseslint.config({ +export default defineConfig({ // ... languageOptions: { parserOptions: { diff --git a/docs/users/Shared_Configurations.mdx b/docs/users/Shared_Configurations.mdx index e77d68bcf6a2..ea4c554e37af 100644 --- a/docs/users/Shared_Configurations.mdx +++ b/docs/users/Shared_Configurations.mdx @@ -21,9 +21,10 @@ See [Getting Started > Quickstart](../getting-started/Quickstart.mdx) first to s // @ts-check import eslint from '@eslint/js'; +import { defineConfig } from 'eslint/config'; import tseslint from 'typescript-eslint'; -export default tseslint.config( +export default defineConfig( eslint.configs.recommended, tseslint.configs.recommended, ); @@ -37,7 +38,7 @@ If your project does not enable [typed linting](../getting-started/Typed_Linting ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( eslint.configs.recommended, tseslint.configs.recommended, tseslint.configs.stylistic, @@ -70,7 +71,7 @@ If your project enables [typed linting](../getting-started/Typed_Linting.mdx), w ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( eslint.configs.recommended, tseslint.configs.recommendedTypeChecked, tseslint.configs.stylisticTypeChecked, @@ -127,7 +128,7 @@ These rules are those whose reports are almost always for a bad practice and/or {/* prettier-ignore */} ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( tseslint.configs.recommended, ); ``` @@ -156,7 +157,7 @@ Rules newly added in this configuration are similarly useful to those in `recomm {/* prettier-ignore */} ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( tseslint.configs.recommendedTypeChecked, ); ``` @@ -185,7 +186,7 @@ Rules added in `strict` are more opinionated than recommended rules and might no {/* prettier-ignore */} ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( tseslint.configs.strict, ); ``` @@ -224,7 +225,7 @@ Rules newly added in this configuration are similarly useful (and opinionated) t {/* prettier-ignore */} ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( tseslint.configs.strictTypeChecked, ); ``` @@ -263,7 +264,7 @@ These rules are generally opinionated about enforcing simpler code patterns. {/* prettier-ignore */} ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( tseslint.configs.stylistic, ); ``` @@ -295,7 +296,7 @@ Rules newly added in this configuration are similarly opinionated to those in `s {/* prettier-ignore */} ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( tseslint.configs.stylisticTypeChecked, ); ``` @@ -362,7 +363,7 @@ If you use type-aware rules from other plugins, you will need to manually disabl ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( eslint.configs.recommended, tseslint.configs.recommendedTypeChecked, { @@ -426,7 +427,7 @@ Additionally, it enables rules that promote using the more modern constructs Typ ```js title="eslint.config.mjs" -export default tseslint.config( +export default defineConfig( eslint.configs.recommended, tseslint.configs.eslintRecommended, ); diff --git a/docs/users/What_About_Formatting.mdx b/docs/users/What_About_Formatting.mdx index a070fdfccfb2..ace2aa4ebd97 100644 --- a/docs/users/What_About_Formatting.mdx +++ b/docs/users/What_About_Formatting.mdx @@ -50,11 +50,12 @@ Using this config by adding it to the end of your `extends`: // @ts-check import eslint from '@eslint/js'; +import { defineConfig } from 'eslint/config'; import someOtherConfig from 'eslint-config-other-configuration-that-enables-formatting-rules'; import prettierConfig from 'eslint-config-prettier'; import tseslint from 'typescript-eslint'; -export default tseslint.config( +export default defineConfig( eslint.configs.recommended, tseslint.configs.recommended, someOtherConfig, diff --git a/eslint.config.mjs b/eslint.config.mjs index b7b469b0dd63..9241cbee26a5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -15,6 +15,7 @@ import reactPlugin from 'eslint-plugin-react'; import reactHooksPlugin from 'eslint-plugin-react-hooks'; import regexpPlugin from 'eslint-plugin-regexp'; import unicornPlugin from 'eslint-plugin-unicorn'; +import { defineConfig } from 'eslint/config'; import globals from 'globals'; import url from 'node:url'; import tseslint from 'typescript-eslint'; @@ -28,7 +29,7 @@ const restrictNamedDeclarations = { selector: 'ExportNamedDeclaration[declaration=null][source=null]', }; -export default tseslint.config( +export default defineConfig( // register all of the plugins up-front { name: 'register-all-plugins', @@ -43,6 +44,7 @@ export default tseslint.config( // @ts-expect-error -- https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/pull/1038 ['jsx-a11y']: jsxA11yPlugin.flatConfigs.recommended.plugins['jsx-a11y'], ['perfectionist']: perfectionistPlugin, + // @ts-expect-error -- https://github.com/vitest-dev/eslint-plugin-vitest/issues/737 ['vitest']: vitestPlugin, // https://github.com/facebook/react/issues/28313 ['react']: reactPlugin, @@ -363,7 +365,6 @@ export default tseslint.config( // test file specific configuration { extends: [ - // @ts-expect-error -- uses `string` instead of `off` | `readonly` | `writable` for the globals setting. vitestPlugin.configs.env, { rules: { diff --git a/packages/typescript-eslint/src/config-helper.ts b/packages/typescript-eslint/src/config-helper.ts index 51456475734f..758052c62f56 100644 --- a/packages/typescript-eslint/src/config-helper.ts +++ b/packages/typescript-eslint/src/config-helper.ts @@ -88,6 +88,9 @@ export type ConfigArray = TSESLint.FlatConfig.ConfigArray; * }, * ); * ``` + * + * @deprecated ESLint core now provides this functionality via `defineConfig()`, + * which we now recommend instead. See {@link https://typescript-eslint.io/packages/typescript-eslint/#config-deprecated}. */ export function config( ...configs: InfiniteDepthConfigWithExtends[] diff --git a/packages/typescript-eslint/tests/config-helper.test.ts b/packages/typescript-eslint/tests/config-helper.test.ts index 8b4a151a32cf..7b3288df295f 100644 --- a/packages/typescript-eslint/tests/config-helper.test.ts +++ b/packages/typescript-eslint/tests/config-helper.test.ts @@ -2,6 +2,8 @@ import type { TSESLint } from '@typescript-eslint/utils'; import tseslint from '../src/index.js'; +/* eslint @typescript-eslint/no-deprecated: ["error", { "allow": [{ "from": "file", "name": "config", "path": "packages/typescript-eslint/src/config-helper.ts" }] }] */ + describe('config helper', () => { it('works without extends', () => { expect( diff --git a/packages/typescript-eslint/tests/type-compatibility.test-d.ts b/packages/typescript-eslint/tests/type-compatibility.test-d.ts index b0e2f71b81d2..dfcf11cbd2f3 100644 --- a/packages/typescript-eslint/tests/type-compatibility.test-d.ts +++ b/packages/typescript-eslint/tests/type-compatibility.test-d.ts @@ -2,6 +2,8 @@ import { defineConfig } from 'eslint/config'; import tseslint from '../src/index'; +/* eslint @typescript-eslint/no-deprecated: ["error", { "allow": [{ "from": "file", "name": "config", "path": "packages/typescript-eslint/src/config-helper.ts" }] }] */ + describe('test for compatibility with config helpers', () => { test('exported plugin is compatible with tseslint.config()', () => { tseslint.config({