Skip to content

docs: add more project service docs and FAQs #9871

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
28 changes: 20 additions & 8 deletions docs/packages/Parser.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@ For an option that allows linting files outside of your TSConfig file(s), see [`
> Default `false`.

Specifies using TypeScript APIs to generate type information for rules.
It 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`).
It will automatically use the nearest `tsconfig.json` for each file (like `project: true`).
It can also be configured to also allow type information to be computed for JavaScript files without the `allowJs` compiler option (unlike `project: true`).

<Tabs groupId="eslint-config">
<TabItem value="Flat Config">
Expand Down Expand Up @@ -320,24 +321,21 @@ module.exports = {
This option brings two main benefits over the older `project`:

- Simpler configurations: most projects shouldn't need to explicitly configure `project` paths or create `tsconfig.eslint.json`s
- Improved performance: this API is optimized on the TypeScript side for speed
- Initial versions of this option demonstrated performance changes in subsets of the typescript-eslint monorepo ranging from 11% slower to 70% faster
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're still working on performance & filling in the gaps on edge cases. IMO we should stop bragging about it until we can be very, very certain it's better the vast majority of the time.

- Predictability: it uses the same type information services as editors, giving better consistency with the types seen in editors

For more information, see:

- [feat(typescript-estree): add EXPERIMENTAL_useProjectService option to use TypeScript project service](https://github.com/typescript-eslint/typescript-eslint/pull/6754)
- [docs: blog post on parserOptions.projectService](https://github.com/typescript-eslint/typescript-eslint/pull/8031)
See [FAQs > Typed Linting > Project Service Issues](../troubleshooting/typed-linting/index.mdx#project-service-issues) for help on working with the project service.

#### `ProjectServiceOptions`

The behavior of `parserOptions.projectService` can be customized by setting it to an object.

```js
module.exports = {
{
parser: '@typescript-eslint/parser',
parserOptions: {
projectService: {
allowDefaultProject: ['*.js'],
defaultProject: 'tsconfig.json',
},
},
};
Expand All @@ -346,10 +344,24 @@ module.exports = {
##### `allowDefaultProject`

Globs of files to allow running with the default project compiler options despite not being matched by the project service.
It takes in an array of string paths that will be resolved relative to the [`tsconfigRootDir`](#tsconfigrootdir).
When set, [`projectService.defaultProject`](#defaultproject) must be set as well.

This is intended to produce type information for config files such as `eslint.config.js` that aren't included in their sibling `tsconfig.json`.
Every file with type information retrieved from the default project incurs a non-trivial performance overhead to linting.
Use this option sparingly.

There are several restrictions on this option to prevent it from being overused:

- `**` is not allowed in globs passed to it
- Files that match `allowDefaultProject` may not also be included in their nearest `tsconfig.json`

##### `defaultProject`

Path to a TSConfig to use instead of TypeScript's default project configuration.
It takes in a string path that will be resolved relative to the [`tsconfigRootDir`](#tsconfigrootdir).

This is required to specify which TSConfig file on disk will be used for [`projectService.allowDefaultProject`](#allowdefaultproject).

##### `maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING`

Expand Down
67 changes: 59 additions & 8 deletions docs/troubleshooting/typed-linting/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,21 @@ If the IDE provides different type information from typescript-eslint's report,

## Are TypeScript project references supported?

Yes, but only with [`EXPERIMENTAL_useProjectService`](../../packages/Parser.mdx#experimental_useprojectservice).
Yes, but only with [`parserOptions.projectService`](../../packages/Parser.mdx#projectservice).

See [issue #2094 discussing project references](https://github.com/typescript-eslint/typescript-eslint/issues/2094) for more details.

## Project Service Issues

<HiddenHeading id="allowdefaultproject-glob-too-wide" />
<HiddenHeading id="allowdefaultprojectforfiles-glob-too-wide" />
Copy link
Member Author

@JoshuaKGoldberg JoshuaKGoldberg Aug 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This second heading is a holdover from the experimental/v7 name. I think anybody newly configuring the service should reasonably be on v8 now. Our docs don't still hold true for the old experimental way of holding the project service.

[`parserOptions.projectService`](../../packages/Parser.mdx#projectservice) is the recommended parser option to enable typed linting as of typescript-eslint v8.
It enforces projects generate type information for typed linting from the same `tsconfig.json` files used by editors such as VS Code.

### I get errors telling me "Having many files run with the default project is known to cause performance issues and slow down linting."
### I get errors telling me "... was not found by the project service. Consider either including it in the tsconfig.json or including it in allowDefaultProject"

These errors are caused by attempting to use the project service to lint a file not explicitly included in its nearest `tsconfig.json`.

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`.
The project service will attempt to build type information for each file being linted using the nearest `tsconfig.json` on disk to that file.
If that `tsconfig.json` does not include the file, and the file isn't allowlisted in [`allowDefaultProject`](../../packages/parser#allowdefaultproject), then the project service will throw this error.

For each file being reported:

Expand All @@ -111,13 +114,61 @@ For each file being reported:
+ "*.js"
]
```
2. Otherwise, consider setting [`parserOptions.createProjectService.allowDefaultProject`](../../packages/parser#allowdefaultproject).
2. Otherwise, if you have a small number of "out of project" files, try setting [`projectService.allowDefaultProject`](../../packages/parser#allowdefaultproject).
3. If not, you can switch to [`parserOptions.project`](../../packages/Parser.mdx#project) for more fine-grained control of projects.

Note also:

- TSConfigs don't include `.js` files by default.
Enabling [`allowJs`](https://www.typescriptlang.org/tsconfig/#allowJs) or [`checkJs`](https://www.typescriptlang.org/tsconfig/#checkJs) is required to do so.
- The project service _only_ looks at `tsconfig.json` files.
It does not look at `tsconfig.eslint.json` or other coincidentally-similarly-named files.

If these steps don't work for you, 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!

<HiddenHeading id="allowdefaultproject-glob-too-wide" />

### 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 project service to lint too many files not explicitly included in a `tsconfig.json` with its [`allowDefaultProject`](../../packages/parser#allowdefaultproject) option.

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!
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` instead of adding it to `allowDefaultProject`. For example, allowing `.js` files:
```diff title="tsconfig.json"
"include": [
"src",
+ "*.js"
]
```
2. If not, you can switch to [`parserOptions.project`](../../packages/Parser.mdx#project) for more fine-grained control of projects.

### I'd like to use TSConfigs other than `tsconfig.json`s for project service type information

Only the TSConfig path used for "out of project" files in [`allowDefaultProject`](../../packages/Parser.mdx#allowdefaultproject) can be customized.
Otherwise, only `tsconfig.json` files on disk will be read.

For example, instead of:

- `tsconfig.json`s for building (and, coincidentally, type information in editors)
- Separate TSConfig(s) like `tsconfig.eslint.json` for linting

Consider using:

- `tsconfig.json`s for linting (and, intentionally, the same type information in editors)
- Separate TSConfig(s) like `tsconfig.build.json` for building

The project service uses the same underlying TypeScript logic as editors such as VS Code.
Using only `tsconfig.json` for typed linting enforces that the types seen in your editor match what's used for linting.

## Traditional Project Issues

Expand Down
31 changes: 25 additions & 6 deletions packages/typescript-estree/tests/lib/createProjectService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe('createProjectService', () => {
expect(settings.allowDefaultProject).toBeUndefined();
});

it('throws an error when options.defaultProject is set and getParsedConfigFile throws a diagnostic error', () => {
it('throws an error with a relative path when options.defaultProject is set to a relative path and getParsedConfigFile throws a diagnostic error', () => {
mockGetParsedConfigFile.mockImplementation(() => {
throw new Error('./tsconfig.json(1,1): error TS1234: Oh no!');
});
Expand All @@ -80,6 +80,25 @@ describe('createProjectService', () => {
);
});

it('throws an error with a local path when options.defaultProject is set to a local path and getParsedConfigFile throws a diagnostic error', () => {
mockGetParsedConfigFile.mockImplementation(() => {
throw new Error('./tsconfig.json(1,1): error TS1234: Oh no!');
});

expect(() =>
createProjectService(
{
allowDefaultProject: ['file.js'],
defaultProject: 'tsconfig.json',
},
undefined,
undefined,
),
).toThrow(
/Could not read default project 'tsconfig.json': .+ error TS1234: Oh no!/,
);
});

it('throws an error when options.defaultProject is set and getParsedConfigFile throws an environment error', () => {
mockGetParsedConfigFile.mockImplementation(() => {
throw new Error(
Expand All @@ -91,13 +110,13 @@ describe('createProjectService', () => {
createProjectService(
{
allowDefaultProject: ['file.js'],
defaultProject: './tsconfig.json',
defaultProject: 'tsconfig.json',
},
undefined,
undefined,
),
).toThrow(
"Could not read default project './tsconfig.json': `getParsedConfigFile` is only supported in a Node-like environment.",
"Could not read default project 'tsconfig.json': `getParsedConfigFile` is only supported in a Node-like environment.",
);
});

Expand All @@ -108,7 +127,7 @@ describe('createProjectService', () => {
const { service } = createProjectService(
{
allowDefaultProject: ['file.js'],
defaultProject: './tsconfig.json',
defaultProject: 'tsconfig.json',
},
undefined,
undefined,
Expand All @@ -127,7 +146,7 @@ describe('createProjectService', () => {
const { service } = createProjectService(
{
allowDefaultProject: ['file.js'],
defaultProject: './tsconfig.json',
defaultProject: 'tsconfig.json',
},
undefined,
tsconfigRootDir,
Expand All @@ -139,7 +158,7 @@ describe('createProjectService', () => {
expect(mockGetParsedConfigFile).toHaveBeenCalledWith(
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('typescript/lib/tsserverlibrary'),
'./tsconfig.json',
'tsconfig.json',
tsconfigRootDir,
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export default tseslint.config(
// Added lines start
projectService: {
allowDefaultProject: ['*.js'],
defaultProject: './tsconfig.json',
defaultProject: 'tsconfig.json',
},
// Added lines end
tsconfigRootDir: import.meta.dirname,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export default tseslint.config(
// Added lines start
projectService: {
allowDefaultProject: ['*.js'],
defaultProject: './tsconfig.json',
defaultProject: 'tsconfig.json',
},
// Added lines end
tsconfigRootDir: import.meta.dirname,
Expand Down
4 changes: 4 additions & 0 deletions packages/website/src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,7 @@ h6 {
td > p:last-child {
margin-bottom: 0;
}

h5 {
font-weight: bold;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was bugging me that the h5s under ProjectServiceOptions were roughly the same font size & weight as the text around them...

Loading