From 550465b509af635e385c91f64f2aa26ed3b6283f Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 24 Mar 2023 08:20:24 -0400 Subject: [PATCH 01/32] WIP: createProjectService --- .../create-program/createProjectProgram.ts | 4 +- .../create-program/createProjectService.ts | 70 +++++++++++++++++++ .../src/parseSettings/createParseSettings.ts | 5 ++ .../src/parseSettings/index.ts | 7 ++ packages/typescript-estree/src/parser.ts | 52 +++++++++++++- .../tests/lib/persistentParse.test.ts | 2 +- .../tests/lib/semanticInfo.test.ts | 2 +- 7 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 packages/typescript-estree/src/create-program/createProjectService.ts diff --git a/packages/typescript-estree/src/create-program/createProjectProgram.ts b/packages/typescript-estree/src/create-program/createProjectProgram.ts index 51a2ebdfdfc..5d4a61e55a5 100644 --- a/packages/typescript-estree/src/create-program/createProjectProgram.ts +++ b/packages/typescript-estree/src/create-program/createProjectProgram.ts @@ -28,10 +28,12 @@ const DEFAULT_EXTRA_FILE_EXTENSIONS = [ */ function createProjectProgram( parseSettings: ParseSettings, + programsForProjects: readonly ts.Program[], ): ASTAndDefiniteProgram | undefined { log('Creating project program for: %s', parseSettings.filePath); - const programsForProjects = getWatchProgramsForProjects(parseSettings); + // const programsForProjects = getWatchProgramsForProjects(parseSettings); + // const programsForProjects = createPrograms(parseSettings); const astAndProgram = firstDefined(programsForProjects, currentProgram => getAstFromProgram(currentProgram, parseSettings), ); diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/typescript-estree/src/create-program/createProjectService.ts new file mode 100644 index 00000000000..ab6ed7a4739 --- /dev/null +++ b/packages/typescript-estree/src/create-program/createProjectService.ts @@ -0,0 +1,70 @@ +import * as ts from 'typescript'; +import * as tsserver from 'typescript/lib/tsserverlibrary'; + +export function createProjectService() { + const compilerOptions = { /* todo */ strict: true }; + const compilerHost = tsserver.createCompilerHost(compilerOptions, true); + + // TODO: see getWatchProgramsForProjects + // We don't watch the disk, we just refer to these when ESLint calls us + // there's a whole separate update pass in maybeInvalidateProgram at the bottom of getWatchProgramsForProjects + // (this "goes nuclear on TypeScript") + const watchFile = ( + path: string, + callback: ts.FileWatcherCallback, + ): ts.FileWatcher => { + // todo (or just ... stub out?) + return { close() {} }; + }; + + const watchDirectory = ( + path: string, + callback: ts.DirectoryWatcherCallback, + ) => { + // todo (or just ... stub out?) + return { close() {} }; + }; + + const system = { + ...ts.sys, + watchFile, + watchDirectory, + setTimeout, + clearTimeout, + setImmediate, + clearImmediate, + }; + + const projectService = new tsserver.server.ProjectService({ + host: system, + + // todo: look into these + cancellationToken: { isCancellationRequested: () => false }, + useSingleInferredProject: false, // ??? + useInferredProjectPerProjectRoot: false, // ??? + typingsInstaller: { + attach: () => {}, + enqueueInstallTypingsRequest: () => {}, + installPackage: async (): Promise => { + throw new Error('pls no'); + }, + isKnownTypesPackageName: () => false, + onProjectClosed: () => {}, + globalTypingsCacheLocation: '', + }, + logger: { + close() {}, + hasLevel: () => false, + loggingEnabled: () => false, + perftrc() {}, + info() {}, + startGroup() {}, + endGroup() {}, + msg() {}, + getLogFileName: () => undefined, + }, + session: undefined, + }); + + return projectService; +} diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index 28943a02c06..f67583d28f7 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -1,5 +1,6 @@ import debug from 'debug'; import type * as ts from 'typescript'; +import { createProjectService } from '../create-program/createProjectService'; import { ensureAbsolutePath } from '../create-program/shared'; import type { TSESTreeOptions } from '../parser-options'; @@ -31,6 +32,10 @@ export function createParseSettings( ? options.tsconfigRootDir : process.cwd(); const parseSettings: MutableParseSettings = { + // todo: sometimes + projectService: createProjectService(), + EXPERIMENTAL_useProjectService: true, + allowInvalidAST: options.allowInvalidAST === true, code, codeFullText, diff --git a/packages/typescript-estree/src/parseSettings/index.ts b/packages/typescript-estree/src/parseSettings/index.ts index 9fc8986ad48..9cfd1e02c51 100644 --- a/packages/typescript-estree/src/parseSettings/index.ts +++ b/packages/typescript-estree/src/parseSettings/index.ts @@ -1,4 +1,5 @@ import type * as ts from 'typescript'; +import type * as tsserverlibrary from 'typescript/lib/tsserverlibrary'; import type { CanonicalPath } from '../create-program/shared'; import type { TSESTree } from '../ts-estree'; @@ -10,6 +11,9 @@ type DebugModule = 'typescript-eslint' | 'eslint' | 'typescript'; * Internal settings used by the parser to run on a file. */ export interface MutableParseSettings { + // todo + projectService: tsserverlibrary.server.ProjectService | undefined; + /** * Prevents the parser from throwing an error if it receives an invalid AST from TypeScript. */ @@ -57,6 +61,9 @@ export interface MutableParseSettings { */ errorOnUnknownASTType: boolean; + // todo + EXPERIMENTAL_useProjectService: boolean; + /** * Whether TS should use the source files for referenced projects instead of the compiled .d.ts files. * diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 4a3e55e038d..205900b5620 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -6,10 +6,12 @@ import { convertError } from './convert'; import { createDefaultProgram } from './create-program/createDefaultProgram'; import { createIsolatedProgram } from './create-program/createIsolatedProgram'; import { createProjectProgram } from './create-program/createProjectProgram'; +import { createProjectService } from './create-program/createProjectService'; import { createNoProgram, createSourceFile, } from './create-program/createSourceFile'; +import { getWatchProgramsForProjects } from './create-program/getWatchProgramsForProjects'; import type { ASTAndProgram, CanonicalPath } from './create-program/shared'; import { createProgramFromConfigFile, @@ -57,8 +59,43 @@ function getProgramAndAST( } } + // todo; wrap in function or some such + if (parseSettings.projectService) { + parseSettings.projectService.openClientFile( + parseSettings.filePath, + parseSettings.codeFullText, + ); + + // todo: maybe we'll slap this into the parseSEttings as a property? + // const projectService = createProjectServiceProgram(/* parseSettings */); + const program = parseSettings.projectService + .getScriptInfo(parseSettings.filePath)! + .getDefaultProject() + .getLanguageService(/*ensureSynchronized*/ true) + .getProgram(); + + // TODO "Eventually, close the file" + // what does this actually do? + // + + if (program) { + const projectServiceProgram = createProjectProgram(parseSettings, [ + // LOL TYPES + // This'll hoepfully be resolved once they fully move to modules + // right now it's two separate files that get dumped out + program as ts.Program, + ]); + if (projectServiceProgram) { + return projectServiceProgram; + } + } + } + if (hasFullTypeInformation) { - const fromProjectProgram = createProjectProgram(parseSettings); + const fromProjectProgram = createProjectProgram( + parseSettings, + getWatchProgramsForProjects(parseSettings), + ); if (fromProjectProgram) { return fromProjectProgram; } @@ -284,3 +321,16 @@ export { clearProgramCache, clearParseAndGenerateServicesCalls, }; + +// next steps +// 0. clean up comments +// 1. ping jake with questions +// * what does closing a file do +// 2. create versions of test suite tailored to this new fancy form +// 3. testing the bujeezus out of it + +// aside: +// * performance testing - need to work on that +// (see TS folks - they do both memory usage and runtime) +// memory usage: makes bigger people happy (OOMs) +// runtime: more people can use it diff --git a/packages/typescript-estree/tests/lib/persistentParse.test.ts b/packages/typescript-estree/tests/lib/persistentParse.test.ts index 63e81d7e260..b28ffaf267a 100644 --- a/packages/typescript-estree/tests/lib/persistentParse.test.ts +++ b/packages/typescript-estree/tests/lib/persistentParse.test.ts @@ -224,7 +224,7 @@ function baseTests( } describe('persistent parse', () => { - describe('includes not ending in a slash', () => { + describe.only('includes not ending in a slash', () => { const tsConfigExcludeBar = { include: ['src'], exclude: ['./src/bar.ts'], diff --git a/packages/typescript-estree/tests/lib/semanticInfo.test.ts b/packages/typescript-estree/tests/lib/semanticInfo.test.ts index 6c50712e0ea..2eaec03f594 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.test.ts @@ -179,7 +179,7 @@ describe('semanticInfo', () => { expect(tsComputedPropertyString.kind).toEqual(ts.SyntaxKind.StringLiteral); }); - it('imported-file tests', () => { + it.only('imported-file tests', () => { const fileName = path.resolve(FIXTURES_DIR, 'import-file.src.ts'); const parseResult = parseCodeAndGenerateServices( fs.readFileSync(fileName, 'utf8'), From 05f4d99f4e1a50dc13945e1d353c8e3d8d84823e Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 16 Apr 2023 16:52:49 -0400 Subject: [PATCH 02/32] Collected updates: reuse program; lean more into tsserver --- .../create-program/createProjectService.ts | 11 ++- .../src/parseSettings/createParseSettings.ts | 5 +- .../lib/__snapshots__/parse.test.ts.snap | 68 ++++++++++++++----- 3 files changed, 59 insertions(+), 25 deletions(-) diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/typescript-estree/src/create-program/createProjectService.ts index ab6ed7a4739..58dfea28076 100644 --- a/packages/typescript-estree/src/create-program/createProjectService.ts +++ b/packages/typescript-estree/src/create-program/createProjectService.ts @@ -1,4 +1,3 @@ -import * as ts from 'typescript'; import * as tsserver from 'typescript/lib/tsserverlibrary'; export function createProjectService() { @@ -11,22 +10,22 @@ export function createProjectService() { // (this "goes nuclear on TypeScript") const watchFile = ( path: string, - callback: ts.FileWatcherCallback, - ): ts.FileWatcher => { + callback: tsserver.FileWatcherCallback, + ): tsserver.FileWatcher => { // todo (or just ... stub out?) return { close() {} }; }; const watchDirectory = ( path: string, - callback: ts.DirectoryWatcherCallback, + callback: tsserver.DirectoryWatcherCallback, ) => { // todo (or just ... stub out?) return { close() {} }; }; const system = { - ...ts.sys, + ...tsserver.sys, watchFile, watchDirectory, setTimeout, @@ -37,8 +36,6 @@ export function createProjectService() { const projectService = new tsserver.server.ProjectService({ host: system, - - // todo: look into these cancellationToken: { isCancellationRequested: () => false }, useSingleInferredProject: false, // ??? useInferredProjectPerProjectRoot: false, // ??? diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index f67583d28f7..fb7ac77cb8d 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -21,6 +21,9 @@ const log = debug( let TSCONFIG_MATCH_CACHE: ExpiringCache | null; +let TSSERVER_PROJECT_SERVICE: ReturnType | null = + null; + export function createParseSettings( code: string | ts.SourceFile, options: Partial = {}, @@ -33,7 +36,7 @@ export function createParseSettings( : process.cwd(); const parseSettings: MutableParseSettings = { // todo: sometimes - projectService: createProjectService(), + projectService: (TSSERVER_PROJECT_SERVICE ??= createProjectService()), EXPERIMENTAL_useProjectService: true, allowInvalidAST: options.allowInvalidAST === true, diff --git a/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap index 8c2e7861435..c51f8e2606a 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap @@ -370,7 +370,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -664,7 +666,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -848,7 +852,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - witho }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -1032,7 +1038,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - witho }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -1252,7 +1260,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .json file - wit }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -1546,7 +1556,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -1840,7 +1852,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -2024,7 +2038,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -2208,7 +2224,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -2392,7 +2410,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .ts file - witho }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -2576,7 +2596,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .ts file - witho }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -2870,7 +2892,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -3164,7 +3188,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -3348,7 +3374,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -3532,7 +3560,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -4010,7 +4040,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .vue file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -4194,7 +4226,9 @@ exports[`parseAndGenerateServices isolated parsing should parse .vue file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "program": "No Program", + "getSymbolAtLocation": [Function], + "getTypeAtLocation": [Function], + "program": "With Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } From 1fed323ffe71260b843fa0257088853075ccafa6 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 16 Apr 2023 17:44:55 -0400 Subject: [PATCH 03/32] chore: fix website config.ts type checking --- packages/website/src/components/linter/config.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/website/src/components/linter/config.ts b/packages/website/src/components/linter/config.ts index f728dc1a635..1ff096177e7 100644 --- a/packages/website/src/components/linter/config.ts +++ b/packages/website/src/components/linter/config.ts @@ -13,6 +13,7 @@ export const defaultParseSettings: ParseSettings = { DEPRECATED__createDefaultProgram: false, errorOnTypeScriptSyntacticAndSemanticIssues: false, errorOnUnknownASTType: false, + EXPERIMENTAL_useProjectService: false, EXPERIMENTAL_useSourceOfProjectReferenceRedirect: false, extraFileExtensions: [], filePath: '', @@ -22,6 +23,7 @@ export const defaultParseSettings: ParseSettings = { preserveNodeMaps: true, programs: null, projects: [], + projectService: undefined, range: true, singleRun: false, suppressDeprecatedPropertyWarnings: false, From ecd5d3dec5db160b31ee7c00a92de29bc4ecf2b8 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 16 Apr 2023 17:56:30 -0400 Subject: [PATCH 04/32] Spell checking --- packages/typescript-estree/src/parser.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 205900b5620..73cd82f5253 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -81,7 +81,7 @@ function getProgramAndAST( if (program) { const projectServiceProgram = createProjectProgram(parseSettings, [ // LOL TYPES - // This'll hoepfully be resolved once they fully move to modules + // This'll hopefully be resolved once they fully move to modules // right now it's two separate files that get dumped out program as ts.Program, ]); @@ -327,7 +327,7 @@ export { // 1. ping jake with questions // * what does closing a file do // 2. create versions of test suite tailored to this new fancy form -// 3. testing the bujeezus out of it +// 3. testing the heck out of it // aside: // * performance testing - need to work on that From 78f8c4aa733f3a060dfca63b542dc3d3a9a9074b Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 16 Apr 2023 21:50:45 -0400 Subject: [PATCH 05/32] remove .only --- .../typescript-estree/src/parseSettings/createParseSettings.ts | 2 +- packages/typescript-estree/tests/lib/semanticInfo.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index fb7ac77cb8d..4bf46d5b39f 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -35,7 +35,7 @@ export function createParseSettings( ? options.tsconfigRootDir : process.cwd(); const parseSettings: MutableParseSettings = { - // todo: sometimes + // todo: sometimes (add EXPERIMENTAL_useProjectService to TSESTreeOptions) projectService: (TSSERVER_PROJECT_SERVICE ??= createProjectService()), EXPERIMENTAL_useProjectService: true, diff --git a/packages/typescript-estree/tests/lib/semanticInfo.test.ts b/packages/typescript-estree/tests/lib/semanticInfo.test.ts index 2eaec03f594..6c50712e0ea 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.test.ts @@ -179,7 +179,7 @@ describe('semanticInfo', () => { expect(tsComputedPropertyString.kind).toEqual(ts.SyntaxKind.StringLiteral); }); - it.only('imported-file tests', () => { + it('imported-file tests', () => { const fileName = path.resolve(FIXTURES_DIR, 'import-file.src.ts'); const parseResult = parseCodeAndGenerateServices( fs.readFileSync(fileName, 'utf8'), From 76acaebf5a45884bfd9065ff8ad28911170c7396 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 18 Apr 2023 13:21:13 -0400 Subject: [PATCH 06/32] Cleaned up todo comments in code and properly restrict new option --- docs/architecture/TypeScript-ESTree.mdx | 11 ++- .../create-program/createProjectProgram.ts | 3 - .../create-program/createProjectService.ts | 60 ++++++------- .../src/parseSettings/createParseSettings.ts | 10 +-- .../src/parseSettings/index.ts | 11 +-- .../typescript-estree/src/parser-options.ts | 11 ++- packages/typescript-estree/src/parser.ts | 85 ++++++------------- .../src/useProgramFromProjectService.ts | 28 ++++++ .../lib/__snapshots__/parse.test.ts.snap | 68 ++++----------- .../tests/lib/persistentParse.test.ts | 2 +- 10 files changed, 126 insertions(+), 163 deletions(-) create mode 100644 packages/typescript-estree/src/useProgramFromProjectService.ts diff --git a/docs/architecture/TypeScript-ESTree.mdx b/docs/architecture/TypeScript-ESTree.mdx index 1df71408595..a16eefc3d60 100644 --- a/docs/architecture/TypeScript-ESTree.mdx +++ b/docs/architecture/TypeScript-ESTree.mdx @@ -147,6 +147,15 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { */ errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; + /** + * ***EXPERIMENTAL FLAG*** - Use this at your own risk. + * + * Whether to create a shared TypeScript server to power program creation. + * + * @see https://github.com/typescript-eslint/typescript-eslint/issues/6575 + */ + EXPERIMENTAL_useProjectService?: boolean; + /** * ***EXPERIMENTAL FLAG*** - Use this at your own risk. * @@ -155,7 +164,7 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { * * This flag REQUIRES at least TS v3.9, otherwise it does nothing. * - * See: https://github.com/typescript-eslint/typescript-eslint/issues/2094 + * @see https://github.com/typescript-eslint/typescript-eslint/issues/2094 */ EXPERIMENTAL_useSourceOfProjectReferenceRedirect?: boolean; diff --git a/packages/typescript-estree/src/create-program/createProjectProgram.ts b/packages/typescript-estree/src/create-program/createProjectProgram.ts index 5d4a61e55a5..294d2e71074 100644 --- a/packages/typescript-estree/src/create-program/createProjectProgram.ts +++ b/packages/typescript-estree/src/create-program/createProjectProgram.ts @@ -5,7 +5,6 @@ import * as ts from 'typescript'; import { firstDefined } from '../node-utils'; import type { ParseSettings } from '../parseSettings'; import { describeFilePath } from './describeFilePath'; -import { getWatchProgramsForProjects } from './getWatchProgramsForProjects'; import type { ASTAndDefiniteProgram } from './shared'; import { getAstFromProgram } from './shared'; @@ -32,8 +31,6 @@ function createProjectProgram( ): ASTAndDefiniteProgram | undefined { log('Creating project program for: %s', parseSettings.filePath); - // const programsForProjects = getWatchProgramsForProjects(parseSettings); - // const programsForProjects = createPrograms(parseSettings); const astAndProgram = firstDefined(programsForProjects, currentProgram => getAstFromProgram(currentProgram, parseSettings), ); diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/typescript-estree/src/create-program/createProjectService.ts index 58dfea28076..6a2585c9207 100644 --- a/packages/typescript-estree/src/create-program/createProjectService.ts +++ b/packages/typescript-estree/src/create-program/createProjectService.ts @@ -1,33 +1,20 @@ import * as tsserver from 'typescript/lib/tsserverlibrary'; -export function createProjectService() { - const compilerOptions = { /* todo */ strict: true }; - const compilerHost = tsserver.createCompilerHost(compilerOptions, true); +const doNothing = (): void => {}; +const createStubFileWatcher = (): tsserver.FileWatcher => ({ + close: doNothing, +}); + +export function createProjectService(): tsserver.server.ProjectService { // TODO: see getWatchProgramsForProjects // We don't watch the disk, we just refer to these when ESLint calls us // there's a whole separate update pass in maybeInvalidateProgram at the bottom of getWatchProgramsForProjects // (this "goes nuclear on TypeScript") - const watchFile = ( - path: string, - callback: tsserver.FileWatcherCallback, - ): tsserver.FileWatcher => { - // todo (or just ... stub out?) - return { close() {} }; - }; - - const watchDirectory = ( - path: string, - callback: tsserver.DirectoryWatcherCallback, - ) => { - // todo (or just ... stub out?) - return { close() {} }; - }; - const system = { ...tsserver.sys, - watchFile, - watchDirectory, + watchDirectory: createStubFileWatcher, + watchFile: createStubFileWatcher, setTimeout, clearTimeout, setImmediate, @@ -36,29 +23,30 @@ export function createProjectService() { const projectService = new tsserver.server.ProjectService({ host: system, - cancellationToken: { isCancellationRequested: () => false }, - useSingleInferredProject: false, // ??? - useInferredProjectPerProjectRoot: false, // ??? + cancellationToken: { isCancellationRequested: (): boolean => false }, + useSingleInferredProject: false, + useInferredProjectPerProjectRoot: false, + // TODO: https://github.com/microsoft/TypeScript/issues/53803 typingsInstaller: { - attach: () => {}, - enqueueInstallTypingsRequest: () => {}, - installPackage: async (): Promise => { - throw new Error('pls no'); + attach: (): void => {}, + enqueueInstallTypingsRequest: (): void => {}, + installPackage: (): Promise => { + throw new Error('This should never be called.'); }, isKnownTypesPackageName: () => false, - onProjectClosed: () => {}, + onProjectClosed: (): void => {}, globalTypingsCacheLocation: '', }, logger: { - close() {}, + close: doNothing, + endGroup: doNothing, + getLogFileName: () => undefined, hasLevel: () => false, + info: doNothing, loggingEnabled: () => false, - perftrc() {}, - info() {}, - startGroup() {}, - endGroup() {}, - msg() {}, - getLogFileName: () => undefined, + msg: doNothing, + perftrc: doNothing, + startGroup: doNothing, }, session: undefined, }); diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index 4bf46d5b39f..7ff6f76b479 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -1,7 +1,7 @@ import debug from 'debug'; import type * as ts from 'typescript'; -import { createProjectService } from '../create-program/createProjectService'; +import { createProjectService } from '../create-program/createProjectService'; import { ensureAbsolutePath } from '../create-program/shared'; import type { TSESTreeOptions } from '../parser-options'; import { isSourceFile } from '../source-files'; @@ -35,10 +35,6 @@ export function createParseSettings( ? options.tsconfigRootDir : process.cwd(); const parseSettings: MutableParseSettings = { - // todo: sometimes (add EXPERIMENTAL_useProjectService to TSESTreeOptions) - projectService: (TSSERVER_PROJECT_SERVICE ??= createProjectService()), - EXPERIMENTAL_useProjectService: true, - allowInvalidAST: options.allowInvalidAST === true, code, codeFullText, @@ -55,6 +51,10 @@ export function createParseSettings( : new Set(), errorOnTypeScriptSyntacticAndSemanticIssues: false, errorOnUnknownASTType: options.errorOnUnknownASTType === true, + EXPERIMENTAL_projectService: + options.EXPERIMENTAL_useProjectService === true + ? (TSSERVER_PROJECT_SERVICE ??= createProjectService()) + : undefined, EXPERIMENTAL_useSourceOfProjectReferenceRedirect: options.EXPERIMENTAL_useSourceOfProjectReferenceRedirect === true, extraFileExtensions: diff --git a/packages/typescript-estree/src/parseSettings/index.ts b/packages/typescript-estree/src/parseSettings/index.ts index 9cfd1e02c51..f8789d75d89 100644 --- a/packages/typescript-estree/src/parseSettings/index.ts +++ b/packages/typescript-estree/src/parseSettings/index.ts @@ -11,9 +11,6 @@ type DebugModule = 'typescript-eslint' | 'eslint' | 'typescript'; * Internal settings used by the parser to run on a file. */ export interface MutableParseSettings { - // todo - projectService: tsserverlibrary.server.ProjectService | undefined; - /** * Prevents the parser from throwing an error if it receives an invalid AST from TypeScript. */ @@ -61,8 +58,12 @@ export interface MutableParseSettings { */ errorOnUnknownASTType: boolean; - // todo - EXPERIMENTAL_useProjectService: boolean; + /** + * Experimental: TypeScript server to power program creation. + */ + EXPERIMENTAL_projectService: + | tsserverlibrary.server.ProjectService + | undefined; /** * Whether TS should use the source files for referenced projects instead of the compiled .d.ts files. diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index da00e007d61..6f75079adca 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -94,6 +94,15 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { */ errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; + /** + * ***EXPERIMENTAL FLAG*** - Use this at your own risk. + * + * Whether to create a shared TypeScript server to power program creation. + * + * @see https://github.com/typescript-eslint/typescript-eslint/issues/6575 + */ + EXPERIMENTAL_useProjectService?: boolean; + /** * ***EXPERIMENTAL FLAG*** - Use this at your own risk. * @@ -102,7 +111,7 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { * * This flag REQUIRES at least TS v3.9, otherwise it does nothing. * - * See: https://github.com/typescript-eslint/typescript-eslint/issues/2094 + * @see https://github.com/typescript-eslint/typescript-eslint/issues/2094 */ EXPERIMENTAL_useSourceOfProjectReferenceRedirect?: boolean; diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 73cd82f5253..33504716e45 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -6,7 +6,6 @@ import { convertError } from './convert'; import { createDefaultProgram } from './create-program/createDefaultProgram'; import { createIsolatedProgram } from './create-program/createIsolatedProgram'; import { createProjectProgram } from './create-program/createProjectProgram'; -import { createProjectService } from './create-program/createProjectService'; import { createNoProgram, createSourceFile, @@ -27,6 +26,7 @@ import type { ParseSettings } from './parseSettings'; import { createParseSettings } from './parseSettings/createParseSettings'; import { getFirstSemanticOrSyntacticError } from './semantic-or-syntactic-errors'; import type { TSESTree } from './ts-estree'; +import { useProgramFromProjectService } from './useProgramFromProjectService'; const log = debug('typescript-eslint:typescript-estree:parser'); @@ -59,62 +59,40 @@ function getProgramAndAST( } } - // todo; wrap in function or some such - if (parseSettings.projectService) { - parseSettings.projectService.openClientFile( - parseSettings.filePath, - parseSettings.codeFullText, - ); - - // todo: maybe we'll slap this into the parseSEttings as a property? - // const projectService = createProjectServiceProgram(/* parseSettings */); - const program = parseSettings.projectService - .getScriptInfo(parseSettings.filePath)! - .getDefaultProject() - .getLanguageService(/*ensureSynchronized*/ true) - .getProgram(); - - // TODO "Eventually, close the file" - // what does this actually do? - // - - if (program) { - const projectServiceProgram = createProjectProgram(parseSettings, [ - // LOL TYPES - // This'll hopefully be resolved once they fully move to modules - // right now it's two separate files that get dumped out - program as ts.Program, - ]); - if (projectServiceProgram) { - return projectServiceProgram; - } - } + // no need to waste time creating a program as the caller didn't want parser services + // so we can save time and just create a lonesome source file + if (!hasFullTypeInformation) { + return createNoProgram(parseSettings); } - if (hasFullTypeInformation) { - const fromProjectProgram = createProjectProgram( + if (parseSettings.EXPERIMENTAL_projectService) { + const fromProjectService = useProgramFromProjectService( + parseSettings.EXPERIMENTAL_projectService, parseSettings, - getWatchProgramsForProjects(parseSettings), ); - if (fromProjectProgram) { - return fromProjectProgram; + if (fromProjectService) { + return fromProjectService; } + } + const fromProjectProgram = createProjectProgram( + parseSettings, + getWatchProgramsForProjects(parseSettings), + ); + if (fromProjectProgram) { + return fromProjectProgram; + } + + // eslint-disable-next-line deprecation/deprecation -- will be cleaned up with the next major + if (parseSettings.DEPRECATED__createDefaultProgram) { // eslint-disable-next-line deprecation/deprecation -- will be cleaned up with the next major - if (parseSettings.DEPRECATED__createDefaultProgram) { - // eslint-disable-next-line deprecation/deprecation -- will be cleaned up with the next major - const fromDefaultProgram = createDefaultProgram(parseSettings); - if (fromDefaultProgram) { - return fromDefaultProgram; - } + const fromDefaultProgram = createDefaultProgram(parseSettings); + if (fromDefaultProgram) { + return fromDefaultProgram; } - - return createIsolatedProgram(parseSettings); } - // no need to waste time creating a program as the caller didn't want parser services - // so we can save time and just create a lonesome source file - return createNoProgram(parseSettings); + return createIsolatedProgram(parseSettings); } interface EmptyObject {} @@ -321,16 +299,3 @@ export { clearProgramCache, clearParseAndGenerateServicesCalls, }; - -// next steps -// 0. clean up comments -// 1. ping jake with questions -// * what does closing a file do -// 2. create versions of test suite tailored to this new fancy form -// 3. testing the heck out of it - -// aside: -// * performance testing - need to work on that -// (see TS folks - they do both memory usage and runtime) -// memory usage: makes bigger people happy (OOMs) -// runtime: more people can use it diff --git a/packages/typescript-estree/src/useProgramFromProjectService.ts b/packages/typescript-estree/src/useProgramFromProjectService.ts new file mode 100644 index 00000000000..9e4424635e1 --- /dev/null +++ b/packages/typescript-estree/src/useProgramFromProjectService.ts @@ -0,0 +1,28 @@ +import type * as ts from 'typescript'; +import type { server } from 'typescript/lib/tsserverlibrary'; + +import { createProjectProgram } from './create-program/createProjectProgram'; +import type { ASTAndDefiniteProgram } from './create-program/shared'; +import type { MutableParseSettings } from './parseSettings'; + +export function useProgramFromProjectService( + projectService: server.ProjectService, + parseSettings: Readonly, +): ASTAndDefiniteProgram | undefined { + projectService.openClientFile( + parseSettings.filePath, + parseSettings.codeFullText, + ); + + const program = projectService + .getScriptInfo(parseSettings.filePath)! + .getDefaultProject() + .getLanguageService(/*ensureSynchronized*/ true) + .getProgram(); + + if (!program) { + return undefined; + } + + return createProjectProgram(parseSettings, [program as ts.Program]); +} diff --git a/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap index c51f8e2606a..8c2e7861435 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap @@ -370,9 +370,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -666,9 +664,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -852,9 +848,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - witho }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -1038,9 +1032,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .js file - witho }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -1260,9 +1252,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .json file - wit }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -1556,9 +1546,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -1852,9 +1840,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -2038,9 +2024,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -2224,9 +2208,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .jsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -2410,9 +2392,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .ts file - witho }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -2596,9 +2576,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .ts file - witho }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -2892,9 +2870,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -3188,9 +3164,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -3374,9 +3348,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -3560,9 +3532,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .tsx file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -4040,9 +4010,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .vue file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } @@ -4226,9 +4194,7 @@ exports[`parseAndGenerateServices isolated parsing should parse .vue file - with }, "services": { "esTreeNodeToTSNodeMap": WeakMap {}, - "getSymbolAtLocation": [Function], - "getTypeAtLocation": [Function], - "program": "With Program", + "program": "No Program", "tsNodeToESTreeNodeMap": WeakMap {}, }, } diff --git a/packages/typescript-estree/tests/lib/persistentParse.test.ts b/packages/typescript-estree/tests/lib/persistentParse.test.ts index b28ffaf267a..63e81d7e260 100644 --- a/packages/typescript-estree/tests/lib/persistentParse.test.ts +++ b/packages/typescript-estree/tests/lib/persistentParse.test.ts @@ -224,7 +224,7 @@ function baseTests( } describe('persistent parse', () => { - describe.only('includes not ending in a slash', () => { + describe('includes not ending in a slash', () => { const tsConfigExcludeBar = { include: ['src'], exclude: ['./src/bar.ts'], From a68dece47cb5a0d40847650bf7f366f3d3ac0043 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 18 Apr 2023 13:29:50 -0400 Subject: [PATCH 07/32] Added separate test run in CI for experimental option --- .github/workflows/ci.yml | 32 +++++++++++++++++++ packages/types/src/parser-options.ts | 1 + .../src/parseSettings/createParseSettings.ts | 5 ++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6536fe37b7a..9ab05a9c94a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -192,6 +192,38 @@ jobs: # Sadly 1 day is the minimum retention-days: 1 + unit_tests_tsserver: + name: Run Unit Tests with Experimental TSServer + needs: [build] + runs-on: ubuntu-latest + strategy: + matrix: + package: + [ + 'eslint-plugin', + 'eslint-plugin-internal', + 'eslint-plugin-tslint', + 'typescript-estree', + ] + env: + COLLECT_COVERAGE: false + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 2 + - name: Install + uses: ./.github/actions/prepare-install + with: + node-version: ${{ matrix.node-version }} + - name: Build + uses: ./.github/actions/prepare-build + - name: Run unit tests for ${{ matrix.package }} + run: npx nx test ${{ matrix.package }} --coverage=false + env: + CI: true + TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER: true + website_build: name: Build Website needs: [install, build] diff --git a/packages/types/src/parser-options.ts b/packages/types/src/parser-options.ts index 815ad440913..23d2d25f062 100644 --- a/packages/types/src/parser-options.ts +++ b/packages/types/src/parser-options.ts @@ -47,6 +47,7 @@ interface ParserOptions { debugLevel?: DebugLevel; errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; errorOnUnknownASTType?: boolean; + EXPERIMENTAL_useProjectService?: boolean; // purposely undocumented for now EXPERIMENTAL_useSourceOfProjectReferenceRedirect?: boolean; // purposely undocumented for now extraFileExtensions?: string[]; filePath?: string; diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index 7ff6f76b479..99c1774e010 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -24,6 +24,8 @@ let TSCONFIG_MATCH_CACHE: ExpiringCache | null; let TSSERVER_PROJECT_SERVICE: ReturnType | null = null; +process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER = 'true'; + export function createParseSettings( code: string | ts.SourceFile, options: Partial = {}, @@ -52,7 +54,8 @@ export function createParseSettings( errorOnTypeScriptSyntacticAndSemanticIssues: false, errorOnUnknownASTType: options.errorOnUnknownASTType === true, EXPERIMENTAL_projectService: - options.EXPERIMENTAL_useProjectService === true + options.EXPERIMENTAL_useProjectService === true || + process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER === 'true' ? (TSSERVER_PROJECT_SERVICE ??= createProjectService()) : undefined, EXPERIMENTAL_useSourceOfProjectReferenceRedirect: From 6f6f02af1404287e2a2c8ffba9a1dd24ec633904 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 18 Apr 2023 13:30:58 -0400 Subject: [PATCH 08/32] fix: no node-version --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ab05a9c94a..c0e30f6f744 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -215,7 +215,7 @@ jobs: - name: Install uses: ./.github/actions/prepare-install with: - node-version: ${{ matrix.node-version }} + node-version: 18 - name: Build uses: ./.github/actions/prepare-build - name: Run unit tests for ${{ matrix.package }} From e43145f686edd2467d9104fc94c7cee3f96a7c47 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 18 Apr 2023 13:32:07 -0400 Subject: [PATCH 09/32] Remove process.env manual set --- .../typescript-estree/src/parseSettings/createParseSettings.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index 99c1774e010..33ca8de5d23 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -24,8 +24,6 @@ let TSCONFIG_MATCH_CACHE: ExpiringCache | null; let TSSERVER_PROJECT_SERVICE: ReturnType | null = null; -process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER = 'true'; - export function createParseSettings( code: string | ts.SourceFile, options: Partial = {}, From 214fc8d0164b3b475104c50d00a3009e59a1b4b2 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 18 Apr 2023 13:33:05 -0400 Subject: [PATCH 10/32] Fix linter config.ts --- packages/website/src/components/linter/config.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/website/src/components/linter/config.ts b/packages/website/src/components/linter/config.ts index 1ff096177e7..fc6389b4e1b 100644 --- a/packages/website/src/components/linter/config.ts +++ b/packages/website/src/components/linter/config.ts @@ -13,7 +13,7 @@ export const defaultParseSettings: ParseSettings = { DEPRECATED__createDefaultProgram: false, errorOnTypeScriptSyntacticAndSemanticIssues: false, errorOnUnknownASTType: false, - EXPERIMENTAL_useProjectService: false, + EXPERIMENTAL_projectService: undefined, EXPERIMENTAL_useSourceOfProjectReferenceRedirect: false, extraFileExtensions: [], filePath: '', @@ -23,7 +23,6 @@ export const defaultParseSettings: ParseSettings = { preserveNodeMaps: true, programs: null, projects: [], - projectService: undefined, range: true, singleRun: false, suppressDeprecatedPropertyWarnings: false, From a2f71a739063e4d4eeeecae720070bd1f26c8dbd Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 18 Apr 2023 15:36:54 -0400 Subject: [PATCH 11/32] Tweaked project creation to try to explicitly set cwd --- .../src/create-program/createProjectService.ts | 10 +++++----- .../src/useProgramFromProjectService.ts | 10 ++++++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/typescript-estree/src/create-program/createProjectService.ts index 6a2585c9207..0475184652b 100644 --- a/packages/typescript-estree/src/create-program/createProjectService.ts +++ b/packages/typescript-estree/src/create-program/createProjectService.ts @@ -11,14 +11,14 @@ export function createProjectService(): tsserver.server.ProjectService { // We don't watch the disk, we just refer to these when ESLint calls us // there's a whole separate update pass in maybeInvalidateProgram at the bottom of getWatchProgramsForProjects // (this "goes nuclear on TypeScript") - const system = { + const system: tsserver.server.ServerHost = { ...tsserver.sys, - watchDirectory: createStubFileWatcher, - watchFile: createStubFileWatcher, - setTimeout, + clearImmediate, clearTimeout, setImmediate, - clearImmediate, + setTimeout, + watchDirectory: createStubFileWatcher, + watchFile: createStubFileWatcher, }; const projectService = new tsserver.server.ProjectService({ diff --git a/packages/typescript-estree/src/useProgramFromProjectService.ts b/packages/typescript-estree/src/useProgramFromProjectService.ts index 9e4424635e1..74447dfef33 100644 --- a/packages/typescript-estree/src/useProgramFromProjectService.ts +++ b/packages/typescript-estree/src/useProgramFromProjectService.ts @@ -9,14 +9,20 @@ export function useProgramFromProjectService( projectService: server.ProjectService, parseSettings: Readonly, ): ASTAndDefiniteProgram | undefined { + projectService.setCompilerOptionsForInferredProjects({ + rootDir: parseSettings.tsconfigRootDir, + }); + projectService.openClientFile( parseSettings.filePath, parseSettings.codeFullText, + /* scriptKind */ undefined, + parseSettings.tsconfigRootDir, ); + const scriptInfo = projectService.getScriptInfo(parseSettings.filePath); const program = projectService - .getScriptInfo(parseSettings.filePath)! - .getDefaultProject() + .getDefaultProjectForFile(scriptInfo!.fileName, true)! .getLanguageService(/*ensureSynchronized*/ true) .getProgram(); From 4ffaffa2be348401bff171a91da93e98c2dbfe5c Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 24 Apr 2023 17:27:29 -0400 Subject: [PATCH 12/32] Progress on updating unit tests for absolute paths --- .../eslint-plugin-tslint/src/rules/config.ts | 3 +- .../typescript-estree/src/clear-caches.ts | 6 +- .../src/useProgramFromProjectService.ts | 9 +-- .../tests/lib/persistentParse.test.ts | 4 + .../tests/lib/semanticInfo.test.ts | 73 ++++++++++--------- packages/utils/src/ts-eslint/Rule.ts | 2 +- 6 files changed, 55 insertions(+), 42 deletions(-) diff --git a/packages/eslint-plugin-tslint/src/rules/config.ts b/packages/eslint-plugin-tslint/src/rules/config.ts index 847b6846ea9..3457c550d05 100644 --- a/packages/eslint-plugin-tslint/src/rules/config.ts +++ b/packages/eslint-plugin-tslint/src/rules/config.ts @@ -1,4 +1,5 @@ import { ESLintUtils } from '@typescript-eslint/utils'; +import path from 'path'; import type { RuleSeverity } from 'tslint'; import { Configuration } from 'tslint'; @@ -118,7 +119,7 @@ export default createRule({ context, [{ rules: tslintRules, rulesDirectory: tslintRulesDirectory, lintFile }], ) { - const fileName = context.getFilename(); + const fileName = path.resolve(context.getCwd(), context.getFilename()); const sourceCode = context.getSourceCode().text; const services = ESLintUtils.getParserServices(context); const program = services.program; diff --git a/packages/typescript-estree/src/clear-caches.ts b/packages/typescript-estree/src/clear-caches.ts index aea4d6cf845..015fd18e29c 100644 --- a/packages/typescript-estree/src/clear-caches.ts +++ b/packages/typescript-estree/src/clear-caches.ts @@ -1,6 +1,9 @@ import { clearWatchCaches } from './create-program/getWatchProgramsForProjects'; import { clearProgramCache as clearProgramCacheOriginal } from './parser'; -import { clearTSConfigMatchCache } from './parseSettings/createParseSettings'; +import { + clearTSConfigMatchCache, + clearTSServerProjectService, +} from './parseSettings/createParseSettings'; import { clearGlobCache } from './parseSettings/resolveProjectList'; /** @@ -14,6 +17,7 @@ export function clearCaches(): void { clearProgramCacheOriginal(); clearWatchCaches(); clearTSConfigMatchCache(); + clearTSServerProjectService(); clearGlobCache(); } diff --git a/packages/typescript-estree/src/useProgramFromProjectService.ts b/packages/typescript-estree/src/useProgramFromProjectService.ts index 74447dfef33..de3ac93886f 100644 --- a/packages/typescript-estree/src/useProgramFromProjectService.ts +++ b/packages/typescript-estree/src/useProgramFromProjectService.ts @@ -9,16 +9,15 @@ export function useProgramFromProjectService( projectService: server.ProjectService, parseSettings: Readonly, ): ASTAndDefiniteProgram | undefined { - projectService.setCompilerOptionsForInferredProjects({ - rootDir: parseSettings.tsconfigRootDir, - }); - - projectService.openClientFile( + const opened = projectService.openClientFile( parseSettings.filePath, parseSettings.codeFullText, /* scriptKind */ undefined, parseSettings.tsconfigRootDir, ); + if (!opened.configFileName) { + return undefined; + } const scriptInfo = projectService.getScriptInfo(parseSettings.filePath); const program = projectService diff --git a/packages/typescript-estree/tests/lib/persistentParse.test.ts b/packages/typescript-estree/tests/lib/persistentParse.test.ts index 63e81d7e260..49d6d0c5f86 100644 --- a/packages/typescript-estree/tests/lib/persistentParse.test.ts +++ b/packages/typescript-estree/tests/lib/persistentParse.test.ts @@ -4,6 +4,7 @@ import tmp from 'tmp'; import { clearWatchCaches } from '../../src/create-program/getWatchProgramsForProjects'; import { parseAndGenerateServices } from '../../src/parser'; +import { clearCaches } from '../../src'; const CONTENTS = { foo: 'console.log("foo")', @@ -177,6 +178,7 @@ function baseTests( // change the config file so it now includes all files writeTSConfig(PROJECT_DIR, tsConfigIncludeAll); + clearCaches(); expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile('bar', PROJECT_DIR)).not.toThrow(); @@ -274,6 +276,7 @@ describe('persistent parse', () => { // write a new file and attempt to parse it writeFile(PROJECT_DIR, 'bar'); + clearCaches(); expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile('bar', PROJECT_DIR)).not.toThrow(); @@ -289,6 +292,7 @@ describe('persistent parse', () => { // write a new file and attempt to parse it writeFile(PROJECT_DIR, bazSlashBar); + clearCaches(); expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile(bazSlashBar, PROJECT_DIR)).not.toThrow(); diff --git a/packages/typescript-estree/tests/lib/semanticInfo.test.ts b/packages/typescript-estree/tests/lib/semanticInfo.test.ts index 6c50712e0ea..5d4daa7bc63 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.test.ts @@ -257,44 +257,49 @@ describe('semanticInfo', () => { ); }); - it('non-existent project file', () => { - const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); - const badConfig = createOptions(fileName); - badConfig.project = './tsconfigs.json'; - expect(() => - parseCodeAndGenerateServices( - fs.readFileSync(fileName, 'utf8'), - badConfig, - ), - ).toThrow(/Cannot read file .+tsconfigs\.json'/); - }); - - it('fail to read project file', () => { - const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); - const badConfig = createOptions(fileName); - badConfig.project = '.'; - expect(() => + if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { + it('non-existent project file', () => { + const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); + const badConfig = createOptions(fileName); + badConfig.project = './tsconfigs.json'; parseCodeAndGenerateServices( fs.readFileSync(fileName, 'utf8'), badConfig, - ), - ).toThrow( - // case insensitive because unix based systems are case insensitive - /Cannot read file .+semanticInfo'./i, - ); - }); + ); + expect(() => + parseCodeAndGenerateServices( + fs.readFileSync(fileName, 'utf8'), + badConfig, + ), + ).toThrow(/Cannot read file .+tsconfigs\.json'/); + }); + it('fail to read project file', () => { + const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); + const badConfig = createOptions(fileName); + badConfig.project = '.'; + expect(() => + parseCodeAndGenerateServices( + fs.readFileSync(fileName, 'utf8'), + badConfig, + ), + ).toThrow( + // case insensitive because unix based systems are case insensitive + /Cannot read file .+semanticInfo'./i, + ); + }); - it('malformed project file', () => { - const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); - const badConfig = createOptions(fileName); - badConfig.project = './badTSConfig/tsconfig.json'; - expect(() => - parseCodeAndGenerateServices( - fs.readFileSync(fileName, 'utf8'), - badConfig, - ), - ).toThrowErrorMatchingSnapshot(); - }); + it('malformed project file', () => { + const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); + const badConfig = createOptions(fileName); + badConfig.project = './badTSConfig/tsconfig.json'; + expect(() => + parseCodeAndGenerateServices( + fs.readFileSync(fileName, 'utf8'), + badConfig, + ), + ).toThrowErrorMatchingSnapshot(); + }); + } it('default program produced with option', () => { const parseResult = parseCodeAndGenerateServices('var foo = 5;', { diff --git a/packages/utils/src/ts-eslint/Rule.ts b/packages/utils/src/ts-eslint/Rule.ts index b0705cc5c47..9f4f0ba8771 100644 --- a/packages/utils/src/ts-eslint/Rule.ts +++ b/packages/utils/src/ts-eslint/Rule.ts @@ -221,7 +221,7 @@ interface RuleContext< * It is a path to a directory that should be considered as the current working directory. * @since 6.6.0 */ - getCwd?(): string; + getCwd(): string; /** * Returns the filename associated with the source. From 20d9f82810d716063d0253be3b6704387955dc4f Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 24 Apr 2023 17:33:32 -0400 Subject: [PATCH 13/32] fix: add missing clearTSServerProjectService --- .../src/parseSettings/createParseSettings.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index 33ca8de5d23..e426daaa498 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -143,6 +143,10 @@ export function clearTSConfigMatchCache(): void { TSCONFIG_MATCH_CACHE?.clear(); } +export function clearTSServerProjectService(): void { + TSSERVER_PROJECT_SERVICE = null; +} + /** * Ensures source code is a string. */ From 6ef8498a13feafc25b63c2e677a5dd1bd2b91bdb Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 24 Apr 2023 23:26:59 -0400 Subject: [PATCH 14/32] Add more path relativity fixes --- packages/parser/tests/lib/parser.ts | 7 +++--- .../tests/TypeOrValueSpecifier.test.ts | 12 +++++----- .../src/useProgramFromProjectService.ts | 24 +++++++++++++++++-- .../__snapshots__/semanticInfo.test.ts.snap | 2 -- .../tests/lib/persistentParse.test.ts | 2 +- 5 files changed, 33 insertions(+), 14 deletions(-) diff --git a/packages/parser/tests/lib/parser.ts b/packages/parser/tests/lib/parser.ts index 952e388eb8c..185221178d1 100644 --- a/packages/parser/tests/lib/parser.ts +++ b/packages/parser/tests/lib/parser.ts @@ -1,6 +1,7 @@ import * as scopeManager from '@typescript-eslint/scope-manager'; import type { ParserOptions } from '@typescript-eslint/types'; import * as typescriptESTree from '@typescript-eslint/typescript-estree'; +import path from 'path'; import { parse, parseForESLint } from '../../src/parser'; @@ -33,10 +34,10 @@ describe('parser', () => { jsx: false, }, // ts-estree specific - filePath: 'isolated-file.src.ts', + filePath: './isolated-file.src.ts', project: 'tsconfig.json', errorOnTypeScriptSyntacticAndSemanticIssues: false, - tsconfigRootDir: 'tests/fixtures/services', + tsconfigRootDir: path.join(__dirname, '../fixtures/services'), extraFileExtensions: ['.foo'], }; parseForESLint(code, config); @@ -89,7 +90,7 @@ describe('parser', () => { filePath: 'isolated-file.src.ts', project: 'tsconfig.json', errorOnTypeScriptSyntacticAndSemanticIssues: false, - tsconfigRootDir: 'tests/fixtures/services', + tsconfigRootDir: path.join(__dirname, '../fixtures/services'), extraFileExtensions: ['.foo'], }; parseForESLint(code, config); diff --git a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts index 4deefa9e96d..08ece931a6f 100644 --- a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts +++ b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts @@ -197,7 +197,7 @@ describe('TypeOrValueSpecifier', () => { 'type Foo = {prop: string}; type Test = Foo;', ].map((test): [string, TypeOrValueSpecifier] => [ test, - { from: 'file', name: 'Foo', path: 'tests/fixtures/file.ts' }, + { from: 'file', name: 'Foo', path: 'file.ts' }, ]), [ 'interface Foo {prop: string}; type Test = Foo;', @@ -207,7 +207,7 @@ describe('TypeOrValueSpecifier', () => { { from: 'file', name: ['Foo', 'Bar'], - path: 'tests/fixtures/file.ts', + path: 'file.ts', }, ]), ].flat(), @@ -224,14 +224,14 @@ describe('TypeOrValueSpecifier', () => { ], [ 'interface Foo {prop: string}; type Test = Foo;', - { from: 'file', name: 'Foo', path: 'tests/fixtures/wrong-file.ts' }, + { from: 'file', name: 'Foo', path: 'wrong-file.ts' }, ], [ 'interface Foo {prop: string}; type Test = Foo;', { from: 'file', name: ['Foo', 'Bar'], - path: 'tests/fixtures/wrong-file.ts', + path: 'wrong-file.ts', }, ], ])("doesn't match a mismatched file specifier: %s", runTestNegative); @@ -279,14 +279,14 @@ describe('TypeOrValueSpecifier', () => { ['type Test = RegExp;', { from: 'file', name: ['RegExp', 'BigInt'] }], [ 'type Test = RegExp;', - { from: 'file', name: 'RegExp', path: 'tests/fixtures/file.ts' }, + { from: 'file', name: 'RegExp', path: 'file.ts' }, ], [ 'type Test = RegExp;', { from: 'file', name: ['RegExp', 'BigInt'], - path: 'tests/fixtures/file.ts', + path: 'file.ts', }, ], [ diff --git a/packages/typescript-estree/src/useProgramFromProjectService.ts b/packages/typescript-estree/src/useProgramFromProjectService.ts index de3ac93886f..d0cf7bd27d2 100644 --- a/packages/typescript-estree/src/useProgramFromProjectService.ts +++ b/packages/typescript-estree/src/useProgramFromProjectService.ts @@ -1,8 +1,12 @@ +import path from 'path'; import type * as ts from 'typescript'; import type { server } from 'typescript/lib/tsserverlibrary'; import { createProjectProgram } from './create-program/createProjectProgram'; -import type { ASTAndDefiniteProgram } from './create-program/shared'; +import { + ASTAndDefiniteProgram, + getCanonicalFileName, +} from './create-program/shared'; import type { MutableParseSettings } from './parseSettings'; export function useProgramFromProjectService( @@ -10,7 +14,7 @@ export function useProgramFromProjectService( parseSettings: Readonly, ): ASTAndDefiniteProgram | undefined { const opened = projectService.openClientFile( - parseSettings.filePath, + absolutify(parseSettings.filePath), parseSettings.codeFullText, /* scriptKind */ undefined, parseSettings.tsconfigRootDir, @@ -29,5 +33,21 @@ export function useProgramFromProjectService( return undefined; } + const { configFilePath } = program.getCompilerOptions(); + if ( + !parseSettings.projects + .map(absolutify) + .map(getCanonicalFileName) + .includes(getCanonicalFileName(configFilePath as string)) + ) { + throw new Error(`Config file ${configFilePath} not known.`); + } + return createProjectProgram(parseSettings, [program as ts.Program]); + + function absolutify(filePath: string): string { + return path.isAbsolute(filePath) + ? filePath + : path.join(projectService.host.getCurrentDirectory(), filePath); + } } diff --git a/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap index 0d94ba3a46b..79772deb70e 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap @@ -1734,5 +1734,3 @@ exports[`semanticInfo fixtures/non-existent-estree-nodes.src 1`] = ` "type": "Program", } `; - -exports[`semanticInfo malformed project file 1`] = `"Compiler option 'compileOnSave' requires a value of type boolean."`; diff --git a/packages/typescript-estree/tests/lib/persistentParse.test.ts b/packages/typescript-estree/tests/lib/persistentParse.test.ts index 49d6d0c5f86..8e500096044 100644 --- a/packages/typescript-estree/tests/lib/persistentParse.test.ts +++ b/packages/typescript-estree/tests/lib/persistentParse.test.ts @@ -4,7 +4,7 @@ import tmp from 'tmp'; import { clearWatchCaches } from '../../src/create-program/getWatchProgramsForProjects'; import { parseAndGenerateServices } from '../../src/parser'; -import { clearCaches } from '../../src'; +import { clearCaches } from '../../src/clear-caches'; const CONTENTS = { foo: 'console.log("foo")', From 195462d133446027ea8e23b1cb8d8f587d345a51 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 24 Apr 2023 23:49:55 -0400 Subject: [PATCH 15/32] Lint fixes and watch program relativity --- .vscode/launch.json | 34 +++++++++++++++++++ .../getWatchProgramsForProjects.ts | 5 ++- .../src/parseSettings/createParseSettings.ts | 2 ++ .../src/useProgramFromProjectService.ts | 6 ++-- .../tests/lib/persistentParse.test.ts | 2 +- 5 files changed, 44 insertions(+), 5 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 48cba7571e8..9179523de23 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -72,6 +72,40 @@ "${workspaceFolder}/packages/scope-manager/dist/index.js", ], }, + { + "type": "node", + "request": "launch", + "name": "Jest Test Current eslint-plugin-tslint Rule", + "cwd": "${workspaceFolder}/packages/eslint-plugin-tslint/", + "program": "${workspaceFolder}/node_modules/jest/bin/jest.js", + "args": [ + "--runInBand", + "--no-cache", + "--no-coverage", + "${fileBasenameNoExtension}" + ], + "sourceMaps": true, + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "skipFiles": [ + "${workspaceFolder}/packages/utils/src/index.ts", + "${workspaceFolder}/packages/utils/dist/index.js", + "${workspaceFolder}/packages/utils/src/ts-estree.ts", + "${workspaceFolder}/packages/utils/dist/ts-estree.js", + "${workspaceFolder}/packages/type-utils/src/index.ts", + "${workspaceFolder}/packages/type-utils/dist/index.js", + "${workspaceFolder}/packages/parser/src/index.ts", + "${workspaceFolder}/packages/parser/dist/index.js", + "${workspaceFolder}/packages/typescript-estree/src/index.ts", + "${workspaceFolder}/packages/typescript-estree/dist/index.js", + "${workspaceFolder}/packages/types/src/index.ts", + "${workspaceFolder}/packages/types/dist/index.js", + "${workspaceFolder}/packages/visitor-keys/src/index.ts", + "${workspaceFolder}/packages/visitor-keys/dist/index.js", + "${workspaceFolder}/packages/scope-manager/dist/index.js", + "${workspaceFolder}/packages/scope-manager/dist/index.js", + ], + }, { "type": "node", "request": "launch", diff --git a/packages/typescript-estree/src/create-program/getWatchProgramsForProjects.ts b/packages/typescript-estree/src/create-program/getWatchProgramsForProjects.ts index d56313e489d..565116cb847 100644 --- a/packages/typescript-estree/src/create-program/getWatchProgramsForProjects.ts +++ b/packages/typescript-estree/src/create-program/getWatchProgramsForProjects.ts @@ -261,7 +261,10 @@ function createWatchProgram( const watchCompilerHost = ts.createWatchCompilerHost( tsconfigPath, createDefaultCompilerOptionsFromExtra(parseSettings), - ts.sys, + { + ...ts.sys, + getCurrentDirectory: () => parseSettings.tsconfigRootDir, + }, ts.createAbstractBuilder, diagnosticReporter, /*reportWatchStatus*/ () => {}, diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index e426daaa498..5e2547f372c 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -24,6 +24,8 @@ let TSCONFIG_MATCH_CACHE: ExpiringCache | null; let TSSERVER_PROJECT_SERVICE: ReturnType | null = null; +process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER = 'true'; + export function createParseSettings( code: string | ts.SourceFile, options: Partial = {}, diff --git a/packages/typescript-estree/src/useProgramFromProjectService.ts b/packages/typescript-estree/src/useProgramFromProjectService.ts index d0cf7bd27d2..161deded69a 100644 --- a/packages/typescript-estree/src/useProgramFromProjectService.ts +++ b/packages/typescript-estree/src/useProgramFromProjectService.ts @@ -4,7 +4,7 @@ import type { server } from 'typescript/lib/tsserverlibrary'; import { createProjectProgram } from './create-program/createProjectProgram'; import { - ASTAndDefiniteProgram, + type ASTAndDefiniteProgram, getCanonicalFileName, } from './create-program/shared'; import type { MutableParseSettings } from './parseSettings'; @@ -33,12 +33,12 @@ export function useProgramFromProjectService( return undefined; } - const { configFilePath } = program.getCompilerOptions(); + const configFilePath = program.getCompilerOptions().configFilePath as string; if ( !parseSettings.projects .map(absolutify) .map(getCanonicalFileName) - .includes(getCanonicalFileName(configFilePath as string)) + .includes(getCanonicalFileName(configFilePath)) ) { throw new Error(`Config file ${configFilePath} not known.`); } diff --git a/packages/typescript-estree/tests/lib/persistentParse.test.ts b/packages/typescript-estree/tests/lib/persistentParse.test.ts index 8e500096044..d19210016e8 100644 --- a/packages/typescript-estree/tests/lib/persistentParse.test.ts +++ b/packages/typescript-estree/tests/lib/persistentParse.test.ts @@ -2,9 +2,9 @@ import fs from 'fs'; import path from 'path'; import tmp from 'tmp'; +import { clearCaches } from '../../src/clear-caches'; import { clearWatchCaches } from '../../src/create-program/getWatchProgramsForProjects'; import { parseAndGenerateServices } from '../../src/parser'; -import { clearCaches } from '../../src/clear-caches'; const CONTENTS = { foo: 'console.log("foo")', From 0db7485d020ae6445b3dcfdf375d7c3466eb6642 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 24 Apr 2023 23:50:13 -0400 Subject: [PATCH 16/32] No, not always true --- .../typescript-estree/src/parseSettings/createParseSettings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index 5e2547f372c..d061f284f19 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -24,7 +24,7 @@ let TSCONFIG_MATCH_CACHE: ExpiringCache | null; let TSSERVER_PROJECT_SERVICE: ReturnType | null = null; -process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER = 'true'; +// process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER = 'true'; export function createParseSettings( code: string | ts.SourceFile, From b583bb3eb45f97908b58175dd2d25beedde17d21 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 25 Apr 2023 00:31:27 -0400 Subject: [PATCH 17/32] Fix around semanticInfo.test.ts --- .../src/parseSettings/createParseSettings.ts | 2 - .../src/useProgramFromProjectService.ts | 2 +- .../__snapshots__/semanticInfo.test.ts.snap | 2 + .../tests/lib/semanticInfo.test.ts | 55 +++++++++---------- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index d061f284f19..e426daaa498 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -24,8 +24,6 @@ let TSCONFIG_MATCH_CACHE: ExpiringCache | null; let TSSERVER_PROJECT_SERVICE: ReturnType | null = null; -// process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER = 'true'; - export function createParseSettings( code: string | ts.SourceFile, options: Partial = {}, diff --git a/packages/typescript-estree/src/useProgramFromProjectService.ts b/packages/typescript-estree/src/useProgramFromProjectService.ts index 161deded69a..3c78ea6d948 100644 --- a/packages/typescript-estree/src/useProgramFromProjectService.ts +++ b/packages/typescript-estree/src/useProgramFromProjectService.ts @@ -40,7 +40,7 @@ export function useProgramFromProjectService( .map(getCanonicalFileName) .includes(getCanonicalFileName(configFilePath)) ) { - throw new Error(`Config file ${configFilePath} not known.`); + throw new Error(`Cannot read file '${parseSettings.projects[0]}'`); } return createProjectProgram(parseSettings, [program as ts.Program]); diff --git a/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap index 79772deb70e..0d94ba3a46b 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap @@ -1734,3 +1734,5 @@ exports[`semanticInfo fixtures/non-existent-estree-nodes.src 1`] = ` "type": "Program", } `; + +exports[`semanticInfo malformed project file 1`] = `"Compiler option 'compileOnSave' requires a value of type boolean."`; diff --git a/packages/typescript-estree/tests/lib/semanticInfo.test.ts b/packages/typescript-estree/tests/lib/semanticInfo.test.ts index 5d4daa7bc63..af90bedb840 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.test.ts @@ -3,7 +3,7 @@ import glob from 'glob'; import * as path from 'path'; import * as ts from 'typescript'; -import { clearWatchCaches } from '../../src/create-program/getWatchProgramsForProjects'; +import { clearCaches } from '../../src'; import { createProgramFromConfigFile as createProgram } from '../../src/create-program/useProvidedPrograms'; import type { ParseAndGenerateServicesResult } from '../../src/parser'; import { parseAndGenerateServices } from '../../src/parser'; @@ -37,7 +37,7 @@ function createOptions(fileName: string): TSESTreeOptions & { cwd?: string } { } // ensure tsconfig-parser watch caches are clean for each test -beforeEach(() => clearWatchCaches()); +beforeEach(() => clearCaches()); describe('semanticInfo', () => { // test all AST snapshots @@ -257,37 +257,34 @@ describe('semanticInfo', () => { ); }); - if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { - it('non-existent project file', () => { - const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); - const badConfig = createOptions(fileName); - badConfig.project = './tsconfigs.json'; + it('non-existent project file', () => { + const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); + const badConfig = createOptions(fileName); + badConfig.project = './tsconfigs.json'; + expect(() => parseCodeAndGenerateServices( fs.readFileSync(fileName, 'utf8'), badConfig, - ); - expect(() => - parseCodeAndGenerateServices( - fs.readFileSync(fileName, 'utf8'), - badConfig, - ), - ).toThrow(/Cannot read file .+tsconfigs\.json'/); - }); - it('fail to read project file', () => { - const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); - const badConfig = createOptions(fileName); - badConfig.project = '.'; - expect(() => - parseCodeAndGenerateServices( - fs.readFileSync(fileName, 'utf8'), - badConfig, - ), - ).toThrow( - // case insensitive because unix based systems are case insensitive - /Cannot read file .+semanticInfo'./i, - ); - }); + ), + ).toThrow(/Cannot read file .+tsconfigs\.json'/); + }); + it('fail to read project file', () => { + const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); + const badConfig = createOptions(fileName); + badConfig.project = '.'; + expect(() => + parseCodeAndGenerateServices( + fs.readFileSync(fileName, 'utf8'), + badConfig, + ), + ).toThrow( + // case insensitive because unix based systems are case insensitive + /Cannot read file .+semanticInfo'/i, + ); + }); + + if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { it('malformed project file', () => { const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); const badConfig = createOptions(fileName); From 5b9ca229eacf4f261a51c4bfd050bca2198702e0 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 25 Apr 2023 13:04:12 -0400 Subject: [PATCH 18/32] Switch snapshot to inline --- .../tests/lib/__snapshots__/semanticInfo.test.ts.snap | 2 -- packages/typescript-estree/tests/lib/semanticInfo.test.ts | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap index 0d94ba3a46b..79772deb70e 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.test.ts.snap @@ -1734,5 +1734,3 @@ exports[`semanticInfo fixtures/non-existent-estree-nodes.src 1`] = ` "type": "Program", } `; - -exports[`semanticInfo malformed project file 1`] = `"Compiler option 'compileOnSave' requires a value of type boolean."`; diff --git a/packages/typescript-estree/tests/lib/semanticInfo.test.ts b/packages/typescript-estree/tests/lib/semanticInfo.test.ts index af90bedb840..a186236d40a 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.test.ts @@ -294,7 +294,9 @@ describe('semanticInfo', () => { fs.readFileSync(fileName, 'utf8'), badConfig, ), - ).toThrowErrorMatchingSnapshot(); + ).toThrowErrorMatchingInlineSnapshot( + `"Compiler option 'compileOnSave' requires a value of type boolean."`, + ); }); } From a9aec015913d55850e0e8b0051da7b1807190f26 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 27 Apr 2023 14:09:03 -0400 Subject: [PATCH 19/32] perf: only openExternalProject once per project --- .../typescript-estree/src/clear-caches.ts | 6 +-- ...ice.ts => createOrUpdateProjectService.ts} | 41 +++++++++++++++- .../src/parseSettings/createParseSettings.ts | 48 ++++++++++--------- .../src/useProgramFromProjectService.ts | 2 +- 4 files changed, 68 insertions(+), 29 deletions(-) rename packages/typescript-estree/src/create-program/{createProjectService.ts => createOrUpdateProjectService.ts} (61%) diff --git a/packages/typescript-estree/src/clear-caches.ts b/packages/typescript-estree/src/clear-caches.ts index 015fd18e29c..c2f3dc990fa 100644 --- a/packages/typescript-estree/src/clear-caches.ts +++ b/packages/typescript-estree/src/clear-caches.ts @@ -1,9 +1,7 @@ +import { clearTSServerProjectService } from './create-program/createOrUpdateProjectService'; import { clearWatchCaches } from './create-program/getWatchProgramsForProjects'; import { clearProgramCache as clearProgramCacheOriginal } from './parser'; -import { - clearTSConfigMatchCache, - clearTSServerProjectService, -} from './parseSettings/createParseSettings'; +import { clearTSConfigMatchCache } from './parseSettings/createParseSettings'; import { clearGlobCache } from './parseSettings/resolveProjectList'; /** diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/typescript-estree/src/create-program/createOrUpdateProjectService.ts similarity index 61% rename from packages/typescript-estree/src/create-program/createProjectService.ts rename to packages/typescript-estree/src/create-program/createOrUpdateProjectService.ts index 0475184652b..95724dd9211 100644 --- a/packages/typescript-estree/src/create-program/createProjectService.ts +++ b/packages/typescript-estree/src/create-program/createOrUpdateProjectService.ts @@ -6,7 +6,46 @@ const createStubFileWatcher = (): tsserver.FileWatcher => ({ close: doNothing, }); -export function createProjectService(): tsserver.server.ProjectService { +const SEEN_PROJECTS = new Set([]); + +let TSSERVER_PROJECT_SERVICE: ReturnType< + typeof createOrUpdateProjectService +> | null = null; + +export function clearTSServerProjectService(): void { + SEEN_PROJECTS.clear(); + TSSERVER_PROJECT_SERVICE = null; +} + +export function createOrUpdateProjectService( + codeFullText: string, + filePath: string, + projectConfigFiles: string[] | null, +): tsserver.server.ProjectService { + TSSERVER_PROJECT_SERVICE ??= createProjectService(); + + if (projectConfigFiles) { + for (const projectFileName of projectConfigFiles) { + if (!SEEN_PROJECTS.has(projectFileName)) { + SEEN_PROJECTS.add(projectFileName); + TSSERVER_PROJECT_SERVICE.openExternalProject({ + options: {}, + projectFileName, + rootFiles: [ + { + content: codeFullText, + fileName: filePath, + }, + ], + }); + } + } + } + + return TSSERVER_PROJECT_SERVICE; +} + +function createProjectService(): tsserver.server.ProjectService { // TODO: see getWatchProgramsForProjects // We don't watch the disk, we just refer to these when ESLint calls us // there's a whole separate update pass in maybeInvalidateProgram at the bottom of getWatchProgramsForProjects diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index e426daaa498..1a3e72dce66 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -1,7 +1,7 @@ import debug from 'debug'; import type * as ts from 'typescript'; -import { createProjectService } from '../create-program/createProjectService'; +import { createOrUpdateProjectService } from '../create-program/createOrUpdateProjectService'; import { ensureAbsolutePath } from '../create-program/shared'; import type { TSESTreeOptions } from '../parser-options'; import { isSourceFile } from '../source-files'; @@ -21,19 +21,31 @@ const log = debug( let TSCONFIG_MATCH_CACHE: ExpiringCache | null; -let TSSERVER_PROJECT_SERVICE: ReturnType | null = - null; - export function createParseSettings( code: string | ts.SourceFile, options: Partial = {}, ): MutableParseSettings { const codeFullText = enforceCodeString(code); - const singleRun = inferSingleRun(options); const tsconfigRootDir = typeof options.tsconfigRootDir === 'string' ? options.tsconfigRootDir : process.cwd(); + const filePath = ensureAbsolutePath( + typeof options.filePath === 'string' && options.filePath !== '' + ? options.filePath + : getFileName(options.jsx), + tsconfigRootDir, + ); + const singleRun = inferSingleRun(options); + const tsconfigMatchCache = (TSCONFIG_MATCH_CACHE ??= new ExpiringCache( + singleRun + ? 'Infinity' + : options.cacheLifetime?.glob ?? DEFAULT_TSCONFIG_CACHE_DURATION_SECONDS, + )); + const projectConfigFiles = getProjectConfigFiles( + { filePath, tsconfigMatchCache, tsconfigRootDir }, + options.project, + ); const parseSettings: MutableParseSettings = { allowInvalidAST: options.allowInvalidAST === true, code, @@ -54,7 +66,11 @@ export function createParseSettings( EXPERIMENTAL_projectService: options.EXPERIMENTAL_useProjectService === true || process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER === 'true' - ? (TSSERVER_PROJECT_SERVICE ??= createProjectService()) + ? createOrUpdateProjectService( + codeFullText, + filePath, + projectConfigFiles, + ) : undefined, EXPERIMENTAL_useSourceOfProjectReferenceRedirect: options.EXPERIMENTAL_useSourceOfProjectReferenceRedirect === true, @@ -63,12 +79,7 @@ export function createParseSettings( options.extraFileExtensions.every(ext => typeof ext === 'string') ? options.extraFileExtensions : [], - filePath: ensureAbsolutePath( - typeof options.filePath === 'string' && options.filePath !== '' - ? options.filePath - : getFileName(options.jsx), - tsconfigRootDir, - ), + filePath, jsx: options.jsx === true, loc: options.loc === true, log: @@ -86,12 +97,7 @@ export function createParseSettings( options.suppressDeprecatedPropertyWarnings ?? process.env.NODE_ENV !== 'test', tokens: options.tokens === true ? [] : null, - tsconfigMatchCache: (TSCONFIG_MATCH_CACHE ??= new ExpiringCache( - singleRun - ? 'Infinity' - : options.cacheLifetime?.glob ?? - DEFAULT_TSCONFIG_CACHE_DURATION_SECONDS, - )), + tsconfigMatchCache, tsconfigRootDir, }; @@ -127,7 +133,7 @@ export function createParseSettings( if (!parseSettings.programs) { parseSettings.projects = resolveProjectList({ cacheLifetime: options.cacheLifetime, - project: getProjectConfigFiles(parseSettings, options.project), + project: projectConfigFiles, projectFolderIgnoreList: options.projectFolderIgnoreList, singleRun: parseSettings.singleRun, tsconfigRootDir: tsconfigRootDir, @@ -143,10 +149,6 @@ export function clearTSConfigMatchCache(): void { TSCONFIG_MATCH_CACHE?.clear(); } -export function clearTSServerProjectService(): void { - TSSERVER_PROJECT_SERVICE = null; -} - /** * Ensures source code is a string. */ diff --git a/packages/typescript-estree/src/useProgramFromProjectService.ts b/packages/typescript-estree/src/useProgramFromProjectService.ts index 3c78ea6d948..6b01d41ed39 100644 --- a/packages/typescript-estree/src/useProgramFromProjectService.ts +++ b/packages/typescript-estree/src/useProgramFromProjectService.ts @@ -17,7 +17,7 @@ export function useProgramFromProjectService( absolutify(parseSettings.filePath), parseSettings.codeFullText, /* scriptKind */ undefined, - parseSettings.tsconfigRootDir, + parseSettings.projects[0], ); if (!opened.configFileName) { return undefined; From be6cc5dc540ec03aabe6d98fe0af23c4b4924262 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 27 Apr 2023 17:29:47 -0400 Subject: [PATCH 20/32] Revert "perf: only openExternalProject once per project" This reverts commit a9aec015913d55850e0e8b0051da7b1807190f26. --- .../typescript-estree/src/clear-caches.ts | 6 ++- ...jectService.ts => createProjectService.ts} | 41 +--------------- .../src/parseSettings/createParseSettings.ts | 48 +++++++++---------- .../src/useProgramFromProjectService.ts | 2 +- 4 files changed, 29 insertions(+), 68 deletions(-) rename packages/typescript-estree/src/create-program/{createOrUpdateProjectService.ts => createProjectService.ts} (61%) diff --git a/packages/typescript-estree/src/clear-caches.ts b/packages/typescript-estree/src/clear-caches.ts index c2f3dc990fa..015fd18e29c 100644 --- a/packages/typescript-estree/src/clear-caches.ts +++ b/packages/typescript-estree/src/clear-caches.ts @@ -1,7 +1,9 @@ -import { clearTSServerProjectService } from './create-program/createOrUpdateProjectService'; import { clearWatchCaches } from './create-program/getWatchProgramsForProjects'; import { clearProgramCache as clearProgramCacheOriginal } from './parser'; -import { clearTSConfigMatchCache } from './parseSettings/createParseSettings'; +import { + clearTSConfigMatchCache, + clearTSServerProjectService, +} from './parseSettings/createParseSettings'; import { clearGlobCache } from './parseSettings/resolveProjectList'; /** diff --git a/packages/typescript-estree/src/create-program/createOrUpdateProjectService.ts b/packages/typescript-estree/src/create-program/createProjectService.ts similarity index 61% rename from packages/typescript-estree/src/create-program/createOrUpdateProjectService.ts rename to packages/typescript-estree/src/create-program/createProjectService.ts index 95724dd9211..0475184652b 100644 --- a/packages/typescript-estree/src/create-program/createOrUpdateProjectService.ts +++ b/packages/typescript-estree/src/create-program/createProjectService.ts @@ -6,46 +6,7 @@ const createStubFileWatcher = (): tsserver.FileWatcher => ({ close: doNothing, }); -const SEEN_PROJECTS = new Set([]); - -let TSSERVER_PROJECT_SERVICE: ReturnType< - typeof createOrUpdateProjectService -> | null = null; - -export function clearTSServerProjectService(): void { - SEEN_PROJECTS.clear(); - TSSERVER_PROJECT_SERVICE = null; -} - -export function createOrUpdateProjectService( - codeFullText: string, - filePath: string, - projectConfigFiles: string[] | null, -): tsserver.server.ProjectService { - TSSERVER_PROJECT_SERVICE ??= createProjectService(); - - if (projectConfigFiles) { - for (const projectFileName of projectConfigFiles) { - if (!SEEN_PROJECTS.has(projectFileName)) { - SEEN_PROJECTS.add(projectFileName); - TSSERVER_PROJECT_SERVICE.openExternalProject({ - options: {}, - projectFileName, - rootFiles: [ - { - content: codeFullText, - fileName: filePath, - }, - ], - }); - } - } - } - - return TSSERVER_PROJECT_SERVICE; -} - -function createProjectService(): tsserver.server.ProjectService { +export function createProjectService(): tsserver.server.ProjectService { // TODO: see getWatchProgramsForProjects // We don't watch the disk, we just refer to these when ESLint calls us // there's a whole separate update pass in maybeInvalidateProgram at the bottom of getWatchProgramsForProjects diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index 1a3e72dce66..e426daaa498 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -1,7 +1,7 @@ import debug from 'debug'; import type * as ts from 'typescript'; -import { createOrUpdateProjectService } from '../create-program/createOrUpdateProjectService'; +import { createProjectService } from '../create-program/createProjectService'; import { ensureAbsolutePath } from '../create-program/shared'; import type { TSESTreeOptions } from '../parser-options'; import { isSourceFile } from '../source-files'; @@ -21,31 +21,19 @@ const log = debug( let TSCONFIG_MATCH_CACHE: ExpiringCache | null; +let TSSERVER_PROJECT_SERVICE: ReturnType | null = + null; + export function createParseSettings( code: string | ts.SourceFile, options: Partial = {}, ): MutableParseSettings { const codeFullText = enforceCodeString(code); + const singleRun = inferSingleRun(options); const tsconfigRootDir = typeof options.tsconfigRootDir === 'string' ? options.tsconfigRootDir : process.cwd(); - const filePath = ensureAbsolutePath( - typeof options.filePath === 'string' && options.filePath !== '' - ? options.filePath - : getFileName(options.jsx), - tsconfigRootDir, - ); - const singleRun = inferSingleRun(options); - const tsconfigMatchCache = (TSCONFIG_MATCH_CACHE ??= new ExpiringCache( - singleRun - ? 'Infinity' - : options.cacheLifetime?.glob ?? DEFAULT_TSCONFIG_CACHE_DURATION_SECONDS, - )); - const projectConfigFiles = getProjectConfigFiles( - { filePath, tsconfigMatchCache, tsconfigRootDir }, - options.project, - ); const parseSettings: MutableParseSettings = { allowInvalidAST: options.allowInvalidAST === true, code, @@ -66,11 +54,7 @@ export function createParseSettings( EXPERIMENTAL_projectService: options.EXPERIMENTAL_useProjectService === true || process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER === 'true' - ? createOrUpdateProjectService( - codeFullText, - filePath, - projectConfigFiles, - ) + ? (TSSERVER_PROJECT_SERVICE ??= createProjectService()) : undefined, EXPERIMENTAL_useSourceOfProjectReferenceRedirect: options.EXPERIMENTAL_useSourceOfProjectReferenceRedirect === true, @@ -79,7 +63,12 @@ export function createParseSettings( options.extraFileExtensions.every(ext => typeof ext === 'string') ? options.extraFileExtensions : [], - filePath, + filePath: ensureAbsolutePath( + typeof options.filePath === 'string' && options.filePath !== '' + ? options.filePath + : getFileName(options.jsx), + tsconfigRootDir, + ), jsx: options.jsx === true, loc: options.loc === true, log: @@ -97,7 +86,12 @@ export function createParseSettings( options.suppressDeprecatedPropertyWarnings ?? process.env.NODE_ENV !== 'test', tokens: options.tokens === true ? [] : null, - tsconfigMatchCache, + tsconfigMatchCache: (TSCONFIG_MATCH_CACHE ??= new ExpiringCache( + singleRun + ? 'Infinity' + : options.cacheLifetime?.glob ?? + DEFAULT_TSCONFIG_CACHE_DURATION_SECONDS, + )), tsconfigRootDir, }; @@ -133,7 +127,7 @@ export function createParseSettings( if (!parseSettings.programs) { parseSettings.projects = resolveProjectList({ cacheLifetime: options.cacheLifetime, - project: projectConfigFiles, + project: getProjectConfigFiles(parseSettings, options.project), projectFolderIgnoreList: options.projectFolderIgnoreList, singleRun: parseSettings.singleRun, tsconfigRootDir: tsconfigRootDir, @@ -149,6 +143,10 @@ export function clearTSConfigMatchCache(): void { TSCONFIG_MATCH_CACHE?.clear(); } +export function clearTSServerProjectService(): void { + TSSERVER_PROJECT_SERVICE = null; +} + /** * Ensures source code is a string. */ diff --git a/packages/typescript-estree/src/useProgramFromProjectService.ts b/packages/typescript-estree/src/useProgramFromProjectService.ts index 6b01d41ed39..3c78ea6d948 100644 --- a/packages/typescript-estree/src/useProgramFromProjectService.ts +++ b/packages/typescript-estree/src/useProgramFromProjectService.ts @@ -17,7 +17,7 @@ export function useProgramFromProjectService( absolutify(parseSettings.filePath), parseSettings.codeFullText, /* scriptKind */ undefined, - parseSettings.projects[0], + parseSettings.tsconfigRootDir, ); if (!opened.configFileName) { return undefined; From 80110c16cc9dd0a4cd7fd80c2e6a00b422b8f2b1 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 27 Apr 2023 18:00:04 -0400 Subject: [PATCH 21/32] Reverted changes to allow alternate TSConfig names --- .../tests/rules/consistent-type-imports.test.ts | 1 + .../tests/rules/no-unnecessary-condition.test.ts | 1 + .../eslint-plugin/tests/rules/no-unsafe-assignment.test.ts | 1 + packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts | 1 + .../tests/rules/no-unsafe-member-access.test.ts | 1 + packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts | 1 + .../tests/rules/no-unused-vars/no-unused-vars.test.ts | 1 + .../tests/rules/non-nullable-type-assertion-style.test.ts | 1 + packages/rule-tester/tests/RuleTester.test.ts | 6 ++++-- .../src/parseSettings/createParseSettings.ts | 6 ++++-- 10 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts index 4d480066467..815570e441c 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts @@ -14,6 +14,7 @@ const ruleTester = new RuleTester({ }); const withMetaParserOptions = { + EXPERIMENTAL_useProjectService: false, tsconfigRootDir: getFixturesRootDir(), project: './tsconfig-withmeta.json', }; diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts index 785ae3e8c1a..cdf036d295c 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -594,6 +594,7 @@ function getElem(dict: Record, key: string) { } `, parserOptions: { + EXPERIMENTAL_useProjectService: false, tsconfigRootDir: getFixturesRootDir(), project: './tsconfig.noUncheckedIndexedAccess.json', }, diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-assignment.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-assignment.test.ts index f0d32d2ed47..920ecd8ec15 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-assignment.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-assignment.test.ts @@ -69,6 +69,7 @@ function assignmentTest( const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', parserOptions: { + EXPERIMENTAL_useProjectService: false, project: './tsconfig.noImplicitThis.json', tsconfigRootDir: getFixturesRootDir(), }, diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts index db71189a697..2ad9d549aa2 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts @@ -9,6 +9,7 @@ import { const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', parserOptions: { + EXPERIMENTAL_useProjectService: false, project: './tsconfig.noImplicitThis.json', tsconfigRootDir: getFixturesRootDir(), }, diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts index 5ab598c3a5a..a2ae6d06be6 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts @@ -9,6 +9,7 @@ import { const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', parserOptions: { + EXPERIMENTAL_useProjectService: false, project: './tsconfig.noImplicitThis.json', tsconfigRootDir: getFixturesRootDir(), }, diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts index 47ec9701a77..50dd63b3716 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts @@ -9,6 +9,7 @@ import { const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', parserOptions: { + EXPERIMENTAL_useProjectService: false, project: './tsconfig.noImplicitThis.json', tsconfigRootDir: getFixturesRootDir(), }, diff --git a/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts b/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts index ee2191a3f4c..9c0a9dc7e44 100644 --- a/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts @@ -12,6 +12,7 @@ const ruleTester = new RuleTester({ }); const withMetaParserOptions = { + EXPERIMENTAL_useProjectService: false, tsconfigRootDir: getFixturesRootDir(), project: './tsconfig-withmeta.json', }; diff --git a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts index 6826230b4fd..5f215185c6e 100644 --- a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts +++ b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts @@ -202,6 +202,7 @@ const y = x!; const ruleTesterWithNoUncheckedIndexAccess = new RuleTester({ parserOptions: { + EXPERIMENTAL_useProjectService: false, sourceType: 'module', tsconfigRootDir: getFixturesRootDir(), project: './tsconfig.noUncheckedIndexedAccess.json', diff --git a/packages/rule-tester/tests/RuleTester.test.ts b/packages/rule-tester/tests/RuleTester.test.ts index 6ed3edc23f3..37c7acd802c 100644 --- a/packages/rule-tester/tests/RuleTester.test.ts +++ b/packages/rule-tester/tests/RuleTester.test.ts @@ -8,10 +8,10 @@ import type { RuleTesterTestFrameworkFunctionBase } from '../src/TestFramework'; import * as dependencyConstraintsModule from '../src/utils/dependencyConstraints'; // we can't spy on the exports of an ES module - so we instead have to mock the entire module -jest.mock('../src/dependencyConstraints', () => { +jest.mock('../src/utils/dependencyConstraints', () => { const dependencyConstraints = jest.requireActual< typeof dependencyConstraintsModule - >('../src/dependencyConstraints'); + >('../src/utils/dependencyConstraints'); return { ...dependencyConstraints, @@ -169,6 +169,7 @@ describe('RuleTester', () => { { code: 'type-aware parser options should override the constructor config', parserOptions: { + EXPERIMENTAL_useProjectService: false, project: 'tsconfig.test-specific.json', tsconfigRootDir: '/set/in/the/test/', }, @@ -209,6 +210,7 @@ describe('RuleTester', () => { "code": "type-aware parser options should override the constructor config", "filename": "/set/in/the/test/file.ts", "parserOptions": { + "EXPERIMENTAL_useProjectService": false, "project": "tsconfig.test-specific.json", "tsconfigRootDir": "/set/in/the/test/", }, diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index e426daaa498..f6506f87eeb 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -52,8 +52,10 @@ export function createParseSettings( errorOnTypeScriptSyntacticAndSemanticIssues: false, errorOnUnknownASTType: options.errorOnUnknownASTType === true, EXPERIMENTAL_projectService: - options.EXPERIMENTAL_useProjectService === true || - process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER === 'true' + (options.EXPERIMENTAL_useProjectService === true && + process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'false') || + (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER === 'true' && + options.EXPERIMENTAL_useProjectService !== false) ? (TSSERVER_PROJECT_SERVICE ??= createProjectService()) : undefined, EXPERIMENTAL_useSourceOfProjectReferenceRedirect: From c326886b5314d24fa35cd407c606353b7200b53b Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 27 Apr 2023 20:07:37 -0400 Subject: [PATCH 22/32] Remove project existence checking --- .../src/useProgramFromProjectService.ts | 15 +--- .../typescript-estree/tests/lib/parse.test.ts | 76 ++++++++++--------- .../tests/lib/semanticInfo.test.ts | 52 ++++++------- 3 files changed, 66 insertions(+), 77 deletions(-) diff --git a/packages/typescript-estree/src/useProgramFromProjectService.ts b/packages/typescript-estree/src/useProgramFromProjectService.ts index 3c78ea6d948..16a7933a671 100644 --- a/packages/typescript-estree/src/useProgramFromProjectService.ts +++ b/packages/typescript-estree/src/useProgramFromProjectService.ts @@ -3,10 +3,7 @@ import type * as ts from 'typescript'; import type { server } from 'typescript/lib/tsserverlibrary'; import { createProjectProgram } from './create-program/createProjectProgram'; -import { - type ASTAndDefiniteProgram, - getCanonicalFileName, -} from './create-program/shared'; +import { type ASTAndDefiniteProgram } from './create-program/shared'; import type { MutableParseSettings } from './parseSettings'; export function useProgramFromProjectService( @@ -33,16 +30,6 @@ export function useProgramFromProjectService( return undefined; } - const configFilePath = program.getCompilerOptions().configFilePath as string; - if ( - !parseSettings.projects - .map(absolutify) - .map(getCanonicalFileName) - .includes(getCanonicalFileName(configFilePath)) - ) { - throw new Error(`Cannot read file '${parseSettings.projects[0]}'`); - } - return createProjectProgram(parseSettings, [program as ts.Program]); function absolutify(filePath: string): string { diff --git a/packages/typescript-estree/tests/lib/parse.test.ts b/packages/typescript-estree/tests/lib/parse.test.ts index 6e2adac8f87..9040150d46d 100644 --- a/packages/typescript-estree/tests/lib/parse.test.ts +++ b/packages/typescript-estree/tests/lib/parse.test.ts @@ -690,48 +690,50 @@ describe('parseAndGenerateServices', () => { }); }); - describe('projectFolderIgnoreList', () => { - beforeEach(() => { - parser.clearCaches(); - }); - - const PROJECT_DIR = resolve(FIXTURES_DIR, '../projectFolderIgnoreList'); - const code = 'var a = true'; - const config: TSESTreeOptions = { - comment: true, - tokens: true, - range: true, - loc: true, - tsconfigRootDir: PROJECT_DIR, - project: './**/tsconfig.json', - }; + if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { + describe('projectFolderIgnoreList', () => { + beforeEach(() => { + parser.clearCaches(); + }); - const testParse = - ( - filePath: 'ignoreme' | 'includeme', - projectFolderIgnoreList?: TSESTreeOptions['projectFolderIgnoreList'], - ) => - (): void => { - parser.parseAndGenerateServices(code, { - ...config, - projectFolderIgnoreList, - filePath: join(PROJECT_DIR, filePath, './file.ts'), - }); + const PROJECT_DIR = resolve(FIXTURES_DIR, '../projectFolderIgnoreList'); + const code = 'var a = true'; + const config: TSESTreeOptions = { + comment: true, + tokens: true, + range: true, + loc: true, + tsconfigRootDir: PROJECT_DIR, + project: './**/tsconfig.json', }; - it('ignores nothing when given nothing', () => { - expect(testParse('ignoreme')).not.toThrow(); - expect(testParse('includeme')).not.toThrow(); - }); + const testParse = + ( + filePath: 'ignoreme' | 'includeme', + projectFolderIgnoreList?: TSESTreeOptions['projectFolderIgnoreList'], + ) => + (): void => { + parser.parseAndGenerateServices(code, { + ...config, + projectFolderIgnoreList, + filePath: join(PROJECT_DIR, filePath, './file.ts'), + }); + }; - it('ignores a folder when given a string glob', () => { - const ignore = ['**/ignoreme/**']; - // cspell:disable-next-line - expect(testParse('ignoreme', ignore)).toThrow(); - // cspell:disable-next-line - expect(testParse('includeme', ignore)).not.toThrow(); + it('ignores nothing when given nothing', () => { + expect(testParse('ignoreme')).not.toThrow(); + expect(testParse('includeme')).not.toThrow(); + }); + + it('ignores a folder when given a string glob', () => { + const ignore = ['**/ignoreme/**']; + // cspell:disable-next-line + expect(testParse('ignoreme', ignore)).toThrow(); + // cspell:disable-next-line + expect(testParse('includeme', ignore)).not.toThrow(); + }); }); - }); + } describe('cacheLifetime', () => { describe('glob', () => { diff --git a/packages/typescript-estree/tests/lib/semanticInfo.test.ts b/packages/typescript-estree/tests/lib/semanticInfo.test.ts index a186236d40a..a12c0a5c498 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.test.ts @@ -257,34 +257,34 @@ describe('semanticInfo', () => { ); }); - it('non-existent project file', () => { - const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); - const badConfig = createOptions(fileName); - badConfig.project = './tsconfigs.json'; - expect(() => - parseCodeAndGenerateServices( - fs.readFileSync(fileName, 'utf8'), - badConfig, - ), - ).toThrow(/Cannot read file .+tsconfigs\.json'/); - }); + if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { + it('non-existent project file', () => { + const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); + const badConfig = createOptions(fileName); + badConfig.project = './tsconfigs.json'; + expect(() => + parseCodeAndGenerateServices( + fs.readFileSync(fileName, 'utf8'), + badConfig, + ), + ).toThrow(/Cannot read file .+tsconfigs\.json'/); + }); - it('fail to read project file', () => { - const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); - const badConfig = createOptions(fileName); - badConfig.project = '.'; - expect(() => - parseCodeAndGenerateServices( - fs.readFileSync(fileName, 'utf8'), - badConfig, - ), - ).toThrow( - // case insensitive because unix based systems are case insensitive - /Cannot read file .+semanticInfo'/i, - ); - }); + it('fail to read project file', () => { + const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); + const badConfig = createOptions(fileName); + badConfig.project = '.'; + expect(() => + parseCodeAndGenerateServices( + fs.readFileSync(fileName, 'utf8'), + badConfig, + ), + ).toThrow( + // case insensitive because unix based systems are case insensitive + /Cannot read file .+semanticInfo'/i, + ); + }); - if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { it('malformed project file', () => { const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); const badConfig = createOptions(fileName); From a5772e22678abdaa033a64796824085d1605d572 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 27 Apr 2023 20:13:01 -0400 Subject: [PATCH 23/32] Add linting from root --- .github/workflows/ci.yml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb110f768d2..5ae9d0f6a14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,10 +91,29 @@ jobs: node-version: ${{ env.PRIMARY_NODE_VERSION }} - name: Build uses: ./.github/actions/prepare-build - - - name: Run Check + - name: Run Lint run: yarn ${{ matrix.lint-task }} + lint_with_build_from_root: + name: Lint From Root + # because we lint with our own tooling, we need to build + needs: [build] + runs-on: ubuntu-latest + strategy: + matrix: + tsserver: ['false', 'true'] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install + uses: ./.github/actions/prepare-install + with: + node-version: ${{ env.PRIMARY_NODE_VERSION }} + - name: Build + uses: ./.github/actions/prepare-build + - name: Run Lint + run: TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER=${{ matrix.tsserver }} npx eslint . + stylelint: name: Stylelint needs: [install] From 128837a9dfa0a8b81df3cc0506f4a0b0c6316cc8 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 27 Apr 2023 20:27:18 -0400 Subject: [PATCH 24/32] Refactor CI naming a bit, and bumping to MacOS image... --- .github/workflows/ci.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ae9d0f6a14..7fdebde73f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,7 +98,7 @@ jobs: name: Lint From Root # because we lint with our own tooling, we need to build needs: [build] - runs-on: ubuntu-latest + runs-on: macos-latest strategy: matrix: tsserver: ['false', 'true'] @@ -111,8 +111,10 @@ jobs: node-version: ${{ env.PRIMARY_NODE_VERSION }} - name: Build uses: ./.github/actions/prepare-build - - name: Run Lint - run: TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER=${{ matrix.tsserver }} npx eslint . + - env: + TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER: true + name: Run Lint With TSServer ${{ matrix.tsserver }} + run: npx eslint . stylelint: name: Stylelint From 4f52573323e675ffdfb6b9b93361ff53f9692990 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 28 Apr 2023 00:10:30 -0400 Subject: [PATCH 25/32] Alas, linting from root style runs out of memory --- .github/workflows/ci.yml | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7fdebde73f7..d54750c9a73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,28 +94,6 @@ jobs: - name: Run Lint run: yarn ${{ matrix.lint-task }} - lint_with_build_from_root: - name: Lint From Root - # because we lint with our own tooling, we need to build - needs: [build] - runs-on: macos-latest - strategy: - matrix: - tsserver: ['false', 'true'] - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Install - uses: ./.github/actions/prepare-install - with: - node-version: ${{ env.PRIMARY_NODE_VERSION }} - - name: Build - uses: ./.github/actions/prepare-build - - env: - TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER: true - name: Run Lint With TSServer ${{ matrix.tsserver }} - run: npx eslint . - stylelint: name: Stylelint needs: [install] From b1da422528e61ccced378d19a1559a0a9d32b39f Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 28 Apr 2023 00:48:55 -0400 Subject: [PATCH 26/32] Added a test, why not --- .../tests/lib/createProjectService.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/typescript-estree/tests/lib/createProjectService.test.ts diff --git a/packages/typescript-estree/tests/lib/createProjectService.test.ts b/packages/typescript-estree/tests/lib/createProjectService.test.ts new file mode 100644 index 00000000000..f6f6d117e3b --- /dev/null +++ b/packages/typescript-estree/tests/lib/createProjectService.test.ts @@ -0,0 +1,7 @@ +import { createProjectService } from '../../src/create-program/createProjectService'; + +describe('createProjectService', () => { + it('does not crash', () => { + createProjectService(); + }); +}); From 814a8ad8a7aeb57a7b2cb9862cf414b2b901a14f Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 3 May 2023 16:37:28 -0400 Subject: [PATCH 27/32] fix: don't fall back to default program/project creation --- .../src/parseSettings/createParseSettings.ts | 4 ++-- packages/typescript-estree/src/parser.ts | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index f6506f87eeb..4c1a704ae04 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -125,8 +125,8 @@ export function createParseSettings( ); } - // Providing a program overrides project resolution - if (!parseSettings.programs) { + // Providing a program or project service overrides project resolution + if (!parseSettings.programs && !parseSettings.EXPERIMENTAL_projectService) { parseSettings.projects = resolveProjectList({ cacheLifetime: options.cacheLifetime, project: getProjectConfigFiles(parseSettings, options.project), diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 33504716e45..12090b7e220 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -49,6 +49,16 @@ function getProgramAndAST( parseSettings: ParseSettings, hasFullTypeInformation: boolean, ): ASTAndProgram { + if (parseSettings.EXPERIMENTAL_projectService) { + const fromProjectService = useProgramFromProjectService( + parseSettings.EXPERIMENTAL_projectService, + parseSettings, + ); + if (fromProjectService) { + return fromProjectService; + } + } + if (parseSettings.programs) { const fromProvidedPrograms = useProvidedPrograms( parseSettings.programs, @@ -65,16 +75,6 @@ function getProgramAndAST( return createNoProgram(parseSettings); } - if (parseSettings.EXPERIMENTAL_projectService) { - const fromProjectService = useProgramFromProjectService( - parseSettings.EXPERIMENTAL_projectService, - parseSettings, - ); - if (fromProjectService) { - return fromProjectService; - } - } - const fromProjectProgram = createProjectProgram( parseSettings, getWatchProgramsForProjects(parseSettings), From a5180c7a8435ae9b37d3f551370b9167cef01b7e Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 3 May 2023 17:50:19 -0400 Subject: [PATCH 28/32] Fixed up more test exclusions --- .../lib/__snapshots__/parse.test.ts.snap | 82 ---- .../tests/lib/parse.project-true.test.ts | 22 +- .../typescript-estree/tests/lib/parse.test.ts | 391 ++++++++++-------- .../tests/lib/persistentParse.test.ts | 66 +-- .../tests/lib/semanticInfo-singleRun.test.ts | 248 +++++------ .../tests/lib/semanticInfo.test.ts | 56 +-- 6 files changed, 436 insertions(+), 429 deletions(-) diff --git a/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap index 8c2e7861435..b1769a32059 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/parse.test.ts.snap @@ -1,87 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`parseAndGenerateServices invalid file error messages "parserOptions.extraFileExtensions" is empty the extension does not match 1`] = ` -"ESLint was configured to run on \`/other/unknownFileType.unknown\` using \`parserOptions.project\`: /tsconfig.json -The extension for the file (\`.unknown\`) is non-standard. You should add \`parserOptions.extraFileExtensions\` to your config." -`; - -exports[`parseAndGenerateServices invalid file error messages "parserOptions.extraFileExtensions" is non-empty invalid extension 1`] = ` -"ESLint was configured to run on \`/other/unknownFileType.unknown\` using \`parserOptions.project\`: /tsconfig.json -Found unexpected extension \`unknown\` specified with the \`parserOptions.extraFileExtensions\` option. Did you mean \`.unknown\`? -The extension for the file (\`.unknown\`) is non-standard. It should be added to your existing \`parserOptions.extraFileExtensions\`." -`; - -exports[`parseAndGenerateServices invalid file error messages "parserOptions.extraFileExtensions" is non-empty the extension does not match 1`] = ` -"ESLint was configured to run on \`/other/unknownFileType.unknown\` using \`parserOptions.project\`: /tsconfig.json -The extension for the file (\`.unknown\`) is non-standard. It should be added to your existing \`parserOptions.extraFileExtensions\`." -`; - -exports[`parseAndGenerateServices invalid file error messages "parserOptions.extraFileExtensions" is non-empty the extension matches duplicate extension 1`] = ` -"ESLint was configured to run on \`/ts/notIncluded.ts\` using \`parserOptions.project\`: /tsconfig.json -You unnecessarily included the extension \`.ts\` with the \`parserOptions.extraFileExtensions\` option. This extension is already handled by the parser by default. -However, that TSConfig does not include this file. Either: -- Change ESLint's list of included files to not include this file -- Change that TSConfig to include this file -- Create a new TSConfig that includes this file and include it in your parserOptions.project -See the typescript-eslint docs for more info: https://typescript-eslint.io/linting/troubleshooting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" -`; - -exports[`parseAndGenerateServices invalid file error messages "parserOptions.extraFileExtensions" is non-empty the extension matches the file isn't included 1`] = ` -"ESLint was configured to run on \`/other/notIncluded.vue\` using \`parserOptions.project\`: /tsconfig.json -However, that TSConfig does not include this file. Either: -- Change ESLint's list of included files to not include this file -- Change that TSConfig to include this file -- Create a new TSConfig that includes this file and include it in your parserOptions.project -See the typescript-eslint docs for more info: https://typescript-eslint.io/linting/troubleshooting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" -`; - -exports[`parseAndGenerateServices invalid file error messages project includes errors for not included files 1`] = ` -"ESLint was configured to run on \`/ts/notIncluded0j1.ts\` using \`parserOptions.project\`: /tsconfig.json -However, that TSConfig does not include this file. Either: -- Change ESLint's list of included files to not include this file -- Change that TSConfig to include this file -- Create a new TSConfig that includes this file and include it in your parserOptions.project -See the typescript-eslint docs for more info: https://typescript-eslint.io/linting/troubleshooting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" -`; - -exports[`parseAndGenerateServices invalid file error messages project includes errors for not included files 2`] = ` -"ESLint was configured to run on \`/ts/notIncluded02.tsx\` using \`parserOptions.project\`: /tsconfig.json -However, that TSConfig does not include this file. Either: -- Change ESLint's list of included files to not include this file -- Change that TSConfig to include this file -- Create a new TSConfig that includes this file and include it in your parserOptions.project -See the typescript-eslint docs for more info: https://typescript-eslint.io/linting/troubleshooting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" -`; - -exports[`parseAndGenerateServices invalid file error messages project includes errors for not included files 3`] = ` -"ESLint was configured to run on \`/js/notIncluded01.js\` using \`parserOptions.project\`: /tsconfig.json -However, that TSConfig does not include this file. Either: -- Change ESLint's list of included files to not include this file -- Change that TSConfig to include this file -- Create a new TSConfig that includes this file and include it in your parserOptions.project -See the typescript-eslint docs for more info: https://typescript-eslint.io/linting/troubleshooting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" -`; - -exports[`parseAndGenerateServices invalid file error messages project includes errors for not included files 4`] = ` -"ESLint was configured to run on \`/js/notIncluded02.jsx\` using \`parserOptions.project\`: /tsconfig.json -However, that TSConfig does not include this file. Either: -- Change ESLint's list of included files to not include this file -- Change that TSConfig to include this file -- Create a new TSConfig that includes this file and include it in your parserOptions.project -See the typescript-eslint docs for more info: https://typescript-eslint.io/linting/troubleshooting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" -`; - -exports[`parseAndGenerateServices invalid project error messages throws when non of multiple projects include the file 1`] = ` -"ESLint was configured to run on \`/ts/notIncluded0j1.ts\` using \`parserOptions.project\`: -- /tsconfig.json -- /tsconfig.extra.json -However, none of those TSConfigs include this file. Either: -- Change ESLint's list of included files to not include this file -- Change one of those TSConfigs to include this file -- Create a new TSConfig that includes this file and include it in your parserOptions.project -See the typescript-eslint docs for more info: https://typescript-eslint.io/linting/troubleshooting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" -`; - exports[`parseAndGenerateServices isolated parsing should parse .js file - with JSX content - parserOptions.jsx = false 1`] = ` { "ast": { diff --git a/packages/typescript-estree/tests/lib/parse.project-true.test.ts b/packages/typescript-estree/tests/lib/parse.project-true.test.ts index ff2ea0d0e3f..ca81ab99f50 100644 --- a/packages/typescript-estree/tests/lib/parse.project-true.test.ts +++ b/packages/typescript-estree/tests/lib/parse.project-true.test.ts @@ -35,15 +35,17 @@ describe('parseAndGenerateServices', () => { }); }); - 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'./, - ); - }); + if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { + 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'./, + ); + }); + } }); }); diff --git a/packages/typescript-estree/tests/lib/parse.test.ts b/packages/typescript-estree/tests/lib/parse.test.ts index 9040150d46d..b00286b66bb 100644 --- a/packages/typescript-estree/tests/lib/parse.test.ts +++ b/packages/typescript-estree/tests/lib/parse.test.ts @@ -339,6 +339,7 @@ describe('parseAndGenerateServices', () => { describe('isolated parsing', () => { const config: TSESTreeOptions = { + EXPERIMENTAL_useProjectService: false, comment: true, tokens: true, range: true, @@ -512,124 +513,184 @@ describe('parseAndGenerateServices', () => { }); }); - describe('invalid file error messages', () => { - const PROJECT_DIR = resolve(FIXTURES_DIR, '../invalidFileErrors'); - const code = 'var a = true'; - const config: TSESTreeOptions = { - comment: true, - tokens: true, - range: true, - loc: true, - tsconfigRootDir: PROJECT_DIR, - project: './tsconfig.json', - }; - const testParse = - (filePath: string, extraFileExtensions: string[] = ['.vue']) => - (): void => { - try { - parser.parseAndGenerateServices(code, { - ...config, - extraFileExtensions, - filePath: join(PROJECT_DIR, filePath), - }); - } catch (error) { - throw alignErrorPath(error as Error); - } + if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { + describe('invalid file error messages', () => { + const PROJECT_DIR = resolve(FIXTURES_DIR, '../invalidFileErrors'); + const code = 'var a = true'; + const config: TSESTreeOptions = { + comment: true, + tokens: true, + range: true, + loc: true, + tsconfigRootDir: PROJECT_DIR, + project: './tsconfig.json', }; + const testParse = + (filePath: string, extraFileExtensions: string[] = ['.vue']) => + (): void => { + try { + parser.parseAndGenerateServices(code, { + ...config, + extraFileExtensions, + filePath: join(PROJECT_DIR, filePath), + }); + } catch (error) { + throw alignErrorPath(error as Error); + } + }; - describe('project includes', () => { - it("doesn't error for matched files", () => { - expect(testParse('ts/included01.ts')).not.toThrow(); - expect(testParse('ts/included02.tsx')).not.toThrow(); - expect(testParse('js/included01.js')).not.toThrow(); - expect(testParse('js/included02.jsx')).not.toThrow(); - }); + describe('project includes', () => { + it("doesn't error for matched files", () => { + expect(testParse('ts/included01.ts')).not.toThrow(); + expect(testParse('ts/included02.tsx')).not.toThrow(); + expect(testParse('js/included01.js')).not.toThrow(); + expect(testParse('js/included02.jsx')).not.toThrow(); + }); - it('errors for not included files', () => { - expect( - testParse('ts/notIncluded0j1.ts'), - ).toThrowErrorMatchingSnapshot(); - expect( - testParse('ts/notIncluded02.tsx'), - ).toThrowErrorMatchingSnapshot(); - expect(testParse('js/notIncluded01.js')).toThrowErrorMatchingSnapshot(); - expect( - testParse('js/notIncluded02.jsx'), - ).toThrowErrorMatchingSnapshot(); + it('errors for not included files', () => { + expect(testParse('ts/notIncluded0j1.ts')) + .toThrowErrorMatchingInlineSnapshot(` + "ESLint was configured to run on \`/ts/notIncluded0j1.ts\` using \`parserOptions.project\`: /tsconfig.json + However, that TSConfig does not include this file. Either: + - Change ESLint's list of included files to not include this file + - Change that TSConfig to include this file + - Create a new TSConfig that includes this file and include it in your parserOptions.project + See the typescript-eslint docs for more info: https://typescript-eslint.io/linting/troubleshooting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" + `); + expect(testParse('ts/notIncluded02.tsx')) + .toThrowErrorMatchingInlineSnapshot(` + "ESLint was configured to run on \`/ts/notIncluded02.tsx\` using \`parserOptions.project\`: /tsconfig.json + However, that TSConfig does not include this file. Either: + - Change ESLint's list of included files to not include this file + - Change that TSConfig to include this file + - Create a new TSConfig that includes this file and include it in your parserOptions.project + See the typescript-eslint docs for more info: https://typescript-eslint.io/linting/troubleshooting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" + `); + expect(testParse('js/notIncluded01.js')) + .toThrowErrorMatchingInlineSnapshot(` + "ESLint was configured to run on \`/js/notIncluded01.js\` using \`parserOptions.project\`: /tsconfig.json + However, that TSConfig does not include this file. Either: + - Change ESLint's list of included files to not include this file + - Change that TSConfig to include this file + - Create a new TSConfig that includes this file and include it in your parserOptions.project + See the typescript-eslint docs for more info: https://typescript-eslint.io/linting/troubleshooting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" + `); + expect(testParse('js/notIncluded02.jsx')) + .toThrowErrorMatchingInlineSnapshot(` + "ESLint was configured to run on \`/js/notIncluded02.jsx\` using \`parserOptions.project\`: /tsconfig.json + However, that TSConfig does not include this file. Either: + - Change ESLint's list of included files to not include this file + - Change that TSConfig to include this file + - Create a new TSConfig that includes this file and include it in your parserOptions.project + See the typescript-eslint docs for more info: https://typescript-eslint.io/linting/troubleshooting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" + `); + }); }); - }); - describe('"parserOptions.extraFileExtensions" is empty', () => { - it('should not error', () => { - expect(testParse('ts/included01.ts', [])).not.toThrow(); - }); + describe('"parserOptions.extraFileExtensions" is empty', () => { + it('should not error', () => { + expect(testParse('ts/included01.ts', [])).not.toThrow(); + }); - it('the extension does not match', () => { - expect( - testParse('other/unknownFileType.unknown', []), - ).toThrowErrorMatchingSnapshot(); + it('the extension does not match', () => { + expect(testParse('other/unknownFileType.unknown', [])) + .toThrowErrorMatchingInlineSnapshot(` + "ESLint was configured to run on \`/other/unknownFileType.unknown\` using \`parserOptions.project\`: /tsconfig.json + The extension for the file (\`.unknown\`) is non-standard. You should add \`parserOptions.extraFileExtensions\` to your config." + `); + }); }); - }); - describe('"parserOptions.extraFileExtensions" is non-empty', () => { - describe('the extension matches', () => { - it('the file is included', () => { - expect(testParse('other/included.vue')).not.toThrow(); - }); + describe('"parserOptions.extraFileExtensions" is non-empty', () => { + describe('the extension matches', () => { + it('the file is included', () => { + expect(testParse('other/included.vue')).not.toThrow(); + }); - it("the file isn't included", () => { - expect( - testParse('other/notIncluded.vue'), - ).toThrowErrorMatchingSnapshot(); - }); + it("the file isn't included", () => { + expect(testParse('other/notIncluded.vue')) + .toThrowErrorMatchingInlineSnapshot(` + "ESLint was configured to run on \`/other/notIncluded.vue\` using \`parserOptions.project\`: /tsconfig.json + However, that TSConfig does not include this file. Either: + - Change ESLint's list of included files to not include this file + - Change that TSConfig to include this file + - Create a new TSConfig that includes this file and include it in your parserOptions.project + See the typescript-eslint docs for more info: https://typescript-eslint.io/linting/troubleshooting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" + `); + }); - it('duplicate extension', () => { - expect( - testParse('ts/notIncluded.ts', ['.ts']), - ).toThrowErrorMatchingSnapshot(); + it('duplicate extension', () => { + expect(testParse('ts/notIncluded.ts', ['.ts'])) + .toThrowErrorMatchingInlineSnapshot(` + "ESLint was configured to run on \`/ts/notIncluded.ts\` using \`parserOptions.project\`: /tsconfig.json + You unnecessarily included the extension \`.ts\` with the \`parserOptions.extraFileExtensions\` option. This extension is already handled by the parser by default. + However, that TSConfig does not include this file. Either: + - Change ESLint's list of included files to not include this file + - Change that TSConfig to include this file + - Create a new TSConfig that includes this file and include it in your parserOptions.project + See the typescript-eslint docs for more info: https://typescript-eslint.io/linting/troubleshooting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" + `); + }); }); - }); - it('invalid extension', () => { - expect( - testParse('other/unknownFileType.unknown', ['unknown']), - ).toThrowErrorMatchingSnapshot(); - }); + it('invalid extension', () => { + expect(testParse('other/unknownFileType.unknown', ['unknown'])) + .toThrowErrorMatchingInlineSnapshot(` + "ESLint was configured to run on \`/other/unknownFileType.unknown\` using \`parserOptions.project\`: /tsconfig.json + Found unexpected extension \`unknown\` specified with the \`parserOptions.extraFileExtensions\` option. Did you mean \`.unknown\`? + The extension for the file (\`.unknown\`) is non-standard. It should be added to your existing \`parserOptions.extraFileExtensions\`." + `); + }); - it('the extension does not match', () => { - expect( - testParse('other/unknownFileType.unknown'), - ).toThrowErrorMatchingSnapshot(); + it('the extension does not match', () => { + expect(testParse('other/unknownFileType.unknown')) + .toThrowErrorMatchingInlineSnapshot(` + "ESLint was configured to run on \`/other/unknownFileType.unknown\` using \`parserOptions.project\`: /tsconfig.json + The extension for the file (\`.unknown\`) is non-standard. It should be added to your existing \`parserOptions.extraFileExtensions\`." + `); + }); }); }); - }); - - describe('invalid project error messages', () => { - it('throws when non of multiple projects include the file', () => { - const PROJECT_DIR = resolve(FIXTURES_DIR, '../invalidFileErrors'); - const code = 'var a = true'; - const config: TSESTreeOptions = { - comment: true, - tokens: true, - range: true, - loc: true, - tsconfigRootDir: PROJECT_DIR, - project: ['./**/tsconfig.json', './**/tsconfig.extra.json'], - }; - const testParse = (filePath: string) => (): void => { - try { - parser.parseAndGenerateServices(code, { - ...config, - filePath: join(PROJECT_DIR, filePath), - }); - } catch (error) { - throw alignErrorPath(error as Error); - } - }; - expect(testParse('ts/notIncluded0j1.ts')).toThrowErrorMatchingSnapshot(); + describe('invalid project error messages', () => { + if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { + it('throws when none of multiple projects include the file', () => { + const PROJECT_DIR = resolve(FIXTURES_DIR, '../invalidFileErrors'); + const code = 'var a = true'; + const config: TSESTreeOptions = { + comment: true, + tokens: true, + range: true, + loc: true, + tsconfigRootDir: PROJECT_DIR, + project: ['./**/tsconfig.json', './**/tsconfig.extra.json'], + }; + const testParse = (filePath: string) => (): void => { + try { + parser.parseAndGenerateServices(code, { + ...config, + filePath: join(PROJECT_DIR, filePath), + }); + } catch (error) { + throw alignErrorPath(error as Error); + } + }; + + expect(testParse('ts/notIncluded0j1.ts')) + .toThrowErrorMatchingInlineSnapshot(` + "ESLint was configured to run on \`/ts/notIncluded0j1.ts\` using \`parserOptions.project\`: + - /tsconfig.json + - /tsconfig.extra.json + However, none of those TSConfigs include this file. Either: + - Change ESLint's list of included files to not include this file + - Change one of those TSConfigs to include this file + - Create a new TSConfig that includes this file and include it in your parserOptions.project + See the typescript-eslint docs for more info: https://typescript-eslint.io/linting/troubleshooting#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file" + `); + }); + } }); - }); + } describe('debug options', () => { const debugEnable = jest.fn(); @@ -672,22 +733,24 @@ describe('parseAndGenerateServices', () => { ); }); - it('should turn on typescript debugger', () => { - expect(() => - parser.parseAndGenerateServices('const x = 1;', { - debugLevel: ['typescript'], - filePath: './path-that-doesnt-exist.ts', - project: ['./tsconfig-that-doesnt-exist.json'], - }), - ) // should throw because the file and tsconfig don't exist - .toThrow(); - expect(createDefaultCompilerOptionsFromExtra).toHaveBeenCalled(); - expect(createDefaultCompilerOptionsFromExtra).toHaveReturnedWith( - expect.objectContaining({ - extendedDiagnostics: true, - }), - ); - }); + if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { + it('should turn on typescript debugger', () => { + expect(() => + parser.parseAndGenerateServices('const x = 1;', { + debugLevel: ['typescript'], + filePath: './path-that-doesnt-exist.ts', + project: ['./tsconfig-that-doesnt-exist.json'], + }), + ) // should throw because the file and tsconfig don't exist + .toThrow(); + expect(createDefaultCompilerOptionsFromExtra).toHaveBeenCalled(); + expect(createDefaultCompilerOptionsFromExtra).toHaveReturnedWith( + expect.objectContaining({ + extendedDiagnostics: true, + }), + ); + }); + } }); if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { @@ -733,64 +796,64 @@ describe('parseAndGenerateServices', () => { expect(testParse('includeme', ignore)).not.toThrow(); }); }); - } - describe('cacheLifetime', () => { - describe('glob', () => { - function doParse(lifetime: CacheDurationSeconds): void { - parser.parseAndGenerateServices('const x = 1', { - cacheLifetime: { - glob: lifetime, - }, - filePath: join(FIXTURES_DIR, 'file.ts'), - tsconfigRootDir: FIXTURES_DIR, - project: ['./**/tsconfig.json', './**/tsconfig.extra.json'], - }); - } + describe('cacheLifetime', () => { + describe('glob', () => { + function doParse(lifetime: CacheDurationSeconds): void { + parser.parseAndGenerateServices('const x = 1', { + cacheLifetime: { + glob: lifetime, + }, + filePath: join(FIXTURES_DIR, 'file.ts'), + tsconfigRootDir: FIXTURES_DIR, + project: ['./**/tsconfig.json', './**/tsconfig.extra.json'], + }); + } - it('should cache globs if the lifetime is non-zero', () => { - doParse(30); - expect(globbySyncMock).toHaveBeenCalledTimes(1); - doParse(30); - // shouldn't call globby again due to the caching - expect(globbySyncMock).toHaveBeenCalledTimes(1); - }); + it('should cache globs if the lifetime is non-zero', () => { + doParse(30); + expect(globbySyncMock).toHaveBeenCalledTimes(1); + doParse(30); + // shouldn't call globby again due to the caching + expect(globbySyncMock).toHaveBeenCalledTimes(1); + }); - it('should not cache globs if the lifetime is zero', () => { - doParse(0); - expect(globbySyncMock).toHaveBeenCalledTimes(1); - doParse(0); - // should call globby again because we specified immediate cache expiry - expect(globbySyncMock).toHaveBeenCalledTimes(2); - }); + it('should not cache globs if the lifetime is zero', () => { + doParse(0); + expect(globbySyncMock).toHaveBeenCalledTimes(1); + doParse(0); + // should call globby again because we specified immediate cache expiry + expect(globbySyncMock).toHaveBeenCalledTimes(2); + }); - it('should evict the cache if the entry expires', () => { - hrtimeSpy.mockReturnValueOnce([1, 0]); + it('should evict the cache if the entry expires', () => { + hrtimeSpy.mockReturnValueOnce([1, 0]); - doParse(30); - expect(globbySyncMock).toHaveBeenCalledTimes(1); + doParse(30); + expect(globbySyncMock).toHaveBeenCalledTimes(1); - // wow so much time has passed - hrtimeSpy.mockReturnValueOnce([Number.MAX_VALUE, 0]); + // wow so much time has passed + hrtimeSpy.mockReturnValueOnce([Number.MAX_VALUE, 0]); - doParse(30); - // shouldn't call globby again due to the caching - expect(globbySyncMock).toHaveBeenCalledTimes(2); - }); + doParse(30); + // shouldn't call globby again due to the caching + expect(globbySyncMock).toHaveBeenCalledTimes(2); + }); - it('should infinitely cache if passed Infinity', () => { - hrtimeSpy.mockReturnValueOnce([1, 0]); + it('should infinitely cache if passed Infinity', () => { + hrtimeSpy.mockReturnValueOnce([1, 0]); - doParse('Infinity'); - expect(globbySyncMock).toHaveBeenCalledTimes(1); + doParse('Infinity'); + expect(globbySyncMock).toHaveBeenCalledTimes(1); - // wow so much time has passed - hrtimeSpy.mockReturnValueOnce([Number.MAX_VALUE, 0]); + // wow so much time has passed + hrtimeSpy.mockReturnValueOnce([Number.MAX_VALUE, 0]); - doParse('Infinity'); - // shouldn't call globby again due to the caching - expect(globbySyncMock).toHaveBeenCalledTimes(1); + doParse('Infinity'); + // shouldn't call globby again due to the caching + expect(globbySyncMock).toHaveBeenCalledTimes(1); + }); }); }); - }); + } }); diff --git a/packages/typescript-estree/tests/lib/persistentParse.test.ts b/packages/typescript-estree/tests/lib/persistentParse.test.ts index d19210016e8..710b9c54ab5 100644 --- a/packages/typescript-estree/tests/lib/persistentParse.test.ts +++ b/packages/typescript-estree/tests/lib/persistentParse.test.ts @@ -87,6 +87,11 @@ function baseTests( tsConfigExcludeBar: Record, tsConfigIncludeAll: Record, ): void { + // The experimental project server creates a default project for files + if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER === 'true') { + return; + } + it('parses both files successfully when included', () => { const PROJECT_DIR = setup(tsConfigIncludeAll); @@ -259,45 +264,48 @@ describe('persistent parse', () => { /* If there is no includes, then typescript will ask for a slightly different set of watchers. */ - describe('tsconfig with no includes / files', () => { - const tsConfigExcludeBar = { - exclude: ['./src/bar.ts'], - }; - const tsConfigIncludeAll = {}; - baseTests(tsConfigExcludeBar, tsConfigIncludeAll); + if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { + describe('tsconfig with no includes / files', () => { + const tsConfigExcludeBar = { + exclude: ['./src/bar.ts'], + }; + const tsConfigIncludeAll = {}; - it('handles tsconfigs with no includes/excludes (single level)', () => { - const PROJECT_DIR = setup({}, false); + baseTests(tsConfigExcludeBar, tsConfigIncludeAll); - // parse once to: assert the config as correct, and to make sure the program is setup - expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); - expect(() => parseFile('bar', PROJECT_DIR)).toThrow(); + it('handles tsconfigs with no includes/excludes (single level)', () => { + const PROJECT_DIR = setup({}, false); - // write a new file and attempt to parse it - writeFile(PROJECT_DIR, 'bar'); - clearCaches(); + // parse once to: assert the config as correct, and to make sure the program is setup + expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); + expect(() => parseFile('bar', PROJECT_DIR)).toThrow(); - expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); - expect(() => parseFile('bar', PROJECT_DIR)).not.toThrow(); - }); + // write a new file and attempt to parse it + writeFile(PROJECT_DIR, 'bar'); + clearCaches(); + + expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); + expect(() => parseFile('bar', PROJECT_DIR)).not.toThrow(); + }); - it('handles tsconfigs with no includes/excludes (nested)', () => { - const PROJECT_DIR = setup({}, false); - const bazSlashBar = 'baz/bar' as const; + it('handles tsconfigs with no includes/excludes (nested)', () => { + const PROJECT_DIR = setup({}, false); + const bazSlashBar = 'baz/bar' as const; - // parse once to: assert the config as correct, and to make sure the program is setup - expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); - expect(() => parseFile(bazSlashBar, PROJECT_DIR)).toThrow(); + // parse once to: assert the config as correct, and to make sure the program is setup + expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); + expect(() => parseFile(bazSlashBar, PROJECT_DIR)).toThrow(); - // write a new file and attempt to parse it - writeFile(PROJECT_DIR, bazSlashBar); - clearCaches(); + // write a new file and attempt to parse it + writeFile(PROJECT_DIR, bazSlashBar); + clearCaches(); - expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); - expect(() => parseFile(bazSlashBar, PROJECT_DIR)).not.toThrow(); + expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); + expect(() => parseFile(bazSlashBar, PROJECT_DIR)).not.toThrow(); + }); }); - }); + } /* If there is no includes, then typescript will ask for a slightly different set of watchers. diff --git a/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts b/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts index b605dd6ea5f..a458c7777fc 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts @@ -135,124 +135,132 @@ describe('semanticInfo - singleRun', () => { process.env.CI = originalEnvCI; }); - it('should lazily create the required program out of the provided "parserOptions.project" one time when TSESTREE_SINGLE_RUN=true', () => { - /** - * Single run because of explicit environment variable TSESTREE_SINGLE_RUN - */ - const originalTSESTreeSingleRun = process.env.TSESTREE_SINGLE_RUN; - process.env.TSESTREE_SINGLE_RUN = 'true'; - - const resultProgram = parseAndGenerateServices(code, options).services - .program; - expect(resultProgram).toEqual(mockProgram); - - // Call parseAndGenerateServices() again to ensure caching of Programs is working correctly... - parseAndGenerateServices(code, options); - // ...by asserting this was only called once per project - expect(createProgramFromConfigFile).toHaveBeenCalledTimes(tsconfigs.length); - - expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( - 1, - resolvedProject(tsconfigs[0]), - ); - expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( - 2, - resolvedProject(tsconfigs[1]), - ); - - // Restore process data - process.env.TSESTREE_SINGLE_RUN = originalTSESTreeSingleRun; - }); - - it('should lazily create the required program out of the provided "parserOptions.project" one time when singleRun is inferred from CI=true', () => { - /** - * Single run because of CI=true (we need to make sure we respect the original value - * so that we won't interfere with our own usage of the variable) - */ - const originalEnvCI = process.env.CI; - process.env.CI = 'true'; - - const resultProgram = parseAndGenerateServices(code, options).services - .program; - expect(resultProgram).toEqual(mockProgram); - - // Call parseAndGenerateServices() again to ensure caching of Programs is working correctly... - parseAndGenerateServices(code, options); - // ...by asserting this was only called once per project - expect(createProgramFromConfigFile).toHaveBeenCalledTimes(tsconfigs.length); - - expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( - 1, - resolvedProject(tsconfigs[0]), - ); - expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( - 2, - resolvedProject(tsconfigs[1]), - ); - - // Restore process data - process.env.CI = originalEnvCI; - }); - - it('should lazily create the required program out of the provided "parserOptions.project" one time when singleRun is inferred from process.argv', () => { - /** - * Single run because of process.argv - */ - const originalProcessArgv = process.argv; - process.argv = ['', path.normalize('node_modules/.bin/eslint'), '']; - - const resultProgram = parseAndGenerateServices(code, options).services - .program; - expect(resultProgram).toEqual(mockProgram); - - // Call parseAndGenerateServices() again to ensure caching of Programs is working correctly... - parseAndGenerateServices(code, options); - // ...by asserting this was only called once per project - expect(createProgramFromConfigFile).toHaveBeenCalledTimes(tsconfigs.length); - - expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( - 1, - resolvedProject(tsconfigs[0]), - ); - expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( - 2, - resolvedProject(tsconfigs[1]), - ); - - // Restore process data - process.argv = originalProcessArgv; - }); - - it('should stop iterating through and lazily creating programs for the given "parserOptions.project" once a matching one has been found', () => { - /** - * Single run because of explicit environment variable TSESTREE_SINGLE_RUN - */ - const originalTSESTreeSingleRun = process.env.TSESTREE_SINGLE_RUN; - process.env.TSESTREE_SINGLE_RUN = 'true'; - - const optionsWithReversedTsconfigs = { - ...options, - // Now the matching tsconfig comes first - project: [...options.project].reverse(), - }; - - const resultProgram = parseAndGenerateServices( - code, - optionsWithReversedTsconfigs, - ).services.program; - expect(resultProgram).toEqual(mockProgram); - - // Call parseAndGenerateServices() again to ensure caching of Programs is working correctly... - parseAndGenerateServices(code, options); - // ...by asserting this was only called only once - expect(createProgramFromConfigFile).toHaveBeenCalledTimes(1); - - expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( - 1, - resolvedProject(tsconfigs[1]), - ); - - // Restore process data - process.env.TSESTREE_SINGLE_RUN = originalTSESTreeSingleRun; - }); + if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { + it('should lazily create the required program out of the provided "parserOptions.project" one time when TSESTREE_SINGLE_RUN=true', () => { + /** + * Single run because of explicit environment variable TSESTREE_SINGLE_RUN + */ + const originalTSESTreeSingleRun = process.env.TSESTREE_SINGLE_RUN; + process.env.TSESTREE_SINGLE_RUN = 'true'; + + const resultProgram = parseAndGenerateServices(code, options).services + .program; + expect(resultProgram).toEqual(mockProgram); + + // Call parseAndGenerateServices() again to ensure caching of Programs is working correctly... + parseAndGenerateServices(code, options); + // ...by asserting this was only called once per project + expect(createProgramFromConfigFile).toHaveBeenCalledTimes( + tsconfigs.length, + ); + + expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( + 1, + resolvedProject(tsconfigs[0]), + ); + expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( + 2, + resolvedProject(tsconfigs[1]), + ); + + // Restore process data + process.env.TSESTREE_SINGLE_RUN = originalTSESTreeSingleRun; + }); + + it('should lazily create the required program out of the provided "parserOptions.project" one time when singleRun is inferred from CI=true', () => { + /** + * Single run because of CI=true (we need to make sure we respect the original value + * so that we won't interfere with our own usage of the variable) + */ + const originalEnvCI = process.env.CI; + process.env.CI = 'true'; + + const resultProgram = parseAndGenerateServices(code, options).services + .program; + expect(resultProgram).toEqual(mockProgram); + + // Call parseAndGenerateServices() again to ensure caching of Programs is working correctly... + parseAndGenerateServices(code, options); + // ...by asserting this was only called once per project + expect(createProgramFromConfigFile).toHaveBeenCalledTimes( + tsconfigs.length, + ); + + expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( + 1, + resolvedProject(tsconfigs[0]), + ); + expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( + 2, + resolvedProject(tsconfigs[1]), + ); + + // Restore process data + process.env.CI = originalEnvCI; + }); + + it('should lazily create the required program out of the provided "parserOptions.project" one time when singleRun is inferred from process.argv', () => { + /** + * Single run because of process.argv + */ + const originalProcessArgv = process.argv; + process.argv = ['', path.normalize('node_modules/.bin/eslint'), '']; + + const resultProgram = parseAndGenerateServices(code, options).services + .program; + expect(resultProgram).toEqual(mockProgram); + + // Call parseAndGenerateServices() again to ensure caching of Programs is working correctly... + parseAndGenerateServices(code, options); + // ...by asserting this was only called once per project + expect(createProgramFromConfigFile).toHaveBeenCalledTimes( + tsconfigs.length, + ); + + expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( + 1, + resolvedProject(tsconfigs[0]), + ); + expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( + 2, + resolvedProject(tsconfigs[1]), + ); + + // Restore process data + process.argv = originalProcessArgv; + }); + + it('should stop iterating through and lazily creating programs for the given "parserOptions.project" once a matching one has been found', () => { + /** + * Single run because of explicit environment variable TSESTREE_SINGLE_RUN + */ + const originalTSESTreeSingleRun = process.env.TSESTREE_SINGLE_RUN; + process.env.TSESTREE_SINGLE_RUN = 'true'; + + const optionsWithReversedTsconfigs = { + ...options, + // Now the matching tsconfig comes first + project: [...options.project].reverse(), + }; + + const resultProgram = parseAndGenerateServices( + code, + optionsWithReversedTsconfigs, + ).services.program; + expect(resultProgram).toEqual(mockProgram); + + // Call parseAndGenerateServices() again to ensure caching of Programs is working correctly... + parseAndGenerateServices(code, options); + // ...by asserting this was only called only once + expect(createProgramFromConfigFile).toHaveBeenCalledTimes(1); + + expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( + 1, + resolvedProject(tsconfigs[1]), + ); + + // Restore process data + process.env.TSESTREE_SINGLE_RUN = originalTSESTreeSingleRun; + }); + } }); diff --git a/packages/typescript-estree/tests/lib/semanticInfo.test.ts b/packages/typescript-estree/tests/lib/semanticInfo.test.ts index a12c0a5c498..0379a204858 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.test.ts @@ -246,16 +246,18 @@ describe('semanticInfo', () => { expect(parseResult.services.program).toBeDefined(); }); - it(`non-existent file should throw error when project provided`, () => { - expect(() => - parseCodeAndGenerateServices( - `function M() { return Base }`, - createOptions(''), - ), - ).toThrow( - /ESLint was configured to run on `\/estree\.ts` using/, - ); - }); + if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { + it(`non-existent file should throw error when project provided`, () => { + expect(() => + parseCodeAndGenerateServices( + `function M() { return Base }`, + createOptions(''), + ), + ).toThrow( + /ESLint was configured to run on `\/estree\.ts` using/, + ); + }); + } if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { it('non-existent project file', () => { @@ -318,20 +320,22 @@ describe('semanticInfo', () => { ); }); - it(`first matching provided program instance is returned in result`, () => { - const filename = testFiles[0]; - const program1 = createProgram(path.join(FIXTURES_DIR, 'tsconfig.json')); - const program2 = createProgram(path.join(FIXTURES_DIR, 'tsconfig.json')); - const code = fs.readFileSync(path.join(FIXTURES_DIR, filename), 'utf8'); - const options = createOptions(filename); - const optionsProjectString = { - ...options, - programs: [program1, program2], - project: './tsconfig.json', - }; - const parseResult = parseAndGenerateServices(code, optionsProjectString); - expect(parseResult.services.program).toBe(program1); - }); + if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER !== 'true') { + it(`first matching provided program instance is returned in result`, () => { + const filename = testFiles[0]; + const program1 = createProgram(path.join(FIXTURES_DIR, 'tsconfig.json')); + const program2 = createProgram(path.join(FIXTURES_DIR, 'tsconfig.json')); + const code = fs.readFileSync(path.join(FIXTURES_DIR, filename), 'utf8'); + const options = createOptions(filename); + const optionsProjectString = { + ...options, + programs: [program1, program2], + project: './tsconfig.json', + }; + const parseResult = parseAndGenerateServices(code, optionsProjectString); + expect(parseResult.services.program).toBe(program1); + }); + } it('file not in provided program instance(s)', () => { const filename = 'non-existent-file.ts'; @@ -373,6 +377,10 @@ describe('semanticInfo', () => { function testIsolatedFile( parseResult: ParseAndGenerateServicesResult, ): void { + if (process.env.TYPESCRIPT_ESLINT_EXPERIMENTAL_TSSERVER === 'true') { + return; + } + // get type checker expectToHaveParserServices(parseResult.services); const checker = parseResult.services.program.getTypeChecker(); From 37d3548c85e691946d3ac4f52ff5073a87aee8dc Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 3 May 2023 17:58:17 -0400 Subject: [PATCH 29/32] Move tsserver import to a require --- .../src/create-program/createProjectService.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/typescript-estree/src/create-program/createProjectService.ts index 0475184652b..b453d782424 100644 --- a/packages/typescript-estree/src/create-program/createProjectService.ts +++ b/packages/typescript-estree/src/create-program/createProjectService.ts @@ -1,17 +1,21 @@ -import * as tsserver from 'typescript/lib/tsserverlibrary'; +import type * as tsserverType from 'typescript/lib/tsserverlibrary'; const doNothing = (): void => {}; -const createStubFileWatcher = (): tsserver.FileWatcher => ({ +const createStubFileWatcher = (): tsserverType.FileWatcher => ({ close: doNothing, }); -export function createProjectService(): tsserver.server.ProjectService { +export function createProjectService(): tsserverType.server.ProjectService { + // We import this lazily to avoid its cost for users who don't use the service + const tsserver = + require('typescript/lib/tsserverlibrary') as typeof tsserverType; + // TODO: see getWatchProgramsForProjects // We don't watch the disk, we just refer to these when ESLint calls us // there's a whole separate update pass in maybeInvalidateProgram at the bottom of getWatchProgramsForProjects // (this "goes nuclear on TypeScript") - const system: tsserver.server.ServerHost = { + const system: tsserverType.server.ServerHost = { ...tsserver.sys, clearImmediate, clearTimeout, From 00ddecdcce6c56b6d71fe1b515bbe91a80dc53cd Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 3 May 2023 18:19:41 -0400 Subject: [PATCH 30/32] rename: tsserverType -> ts --- .../src/create-program/createProjectService.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/typescript-estree/src/create-program/createProjectService.ts index b453d782424..96870db84fc 100644 --- a/packages/typescript-estree/src/create-program/createProjectService.ts +++ b/packages/typescript-estree/src/create-program/createProjectService.ts @@ -1,21 +1,20 @@ -import type * as tsserverType from 'typescript/lib/tsserverlibrary'; +import type * as ts from 'typescript/lib/tsserverlibrary'; const doNothing = (): void => {}; -const createStubFileWatcher = (): tsserverType.FileWatcher => ({ +const createStubFileWatcher = (): ts.FileWatcher => ({ close: doNothing, }); -export function createProjectService(): tsserverType.server.ProjectService { +export function createProjectService(): ts.server.ProjectService { // We import this lazily to avoid its cost for users who don't use the service - const tsserver = - require('typescript/lib/tsserverlibrary') as typeof tsserverType; + const tsserver = require('typescript/lib/tsserverlibrary') as typeof ts; // TODO: see getWatchProgramsForProjects // We don't watch the disk, we just refer to these when ESLint calls us // there's a whole separate update pass in maybeInvalidateProgram at the bottom of getWatchProgramsForProjects // (this "goes nuclear on TypeScript") - const system: tsserverType.server.ServerHost = { + const system: ts.server.ServerHost = { ...tsserver.sys, clearImmediate, clearTimeout, From da456692e664556fc9e05066e11298bebe5d034a Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 13 Jun 2023 16:23:16 -0400 Subject: [PATCH 31/32] Use path.resolve, and then simplify parserSettings usage --- packages/parser/tests/lib/parser.ts | 2 +- .../create-program/createProjectProgram.ts | 2 +- .../create-program/createProjectService.ts | 8 ++++---- .../src/create-program/shared.ts | 6 +++--- .../src/create-program/useProvidedPrograms.ts | 19 ++++++++++--------- .../src/parseSettings/createParseSettings.ts | 5 ++--- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/parser/tests/lib/parser.ts b/packages/parser/tests/lib/parser.ts index 185221178d1..e6bd731db07 100644 --- a/packages/parser/tests/lib/parser.ts +++ b/packages/parser/tests/lib/parser.ts @@ -37,7 +37,7 @@ describe('parser', () => { filePath: './isolated-file.src.ts', project: 'tsconfig.json', errorOnTypeScriptSyntacticAndSemanticIssues: false, - tsconfigRootDir: path.join(__dirname, '../fixtures/services'), + tsconfigRootDir: path.resolve(__dirname, '../fixtures/services'), extraFileExtensions: ['.foo'], }; parseForESLint(code, config); diff --git a/packages/typescript-estree/src/create-program/createProjectProgram.ts b/packages/typescript-estree/src/create-program/createProjectProgram.ts index 294d2e71074..edfe00992c1 100644 --- a/packages/typescript-estree/src/create-program/createProjectProgram.ts +++ b/packages/typescript-estree/src/create-program/createProjectProgram.ts @@ -32,7 +32,7 @@ function createProjectProgram( log('Creating project program for: %s', parseSettings.filePath); const astAndProgram = firstDefined(programsForProjects, currentProgram => - getAstFromProgram(currentProgram, parseSettings), + getAstFromProgram(currentProgram, parseSettings.filePath), ); // The file was either matched within the tsconfig, or we allow creating a default program diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/typescript-estree/src/create-program/createProjectService.ts index 96870db84fc..21aaa79bdce 100644 --- a/packages/typescript-estree/src/create-program/createProjectService.ts +++ b/packages/typescript-estree/src/create-program/createProjectService.ts @@ -6,7 +6,9 @@ const createStubFileWatcher = (): ts.FileWatcher => ({ close: doNothing, }); -export function createProjectService(): ts.server.ProjectService { +export type TypeScriptProjectService = ts.server.ProjectService; + +export function createProjectService(): TypeScriptProjectService { // We import this lazily to avoid its cost for users who don't use the service const tsserver = require('typescript/lib/tsserverlibrary') as typeof ts; @@ -24,7 +26,7 @@ export function createProjectService(): ts.server.ProjectService { watchFile: createStubFileWatcher, }; - const projectService = new tsserver.server.ProjectService({ + return new tsserver.server.ProjectService({ host: system, cancellationToken: { isCancellationRequested: (): boolean => false }, useSingleInferredProject: false, @@ -53,6 +55,4 @@ export function createProjectService(): ts.server.ProjectService { }, session: undefined, }); - - return projectService; } diff --git a/packages/typescript-estree/src/create-program/shared.ts b/packages/typescript-estree/src/create-program/shared.ts index 7fa97afa849..ce8a4d10649 100644 --- a/packages/typescript-estree/src/create-program/shared.ts +++ b/packages/typescript-estree/src/create-program/shared.ts @@ -97,12 +97,12 @@ function getExtension(fileName: string | undefined): string | null { function getAstFromProgram( currentProgram: Program, - parseSettings: ParseSettings, + filePath: string, ): ASTAndDefiniteProgram | undefined { - const ast = currentProgram.getSourceFile(parseSettings.filePath); + const ast = currentProgram.getSourceFile(filePath); // working around https://github.com/typescript-eslint/typescript-eslint/issues/1573 - const expectedExt = getExtension(parseSettings.filePath); + const expectedExt = getExtension(filePath); const returnedExt = getExtension(ast?.fileName); if (expectedExt !== returnedExt) { return undefined; diff --git a/packages/typescript-estree/src/create-program/useProvidedPrograms.ts b/packages/typescript-estree/src/create-program/useProvidedPrograms.ts index 96093e9a3af..c2b67e79575 100644 --- a/packages/typescript-estree/src/create-program/useProvidedPrograms.ts +++ b/packages/typescript-estree/src/create-program/useProvidedPrograms.ts @@ -3,24 +3,25 @@ import * as fs from 'fs'; import * as path from 'path'; import * as ts from 'typescript'; -import type { ParseSettings } from '../parseSettings'; import type { ASTAndDefiniteProgram } from './shared'; import { CORE_COMPILER_OPTIONS, getAstFromProgram } from './shared'; const log = debug('typescript-eslint:typescript-estree:useProvidedProgram'); +export interface ProvidedProgramsSettings { + filePath: string; + tsconfigRootDir: string; +} + function useProvidedPrograms( programInstances: Iterable, - parseSettings: ParseSettings, + { filePath, tsconfigRootDir }: ProvidedProgramsSettings, ): ASTAndDefiniteProgram | undefined { - log( - 'Retrieving ast for %s from provided program instance(s)', - parseSettings.filePath, - ); + log('Retrieving ast for %s from provided program instance(s)', filePath); let astAndProgram: ASTAndDefiniteProgram | undefined; for (const programInstance of programInstances) { - astAndProgram = getAstFromProgram(programInstance, parseSettings); + astAndProgram = getAstFromProgram(programInstance, filePath); // Stop at the first applicable program instance if (astAndProgram) { break; @@ -29,8 +30,8 @@ function useProvidedPrograms( if (!astAndProgram) { const relativeFilePath = path.relative( - parseSettings.tsconfigRootDir || process.cwd(), - parseSettings.filePath, + tsconfigRootDir || process.cwd(), + filePath, ); const errorLines = [ '"parserOptions.programs" has been provided for @typescript-eslint/parser.', diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index 4c1a704ae04..5413bff6e0c 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -1,6 +1,7 @@ import debug from 'debug'; import type * as ts from 'typescript'; +import type { TypeScriptProjectService } from '../create-program/createProjectService'; import { createProjectService } from '../create-program/createProjectService'; import { ensureAbsolutePath } from '../create-program/shared'; import type { TSESTreeOptions } from '../parser-options'; @@ -20,9 +21,7 @@ const log = debug( ); let TSCONFIG_MATCH_CACHE: ExpiringCache | null; - -let TSSERVER_PROJECT_SERVICE: ReturnType | null = - null; +let TSSERVER_PROJECT_SERVICE: TypeScriptProjectService | null = null; export function createParseSettings( code: string | ts.SourceFile, From 93d369cb5f6f3e5732a70b1f103ff611569d172b Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 11 Jul 2023 20:54:00 -0400 Subject: [PATCH 32/32] Remove unneeded typingsInstaller --- .../src/create-program/createProjectService.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/typescript-estree/src/create-program/createProjectService.ts index 47a62bc1e9b..333d221f85b 100644 --- a/packages/typescript-estree/src/create-program/createProjectService.ts +++ b/packages/typescript-estree/src/create-program/createProjectService.ts @@ -32,17 +32,6 @@ export function createProjectService(): TypeScriptProjectService { cancellationToken: { isCancellationRequested: (): boolean => false }, useSingleInferredProject: false, useInferredProjectPerProjectRoot: false, - // TODO: https://github.com/microsoft/TypeScript/issues/53803 - typingsInstaller: { - attach: (): void => {}, - enqueueInstallTypingsRequest: (): void => {}, - installPackage: (): Promise => { - throw new Error('This should never be called.'); - }, - isKnownTypesPackageName: () => false, - onProjectClosed: (): void => {}, - globalTypingsCacheLocation: '', - }, logger: { close: doNothing, endGroup: doNothing,