-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
docs: blog post on parserOptions.projectService #8031
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
Draft
JoshuaKGoldberg
wants to merge
12
commits into
typescript-eslint:main
Choose a base branch
from
JoshuaKGoldberg:blog-parser-options-project-service
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
be19629
docs: blog post on parserOptions.projectService
JoshuaKGoldberg 105a406
Update packages/website/blog/2023-09-18-parser-options-project-true.md
JoshuaKGoldberg a107204
Better diffs and a bit of streamlining
JoshuaKGoldberg 31cbb5d
Account for solo and project references at each scale
JoshuaKGoldberg c0b6d90
Apply suggestions from code review
JoshuaKGoldberg 17fd25f
Merge branch 'main'
JoshuaKGoldberg a7097ac
Adjusted vision section
JoshuaKGoldberg 123052c
Merge branch 'main' into blog-parser-options-project-service
JoshuaKGoldberg 3ca6cbc
Refreshed blog post for latest versions and names
JoshuaKGoldberg f40e7a1
Merge branch 'v8' into blog-parser-options-project-service
JoshuaKGoldberg acc7d9f
Merge branch 'main' into blog-parser-options-project-service
JoshuaKGoldberg 2cead57
Real world examples
JoshuaKGoldberg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
266 changes: 266 additions & 0 deletions
266
packages/website/blog/2024-08-25-parser-options-project-service.mdx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,266 @@ | ||
--- | ||
authors: | ||
- image_url: https://www.joshuakgoldberg.com/img/josh.jpg | ||
name: Josh Goldberg | ||
title: typescript-eslint Maintainer | ||
url: https://github.com/JoshuaKGoldberg | ||
description: Using a faster and more convenient "Project Service" API for configuring typed linting. | ||
slug: parser-options-project-service | ||
tags: [parser, parser options, project, project service, tsconfig] | ||
title: Typed Linting with `parserOptions.projectService` | ||
--- | ||
|
||
import Tabs from '@theme/Tabs'; | ||
import TabItem from '@theme/TabItem'; | ||
|
||
["Typed linting"](/linting/typed-linting), or enabling ESLint rules to tap into the power of the TypeScript type checker, is one of the best parts of typescript-eslint. | ||
It enables a slew of more powerful lint rules that check for nuanced bugs, best practice violations, and other code issues. | ||
But typed linting hasn't always been straightforward to configure or performant at runtime. | ||
|
||
With typescript-eslint 8.0, we marked as stable a **`parserOptions.projectService`** option that uses more powerful TypeScript APIs than previous typed linting implementations. | ||
We've found it to bring the following benefits: | ||
|
||
- ✍️ **Configuration**: simpler ESLint configs for typed linting and no ESLint-specific TSConfig file | ||
- 🧠 **Predictability**: uses the same type information services as editors, including more reliability | ||
- ⚡️ **Speed**: faster linting times out-of-the-box both in CLIs and in editors in many cases | ||
|
||
This blog post will cover how `parserOptions.projectService` simplifies configurations and brings linting type information much closer to what editors such as VS Code run with. | ||
|
||
<!-- truncate --> | ||
|
||
## Introducing the Project Service | ||
|
||
Back in [Relative TSConfig Projects with `parserOptions.project = true` > Project Services](2023-09-18-parser-options-project-true.md#project-services), we'd mentioned a replacement for `parserOptions.project`: | ||
|
||
> The downside of having users specify `parserOptions.project` at all is that `@typescript-eslint/parser` needs manual logic to create TypeScript Programs and associate them with linted files. | ||
> Manual Program creation logic comes with a few issues: | ||
> | ||
> - Complex project setups can be difficult to get right. | ||
> - For example, [typescript-eslint does not yet support Project References](https://github.com/typescript-eslint/typescript-eslint/issues/2094). | ||
> - The TypeScript compiler options used in the user's editor might differ from the compiler options in the TSConfigs they specified on disk. | ||
> - Files not included in created Programs can't be linted with type information, even though editors still typically surface type information when editing those files. | ||
> - Most commonly, `.eslintrc.(c)js` files can be tricky to lint, resulting in the dreaded [_TSConfig does not include this file_ error](/linting/troubleshooting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file). | ||
> | ||
> We're working on an option to instead call the same TypeScript "Project Service" APIs that editors such as VS Code use to create Programs for us instead. | ||
> Project Services will automatically detect the TSConfig for each file (like `project: true`), and will also allow type information to be computed for JavaScript files without the `allowJs` compiler option (unlike `project: true`). | ||
|
||
Following a year of discussion and testing, we believe the new Project Service API is ready to be used by real-world projects. | ||
We've found it to be generally faster at runtime and more straightforward to configure. | ||
|
||
We're therefore promoting the `parserOptions.EXPERIMENTAL_useProjectService` option to the stable name **`parserOptions.projectService`** in typescript-eslint v8. | ||
|
||
## ✍️ Onboarding to the Project Service | ||
|
||
You can change over to the new Project Service API by replacing `project` with `projectService` in your ESLint configuration: | ||
|
||
<Tabs groupId="eslint-config"> | ||
<TabItem value="Flat Config"> | ||
|
||
```js title="eslint.config.js" | ||
export default tseslint.config({ | ||
// ... | ||
languageOptions: { | ||
parserOptions: { | ||
// Remove this line | ||
project: true, | ||
// Add this line | ||
projectService: true, | ||
tsconfigRootDir: import.meta.dirname, | ||
}, | ||
}, | ||
// ... | ||
}); | ||
``` | ||
|
||
:::note | ||
[`import.meta.dirname`](https://nodejs.org/api/esm.html#importmetadirname) is only present for ESM files in Node.js >=20.11.0 / >= 21.2.0.<br /> | ||
For CommonJS modules and/or older versions of Node.js, [use `__dirname` or an alternative](https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules). | ||
::: | ||
|
||
</TabItem> | ||
<TabItem value="Legacy Config"> | ||
|
||
```js title=".eslintrc.cjs" | ||
module.exports = { | ||
// ... | ||
parser: '@typescript-eslint/parser', | ||
parserOptions: { | ||
// Remove this line | ||
project: true, | ||
// Add this line | ||
projectService: true, | ||
tsconfigRootDir: __dirname, | ||
}, | ||
// ... | ||
}; | ||
``` | ||
|
||
</TabItem> | ||
</Tabs> | ||
|
||
Other settings, including how you run ESLint and configure rules, should work the same. | ||
|
||
See [Packages > Parser > `projectService`](/packages/parser#projectservice) for more details. | ||
|
||
### Including Additional Files | ||
|
||
One long-standing pain point of typed linting is enabling type information for files not included in the project's `tsconfig.json`. | ||
Common solutions in the traditional Program API were to either skip type checking for those files or to create a separate `tsconfig.eslint.json` that enabled `compilerOptions.allowJs = true`. | ||
|
||
Now, the new Project Service API allows for a configuration object specifying: | ||
|
||
- `allowDefaultProjectForFiles`: a glob of "out-of-project" files to lint with type information | ||
- `defaultProject`: path to a TSConfig to use for out-of-project file type information | ||
|
||
For example, the following config solves the common case of projects that have root-level files like `eslint.config.js` and `vitest.config.ts`: | ||
|
||
<Tabs groupId="eslint-config"> | ||
<TabItem value="Flat Config"> | ||
|
||
```js title="eslint.config.js" | ||
export default tseslint.config({ | ||
// ... | ||
languageOptions: { | ||
parserOptions: { | ||
projectService: { | ||
allowDefaultProjectForFiles: ['*.js'], | ||
defaultProject: 'tsconfig.json', | ||
}, | ||
tsconfigRootDir: import.meta.dirname, | ||
}, | ||
}, | ||
// ... | ||
}); | ||
``` | ||
|
||
</TabItem> | ||
<TabItem value="Legacy Config"> | ||
|
||
```js title=".eslintrc.cjs" | ||
module.exports = { | ||
// ... | ||
parser: '@typescript-eslint/parser', | ||
parserOptions: { | ||
projectService: { | ||
allowDefaultProjectForFiles: ['*.js'], | ||
defaultProject: 'tsconfig.json', | ||
}, | ||
tsconfigRootDir: __dirname, | ||
}, | ||
// ... | ||
}; | ||
``` | ||
|
||
</TabItem> | ||
</Tabs> | ||
|
||
:::tip | ||
This means most projects should be able to remove lint-only `tsconfig.eslint.json` files! | ||
🥳 | ||
::: | ||
|
||
See [Packages > Parser > `projectService` > `ProjectServiceOptions`](/packages/parser#projectserviceOptions) for more details. | ||
|
||
## 🧠 Predictability and Reliability | ||
|
||
One of the challenges of typed linting is using TypeScript APIs designed for TypeScript projects in ESLint's runtime. | ||
[ESLint does not provide parsers session information](https://github.com/eslint/rfcs/pull/102). | ||
As a result, `@typescript-eslint/parser` has to use approximations to guess whether it was in the more performance-friendly "single-run" mode (rather than in ESLint's multi-pass `--fix` mode or a long-lived editor session). | ||
Single-run mode comes with several bugs. | ||
For example: | ||
|
||
- ESLint's `--fix` mode breaks type information after the first run ([#9577](https://github.com/typescript-eslint/typescript-eslint/pull/9577)) | ||
- Extra file extensions, such as those used by `.svelte` and `.vue`, are not supported ([#9504](https://github.com/typescript-eslint/typescript-eslint/issues/9504)) | ||
|
||
typescript-eslint's single-run inference enables uses common heuristics such as checking for `'--fix'` in `process.argv`, the presence of `process.env.CI`, and the presence of `parserOptions.extraFileExtensions`. | ||
It can be disabled with [`parserOptions.disallowAutomaticSingleRunInference`](/packages/parser#disallowautomaticsingleruninference). | ||
|
||
Enabling single-run mode generally improves performance by 10-20%. | ||
typescript-eslint@v8 enables inference of single-run mode by default. | ||
If your project is stuck on `parserOptions.project`, we recommend keeping single-run inference on if possible. | ||
|
||
`parserOptions.projectService` does not suffer from the same bugs as `parserOptions.project` with single-run mode. | ||
It supports extra file extensions out-of-the-box and does not slow down when used with ESLint's `--fix`. | ||
|
||
We recommend switching to `parserOptions.projectService` if possible. | ||
|
||
## ⚡️ Performance Comparisons | ||
|
||
In addition to simplifying configuration, we have also found the new Project Service API to result in _roughly equivalent or faster_ lint times compared to the equivalent traditional program APIs. | ||
|
||
To prove this, we measured the average of 10 lint times on three real repositories: | ||
|
||
- [Babel](https://github.com/babel/babel): | ||
- [create-t3-app](https://github.com/t3-oss/create-t3-app): | ||
- [tRPC](https://github.com/trpc/trpc): | ||
|
||
> TODO: Reproduce these findings locally! | ||
|
||
<table> | ||
<thead> | ||
<tr> | ||
<th>Repository</th> | ||
<th>📜 Traditional Projects</th> | ||
<th>🆕 Project Service</th> | ||
<th>𝚫 Delta</th> | ||
<th>Source</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<th>Babel</th> | ||
<td>~38 seconds</td> | ||
<td>~36 seconds</td> | ||
<td>~5% faster</td> | ||
<td>[^1]</td> | ||
</tr> | ||
<tr> | ||
<th>create-t3-app</th> | ||
<td>6.662 s ± 0.235 s</td> | ||
<td>5.344 s ± 0.126 s</td> | ||
<td>20% faster</td> | ||
<td>[^2]</td> | ||
</tr> | ||
<tr> | ||
<th>tRPC</th> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
<td>[^3]</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
|
||
In summary, we've found linting _some_ large real-world repositories **can be 5% to of 20% faster**. 🚀 | ||
|
||
### Performance Caveats | ||
|
||
Note that using Project Service APIs for linting is still a relatively new use case. | ||
Neither TypeScript nor typescript-eslint has had much time to optimize internally for the new usage. | ||
Some repositories structured differently from the ones we've tested on may be slower. | ||
|
||
See [typescript-eslint/performance](https://github.com/typescript-eslint/performance) for details on cases where the project service is slower than traditional projects. | ||
|
||
## Next Steps | ||
|
||
### Giving Feedback | ||
|
||
We'd love to hear from you on how this option works for you. | ||
Does it live up to what we've promised, and/or does it have bugs we haven't fixed yet? | ||
Please do post in the [Community Feedback: Project Service APIs](https://github.com/typescript-eslint/typescript-eslint/discussions/8030) GitHub Discussion on how it goes for you. | ||
|
||
For support in setting up the new APIs, feel free to ask on [the typescript-eslint Discord](https://discord.gg/FSxKq8Tdyg)'s `#project-service` channel. | ||
We'd be happy to help you try out `parserOptions.projectService`. | ||
|
||
### Long Term Vision | ||
|
||
Our hope is that the Project Service API becomes the standard way to work with typed linting over the next few major versions. | ||
Our priority will be to improve the new Project Service API so that it works in all places the traditional project program behavior does. | ||
We won't remove the traditional project program behavior unless and until the new Project Service API is able to fully replace it. | ||
|
||
So, please, try out the new Project Service API. | ||
It should help make your typed linting faster and more straightforward to configure. 💜 | ||
|
||
[^1]: https://github.com/babel/babel/pull/16192#issue-2054613116 | ||
|
||
[^2]: https://github.com/t3-oss/create-t3-app/pull/1936/#discussion_r1667389041 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: typo, I made a guess at the intended wording: