Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3fd666f

Browse files
feat(typescript-estree): add maximumDefaultProjectFileMatchCount and wide allowDefaultProjectForFiles glob restrictions (typescript-eslint#8925)
* feat(typescript-estree): add maximumDefaultProjectFileMatchCount * lil touchup for the asterisk * Spelling and Windows normalization * Also reset new cache in persistentParse.test.ts
1 parent 4bed24d commit 3fd666f

15 files changed

+273
-21
lines changed

.cspell.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"Airbnb",
5151
"Airbnb's",
5252
"ambiently",
53+
"allowdefaultprojectforfiles",
5354
"Armano",
5455
"astexplorer",
5556
"Astro",
@@ -150,6 +151,7 @@
150151
"unoptimized",
151152
"unprefixed",
152153
"upsert",
154+
"useprojectservice",
153155
"Waiblinger",
154156
"warnonunsupportedtypescriptversion",
155157
"Zacher"

docs/packages/TypeScript_ESTree.mdx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,15 @@ interface ProjectServiceOptions {
288288
* Path to a TSConfig to use instead of TypeScript's default project configuration.
289289
*/
290290
defaultProject?: string;
291+
292+
/**
293+
* The maximum number of files {@link allowDefaultProjectForFiles} may match.
294+
* Each file match slows down linting, so if you do need to use this, please
295+
* file an informative issue on typescript-eslint explaining why - so we can
296+
* help you avoid using it!
297+
* @default 8
298+
*/
299+
maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING?: number;
291300
}
292301

293302
interface ParserServices {

docs/troubleshooting/FAQ.mdx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,40 @@ If you don't find an existing extension rule, or the extension rule doesn't work
3232
> We release a new version our tooling every week.
3333
> _Please_ ensure that you [check our the latest list of "extension" rules](/rules/#extension-rules) **_before_** filing an issue.
3434
35+
<HiddenHeading id="allowdefaultprojectforfiles-glob-too-wide" />
36+
37+
## I get errors telling me "Having many files run with the default project is known to cause performance issues and slow down linting."
38+
39+
These errors are caused by using the [`EXPERIMENTAL_useProjectService`](../packages/Parser.mdx#experimental_useprojectservice) `allowDefaultProjectForFiles` with an excessively wide glob.
40+
`allowDefaultProjectForFiles` causes a new TypeScript "program" to be built for each "out of project" file it includes, which incurs a performance overhead for each file.
41+
42+
To resolve this error, narrow the glob(s) used for `allowDefaultProjectForFiles` to include fewer files.
43+
For example:
44+
45+
```diff title="eslint.config.js"
46+
parserOptions: {
47+
EXPERIMENTAL_useProjectService: {
48+
allowDefaultProjectForFiles: [
49+
- "**/*.js",
50+
+ "./*.js"
51+
]
52+
}
53+
}
54+
```
55+
56+
You may also need to include more files in your `tsconfig.json`.
57+
For example:
58+
59+
```diff title="tsconfig.json"
60+
"include": [
61+
"src",
62+
+ "*.js"
63+
]
64+
```
65+
66+
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.
67+
Be sure to include a minimal reproduction we can work with to understand your use case!
68+
3569
## I get errors telling me "ESLint was configured to run ... However, that TSConfig does not / none of those TSConfigs include this file"
3670

3771
These errors are caused by an ESLint config requesting type information be generated for a file that isn't included in the TypeScript configuration.
@@ -499,6 +533,6 @@ If you think you're having issues with performance, see our [Performance Trouble
499533

500534
## Are TypeScript project references supported?
501535

502-
No, TypeScript project references are not yet supported.
536+
Yes, but only with [`EXPERIMENTAL_useProjectService`](../packages/Parser.mdx#experimental_useprojectservice).
503537

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

packages/typescript-estree/src/clear-caches.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { clearWatchCaches } from './create-program/getWatchProgramsForProjects';
2-
import { clearProgramCache as clearProgramCacheOriginal } from './parser';
2+
import {
3+
clearDefaultProjectMatchedFiles,
4+
clearProgramCache as clearProgramCacheOriginal,
5+
} from './parser';
36
import {
47
clearTSConfigMatchCache,
58
clearTSServerProjectService,
@@ -14,6 +17,7 @@ import { clearGlobCache } from './parseSettings/resolveProjectList';
1417
* - In custom lint tooling that iteratively lints one project at a time to prevent OOMs.
1518
*/
1619
export function clearCaches(): void {
20+
clearDefaultProjectMatchedFiles();
1721
clearProgramCacheOriginal();
1822
clearWatchCaches();
1923
clearTSConfigMatchCache();

packages/typescript-estree/src/create-program/createProjectService.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import os from 'node:os';
44
import type * as ts from 'typescript/lib/tsserverlibrary';
55

66
import type { ProjectServiceOptions } from '../parser-options';
7+
import { validateDefaultProjectForFilesGlob } from './validateDefaultProjectForFilesGlob';
8+
9+
const DEFAULT_PROJECT_MATCHED_FILES_THRESHOLD = 8;
710

811
const doNothing = (): void => {};
912

@@ -15,13 +18,17 @@ export type TypeScriptProjectService = ts.server.ProjectService;
1518

1619
export interface ProjectServiceSettings {
1720
allowDefaultProjectForFiles: string[] | undefined;
21+
maximumDefaultProjectFileMatchCount: number;
1822
service: TypeScriptProjectService;
1923
}
2024

2125
export function createProjectService(
22-
options: boolean | ProjectServiceOptions | undefined,
26+
optionsRaw: boolean | ProjectServiceOptions | undefined,
2327
jsDocParsingMode: ts.JSDocParsingMode | undefined,
2428
): ProjectServiceSettings {
29+
const options = typeof optionsRaw === 'object' ? optionsRaw : {};
30+
validateDefaultProjectForFilesGlob(options);
31+
2532
// We import this lazily to avoid its cost for users who don't use the service
2633
// TODO: Once we drop support for TS<5.3 we can import from "typescript" directly
2734
const tsserver = require('typescript/lib/tsserverlibrary') as typeof ts;
@@ -60,7 +67,7 @@ export function createProjectService(
6067
jsDocParsingMode,
6168
});
6269

63-
if (typeof options === 'object' && options.defaultProject) {
70+
if (options.defaultProject) {
6471
let configRead;
6572

6673
try {
@@ -97,10 +104,10 @@ export function createProjectService(
97104
}
98105

99106
return {
100-
allowDefaultProjectForFiles:
101-
typeof options === 'object'
102-
? options.allowDefaultProjectForFiles
103-
: undefined,
107+
allowDefaultProjectForFiles: options.allowDefaultProjectForFiles,
108+
maximumDefaultProjectFileMatchCount:
109+
options.maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING ??
110+
DEFAULT_PROJECT_MATCHED_FILES_THRESHOLD,
104111
service,
105112
};
106113
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { ProjectServiceOptions } from '../parser-options';
2+
3+
export const DEFAULT_PROJECT_FILES_ERROR_EXPLANATION = `
4+
5+
Having many files run with the default project is known to cause performance issues and slow down linting.
6+
7+
See https://typescript-eslint.io/troubleshooting/#allowdefaultprojectforfiles-glob-too-wide
8+
`;
9+
10+
export function validateDefaultProjectForFilesGlob(
11+
options: ProjectServiceOptions,
12+
): void {
13+
if (!options.allowDefaultProjectForFiles?.length) {
14+
return;
15+
}
16+
17+
for (const glob of options.allowDefaultProjectForFiles) {
18+
if (glob === '*') {
19+
throw new Error(
20+
`allowDefaultProjectForFiles contains the overly wide '*'.${DEFAULT_PROJECT_FILES_ERROR_EXPLANATION}`,
21+
);
22+
}
23+
if (glob.includes('**')) {
24+
throw new Error(
25+
`allowDefaultProjectForFiles glob '${glob}' contains a disallowed '**'.${DEFAULT_PROJECT_FILES_ERROR_EXPLANATION}`,
26+
);
27+
}
28+
}
29+
}

packages/typescript-estree/src/parser-options.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,15 @@ export interface ProjectServiceOptions {
114114
* Path to a TSConfig to use instead of TypeScript's default project configuration.
115115
*/
116116
defaultProject?: string;
117+
118+
/**
119+
* The maximum number of files {@link allowDefaultProjectForFiles} may match.
120+
* Each file match slows down linting, so if you do need to use this, please
121+
* file an informative issue on typescript-eslint explaining why - so we can
122+
* help you avoid using it!
123+
* @default 8
124+
*/
125+
maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING?: number;
117126
}
118127

119128
interface ParseAndGenerateServicesOptions extends ParseOptions {

packages/typescript-estree/src/parser.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ function clearProgramCache(): void {
4040
existingPrograms.clear();
4141
}
4242

43+
const defaultProjectMatchedFiles = new Set<string>();
44+
function clearDefaultProjectMatchedFiles(): void {
45+
defaultProjectMatchedFiles.clear();
46+
}
47+
4348
/**
4449
* @param parseSettings Internal settings for parsing the file
4550
* @param hasFullTypeInformation True if the program should be attempted to be calculated from provided tsconfig files
@@ -54,6 +59,7 @@ function getProgramAndAST(
5459
parseSettings.EXPERIMENTAL_projectService,
5560
parseSettings,
5661
hasFullTypeInformation,
62+
defaultProjectMatchedFiles,
5763
);
5864
if (fromProjectService) {
5965
return fromProjectService;
@@ -286,6 +292,7 @@ export {
286292
parse,
287293
parseAndGenerateServices,
288294
ParseAndGenerateServicesResult,
295+
clearDefaultProjectMatchedFiles,
289296
clearProgramCache,
290297
clearParseAndGenerateServicesCalls,
291298
};

packages/typescript-estree/src/useProgramFromProjectService.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,22 @@ import path from 'path';
55
import { createProjectProgram } from './create-program/createProjectProgram';
66
import type { ProjectServiceSettings } from './create-program/createProjectService';
77
import type { ASTAndDefiniteProgram } from './create-program/shared';
8+
import { DEFAULT_PROJECT_FILES_ERROR_EXPLANATION } from './create-program/validateDefaultProjectForFilesGlob';
89
import type { MutableParseSettings } from './parseSettings';
910

1011
const log = debug(
1112
'typescript-eslint:typescript-estree:useProgramFromProjectService',
1213
);
1314

1415
export function useProgramFromProjectService(
15-
{ allowDefaultProjectForFiles, service }: ProjectServiceSettings,
16+
{
17+
allowDefaultProjectForFiles,
18+
maximumDefaultProjectFileMatchCount,
19+
service,
20+
}: ProjectServiceSettings,
1621
parseSettings: Readonly<MutableParseSettings>,
1722
hasFullTypeInformation: boolean,
23+
defaultProjectMatchedFiles: Set<string>,
1824
): ASTAndDefiniteProgram | undefined {
1925
// We don't canonicalize the filename because it caused a performance regression.
2026
// See https://github.com/typescript-eslint/typescript-eslint/issues/8519
@@ -77,6 +83,20 @@ export function useProgramFromProjectService(
7783
return undefined;
7884
}
7985

86+
defaultProjectMatchedFiles.add(filePathAbsolute);
87+
if (defaultProjectMatchedFiles.size > maximumDefaultProjectFileMatchCount) {
88+
throw new Error(
89+
`Too many files (>${maximumDefaultProjectFileMatchCount}) have matched the default project.${DEFAULT_PROJECT_FILES_ERROR_EXPLANATION}
90+
Matching files:
91+
${Array.from(defaultProjectMatchedFiles)
92+
.map(file => `- ${file}`)
93+
.join('\n')}
94+
95+
If you absolutely need more files included, set parserOptions.EXPERIMENTAL_useProjectService.maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING to a larger value.
96+
`,
97+
);
98+
}
99+
80100
log('Found project service program for: %s', filePathAbsolute);
81101

82102
return createProjectProgram(parseSettings, [program]);

packages/typescript-estree/tests/lib/persistentParse.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import tmp from 'tmp';
44

55
import { clearCaches } from '../../src/clear-caches';
66
import { clearWatchCaches } from '../../src/create-program/getWatchProgramsForProjects';
7-
import { parseAndGenerateServices } from '../../src/parser';
7+
import {
8+
clearDefaultProjectMatchedFiles,
9+
parseAndGenerateServices,
10+
} from '../../src/parser';
811

912
const CONTENTS = {
1013
foo: 'console.log("foo")',
@@ -19,6 +22,9 @@ const CONTENTS = {
1922
const cwdCopy = process.cwd();
2023
const tmpDirs = new Set<tmp.DirResult>();
2124
afterEach(() => {
25+
// reset project tracking
26+
clearDefaultProjectMatchedFiles();
27+
2228
// stop watching the files and folders
2329
clearWatchCaches();
2430

0 commit comments

Comments
 (0)