Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(typescript-estree): ensure --fix works with singleRun mode #3655

Merged
merged 3 commits into from Aug 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -496,6 +496,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 @@ -566,12 +572,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 @@ -614,4 +649,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