From efec873139a34cfa01d15a3b5900869d4846231d Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 25 Nov 2022 00:21:23 -0500 Subject: [PATCH 01/18] feat(typescript-estree): allow specifying project: true --- packages/parser/README.md | 2 +- packages/types/src/parser-options.ts | 2 +- packages/typescript-estree/README.md | 5 +- .../src/create-program/createWatchProgram.ts | 2 +- .../src/parseSettings/createParseSettings.ts | 3 +- .../parseSettings/getProjectConfigFiles.ts | 80 +++++++++++++++++++ .../typescript-estree/src/parser-options.ts | 5 +- 7 files changed, 91 insertions(+), 8 deletions(-) create mode 100644 packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts diff --git a/packages/parser/README.md b/packages/parser/README.md index b32ed30beab4..5ffe0eae1b32 100644 --- a/packages/parser/README.md +++ b/packages/parser/README.md @@ -59,7 +59,7 @@ interface ParserOptions { jsxFragmentName?: string | null; lib?: string[]; - project?: string | string[]; + project?: string | string[] | true; projectFolderIgnoreList?: string[]; tsconfigRootDir?: string; extraFileExtensions?: string[]; diff --git a/packages/types/src/parser-options.ts b/packages/types/src/parser-options.ts index b3149231215e..1fe28d6cbf3d 100644 --- a/packages/types/src/parser-options.ts +++ b/packages/types/src/parser-options.ts @@ -51,7 +51,7 @@ interface ParserOptions { filePath?: string; loc?: boolean; program?: Program; - project?: string | string[]; + project?: string | string[] | true; projectFolderIgnoreList?: (string | RegExp)[]; range?: boolean; sourceType?: SourceType; diff --git a/packages/typescript-estree/README.md b/packages/typescript-estree/README.md index cb1d0d0312c7..db0fb946d5e9 100644 --- a/packages/typescript-estree/README.md +++ b/packages/typescript-estree/README.md @@ -180,10 +180,11 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { preserveNodeMaps?: boolean; /** - * Absolute (or relative to `tsconfigRootDir`) paths to the tsconfig(s). + * Absolute (or relative to `tsconfigRootDir`) paths to the tsconfig(s), + * or `true` to find the nearest tsconfig.json to the file. * If this is provided, type information will be returned. */ - project?: string | string[]; + project?: string | string[] | true; /** * If you provide a glob (or globs) to the project option, you can use this option to ignore certain folders from diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index d17835fff3c8..a0567361a412 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -548,4 +548,4 @@ function maybeInvalidateProgram( return null; } -export { clearWatchCaches, createWatchProgram, getProgramsForProjects }; +export { clearWatchCaches, getProgramsForProjects }; diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index b1cde9d4c9ad..b37ba74d39c1 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -8,6 +8,7 @@ import { getCanonicalFileName, } from '../create-program/shared'; import type { TSESTreeOptions } from '../parser-options'; +import { getProjectConfigFiles } from './getProjectConfigFiles'; import type { MutableParseSettings } from './index'; import { inferSingleRun } from './inferSingleRun'; import { warnAboutTSVersion } from './warnAboutTSVersion'; @@ -112,7 +113,7 @@ export function createParseSettings( parseSettings.projects = prepareAndTransformProjects( tsconfigRootDir, - options.project, + getProjectConfigFiles(parseSettings, options.project), projectFolderIgnoreList, ); } diff --git a/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts new file mode 100644 index 000000000000..d5ea750c66f2 --- /dev/null +++ b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts @@ -0,0 +1,80 @@ +import debug from 'debug'; +import fs from 'fs'; +import path from 'path'; + +import type { ParseSettings } from '.'; + +const log = debug('typescript-eslint:typescript-estree:getProjectConfigFiles'); + +interface TSConfigMatch { + exists: boolean; + timestamp: number; +} + +/** + * How many milliseconds we will respect a cache for checking an fs.existsSync + * check for a file file on disk. + */ +const RECHECK_FILE_THRESHOLD_MS = 50; + +const tsconfigMatchCache = new Map(); + +/** + * Checks for a file file on disk, respecting a limited-time cache. + * This keeps a caches checked paths with existence and `Date.now()` timestamp. + * After `RECHECK_FILE_THRESHOLD_MS`, cache entries are ignored. + * + * @param filePath File path to check for existence on disk. + * @returns Whether the file exists on disk. + * @remarks + * We don't (yet!) have a way to attach file watchers on disk, but still need to + * cache file checks for rapid subsequent calls to fs.existsSync. See discussion + * in https://github.com/typescript-eslint/typescript-eslint/issues/101. + */ +function existsSyncCached(filePath: string): boolean { + const cached = tsconfigMatchCache.get(filePath); + const now = Date.now(); + + if (cached && now - cached.timestamp < RECHECK_FILE_THRESHOLD_MS) { + return cached.exists; + } + + const exists = fs.existsSync(filePath); + + tsconfigMatchCache.set(filePath, { + exists, + timestamp: now, + }); + + return exists; +} + +export function getProjectConfigFiles( + parseSettings: ParseSettings, + projects: string | string[] | true | undefined, +): string | string[] | undefined { + if (projects !== true) { + return projects; + } + + log('Looking for tsconfig.json at or above file: %s', parseSettings.filePath); + let directory = path.dirname(parseSettings.filePath); + + do { + const tsconfigPath = path.join(directory, 'tsconfig.json'); + log('Checking tsconfig.json path: %s', tsconfigPath); + + if (existsSyncCached(tsconfigPath)) { + return [tsconfigPath]; + } + + directory = path.basename(directory); + } while ( + directory && + directory.length < parseSettings.tsconfigRootDir.length + ); + + throw new Error( + `project was set to \`true\` but couldn't find any tsconfig.json relative to '${parseSettings.filePath}' within '${parseSettings.tsconfigRootDir}.`, + ); +} diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index cec95c3b413d..faf2aae0b544 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -117,10 +117,11 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { preserveNodeMaps?: boolean; /** - * Absolute (or relative to `tsconfigRootDir`) paths to the tsconfig(s). + * Absolute (or relative to `tsconfigRootDir`) paths to the tsconfig(s), + * or `true` to find the nearest tsconfig.json to the file. * If this is provided, type information will be returned. */ - project?: string | string[]; + project?: string | string[] | true; /** * If you provide a glob (or globs) to the project option, you can use this option to ignore certain folders from From 4927c39602a39db9b4a3ef908676d85b18687c36 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 25 Nov 2022 02:05:51 -0500 Subject: [PATCH 02/18] Also fix unchanged file for lint I guess --- packages/eslint-plugin-tslint/src/rules/config.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/eslint-plugin-tslint/src/rules/config.ts b/packages/eslint-plugin-tslint/src/rules/config.ts index 9fcfa844da11..97b77e7d0806 100644 --- a/packages/eslint-plugin-tslint/src/rules/config.ts +++ b/packages/eslint-plugin-tslint/src/rules/config.ts @@ -98,18 +98,13 @@ export default createRule({ ], }, defaultOptions: [{}], - create(context) { + create( + context, + [{ rules: tslintRules, rulesDirectory: tslintRulesDirectory, lintFile }], + ) { const fileName = context.getFilename(); const sourceCode = context.getSourceCode().text; const parserServices = ESLintUtils.getParserServices(context); - - /** - * The TSLint rules configuration passed in by the user - */ - const [ - { rules: tslintRules, rulesDirectory: tslintRulesDirectory, lintFile }, - ] = context.options; - const program = parserServices.program; /** From d01f2bfcf0d135b76b2d1e11a4382f765cd4f109 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 25 Nov 2022 02:41:45 -0500 Subject: [PATCH 03/18] Added docs --- docs/linting/TYPED_LINTING.md | 32 +++++++++++++++++++++---- docs/linting/typed-linting/MONOREPOS.md | 2 +- packages/parser/README.md | 7 ++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/docs/linting/TYPED_LINTING.md b/docs/linting/TYPED_LINTING.md index 8ffb708d0e3e..a4a999b73ca9 100644 --- a/docs/linting/TYPED_LINTING.md +++ b/docs/linting/TYPED_LINTING.md @@ -12,8 +12,7 @@ module.exports = { parser: '@typescript-eslint/parser', // Added lines start parserOptions: { - tsconfigRootDir: __dirname, - project: ['./tsconfig.json'], + project: true, }, // Added lines end plugins: ['@typescript-eslint'], @@ -28,14 +27,37 @@ module.exports = { In more detail: -- `parserOptions.tsconfigRootDir` tells our parser the absolute path of your project's root directory. -- `parserOptions.project` tells our parser the relative path where your project's `tsconfig.json` is. - - If your project is a multi-package monorepo, see [our docs on configuring a monorepo](./typed-linting/MONOREPOS.md). +- `parserOptions.project` tells our parser to use the closest `tsconfig.json` to each file for informing that file's type information. - `plugin:@typescript-eslint/recommended-requiring-type-checking` is another recommended configuration we provide. This one contains rules that specifically require type information. With that done, run the same lint command you ran before. You may see new rules reporting errors based on type information! +## Specifying TSConfigs + +The `parserOptions.project` option can be turned on 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 the + +For example, if you use a specific `tsconfig.eslint.json` for linting, you'd specify: + +```js title=".eslintrc.js" +module.exports = { + // ... + parserOptions: { + project: './tsconfig.eslint.json', + }, + // ... +}; +``` + +See [the `@typescript-eslint/parser` docs for more details](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/README.md#parseroptionsproject). + +:::note +If your project is a multi-package monorepo, see [our docs on configuring a monorepo](./typed-linting/MONOREPOS.md). +::: + ## FAQs ### How is performance? diff --git a/docs/linting/typed-linting/MONOREPOS.md b/docs/linting/typed-linting/MONOREPOS.md index 5163e997ec84..18aa70b142ef 100644 --- a/docs/linting/typed-linting/MONOREPOS.md +++ b/docs/linting/typed-linting/MONOREPOS.md @@ -53,7 +53,7 @@ module.exports = { parserOptions: { tsconfigRootDir: __dirname, // Remove this line - project: ['./tsconfig.json'], + project: true, // Add this line project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'], }, diff --git a/packages/parser/README.md b/packages/parser/README.md index 5ffe0eae1b32..5ab3ff4929e2 100644 --- a/packages/parser/README.md +++ b/packages/parser/README.md @@ -152,6 +152,9 @@ This option allows you to provide a path to your project's `tsconfig.json`. **Th - Accepted values: ```js + // true + project: true, + // path project: './tsconfig.json'; @@ -162,6 +165,10 @@ This option allows you to provide a path to your project's `tsconfig.json`. **Th project: ['./packages/**/tsconfig.json', './separate-package/tsconfig.json']; ``` +- If `true`, each source file's parse will find the nearest `tsconfig.json` file to that source file. + + - This is done by checking that source file's directory for a `tsconfig.json`, then the parent's directory for a `tsconfig.json`, and so on - until one is found or the current working directory is passed. + - If you use project references, TypeScript will not automatically use project references to resolve files. This means that you will have to add each referenced tsconfig to the `project` field either separately, or via a glob. - Note that using wide globs `**` in your `parserOptions.project` may cause performance implications. Instead of globs that use `**` to recursively check all folders, prefer paths that use a single `*` at a time. For more info see [#2611](https://github.com/typescript-eslint/typescript-eslint/issues/2611). From 4bf121ec2edfdff913c7e2308632243eab68463f Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 25 Nov 2022 02:44:17 -0500 Subject: [PATCH 04/18] More tsconfigRootDir: __dirname removal --- docs/linting/typed-linting/MONOREPOS.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/linting/typed-linting/MONOREPOS.md b/docs/linting/typed-linting/MONOREPOS.md index 18aa70b142ef..1e805184d85b 100644 --- a/docs/linting/typed-linting/MONOREPOS.md +++ b/docs/linting/typed-linting/MONOREPOS.md @@ -51,7 +51,6 @@ module.exports = { ], parser: '@typescript-eslint/parser', parserOptions: { - tsconfigRootDir: __dirname, // Remove this line project: true, // Add this line @@ -76,7 +75,6 @@ module.exports = { ], parser: '@typescript-eslint/parser', parserOptions: { - tsconfigRootDir: __dirname, // Remove this line project: ['./tsconfig.eslint.json', './**/tsconfig.json'], // Add this line From db656f51c987d3e2cc30c700efecb82f7c91e721 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 25 Nov 2022 03:20:30 -0500 Subject: [PATCH 05/18] Added some unit tests --- .../parseSettings/getProjectConfigFiles.ts | 20 ++-- .../tests/lib/getProjectConfigFiles.test.ts | 101 ++++++++++++++++++ 2 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts diff --git a/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts index d5ea750c66f2..1a87e7b83d28 100644 --- a/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts +++ b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts @@ -1,6 +1,6 @@ import debug from 'debug'; -import fs from 'fs'; -import path from 'path'; +import * as fs from 'fs'; +import * as path from 'path'; import type { ParseSettings } from '.'; @@ -50,11 +50,11 @@ function existsSyncCached(filePath: string): boolean { } export function getProjectConfigFiles( - parseSettings: ParseSettings, - projects: string | string[] | true | undefined, + parseSettings: Pick, + project: string | string[] | true | undefined, ): string | string[] | undefined { - if (projects !== true) { - return projects; + if (project !== true) { + return project; } log('Looking for tsconfig.json at or above file: %s', parseSettings.filePath); @@ -68,13 +68,13 @@ export function getProjectConfigFiles( return [tsconfigPath]; } - directory = path.basename(directory); + directory = path.dirname(directory); } while ( - directory && - directory.length < parseSettings.tsconfigRootDir.length + directory.length > 1 && + directory.length >= parseSettings.tsconfigRootDir.length ); throw new Error( - `project was set to \`true\` but couldn't find any tsconfig.json relative to '${parseSettings.filePath}' within '${parseSettings.tsconfigRootDir}.`, + `project was set to \`true\` but couldn't find any tsconfig.json relative to '${parseSettings.filePath}' within '${parseSettings.tsconfigRootDir}'.`, ); } diff --git a/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts b/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts new file mode 100644 index 000000000000..9ba2f56cea7d --- /dev/null +++ b/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts @@ -0,0 +1,101 @@ +import { getProjectConfigFiles } from '../../src/parseSettings/getProjectConfigFiles'; + +const mockExistsSync = jest.fn(); + +jest.mock('fs', () => ({ + ...jest.requireActual('fs'), + existsSync: (filePath: string): boolean => mockExistsSync(filePath), +})); + +const parseSettings = { + filePath: './repos/repo/packages/package/file.ts', + tsconfigRootDir: './repos/repo', +}; + +describe('getProjectConfigFiles', () => { + it('returns the project when given as a string', () => { + const project = './tsconfig.eslint.json'; + + const actual = getProjectConfigFiles(parseSettings, project); + + expect(actual).toBe(project); + }); + + it('returns the project when given as a string array', () => { + const project = ['./tsconfig.eslint.json']; + + const actual = getProjectConfigFiles(parseSettings, project); + + expect(actual).toBe(project); + }); + + it('returns the project when given as undefined', () => { + const project = undefined; + + const actual = getProjectConfigFiles(parseSettings, project); + + expect(actual).toBe(project); + }); + + describe('when caching hits', () => { + beforeAll(() => { + Date.now = (): number => 0; + }); + + it('returns a local tsconfig.json without calling existsSync a second time', () => { + mockExistsSync.mockReturnValue(true); + + getProjectConfigFiles(parseSettings, true); + const actual = getProjectConfigFiles(parseSettings, true); + + expect(actual).toEqual(['repos/repo/packages/package/tsconfig.json']); + expect(mockExistsSync).toHaveBeenCalledTimes(1); + }); + }); + + describe('when caching misses', () => { + beforeAll(() => { + // Tricks Date.now-based caching into always calling to fs.existsSync + let lastDateNow = 0; + Date.now = (): number => (lastDateNow += 1000); + }); + + it('returns a local tsconfig.json when matched', () => { + mockExistsSync.mockReturnValue(true); + + const actual = getProjectConfigFiles(parseSettings, true); + + expect(actual).toEqual(['repos/repo/packages/package/tsconfig.json']); + }); + + it('returns a parent tsconfig.json when matched', () => { + mockExistsSync.mockImplementation( + filePath => filePath === 'repos/repo/tsconfig.json', + ); + + const actual = getProjectConfigFiles(parseSettings, true); + + expect(actual).toEqual(['repos/repo/tsconfig.json']); + }); + + it('throws when searching hits .', () => { + mockExistsSync.mockReturnValue(false); + + expect(() => + getProjectConfigFiles(parseSettings, true), + ).toThrowErrorMatchingInlineSnapshot( + `"project was set to \`true\` but couldn't find any tsconfig.json relative to './repos/repo/packages/package/file.ts' within './repos/repo'."`, + ); + }); + + it('throws when searching passes the tsconfigRootDir', () => { + mockExistsSync.mockReturnValue(false); + + expect(() => + getProjectConfigFiles({ ...parseSettings, tsconfigRootDir: '/' }, true), + ).toThrowErrorMatchingInlineSnapshot( + `"project was set to \`true\` but couldn't find any tsconfig.json relative to './repos/repo/packages/package/file.ts' within '/'."`, + ); + }); + }); +}); From 003d993a691b75dfad7b68de78601a0d142d56a0 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 25 Nov 2022 03:21:34 -0500 Subject: [PATCH 06/18] you don't say --- packages/parser/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/parser/README.md b/packages/parser/README.md index 5ab3ff4929e2..b3e7d1c27da8 100644 --- a/packages/parser/README.md +++ b/packages/parser/README.md @@ -152,7 +152,7 @@ This option allows you to provide a path to your project's `tsconfig.json`. **Th - Accepted values: ```js - // true + // find the tsconfig.json nearest each source file project: true, // path From eb34a95aa4c40c2b7d8954363c9c78bf4c28f6ac Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 25 Nov 2022 03:22:27 -0500 Subject: [PATCH 07/18] Undo createWatchProgram.ts --- .../typescript-estree/src/create-program/createWatchProgram.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index a0567361a412..d17835fff3c8 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -548,4 +548,4 @@ function maybeInvalidateProgram( return null; } -export { clearWatchCaches, getProgramsForProjects }; +export { clearWatchCaches, createWatchProgram, getProgramsForProjects }; From 9ae3ce6929e9e883aca8c8cfccc6dcd38fdaddb9 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 30 Nov 2022 13:45:08 -0500 Subject: [PATCH 08/18] Added parse tests --- .../parseSettings/getProjectConfigFiles.ts | 3 +- .../projectTrue/nested/deep/included.ts | 1 + .../fixtures/projectTrue/nested/included.ts | 1 + .../fixtures/projectTrue/nested/tsconfig.json | 3 ++ .../tests/fixtures/projectTrue/notIncluded.ts | 1 + .../tests/lib/parse.project-true.test.ts | 49 +++++++++++++++++++ 6 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 packages/typescript-estree/tests/fixtures/projectTrue/nested/deep/included.ts create mode 100644 packages/typescript-estree/tests/fixtures/projectTrue/nested/included.ts create mode 100644 packages/typescript-estree/tests/fixtures/projectTrue/nested/tsconfig.json create mode 100644 packages/typescript-estree/tests/fixtures/projectTrue/notIncluded.ts create mode 100644 packages/typescript-estree/tests/lib/parse.project-true.test.ts diff --git a/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts index 1a87e7b83d28..bf7a26a40038 100644 --- a/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts +++ b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts @@ -12,8 +12,7 @@ interface TSConfigMatch { } /** - * How many milliseconds we will respect a cache for checking an fs.existsSync - * check for a file file on disk. + * How many milliseconds we will respect the cache of an fs.existsSync check. */ const RECHECK_FILE_THRESHOLD_MS = 50; diff --git a/packages/typescript-estree/tests/fixtures/projectTrue/nested/deep/included.ts b/packages/typescript-estree/tests/fixtures/projectTrue/nested/deep/included.ts new file mode 100644 index 000000000000..9fe571f28c0d --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/projectTrue/nested/deep/included.ts @@ -0,0 +1 @@ +const b = true; diff --git a/packages/typescript-estree/tests/fixtures/projectTrue/nested/included.ts b/packages/typescript-estree/tests/fixtures/projectTrue/nested/included.ts new file mode 100644 index 000000000000..9fe571f28c0d --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/projectTrue/nested/included.ts @@ -0,0 +1 @@ +const b = true; diff --git a/packages/typescript-estree/tests/fixtures/projectTrue/nested/tsconfig.json b/packages/typescript-estree/tests/fixtures/projectTrue/nested/tsconfig.json new file mode 100644 index 000000000000..d144c8ddb02c --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/projectTrue/nested/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["."] +} diff --git a/packages/typescript-estree/tests/fixtures/projectTrue/notIncluded.ts b/packages/typescript-estree/tests/fixtures/projectTrue/notIncluded.ts new file mode 100644 index 000000000000..7ceea3e98854 --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/projectTrue/notIncluded.ts @@ -0,0 +1 @@ +const c = true; diff --git a/packages/typescript-estree/tests/lib/parse.project-true.test.ts b/packages/typescript-estree/tests/lib/parse.project-true.test.ts new file mode 100644 index 000000000000..3e4e47cb2c94 --- /dev/null +++ b/packages/typescript-estree/tests/lib/parse.project-true.test.ts @@ -0,0 +1,49 @@ +import { join } from 'path'; + +import * as parser from '../../src'; + +const PROJECT_DIR = join(__dirname, '../fixtures/projectTrue'); + +const config = { + tsconfigRootDir: PROJECT_DIR, + project: true, +} satisfies Partial; + +describe('parseAndGenerateServices', () => { + describe('when project is true', () => { + it('finds a parent project when it exists in the project', () => { + const result = parser.parseAndGenerateServices('const a = true', { + ...config, + filePath: join(PROJECT_DIR, 'nested/deep/included.ts'), + }); + + expect(result).toEqual({ + ast: expect.any(Object), + services: expect.any(Object), + }); + }); + + it('finds a sibling project when it exists in the project', () => { + const result = parser.parseAndGenerateServices('const a = true', { + ...config, + filePath: join(PROJECT_DIR, 'nested/included.ts'), + }); + + expect(result).toEqual({ + ast: expect.any(Object), + services: expect.any(Object), + }); + }); + + it('throws an error when a parent project does not exist', () => { + expect(() => + parser.parseAndGenerateServices('const a = true', { + ...config, + filePath: join(PROJECT_DIR, 'notIncluded.ts'), + }), + ).toThrow( + /project was set to `true` but couldn't find any tsconfig.json relative to '.+\/tests\/fixtures\/projectTrue\/notIncluded.ts' within '.+\/tests\/fixtures\/projectTrue'./, + ); + }); + }); +}); From b024ad23fa337b59cbee755eeec1bf7b7ad63475 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 3 Dec 2022 04:37:29 -0500 Subject: [PATCH 09/18] Fixed monorepos link --- docs/linting/Typed_Linting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/linting/Typed_Linting.md b/docs/linting/Typed_Linting.md index 61b1e792be8f..d9563a5ea82d 100644 --- a/docs/linting/Typed_Linting.md +++ b/docs/linting/Typed_Linting.md @@ -56,7 +56,7 @@ module.exports = { See [the `@typescript-eslint/parser` docs for more details](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/README.md#parseroptionsproject). :::note -If your project is a multi-package monorepo, see [our docs on configuring a monorepo](./typed-linting/MONOREPOS.md). +If your project is a multi-package monorepo, see [our docs on configuring a monorepo](./typed-linting/Monorepos.md). ::: ## FAQs From 6b16b041c75b39e6664a42f1999572bda1eec05a Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 8 Dec 2022 12:22:26 -0500 Subject: [PATCH 10/18] Cache under all directories --- .../parseSettings/getProjectConfigFiles.ts | 54 +++++++------------ .../tests/lib/getProjectConfigFiles.test.ts | 46 ++++++++++++---- 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts index bf7a26a40038..61c56e9a3230 100644 --- a/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts +++ b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts @@ -6,48 +6,24 @@ import type { ParseSettings } from '.'; const log = debug('typescript-eslint:typescript-estree:getProjectConfigFiles'); -interface TSConfigMatch { - exists: boolean; - timestamp: number; -} +const tsconfigMatchCache = new Map(); /** - * How many milliseconds we will respect the cache of an fs.existsSync check. + * @remarks Only use this for tests! */ -const RECHECK_FILE_THRESHOLD_MS = 50; - -const tsconfigMatchCache = new Map(); +export function clearMatchCacheForTests() { + tsconfigMatchCache.clear(); +} /** - * Checks for a file file on disk, respecting a limited-time cache. - * This keeps a caches checked paths with existence and `Date.now()` timestamp. - * After `RECHECK_FILE_THRESHOLD_MS`, cache entries are ignored. + * Checks for a matching TSConfig to a file including its parent directories, + * permanently caching results under each directory it checks. * - * @param filePath File path to check for existence on disk. - * @returns Whether the file exists on disk. * @remarks * We don't (yet!) have a way to attach file watchers on disk, but still need to * cache file checks for rapid subsequent calls to fs.existsSync. See discussion * in https://github.com/typescript-eslint/typescript-eslint/issues/101. */ -function existsSyncCached(filePath: string): boolean { - const cached = tsconfigMatchCache.get(filePath); - const now = Date.now(); - - if (cached && now - cached.timestamp < RECHECK_FILE_THRESHOLD_MS) { - return cached.exists; - } - - const exists = fs.existsSync(filePath); - - tsconfigMatchCache.set(filePath, { - exists, - timestamp: now, - }); - - return exists; -} - export function getProjectConfigFiles( parseSettings: Pick, project: string | string[] | true | undefined, @@ -58,16 +34,24 @@ export function getProjectConfigFiles( log('Looking for tsconfig.json at or above file: %s', parseSettings.filePath); let directory = path.dirname(parseSettings.filePath); + const checkedDirectories = [directory]; do { + log('Checking tsconfig.json path: %s', directory); const tsconfigPath = path.join(directory, 'tsconfig.json'); - log('Checking tsconfig.json path: %s', tsconfigPath); - - if (existsSyncCached(tsconfigPath)) { - return [tsconfigPath]; + const cached = + tsconfigMatchCache.get(directory) ?? + (fs.existsSync(tsconfigPath) && tsconfigPath); + + if (cached) { + for (const directory of checkedDirectories) { + tsconfigMatchCache.set(directory, cached); + } + return [cached]; } directory = path.dirname(directory); + checkedDirectories.push(directory); } while ( directory.length > 1 && directory.length >= parseSettings.tsconfigRootDir.length diff --git a/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts b/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts index 9ba2f56cea7d..563307428909 100644 --- a/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts +++ b/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts @@ -1,4 +1,7 @@ -import { getProjectConfigFiles } from '../../src/parseSettings/getProjectConfigFiles'; +import { + clearMatchCacheForTests, + getProjectConfigFiles, +} from '../../src/parseSettings/getProjectConfigFiles'; const mockExistsSync = jest.fn(); @@ -12,6 +15,11 @@ const parseSettings = { tsconfigRootDir: './repos/repo', }; +beforeEach(() => { + clearMatchCacheForTests(); + jest.clearAllMocks(); +}); + describe('getProjectConfigFiles', () => { it('returns the project when given as a string', () => { const project = './tsconfig.eslint.json'; @@ -38,10 +46,6 @@ describe('getProjectConfigFiles', () => { }); describe('when caching hits', () => { - beforeAll(() => { - Date.now = (): number => 0; - }); - it('returns a local tsconfig.json without calling existsSync a second time', () => { mockExistsSync.mockReturnValue(true); @@ -51,15 +55,35 @@ describe('getProjectConfigFiles', () => { expect(actual).toEqual(['repos/repo/packages/package/tsconfig.json']); expect(mockExistsSync).toHaveBeenCalledTimes(1); }); - }); - describe('when caching misses', () => { - beforeAll(() => { - // Tricks Date.now-based caching into always calling to fs.existsSync - let lastDateNow = 0; - Date.now = (): number => (lastDateNow += 1000); + it('returns a parent tsconfig.json when it was previously cached by a different directory search', () => { + mockExistsSync.mockImplementation(input => input === 'a/tsconfig.json'); + + // This should call to fs.existsSync three times: c, b, a + getProjectConfigFiles( + { + filePath: './a/b/c/d.ts', + tsconfigRootDir: './a', + }, + true, + ); + + // This should call to fs.existsSync once: e + // Then it should retrieve c from cache, pointing to a + const actual = getProjectConfigFiles( + { + filePath: './a/b/c/e/f.ts', + tsconfigRootDir: './a', + }, + true, + ); + + expect(actual).toEqual(['a/tsconfig.json']); + expect(mockExistsSync).toHaveBeenCalledTimes(4); }); + }); + describe('when caching misses', () => { it('returns a local tsconfig.json when matched', () => { mockExistsSync.mockReturnValue(true); From 264a2793d54fe43b9de54f762821939d357d5477 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 8 Dec 2022 12:26:52 -0500 Subject: [PATCH 11/18] Added another test, just to be sure --- .../tests/lib/getProjectConfigFiles.test.ts | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts b/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts index 563307428909..dea5e12ca820 100644 --- a/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts +++ b/packages/typescript-estree/tests/lib/getProjectConfigFiles.test.ts @@ -56,7 +56,7 @@ describe('getProjectConfigFiles', () => { expect(mockExistsSync).toHaveBeenCalledTimes(1); }); - it('returns a parent tsconfig.json when it was previously cached by a different directory search', () => { + it('returns a nearby parent tsconfig.json when it was previously cached by a different directory search', () => { mockExistsSync.mockImplementation(input => input === 'a/tsconfig.json'); // This should call to fs.existsSync three times: c, b, a @@ -81,6 +81,32 @@ describe('getProjectConfigFiles', () => { expect(actual).toEqual(['a/tsconfig.json']); expect(mockExistsSync).toHaveBeenCalledTimes(4); }); + + it('returns a distant parent tsconfig.json when it was previously cached by a different directory search', () => { + mockExistsSync.mockImplementation(input => input === 'a/tsconfig.json'); + + // This should call to fs.existsSync 4 times: d, c, b, a + getProjectConfigFiles( + { + filePath: './a/b/c/d/e.ts', + tsconfigRootDir: './a', + }, + true, + ); + + // This should call to fs.existsSync 2: g, f + // Then it should retrieve b from cache, pointing to a + const actual = getProjectConfigFiles( + { + filePath: './a/b/f/g/h.ts', + tsconfigRootDir: './a', + }, + true, + ); + + expect(actual).toEqual(['a/tsconfig.json']); + expect(mockExistsSync).toHaveBeenCalledTimes(6); + }); }); describe('when caching misses', () => { From 3c4850bbe191920df2d38cb51f1c194c4cbc5437 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 9 Dec 2022 20:06:44 -0500 Subject: [PATCH 12/18] lint fix --- .../src/parseSettings/getProjectConfigFiles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts index 61c56e9a3230..853de7763b15 100644 --- a/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts +++ b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts @@ -11,7 +11,7 @@ const tsconfigMatchCache = new Map(); /** * @remarks Only use this for tests! */ -export function clearMatchCacheForTests() { +export function clearMatchCacheForTests(): void { tsconfigMatchCache.clear(); } From 16e52d53157a910afcafbd0ea56600ce7d70fd7a Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 13 Dec 2022 09:45:45 -0500 Subject: [PATCH 13/18] Add back tsconfigRootDir --- docs/linting/Typed_Linting.md | 3 +++ docs/linting/typed-linting/Monorepos.md | 2 ++ 2 files changed, 5 insertions(+) diff --git a/docs/linting/Typed_Linting.md b/docs/linting/Typed_Linting.md index d9563a5ea82d..75a6b921a1e1 100644 --- a/docs/linting/Typed_Linting.md +++ b/docs/linting/Typed_Linting.md @@ -12,6 +12,7 @@ module.exports = { parser: '@typescript-eslint/parser', // Added lines start parserOptions: { + tsconfigRootDir: __dirname, project: true, }, // Added lines end @@ -27,6 +28,8 @@ module.exports = { In more detail: +- `parserOptions.tsconfigRootDir` tells our parser the absolute path of your project's root directory. + - This is helpful if ESLint is invoked from a different directory, such as a subdirectory within the project. - `parserOptions.project` tells our parser to use the closest `tsconfig.json` to each file for informing that file's type information. - If your project is a multi-package monorepo, see [our docs on configuring a monorepo](./typed-linting/Monorepos.md). - `plugin:@typescript-eslint/recommended-requiring-type-checking` is another recommended configuration we provide. This one contains rules that specifically require type information. diff --git a/docs/linting/typed-linting/Monorepos.md b/docs/linting/typed-linting/Monorepos.md index 7decad079d4e..f878c908c39a 100644 --- a/docs/linting/typed-linting/Monorepos.md +++ b/docs/linting/typed-linting/Monorepos.md @@ -51,6 +51,7 @@ module.exports = { ], parser: '@typescript-eslint/parser', parserOptions: { + tsconfigRootDir: __dirname, // Remove this line project: true, // Add this line @@ -75,6 +76,7 @@ module.exports = { ], parser: '@typescript-eslint/parser', parserOptions: { + tsconfigRootDir: __dirname, // Remove this line project: ['./tsconfig.eslint.json', './**/tsconfig.json'], // Add this line From 2be55de1c8bf67b3d2c23bdd98ea3b2800d6539c Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 23 Jan 2023 08:25:45 -0500 Subject: [PATCH 14/18] Apply suggestions from code review Co-authored-by: Brad Zacher --- docs/architecture/Parser.mdx | 4 ++-- docs/linting/Typed_Linting.md | 4 ++-- .../src/parseSettings/getProjectConfigFiles.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/architecture/Parser.mdx b/docs/architecture/Parser.mdx index 45d67306e959..212b7dccde62 100644 --- a/docs/architecture/Parser.mdx +++ b/docs/architecture/Parser.mdx @@ -42,7 +42,7 @@ interface ParserOptions { lib?: string[]; moduleResolver?: string; program?: import('typescript').Program; - project?: string | string[] | true; + project?: string[] | true; projectFolderIgnoreList?: string[]; tsconfigRootDir?: string; warnOnUnsupportedTypeScriptVersion?: boolean; @@ -192,7 +192,7 @@ This option allows you to provide a path to your project's `tsconfig.json`. **Th - If `true`, each source file's parse will find the nearest `tsconfig.json` file to that source file. - - This is done by checking that source file's directory for a `tsconfig.json`, then the parent's directory for a `tsconfig.json`, and so on - until one is found or the current working directory is passed. + - This is done by checking that source file's directory tree for the nearest `tsconfig.json`. - If you use project references, TypeScript will not automatically use project references to resolve files. This means that you will have to add each referenced tsconfig to the `project` field either separately, or via a glob. diff --git a/docs/linting/Typed_Linting.md b/docs/linting/Typed_Linting.md index 75a6b921a1e1..4af1249cb867 100644 --- a/docs/linting/Typed_Linting.md +++ b/docs/linting/Typed_Linting.md @@ -42,7 +42,7 @@ You may see new rules reporting errors based on type information! The `parserOptions.project` option can be turned on 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 the +- `string[]`: any number of glob paths to match TSConfig files relative to the For example, if you use a specific `tsconfig.eslint.json` for linting, you'd specify: @@ -50,7 +50,7 @@ For example, if you use a specific `tsconfig.eslint.json` for linting, you'd spe module.exports = { // ... parserOptions: { - project: './tsconfig.eslint.json', + project: ['./tsconfig.eslint.json'], }, // ... }; diff --git a/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts index 853de7763b15..65ae5d6c61a7 100644 --- a/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts +++ b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts @@ -6,7 +6,7 @@ import type { ParseSettings } from '.'; const log = debug('typescript-eslint:typescript-estree:getProjectConfigFiles'); -const tsconfigMatchCache = new Map(); +const TSCONFIG_MATCH_CACHE = new Map(); /** * @remarks Only use this for tests! @@ -29,7 +29,7 @@ export function getProjectConfigFiles( project: string | string[] | true | undefined, ): string | string[] | undefined { if (project !== true) { - return project; + return Array.isArray(project) ? project : [project]; } log('Looking for tsconfig.json at or above file: %s', parseSettings.filePath); From fb6465c911edecd25bee83d817e1a90fb0f83427 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 24 Jan 2023 19:12:15 -0500 Subject: [PATCH 15/18] Back to string | --- docs/architecture/Parser.mdx | 2 +- docs/linting/Typed_Linting.md | 4 ++-- .../src/parseSettings/getProjectConfigFiles.ts | 12 +++++++----- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/architecture/Parser.mdx b/docs/architecture/Parser.mdx index 212b7dccde62..4c65272161e2 100644 --- a/docs/architecture/Parser.mdx +++ b/docs/architecture/Parser.mdx @@ -42,7 +42,7 @@ interface ParserOptions { lib?: string[]; moduleResolver?: string; program?: import('typescript').Program; - project?: string[] | true; + project?: string | string[] | true; projectFolderIgnoreList?: string[]; tsconfigRootDir?: string; warnOnUnsupportedTypeScriptVersion?: boolean; diff --git a/docs/linting/Typed_Linting.md b/docs/linting/Typed_Linting.md index 4af1249cb867..75a6b921a1e1 100644 --- a/docs/linting/Typed_Linting.md +++ b/docs/linting/Typed_Linting.md @@ -42,7 +42,7 @@ You may see new rules reporting errors based on type information! The `parserOptions.project` option can be turned on with either: - `true`: to always use `tsconfig.json`s nearest to source files -- `string[]`: any number of glob paths to match TSConfig files relative to the +- `string | string[]`: any number of glob paths to match TSConfig files relative to the For example, if you use a specific `tsconfig.eslint.json` for linting, you'd specify: @@ -50,7 +50,7 @@ For example, if you use a specific `tsconfig.eslint.json` for linting, you'd spe module.exports = { // ... parserOptions: { - project: ['./tsconfig.eslint.json'], + project: './tsconfig.eslint.json', }, // ... }; diff --git a/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts index 65ae5d6c61a7..404b6f20cffe 100644 --- a/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts +++ b/packages/typescript-estree/src/parseSettings/getProjectConfigFiles.ts @@ -12,7 +12,7 @@ const TSCONFIG_MATCH_CACHE = new Map(); * @remarks Only use this for tests! */ export function clearMatchCacheForTests(): void { - tsconfigMatchCache.clear(); + TSCONFIG_MATCH_CACHE.clear(); } /** @@ -27,9 +27,11 @@ export function clearMatchCacheForTests(): void { export function getProjectConfigFiles( parseSettings: Pick, project: string | string[] | true | undefined, -): string | string[] | undefined { +): string[] | undefined { if (project !== true) { - return Array.isArray(project) ? project : [project]; + return project === undefined || Array.isArray(project) + ? project + : [project]; } log('Looking for tsconfig.json at or above file: %s', parseSettings.filePath); @@ -40,12 +42,12 @@ export function getProjectConfigFiles( log('Checking tsconfig.json path: %s', directory); const tsconfigPath = path.join(directory, 'tsconfig.json'); const cached = - tsconfigMatchCache.get(directory) ?? + TSCONFIG_MATCH_CACHE.get(directory) ?? (fs.existsSync(tsconfigPath) && tsconfigPath); if (cached) { for (const directory of checkedDirectories) { - tsconfigMatchCache.set(directory, cached); + TSCONFIG_MATCH_CACHE.set(directory, cached); } return [cached]; } From edc11e561c61de2b9c7c19238606735c621cf838 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 4 Feb 2023 12:33:02 -0500 Subject: [PATCH 16/18] Fix website build --- docs/linting/Typed_Linting.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/linting/Typed_Linting.mdx b/docs/linting/Typed_Linting.mdx index ca164ae69b04..a51afdad02b5 100644 --- a/docs/linting/Typed_Linting.mdx +++ b/docs/linting/Typed_Linting.mdx @@ -58,7 +58,7 @@ module.exports = { See [the `@typescript-eslint/parser` docs for more details](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/README.md#parseroptionsproject). :::note -If your project is a multi-package monorepo, see [our docs on configuring a monorepo](./typed-linting/Monorepos.md). +If your project is a multi-package monorepo, see [our docs on configuring a monorepo](./typed-linting/Monorepos.mdx). ::: ## FAQs From 315bcde18566b6490771743fbf2f4142352d6565 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 4 Feb 2023 16:32:31 -0500 Subject: [PATCH 17/18] Fix the build --- .../typescript-estree/src/parseSettings/ExpiringCache.ts | 7 ++++++- packages/typescript-estree/src/parseSettings/index.ts | 4 ++-- packages/website/src/components/linter/config.ts | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/typescript-estree/src/parseSettings/ExpiringCache.ts b/packages/typescript-estree/src/parseSettings/ExpiringCache.ts index 7a1125355629..e28506d1d9bd 100644 --- a/packages/typescript-estree/src/parseSettings/ExpiringCache.ts +++ b/packages/typescript-estree/src/parseSettings/ExpiringCache.ts @@ -3,10 +3,15 @@ import type { CacheDurationSeconds } from '@typescript-eslint/types'; export const DEFAULT_TSCONFIG_CACHE_DURATION_SECONDS = 30; const ZERO_HR_TIME: [number, number] = [0, 0]; +export interface CacheLike { + get(key: Key): Value | void; + set(key: Key, value: Value): this; +} + /** * A map with key-level expiration. */ -export class ExpiringCache { +export class ExpiringCache implements CacheLike { readonly #cacheDurationSeconds: CacheDurationSeconds; readonly #map = new Map< diff --git a/packages/typescript-estree/src/parseSettings/index.ts b/packages/typescript-estree/src/parseSettings/index.ts index 34c38e0e3874..11df4c78489e 100644 --- a/packages/typescript-estree/src/parseSettings/index.ts +++ b/packages/typescript-estree/src/parseSettings/index.ts @@ -2,7 +2,7 @@ import type * as ts from 'typescript'; import type { CanonicalPath } from '../create-program/shared'; import type { TSESTree } from '../ts-estree'; -import type { ExpiringCache } from './ExpiringCache'; +import type { CacheLike } from './ExpiringCache'; type DebugModule = 'typescript-eslint' | 'eslint' | 'typescript'; @@ -119,7 +119,7 @@ export interface MutableParseSettings { /** * Caches searches for TSConfigs from project directories. */ - tsconfigMatchCache: ExpiringCache; + tsconfigMatchCache: CacheLike; /** * The absolute path to the root directory for all provided `project`s. diff --git a/packages/website/src/components/linter/config.ts b/packages/website/src/components/linter/config.ts index f077f3786ee1..5fa05a4a11b7 100644 --- a/packages/website/src/components/linter/config.ts +++ b/packages/website/src/components/linter/config.ts @@ -18,6 +18,7 @@ export const parseSettings: ParseSettings = { range: true, tokens: [], tsconfigRootDir: '/', + tsconfigMatchCache: new Map(), errorOnTypeScriptSyntacticAndSemanticIssues: false, EXPERIMENTAL_useSourceOfProjectReferenceRedirect: false, singleRun: false, From 040a56a4dc6178ea2f8f15b0931ca993fb5b638b Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 9 Feb 2023 16:10:58 -0500 Subject: [PATCH 18/18] A global tsconfigMatchCache, with a test --- .../src/parseSettings/createParseSettings.ts | 6 ++++-- .../tests/lib/createParseSettings.test.ts | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 packages/typescript-estree/tests/lib/createParseSettings.test.ts diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index 38e4e8ee3916..028088765a28 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -16,6 +16,8 @@ const log = debug( 'typescript-eslint:typescript-estree:parser:parseSettings:createParseSettings', ); +let TSCONFIG_MATCH_CACHE: ExpiringCache | null; + export function createParseSettings( code: string, options: Partial = {}, @@ -66,12 +68,12 @@ export function createParseSettings( range: options.range === true, singleRun, tokens: options.tokens === true ? [] : null, - tsconfigMatchCache: new ExpiringCache( + tsconfigMatchCache: (TSCONFIG_MATCH_CACHE ??= new ExpiringCache( singleRun ? 'Infinity' : options.cacheLifetime?.glob ?? DEFAULT_TSCONFIG_CACHE_DURATION_SECONDS, - ), + )), tsconfigRootDir, }; diff --git a/packages/typescript-estree/tests/lib/createParseSettings.test.ts b/packages/typescript-estree/tests/lib/createParseSettings.test.ts new file mode 100644 index 000000000000..043050cdd2bc --- /dev/null +++ b/packages/typescript-estree/tests/lib/createParseSettings.test.ts @@ -0,0 +1,14 @@ +import { createParseSettings } from '../../src/parseSettings/createParseSettings'; + +describe('createParseSettings', () => { + describe('tsconfigMatchCache', () => { + it('reuses the TSConfig match cache when called a subsequent time', () => { + const parseSettings1 = createParseSettings('input.ts'); + const parseSettings2 = createParseSettings('input.ts'); + + expect(parseSettings1.tsconfigMatchCache).toBe( + parseSettings2.tsconfigMatchCache, + ); + }); + }); +}); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy