Skip to content

docs: switch Typed Linting docs to project service #9614

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions docs/getting-started/Typed_Linting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default tseslint.config(
{
languageOptions: {
parserOptions: {
project: true,
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
Expand All @@ -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)).

</TabItem>
Expand All @@ -65,7 +65,7 @@ module.exports = {
parser: '@typescript-eslint/parser',
// Added lines start
parserOptions: {
project: true,
projectService: true,
tsconfigRootDir: __dirname,
},
// Added lines end
Expand All @@ -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)).

</TabItem>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -182,7 +186,7 @@ module.exports = {
</TabItem>
</Tabs>

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).
Expand All @@ -203,7 +207,8 @@ export default tseslint.config(
{
languageOptions: {
parserOptions: {
project: true,
projectService: true,
tsconfigRootDir: import.meta.name,
},
},
},
Expand Down Expand Up @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions docs/troubleshooting/faqs/Frameworks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
},
Expand All @@ -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,
},
};
```
Expand Down
10 changes: 6 additions & 4 deletions docs/troubleshooting/typed-linting/Monorepos.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
141 changes: 75 additions & 66 deletions docs/troubleshooting/typed-linting/Performance.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<Tabs groupId="eslint-config">
<TabItem value="Flat Config">

```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'],
},
},
},
);
```

</TabItem>
<TabItem value="Legacy Config">

```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,
};
```

</TabItem>
</Tabs>

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.
Expand Down Expand Up @@ -193,17 +137,84 @@ 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.

<Tabs groupId="eslint-config">
<TabItem value="Flat Config">

```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'],
},
},
},
);
```

</TabItem>
<TabItem value="Legacy Config">

```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,
};
```

</TabItem>
</Tabs>

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.

We recommend not using this rule, and instead using a tool like [`prettier`](https://www.npmjs.com/package/prettier) to enforce a standardized formatting.

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.
Expand All @@ -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.
Expand All @@ -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.

Expand Down
Loading
Loading