diff --git a/packages/typescript-estree/src/index.ts b/packages/typescript-estree/src/index.ts index b2a0581dc92..d6444271ee9 100644 --- a/packages/typescript-estree/src/index.ts +++ b/packages/typescript-estree/src/index.ts @@ -1,4 +1,12 @@ -export * from './parser'; +export { + AST, + parse, + parseAndGenerateServices, + parseWithNodeMaps, + ParseAndGenerateServicesResult, + ParseWithNodeMapsResult, + clearProgramCache, +} from './parser'; export { ParserServices, TSESTreeOptions } from './parser-options'; export { simpleTraverse } from './simple-traverse'; export * from './ts-estree'; diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 904bb054fdf..54727a63fd3 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -496,6 +496,12 @@ function parseWithNodeMaps( return parseWithNodeMapsInternal(code, options, true); } +let parseAndGenerateServicesCalls: { [fileName: string]: number } = {}; +// Privately exported utility intended for use in typescript-eslint unit tests only +function clearParseAndGenerateServicesCalls(): void { + parseAndGenerateServicesCalls = {}; +} + function parseAndGenerateServices( code: string, options: T, @@ -566,12 +572,41 @@ function parseAndGenerateServices( */ const shouldProvideParserServices = extra.programs != null || (extra.projects && extra.projects.length > 0); - const { ast, program } = getProgramAndAST( - code, - extra.programs, - shouldProvideParserServices, - extra.createDefaultProgram, - )!; + + /** + * If we are in singleRun mode but the parseAndGenerateServices() function has been called more than once for the current file, + * it must mean that we are in the middle of an ESLint automated fix cycle (in which parsing can be performed up to an additional + * 10 times in order to apply all possible fixes for the file). + * + * In this scenario we cannot rely upon the singleRun AOT compiled programs because the SourceFiles will not contain the source + * with the latest fixes applied. Therefore we fallback to creating the quickest possible isolated program from the updated source. + */ + let ast: ts.SourceFile; + let program: ts.Program; + + if (extra.singleRun && options.filePath) { + parseAndGenerateServicesCalls[options.filePath] = + (parseAndGenerateServicesCalls[options.filePath] || 0) + 1; + } + + if ( + extra.singleRun && + options.filePath && + parseAndGenerateServicesCalls[options.filePath] > 1 + ) { + const isolatedAstAndProgram = createIsolatedProgram(code, extra); + ast = isolatedAstAndProgram.ast; + program = isolatedAstAndProgram.program; + } else { + const astAndProgram = getProgramAndAST( + code, + extra.programs, + shouldProvideParserServices, + extra.createDefaultProgram, + )!; + ast = astAndProgram.ast; + program = astAndProgram.program; + } /** * Convert the TypeScript AST to an ESTree-compatible one, and optionally preserve @@ -614,4 +649,5 @@ export { ParseAndGenerateServicesResult, ParseWithNodeMapsResult, clearProgramCache, + clearParseAndGenerateServicesCalls, }; diff --git a/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts b/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts index b8e778e0fb0..62d7c3a841c 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts @@ -1,6 +1,10 @@ import glob from 'glob'; import * as path from 'path'; -import { clearProgramCache, parseAndGenerateServices } from '../../src'; +import { + clearProgramCache, + parseAndGenerateServices, + clearParseAndGenerateServicesCalls, +} from '../../src/parser'; import { getCanonicalFileName } from '../../src/create-program/shared'; const mockProgram = { @@ -91,6 +95,8 @@ describe('semanticInfo - singleRun', () => { clearProgramCache(); // ensure invocations of mock are clean for each test (createProgramFromConfigFile as jest.Mock).mockClear(); + // Do not track invocations per file across tests + clearParseAndGenerateServicesCalls(); }); it('should not create any programs ahead of time by default when there is no way to infer singleRun=true', () => {