Skip to content

Commit

Permalink
fix(typescript-estree): ensure --fix works with singleRun mode
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesHenry committed Jul 23, 2021
1 parent d358785 commit 49c3524
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 7 deletions.
50 changes: 44 additions & 6 deletions packages/typescript-estree/src/parser.ts
Expand Up @@ -496,6 +496,12 @@ function parseWithNodeMaps<T extends TSESTreeOptions = TSESTreeOptions>(
return parseWithNodeMapsInternal(code, options, true);
}

let parseAndGenerateServicesCalls: { [fileName: string]: number } = {};
// Privately exported utility intented 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 @@ -566,12 +572,43 @@ 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;
parseAndGenerateServicesCalls[options.filePath] =
parseAndGenerateServicesCalls[options.filePath] + 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 @@ -614,4 +651,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';
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 49c3524

Please sign in to comment.