From 88d603fbf95e4117c75f475ff53dfc491ccb1b88 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 22 Jul 2024 19:13:21 -0400 Subject: [PATCH] docs: switch Typed Linting docs to project service --- docs/getting-started/Typed_Linting.mdx | 21 ++- docs/troubleshooting/faqs/Frameworks.mdx | 8 +- .../typed-linting/Monorepos.mdx | 10 +- .../typed-linting/Performance.mdx | 141 ++++++++-------- docs/troubleshooting/typed-linting/index.mdx | 150 +++++++++--------- 5 files changed, 175 insertions(+), 155 deletions(-) diff --git a/docs/getting-started/Typed_Linting.mdx b/docs/getting-started/Typed_Linting.mdx index 08aa30b78ace..50516fa8736f 100644 --- a/docs/getting-started/Typed_Linting.mdx +++ b/docs/getting-started/Typed_Linting.mdx @@ -25,7 +25,7 @@ export default tseslint.config( { languageOptions: { parserOptions: { - project: true, + projectService: true, tsconfigRootDir: import.meta.dirname, }, }, @@ -42,7 +42,7 @@ For CommonJS modules and/or older versions of Node.js, [use `__dirname` or an al In more detail: - `tseslint.configs.recommendedTypeChecked` is another [shared configuration](../users/Shared_Configurations.mdx) we provide. This one contains recommended rules that additionally require type information. -- `parserOptions.project: true` indicates to find the closest `tsconfig.json` for each source file (see [Parser#project](../packages/Parser.mdx#project)). +- `parserOptions.projectService: true` indicates to ask TypeScript's type checking service for each source file's type information (see [Parser#projectService](../packages/Parser.mdx#projectService)). - `parserOptions.tsconfigRootDir` tells our parser the absolute path of your project's root directory (see [Parser#tsconfigRootDir](../packages/Parser.mdx#tsconfigrootdir)). @@ -65,7 +65,7 @@ module.exports = { parser: '@typescript-eslint/parser', // Added lines start parserOptions: { - project: true, + projectService: true, tsconfigRootDir: __dirname, }, // Added lines end @@ -76,7 +76,7 @@ module.exports = { In more detail: - `plugin:@typescript-eslint/recommended-type-checked` is another [shared configuration](../users/Shared_Configurations.mdx) we provide. This one contains recommended rules that additionally require type information. -- `parserOptions.project: true` indicates to find the closest `tsconfig.json` for each source file (see [Parser#project](../packages/Parser.mdx#project)). +- `parserOptions.projectService: true` indicates to ask TypeScript's type checking service for each source file's type information (see [Parser#projectService](../packages/Parser.mdx#projectService)). - `parserOptions.tsconfigRootDir` tells our parser the absolute path of your project's root directory (see [Parser#tsconfigRootDir](../packages/Parser.mdx#tsconfigrootdir)). @@ -142,7 +142,11 @@ You can read more about the rules provided by typescript-eslint in our [rules do ### Can I customize the TSConfig used for typed linting? -The `project` option can be turned on with either: +Yes, but it's not recommended in most configurations. +`parserOptions.projectService` uses the same "project service" APIs used by editors such as VS Code to generate TypeScript's type information. +Using a different TSConfig runs the risk of providing different types for typed linting than what your editor or `tsc` see. + +If you absolutely must, the `parserOptions.project` option can be used instead of `parserOptions.projectService` with either: - `true`: to always use `tsconfig.json`s nearest to source files - `string | string[]`: any number of glob paths to match TSConfig files relative to `parserOptions.tsconfigRootDir`, or the current working directory if that is not provided @@ -182,7 +186,7 @@ module.exports = { -See [the `@typescript-eslint/parser` docs for more details](../packages/Parser.mdx#project). +See [the `@typescript-eslint/parser` `project` docs for more details](../packages/Parser.mdx#project). :::note If your project is a multi-package monorepo, see [our docs on configuring a monorepo](../troubleshooting/typed-linting/Monorepos.mdx). @@ -203,7 +207,8 @@ export default tseslint.config( { languageOptions: { parserOptions: { - project: true, + projectService: true, + tsconfigRootDir: import.meta.name, }, }, }, @@ -254,7 +259,7 @@ If you use type-aware rules from other plugins, you will need to manually disabl ### How is performance? Typed rules come with a catch. -By including `parserOptions.project` in your config, you incur the performance penalty of asking TypeScript to do a build of your project before ESLint can do its linting. +By using typed linting in your config, you incur the performance penalty of asking TypeScript to do a build of your project before ESLint can do its linting. For small projects this takes a negligible amount of time (a few seconds or less); for large projects, it can take longer. Most of our users do not mind this cost as the power and safety of type-aware static analysis rules is worth the tradeoff. diff --git a/docs/troubleshooting/faqs/Frameworks.mdx b/docs/troubleshooting/faqs/Frameworks.mdx index 2c0d5a6b7306..fc0e559b56fa 100644 --- a/docs/troubleshooting/faqs/Frameworks.mdx +++ b/docs/troubleshooting/faqs/Frameworks.mdx @@ -24,10 +24,10 @@ export default tseslint.config( { languageOptions: { parserOptions: { - tsconfigRootDir: import.meta.dirname, - project: ['./tsconfig.json'], // Add this line extraFileExtensions: ['.vue'], + projectService: true, + tsconfigRootDir: import.meta.dirname, }, }, }, @@ -41,10 +41,10 @@ export default tseslint.config( module.exports = { // ... the rest of your config ... parserOptions: { - tsconfigRootDir: __dirname, - project: ['./tsconfig.json'], // Add this line extraFileExtensions: ['.vue'], + projectService: true, + tsconfigRootDir: __dirname, }, }; ``` diff --git a/docs/troubleshooting/typed-linting/Monorepos.mdx b/docs/troubleshooting/typed-linting/Monorepos.mdx index eb95d674084f..5924978b9947 100644 --- a/docs/troubleshooting/typed-linting/Monorepos.mdx +++ b/docs/troubleshooting/typed-linting/Monorepos.mdx @@ -7,13 +7,15 @@ title: Monorepo Configuration import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -If you're using a monorepo with `parserOptions.project`, these docs will help you figure out how to setup typed linting. -If you don't want to use typed linting, then you can stop here - you don't need to do anything special. - :::tip -The [new "project service" (`parserOptions.projectService`) in v8](/blog/announcing-typescript-eslint-v8-beta#project-service) requires no additional configuration for monorepos. +**The [new "project service" in v8](/blog/announcing-typescript-eslint-v8-beta#project-service) requires no additional configuration for monorepos.** + +If you're using `parserOptions.projectService`, you don't need this guide. ::: +If you're using a monorepo with `parserOptions.project`, these docs will help you figure out how to setup typed linting. +If you don't want to use typed linting, then you can stop here - you don't need to do anything special. + `parserOptions.project` configurations will look different based on which monorepo setup you use: 1. [One root `tsconfig.json`](#one-root-tsconfigjson) diff --git a/docs/troubleshooting/typed-linting/Performance.mdx b/docs/troubleshooting/typed-linting/Performance.mdx index 4a8efafd9d3d..48407d5db6cd 100644 --- a/docs/troubleshooting/typed-linting/Performance.mdx +++ b/docs/troubleshooting/typed-linting/Performance.mdx @@ -35,65 +35,9 @@ Additionally, if you provide no `include` in your tsconfig, then it is the same Wide globs can cause TypeScript to parse things like build artifacts, which can heavily impact performance. Always ensure you provide globs targeted at the folders you are specifically wanting to lint. -## Wide includes in your ESLint options +## Project Service Issues -Specifying `tsconfig.json` paths in your ESLint commands is also likely to cause much more disk IO than expected. -Instead of globs that use `**` to recursively check all folders, prefer paths that use a single `*` at a time. - - - - -```js title="eslint.config.mjs" -// @ts-check - -import eslint from '@eslint/js'; -import tseslint from 'typescript-eslint'; - -export default tseslint.config( - eslint.configs.recommended, - ...tseslint.configs.recommendedRequiringTypeChecking, - { - languageOptions: { - parserOptions: { - tsconfigRootDir: import.meta.dirname, - // Remove this line - project: ['./**/tsconfig.json'], - // Add this line - project: ['./packages/*/tsconfig.json'], - }, - }, - }, -); -``` - - - - -```js title=".eslintrc.js" -module.exports = { - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended-requiring-type-checking', - ], - parser: '@typescript-eslint/parser', - parserOptions: { - tsconfigRootDir: __dirname, - // Remove this line - project: ['./**/tsconfig.json'], - // Add this line - project: ['./packages/*/tsconfig.json'], - }, - plugins: ['@typescript-eslint'], - root: true, -}; -``` - - - - -See [Glob pattern in parser's option "project" slows down linting](https://github.com/typescript-eslint/typescript-eslint/issues/2611) for more details. - -## Changes to `extraFileExtensions` with `projectService` +### Changes to `extraFileExtensions` with `projectService` Using a different [`extraFileExtensions`](../../packages/Parser.mdx#extrafileextensions) between files in the same project with the [`projectService`](../../packages/Parser.mdx#projectservice) option may cause performance degradations. @@ -193,9 +137,76 @@ typescript-estree:tsserver:info reload projects. typescript-estree:useProgramFromProjectService Extra file extensions updated: [ '.vue' ] ``` -## The `indent` / `@typescript-eslint/indent` rules +## Traditional Project issues -This rule helps ensure your codebase follows a consistent indentation pattern. +### Wide includes in your ESLint options + +:::tip +The [new "project service" in v8](/blog/announcing-typescript-eslint-v8-beta#project-service) requires no additional configuration for wide TSConfig includes. +If you're using `parserOptions.projectService`, this problem is solved for you. +::: + +Specifying `tsconfig.json` paths in an ESLint `parserOptions.project` configuration is also likely to cause much more disk IO than expected. +Instead of globs that use `**` to recursively check all folders, prefer paths that use a single `*` at a time. + + + + +```js title="eslint.config.mjs" +// @ts-check + +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommendedRequiringTypeChecking, + { + languageOptions: { + parserOptions: { + tsconfigRootDir: import.meta.dirname, + // Remove this line + project: ['./**/tsconfig.json'], + // Add this line + project: ['./packages/*/tsconfig.json'], + }, + }, + }, +); +``` + + + + +```js title=".eslintrc.js" +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + ], + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: __dirname, + // Remove this line + project: ['./**/tsconfig.json'], + // Add this line + project: ['./packages/*/tsconfig.json'], + }, + plugins: ['@typescript-eslint'], + root: true, +}; +``` + + + + +See [Glob pattern in parser's option "project" slows down linting](https://github.com/typescript-eslint/typescript-eslint/issues/2611) for more details. + +## Third-Party Plugins + +### `@stylistic/ts/indent` and other stylistic rules rules + +The [`@stylisic/ts/indent` rule](https://eslint.style/rules/ts/indent#ts-indent) helps ensure your codebase follows a consistent indentation pattern. However this involves a _lot_ of computations across every single token in a file. Across a large codebase, these can add up, and severely impact performance. @@ -203,7 +214,7 @@ We recommend not using this rule, and instead using a tool like [`prettier`](htt See our [documentation on formatting](../../users/What_About_Formatting.mdx) for more information. -## `eslint-plugin-prettier` +### `eslint-plugin-prettier` This plugin surfaces Prettier formatting problems at lint time, helping to ensure your code is always formatted. However this comes at a quite a large cost - in order to figure out if there is a difference, it has to do a Prettier format on every file being linted. @@ -219,7 +230,7 @@ npm run prettier --check . See [Prettier's `--check` docs](https://prettier.io/docs/en/cli#--check) for more details. -## `eslint-plugin-import` +### `eslint-plugin-import` This is another great plugin that we use ourselves in this project. However there are a few rules which can cause your lints to be really slow, because they cause the plugin to do its own parsing, and file tracking. @@ -242,15 +253,13 @@ The following rules do not have equivalent checks in TypeScript, so we recommend - `import/no-unused-modules` - `import/no-deprecated` -### `import/extensions` - -#### Enforcing extensions are used +#### `import/extensions` enforcing extensions are used If you want to enforce file extensions are always used and you're **NOT** using `moduleResolution` `node16` or `nodenext`, then there's not really a good alternative for you, and you should continue using the `import/extensions` lint rule. If you want to enforce file extensions are always used and you **ARE** using `moduleResolution` `node16` or `nodenext`, then you don't need to use the lint rule at all because TypeScript will automatically enforce that you include extensions! -#### Enforcing extensions are not used +#### `import/extensions` enforcing extensions are not used On the surface `import/extensions` seems like it should be fast for this use case, however the rule isn't just a pure AST-check - it has to resolve modules on disk so that it doesn't false positive on cases where you are importing modules with an extension as part of their name (eg `foo.js` resolves to `node_modules/foo.js/index.js`, so the `.js` is required). This disk lookup is costly and thus makes the rule slow. diff --git a/docs/troubleshooting/typed-linting/index.mdx b/docs/troubleshooting/typed-linting/index.mdx index 4e0f62c3749f..05e0280d94da 100644 --- a/docs/troubleshooting/typed-linting/index.mdx +++ b/docs/troubleshooting/typed-linting/index.mdx @@ -20,79 +20,6 @@ For now, the workaround is to run the _**Restart ESLint Server**_ command in VS See [ESLint does not re-compute cross-file information on file changes (microsoft/vscode-eslint#1774)](https://github.com/microsoft/vscode-eslint/issues/1774) for more information. - - - -## I get errors telling me "Having many files run with the default project is known to cause performance issues and slow down linting." - -These errors are caused by attempting to use the [`projectService`](../../packages/Parser.mdx#projectservice) to lint a file not explicitly included in a `tsconfig.json`. - -For each file being reported: - -- If you **do not** want to lint the file: - - Use [one of the options ESLint offers to ignore files](https://eslint.org/docs/latest/user-guide/configuring/ignoring-code), such an `ignores` config key. -- If you **do** want to lint the file: - - If you **do not** want to lint the file with [type-aware linting](../../getting-started/Typed_Linting.mdx): [disable type-checked linting for that file](#how-do-i-disable-type-checked-linting-for-a-file). - - If you **do** want to lint the file with [type-aware linting](../../getting-started/Typed_Linting.mdx): - 1. If possible, add the file to the closest `tsconfig.json`'s `include`. For example, allowing `.js` files: - ```diff title="tsconfig.json" - "include": [ - "src", - + "*.js" - ] - ``` - 2. Otherwise, consider setting [`parserOptions.createProjectService.allowDefaultProject`](../../packages/parser#allowdefaultproject). - -typescript-eslint allows up to 8 "out of project" files by default. -Each file causes a new TypeScript "program" to be built for each file it includes, which incurs a performance overhead _for each file_. - -If you cannot do this, please [file an issue on typescript-eslint's typescript-estree package](https://github.com/typescript-eslint/typescript-eslint/issues/new?assignees=&labels=enhancement%2Ctriage&projects=&template=07-enhancement-other.yaml&title=Enhancement%3A+%3Ca+short+description+of+my+proposal%3E) telling us your use case and why you need more out-of-project files linted. -Be sure to include a minimal reproduction we can work with to understand your use case! - -## I get errors telling me "ESLint was configured to run ... However, that TSConfig does not / none of those TSConfigs include this file" - -These errors are caused by an ESLint config requesting type information be generated for a file that isn't included in the TypeScript configuration. - -### Fixing the Error - -- If you **do not** want to lint the file: - - Use [one of the options ESLint offers to ignore files](https://eslint.org/docs/latest/user-guide/configuring/ignoring-code), namely a `.eslintignore` file, or `ignorePatterns` config. -- If you **do** want to lint the file: - - If you **do not** want to lint the file with [type-aware linting](../../getting-started/Typed_Linting.mdx): - - Use [ESLint's configuration objects](https://eslint.org/docs/latest/use/configure/configuration-files#specifying-files-with-arbitrary-extensions) with our [`disable-type-checked`](../../users/Shared_Configurations.mdx#disable-type-checked) config to disable type checking for just that type of file. - - If you **do** want to lint the file with [type-aware linting](../../getting-started/Typed_Linting.mdx): - - Check the `include` option of each of the TSConfigs that you provide to `parserOptions.project` - you must ensure that all files match an `include` glob, or else our tooling will not be able to find it. - - If the file is a `.cjs`, `.js`, or `.mjs` file, make sure [`allowJs`](https://www.typescriptlang.org/tsconfig#allowJs) is enabled. - - If your file shouldn't be a part of one of your existing tsconfigs (for example, it is a script/tool local to the repo), then consider creating a new tsconfig (we advise calling it `tsconfig.eslint.json`) in your project root which lists this file in its `include`. For an example of this, you can check out the configuration we use in this repo: - - [`tsconfig.eslint.json`](https://github.com/typescript-eslint/typescript-eslint/blob/main/tsconfig.eslint.json) - - [`eslint.config.mjs`](https://github.com/typescript-eslint/typescript-eslint/blob/main/eslint.config.mjs) - -### More Details - -This error may appear from the combination of two things: - -- The ESLint configuration for the source file specifies at least one TSConfig file in `parserOptions.project` -- None of those TSConfig files includes the source file being linted - - Note that files with the same name and different extension may not be recognized by TypeScript: see [`parserOptions.project` docs](https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/parser#parseroptionsproject) - -When TSConfig files are specified for parsing a source file, `@typescript-eslint/parser` will use the first TSConfig that is able to include that source file (per [aka.ms/tsconfig#include](https://www.typescriptlang.org/tsconfig#include)) to generate type information. -However, if no specified TSConfig includes the source file, the parser won't be able to generate type information. - -This error most commonly happens on config files or similar that are not included in their project TSConfig(s). -For example, many projects have files like: - -- An `.eslintrc.cjs` / `eslint.config.mjs` with `parserOptions.project: ["./tsconfig.json"]` -- A `tsconfig.json` with `include: ["src"]` - -In that case, viewing the file in an IDE with the ESLint extension will show the error notice that the file couldn't be linted because it isn't included in `tsconfig.json`. - -See our docs on [type aware linting](../../getting-started/Typed_Linting.mdx) for more information. - -## I get errors telling me "The file must be included in at least one of the projects provided" - -You're using an outdated version of `@typescript-eslint/parser`. -Update to the latest version to see a more informative version of this error message, explained [above](#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file 'backlink to I get errors telling me ESLint was configured to run ...'). - ## How do I disable type-checked linting for a file? Use [ESLint's configuration objects](https://eslint.org/docs/latest/use/configure/configuration-files#specifying-files-with-arbitrary-extensions) with our [`disable-type-checked`](../../users/Shared_Configurations.mdx#disable-type-checked) config to disable type checking for a `files` match that includes that file. @@ -160,3 +87,80 @@ If the IDE provides different type information from typescript-eslint's report, Yes, but only with [`EXPERIMENTAL_useProjectService`](../../packages/Parser.mdx#experimental_useprojectservice). See [issue #2094 discussing project references](https://github.com/typescript-eslint/typescript-eslint/issues/2094) for more details. + +## Project Service Issues + + + + +### I get errors telling me "Having many files run with the default project is known to cause performance issues and slow down linting." + +These errors are caused by attempting to use the [`projectService`](../../packages/Parser.mdx#projectservice) to lint a file not explicitly included in a `tsconfig.json`. + +For each file being reported: + +- If you **do not** want to lint the file: + - Use [one of the options ESLint offers to ignore files](https://eslint.org/docs/latest/user-guide/configuring/ignoring-code), such an `ignores` config key. +- If you **do** want to lint the file: + - If you **do not** want to lint the file with [type-aware linting](../../getting-started/Typed_Linting.mdx): [disable type-checked linting for that file](#how-do-i-disable-type-checked-linting-for-a-file). + - If you **do** want to lint the file with [type-aware linting](../../getting-started/Typed_Linting.mdx): + 1. If possible, add the file to the closest `tsconfig.json`'s `include`. For example, allowing `.js` files: + ```diff title="tsconfig.json" + "include": [ + "src", + + "*.js" + ] + ``` + 2. Otherwise, consider setting [`parserOptions.createProjectService.allowDefaultProject`](../../packages/parser#allowdefaultproject). + +typescript-eslint allows up to 8 "out of project" files by default. +Each file causes a new TypeScript "program" to be built for each file it includes, which incurs a performance overhead _for each file_. + +If you cannot do this, please [file an issue on typescript-eslint's typescript-estree package](https://github.com/typescript-eslint/typescript-eslint/issues/new?assignees=&labels=enhancement%2Ctriage&projects=&template=07-enhancement-other.yaml&title=Enhancement%3A+%3Ca+short+description+of+my+proposal%3E) telling us your use case and why you need more out-of-project files linted. +Be sure to include a minimal reproduction we can work with to understand your use case! + +## Traditional Project Issues + +### I get errors telling me "ESLint was configured to run ... However, that TSConfig does not / none of those TSConfigs include this file" + +These errors are caused by an ESLint config requesting type information be generated for a file that isn't included in the TypeScript configuration. + +#### Fixing the Error + +- If you **do not** want to lint the file: + - Use [one of the options ESLint offers to ignore files](https://eslint.org/docs/latest/user-guide/configuring/ignoring-code), namely a `.eslintignore` file, or `ignorePatterns` config. +- If you **do** want to lint the file: + - If you **do not** want to lint the file with [type-aware linting](../../getting-started/Typed_Linting.mdx): + - Use [ESLint's configuration objects](https://eslint.org/docs/latest/use/configure/configuration-files#specifying-files-with-arbitrary-extensions) with our [`disable-type-checked`](../../users/Shared_Configurations.mdx#disable-type-checked) config to disable type checking for just that type of file. + - If you **do** want to lint the file with [type-aware linting](../../getting-started/Typed_Linting.mdx): + - Check the `include` option of each of the TSConfigs that you provide to `parserOptions.project` - you must ensure that all files match an `include` glob, or else our tooling will not be able to find it. + - If the file is a `.cjs`, `.js`, or `.mjs` file, make sure [`allowJs`](https://www.typescriptlang.org/tsconfig#allowJs) is enabled. + - If your file shouldn't be a part of one of your existing tsconfigs (for example, it is a script/tool local to the repo), then consider creating a new tsconfig (we advise calling it `tsconfig.eslint.json`) in your project root which lists this file in its `include`. For an example of this, you can check out the configuration we use in this repo: + - [`tsconfig.eslint.json`](https://github.com/typescript-eslint/typescript-eslint/blob/main/tsconfig.eslint.json) + - [`eslint.config.mjs`](https://github.com/typescript-eslint/typescript-eslint/blob/main/eslint.config.mjs) + +#### More Details + +This error may appear from the combination of two things: + +- The ESLint configuration for the source file specifies at least one TSConfig file in `parserOptions.project` +- None of those TSConfig files includes the source file being linted + - Note that files with the same name and different extension may not be recognized by TypeScript: see [`parserOptions.project` docs](https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/parser#parseroptionsproject) + +When TSConfig files are specified for parsing a source file, `@typescript-eslint/parser` will use the first TSConfig that is able to include that source file (per [aka.ms/tsconfig#include](https://www.typescriptlang.org/tsconfig#include)) to generate type information. +However, if no specified TSConfig includes the source file, the parser won't be able to generate type information. + +This error most commonly happens on config files or similar that are not included in their project TSConfig(s). +For example, many projects have files like: + +- An `.eslintrc.cjs` / `eslint.config.mjs` with `parserOptions.project: true` +- A `tsconfig.json` with `include: ["src"]` + +In that case, viewing the file in an IDE with the ESLint extension will show the error notice that the file couldn't be linted because it isn't included in `tsconfig.json`. + +See our docs on [type aware linting](../../getting-started/Typed_Linting.mdx) for more information. + +### I get errors telling me "The file must be included in at least one of the projects provided" + +You're using an outdated version of `@typescript-eslint/parser`. +Update to the latest version to see a more informative version of this error message, explained [above](#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file 'backlink to I get errors telling me ESLint was configured to run ...').