From 90a587845088da1b205e4d7d77dbc3f9447b1c5a Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Thu, 15 Oct 2020 11:00:57 -0700 Subject: [PATCH] feat(typescript-estree): add flag EXPERIMENTAL_useSourceOfProjectReferenceRedirect (#2669) --- .eslintrc.js | 1 + packages/eslint-plugin-internal/tsconfig.json | 3 +- .../eslint-plugin-tslint/tsconfig.build.json | 6 +- packages/eslint-plugin-tslint/tsconfig.json | 3 +- packages/eslint-plugin/tsconfig.build.json | 3 +- packages/eslint-plugin/tsconfig.json | 7 ++- packages/experimental-utils/tsconfig.json | 7 ++- packages/parser/tsconfig.build.json | 6 +- packages/parser/tsconfig.json | 9 ++- packages/scope-manager/tsconfig.json | 7 ++- packages/types/src/parser-options.ts | 1 + packages/typescript-estree/README.md | 12 ++++ .../src/create-program/createWatchProgram.ts | 62 ++++++++++++++----- .../typescript-estree/src/parser-options.ts | 13 ++++ packages/typescript-estree/src/parser.ts | 7 ++- .../typescript-estree/tsconfig.build.json | 7 ++- 16 files changed, 121 insertions(+), 33 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 5fbcecbe066..c789421b024 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,6 +26,7 @@ module.exports = { ], tsconfigRootDir: __dirname, warnOnUnsupportedTypeScriptVersion: false, + EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true, }, rules: { // diff --git a/packages/eslint-plugin-internal/tsconfig.json b/packages/eslint-plugin-internal/tsconfig.json index 6fddcebe2ae..597a5eaee95 100644 --- a/packages/eslint-plugin-internal/tsconfig.json +++ b/packages/eslint-plugin-internal/tsconfig.json @@ -4,5 +4,6 @@ "composite": false, "rootDir": "." }, - "include": ["src", "typings", "tests"] + "include": ["src", "typings", "tests"], + "references": [{ "path": "../experimental-utils/tsconfig.build.json" }] } diff --git a/packages/eslint-plugin-tslint/tsconfig.build.json b/packages/eslint-plugin-tslint/tsconfig.build.json index d6987c27afe..60cfdc79ee7 100644 --- a/packages/eslint-plugin-tslint/tsconfig.build.json +++ b/packages/eslint-plugin-tslint/tsconfig.build.json @@ -6,9 +6,5 @@ "resolveJsonModule": true }, "include": ["src"], - "references": [ - { "path": "../experimental-utils/tsconfig.build.json" }, - { "path": "../parser/tsconfig.build.json" }, - { "path": "../typescript-estree/tsconfig.build.json" } - ] + "references": [{ "path": "../experimental-utils/tsconfig.build.json" }] } diff --git a/packages/eslint-plugin-tslint/tsconfig.json b/packages/eslint-plugin-tslint/tsconfig.json index 65fc5ce6584..d4bc6ebe57d 100644 --- a/packages/eslint-plugin-tslint/tsconfig.json +++ b/packages/eslint-plugin-tslint/tsconfig.json @@ -5,5 +5,6 @@ "rootDir": "." }, "include": ["src", "tests"], - "exclude": ["tests/test-project", "tests/test-tslint-rules-directory"] + "exclude": ["tests/test-project", "tests/test-tslint-rules-directory"], + "references": [{ "path": "../experimental-utils/tsconfig.build.json" }] } diff --git a/packages/eslint-plugin/tsconfig.build.json b/packages/eslint-plugin/tsconfig.build.json index 52fd3f6be6d..bc597c33129 100644 --- a/packages/eslint-plugin/tsconfig.build.json +++ b/packages/eslint-plugin/tsconfig.build.json @@ -12,7 +12,6 @@ "references": [ { "path": "../experimental-utils/tsconfig.build.json" }, { "path": "../parser/tsconfig.build.json" }, - { "path": "../scope-manager/tsconfig.build.json" }, - { "path": "../typescript-estree/tsconfig.build.json" } + { "path": "../scope-manager/tsconfig.build.json" } ] } diff --git a/packages/eslint-plugin/tsconfig.json b/packages/eslint-plugin/tsconfig.json index 9cea515ba6b..a0cdad04869 100644 --- a/packages/eslint-plugin/tsconfig.json +++ b/packages/eslint-plugin/tsconfig.json @@ -4,5 +4,10 @@ "composite": false, "rootDir": "." }, - "include": ["src", "typings", "tests", "tools"] + "include": ["src", "typings", "tests", "tools"], + "references": [ + { "path": "../experimental-utils/tsconfig.build.json" }, + { "path": "../parser/tsconfig.build.json" }, + { "path": "../scope-manager/tsconfig.build.json" } + ] } diff --git a/packages/experimental-utils/tsconfig.json b/packages/experimental-utils/tsconfig.json index 9cea515ba6b..20ea2496c6b 100644 --- a/packages/experimental-utils/tsconfig.json +++ b/packages/experimental-utils/tsconfig.json @@ -4,5 +4,10 @@ "composite": false, "rootDir": "." }, - "include": ["src", "typings", "tests", "tools"] + "include": ["src", "typings", "tests", "tools"], + "references": [ + { "path": "../scope-manager/tsconfig.build.json" }, + { "path": "../types/tsconfig.build.json" }, + { "path": "../typescript-estree/tsconfig.build.json" } + ] } diff --git a/packages/parser/tsconfig.build.json b/packages/parser/tsconfig.build.json index 065e6086743..1d11694fd08 100644 --- a/packages/parser/tsconfig.build.json +++ b/packages/parser/tsconfig.build.json @@ -9,9 +9,9 @@ "include": ["src"], "references": [ { "path": "../experimental-utils/tsconfig.build.json" }, - { "path": "../types/tsconfig.build.json" }, - { "path": "../typescript-estree/tsconfig.build.json" }, { "path": "../scope-manager/tsconfig.build.json" }, - { "path": "../shared-fixtures/tsconfig.build.json" } + { "path": "../shared-fixtures/tsconfig.build.json" }, + { "path": "../types/tsconfig.build.json" }, + { "path": "../typescript-estree/tsconfig.build.json" } ] } diff --git a/packages/parser/tsconfig.json b/packages/parser/tsconfig.json index a987c8b550d..6f9d2082637 100644 --- a/packages/parser/tsconfig.json +++ b/packages/parser/tsconfig.json @@ -5,5 +5,12 @@ "rootDir": "." }, "include": ["src", "tests", "tools"], - "exclude": ["tests/fixtures"] + "exclude": ["tests/fixtures"], + "references": [ + { "path": "../experimental-utils/tsconfig.build.json" }, + { "path": "../scope-manager/tsconfig.build.json" }, + { "path": "../shared-fixtures/tsconfig.build.json" }, + { "path": "../types/tsconfig.build.json" }, + { "path": "../typescript-estree/tsconfig.build.json" } + ] } diff --git a/packages/scope-manager/tsconfig.json b/packages/scope-manager/tsconfig.json index 9cea515ba6b..649c1abf462 100644 --- a/packages/scope-manager/tsconfig.json +++ b/packages/scope-manager/tsconfig.json @@ -4,5 +4,10 @@ "composite": false, "rootDir": "." }, - "include": ["src", "typings", "tests", "tools"] + "include": ["src", "typings", "tests", "tools"], + "references": [ + { "path": "../types/tsconfig.build.json" }, + { "path": "../typescript-estree/tsconfig.build.json" }, + { "path": "../visitor-keys/tsconfig.build.json" } + ] } diff --git a/packages/types/src/parser-options.ts b/packages/types/src/parser-options.ts index 898f6583ef6..8f6632df9a4 100644 --- a/packages/types/src/parser-options.ts +++ b/packages/types/src/parser-options.ts @@ -37,6 +37,7 @@ interface ParserOptions { debugLevel?: DebugLevel; errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; errorOnUnknownASTType?: boolean; + EXPERIMENTAL_useSourceOfProjectReferenceRedirect?: boolean; // purposely undocumented for now extraFileExtensions?: string[]; filePath?: string; loc?: boolean; diff --git a/packages/typescript-estree/README.md b/packages/typescript-estree/README.md index 4fdbb42dc10..8a59159647d 100644 --- a/packages/typescript-estree/README.md +++ b/packages/typescript-estree/README.md @@ -152,6 +152,18 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { */ errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; + /** + * ***EXPERIMENTAL FLAG*** - Use this at your own risk. + * + * Causes TS to use the source files for referenced projects instead of the compiled .d.ts files. + * This feature is not yet optimized, and is likely to cause OOMs for medium to large projects. + * + * This flag REQUIRES at least TS v3.9, otherwise it does nothing. + * + * See: https://github.com/typescript-eslint/typescript-eslint/issues/2094 + */ + EXPERIMENTAL_useSourceOfProjectReferenceRedirect?: boolean; + /** * When `project` is provided, this controls the non-standard file extensions which will be parsed. * It accepts an array of file extensions, each preceded by a `.`. diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index bf703b38d93..da9ff8384b7 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -117,6 +117,20 @@ function createHash(content: string): string { return content; } +function updateCachedFileList( + tsconfigPath: CanonicalPath, + program: ts.Program, + extra: Extra, +): Set { + const fileList = extra.EXPERIMENTAL_useSourceOfProjectReferenceRedirect + ? new Set( + program.getSourceFiles().map(sf => getCanonicalFileName(sf.fileName)), + ) + : new Set(program.getRootFileNames().map(f => getCanonicalFileName(f))); + programFileListCache.set(tsconfigPath, fileList); + return fileList; +} + /** * Calculate project environments using options provided by consumer and paths from config * @param code The code being linted @@ -154,21 +168,12 @@ function getProgramsForProjects( * before we go into the process of attempting to find and update every program * see if we know of a program that contains this file */ - for (const rawTsconfigPath of extra.projects) { - const tsconfigPath = getTsconfigPath(rawTsconfigPath, extra); - const existingWatch = knownWatchProgramMap.get(tsconfigPath); - if (!existingWatch) { - continue; - } - + for (const [tsconfigPath, existingWatch] of knownWatchProgramMap.entries()) { let fileList = programFileListCache.get(tsconfigPath); let updatedProgram: ts.Program | null = null; if (!fileList) { updatedProgram = existingWatch.getProgram().getProgram(); - fileList = new Set( - updatedProgram.getRootFileNames().map(f => getCanonicalFileName(f)), - ); - programFileListCache.set(tsconfigPath, fileList); + fileList = updateCachedFileList(tsconfigPath, updatedProgram, extra); } if (fileList.has(filePath)) { @@ -209,18 +214,38 @@ function getProgramsForProjects( // sets parent pointers in source files updatedProgram.getTypeChecker(); - results.push(updatedProgram); + // cache and check the file list + const fileList = updateCachedFileList( + tsconfigPath, + updatedProgram, + extra, + ); + if (fileList.has(filePath)) { + log('Found updated program for file. %s', filePath); + // we can return early because we know this program contains the file + return [updatedProgram]; + } + + results.push(updatedProgram); continue; } const programWatch = createWatchProgram(tsconfigPath, extra); - const program = programWatch.getProgram().getProgram(); - - // cache watch program and return current program knownWatchProgramMap.set(tsconfigPath, programWatch); + + const program = programWatch.getProgram().getProgram(); // sets parent pointers in source files program.getTypeChecker(); + + // cache and check the file list + const fileList = updateCachedFileList(tsconfigPath, program, extra); + if (fileList.has(filePath)) { + log('Found program for file. %s', filePath); + // we can return early because we know this program contains the file + return [program]; + } + results.push(program); } @@ -324,6 +349,13 @@ function createWatchProgram( ); watchCompilerHost.trace = log; + /** + * TODO: this needs refinement and development, but we're allowing users to opt-in to this for now for testing and feedback. + * See https://github.com/typescript-eslint/typescript-eslint/issues/2094 + */ + watchCompilerHost.useSourceOfProjectReferenceRedirect = (): boolean => + extra.EXPERIMENTAL_useSourceOfProjectReferenceRedirect; + // Since we don't want to asynchronously update program we want to disable timeout methods // So any changes in the program will be delayed and updated when getProgram is called on watch let callback: (() => void) | undefined; diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index 70db19645a2..33195b0c8d6 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -12,6 +12,7 @@ export interface Extra { debugLevel: Set; errorOnTypeScriptSyntacticAndSemanticIssues: boolean; errorOnUnknownASTType: boolean; + EXPERIMENTAL_useSourceOfProjectReferenceRedirect: boolean; extraFileExtensions: string[]; filePath: string; jsx: boolean; @@ -111,6 +112,18 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { */ errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; + /** + * ***EXPERIMENTAL FLAG*** - Use this at your own risk. + * + * Causes TS to use the source files for referenced projects instead of the compiled .d.ts files. + * This feature is not yet optimized, and is likely to cause OOMs for medium to large projects. + * + * This flag REQUIRES at least TS v3.9, otherwise it does nothing. + * + * See: https://github.com/typescript-eslint/typescript-eslint/issues/2094 + */ + EXPERIMENTAL_useSourceOfProjectReferenceRedirect?: boolean; + /** * When `project` is provided, this controls the non-standard file extensions which will be parsed. * It accepts an array of file extensions, each preceded by a `.`. diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 56dc4109e80..2b9970e48cf 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -93,6 +93,7 @@ function resetExtra(): void { debugLevel: new Set(), errorOnTypeScriptSyntacticAndSemanticIssues: false, errorOnUnknownASTType: false, + EXPERIMENTAL_useSourceOfProjectReferenceRedirect: false, extraFileExtensions: [], filePath: getFileName(), jsx: false, @@ -168,7 +169,7 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void { if ( extra.debugLevel.has('eslint') || // make sure we don't turn off the eslint debug if it was enabled via --debug - debug.enabled('eslint:*') + debug.enabled('eslint:*,-eslint:code-path') ) { // https://github.com/eslint/eslint/blob/9dfc8501fb1956c90dc11e6377b4cb38a6bea65d/bin/eslint.js#L25 namespaces.push('eslint:*,-eslint:code-path'); @@ -284,6 +285,10 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void { extra.createDefaultProgram = typeof options.createDefaultProgram === 'boolean' && options.createDefaultProgram; + + extra.EXPERIMENTAL_useSourceOfProjectReferenceRedirect = + typeof options.EXPERIMENTAL_useSourceOfProjectReferenceRedirect === + 'boolean' && options.EXPERIMENTAL_useSourceOfProjectReferenceRedirect; } function warnAboutTSVersion(): void { diff --git a/packages/typescript-estree/tsconfig.build.json b/packages/typescript-estree/tsconfig.build.json index 215a0282df2..ca949f029eb 100644 --- a/packages/typescript-estree/tsconfig.build.json +++ b/packages/typescript-estree/tsconfig.build.json @@ -6,5 +6,10 @@ "rootDir": "./src", "resolveJsonModule": true }, - "include": ["src", "typings"] + "include": ["src", "typings"], + "references": [ + { "path": "../shared-fixtures/tsconfig.build.json" }, + { "path": "../types/tsconfig.build.json" }, + { "path": "../visitor-keys/tsconfig.build.json" } + ] }