From ac94652d9b3279a91d55229fdf4c10c19e11424a Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 23 Aug 2024 02:33:43 -0400 Subject: [PATCH 1/3] docs: add more project service docs and FAQs --- docs/packages/Parser.mdx | 34 +++++++--- docs/troubleshooting/typed-linting/index.mdx | 67 ++++++++++++++++--- .../tests/lib/createProjectService.test.ts | 8 +-- ...7-announcing-typescript-eslint-v8-beta.mdx | 2 +- ...4-07-31-announcing-typescript-eslint-v8.md | 2 +- 5 files changed, 88 insertions(+), 25 deletions(-) diff --git a/docs/packages/Parser.mdx b/docs/packages/Parser.mdx index c75ca14a8855..60ac67bbcce4 100644 --- a/docs/packages/Parser.mdx +++ b/docs/packages/Parser.mdx @@ -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`). @@ -320,38 +321,49 @@ 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 +- 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', }, }, }; ``` -##### `allowDefaultProject` +##### **`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. -##### `defaultProject` +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` +##### **`maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING`** > Default: `8`. diff --git a/docs/troubleshooting/typed-linting/index.mdx b/docs/troubleshooting/typed-linting/index.mdx index f1453b63f972..1ac1623e145a 100644 --- a/docs/troubleshooting/typed-linting/index.mdx +++ b/docs/troubleshooting/typed-linting/index.mdx @@ -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 - - +[`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: @@ -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! + + + +### 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 diff --git a/packages/typescript-estree/tests/lib/createProjectService.test.ts b/packages/typescript-estree/tests/lib/createProjectService.test.ts index b708188722e1..e6adbc185708 100644 --- a/packages/typescript-estree/tests/lib/createProjectService.test.ts +++ b/packages/typescript-estree/tests/lib/createProjectService.test.ts @@ -70,7 +70,7 @@ describe('createProjectService', () => { createProjectService( { allowDefaultProject: ['file.js'], - defaultProject: './tsconfig.json', + defaultProject: 'tsconfig.json', }, undefined, undefined, @@ -91,7 +91,7 @@ describe('createProjectService', () => { createProjectService( { allowDefaultProject: ['file.js'], - defaultProject: './tsconfig.json', + defaultProject: 'tsconfig.json', }, undefined, undefined, @@ -108,7 +108,7 @@ describe('createProjectService', () => { const { service } = createProjectService( { allowDefaultProject: ['file.js'], - defaultProject: './tsconfig.json', + defaultProject: 'tsconfig.json', }, undefined, undefined, @@ -127,7 +127,7 @@ describe('createProjectService', () => { const { service } = createProjectService( { allowDefaultProject: ['file.js'], - defaultProject: './tsconfig.json', + defaultProject: 'tsconfig.json', }, undefined, tsconfigRootDir, diff --git a/packages/website/blog/2024-05-27-announcing-typescript-eslint-v8-beta.mdx b/packages/website/blog/2024-05-27-announcing-typescript-eslint-v8-beta.mdx index 52d2de3c52b8..69189cb2ab01 100644 --- a/packages/website/blog/2024-05-27-announcing-typescript-eslint-v8-beta.mdx +++ b/packages/website/blog/2024-05-27-announcing-typescript-eslint-v8-beta.mdx @@ -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, diff --git a/packages/website/blog/2024-07-31-announcing-typescript-eslint-v8.md b/packages/website/blog/2024-07-31-announcing-typescript-eslint-v8.md index 141b7b39fc94..4e5f8aa3f2f2 100644 --- a/packages/website/blog/2024-07-31-announcing-typescript-eslint-v8.md +++ b/packages/website/blog/2024-07-31-announcing-typescript-eslint-v8.md @@ -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, From a9fbd9ec5fee3c04b518817b12fd9cfb0c4dab66 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 23 Aug 2024 02:39:28 -0400 Subject: [PATCH 2/3] Use custom.css instead of inline bold --- docs/packages/Parser.mdx | 6 +++--- packages/website/src/css/custom.css | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/packages/Parser.mdx b/docs/packages/Parser.mdx index 60ac67bbcce4..a75c1ca8429b 100644 --- a/docs/packages/Parser.mdx +++ b/docs/packages/Parser.mdx @@ -341,7 +341,7 @@ The behavior of `parserOptions.projectService` can be customized by setting it t }; ``` -##### **`allowDefaultProject`** +##### `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). @@ -356,14 +356,14 @@ 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`** +##### `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`** +##### `maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING` > Default: `8`. diff --git a/packages/website/src/css/custom.css b/packages/website/src/css/custom.css index 825b824a9b6a..1fe7a2879640 100644 --- a/packages/website/src/css/custom.css +++ b/packages/website/src/css/custom.css @@ -211,3 +211,7 @@ h6 { td > p:last-child { margin-bottom: 0; } + +h5 { + font-weight: bold; +} From ef44301201514fb29bccc6d94953b30b13908674 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 23 Aug 2024 11:53:37 -0400 Subject: [PATCH 3/3] Update unit tests --- .../tests/lib/createProjectService.test.ts | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/packages/typescript-estree/tests/lib/createProjectService.test.ts b/packages/typescript-estree/tests/lib/createProjectService.test.ts index e6adbc185708..16b801c007a6 100644 --- a/packages/typescript-estree/tests/lib/createProjectService.test.ts +++ b/packages/typescript-estree/tests/lib/createProjectService.test.ts @@ -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!'); }); @@ -70,7 +70,7 @@ describe('createProjectService', () => { createProjectService( { allowDefaultProject: ['file.js'], - defaultProject: 'tsconfig.json', + defaultProject: './tsconfig.json', }, undefined, undefined, @@ -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( @@ -97,7 +116,7 @@ describe('createProjectService', () => { 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.", ); }); @@ -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, ); });