Skip to content

Commit

Permalink
fix(typescript-estree): ensure --fix works with singleRun mode (#3655)
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesHenry committed Aug 2, 2021
1 parent f0861e0 commit 99eca0d
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 8 deletions.
10 changes: 9 additions & 1 deletion 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';
Expand Down
48 changes: 42 additions & 6 deletions packages/typescript-estree/src/parser.ts
Expand Up @@ -502,6 +502,12 @@ function parseWithNodeMaps<T extends TSESTreeOptions = TSESTreeOptions>(
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<T extends TSESTreeOptions = TSESTreeOptions>(
code: string,
options: T,
Expand Down Expand Up @@ -572,12 +578,41 @@ function parseAndGenerateServices<T extends TSESTreeOptions = TSESTreeOptions>(
*/
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
Expand Down Expand Up @@ -620,4 +655,5 @@ export {
ParseAndGenerateServicesResult,
ParseWithNodeMapsResult,
clearProgramCache,
clearParseAndGenerateServicesCalls,
};
@@ -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 = {
Expand Down Expand Up @@ -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', () => {
Expand Down

0 comments on commit 99eca0d

Please sign in to comment.