Merge diagnosticsProducing and nonDiagnosticsProducing checkers into a single checker supporting lazy diagnostics (#36747)

* Merge diagnosticsProducing and nonDiagnosticsProducing checkers into a single checker supporting lazy diagnostics

* Fix lint
This commit is contained in:
Wesley Wigham
2022-03-23 16:21:06 -07:00
committed by GitHub
parent 26c701c351
commit b5a3a058f5
14 changed files with 597 additions and 519 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1025,8 +1025,7 @@ namespace ts {
let files: SourceFile[];
let symlinks: SymlinkCache | undefined;
let commonSourceDirectory: string;
let diagnosticsProducingTypeChecker: TypeChecker;
let noDiagnosticsTypeChecker: TypeChecker;
let typeChecker: TypeChecker;
let classifiableNames: Set<__String>;
const ambientModuleNameToUnmodifiedFileName = new Map<string, string>();
let fileReasons = createMultiMap<Path, FileIncludeReason>();
@@ -1304,21 +1303,19 @@ namespace ts {
getProgramDiagnostics,
getTypeChecker,
getClassifiableNames,
getDiagnosticsProducingTypeChecker,
getCommonSourceDirectory,
emit,
getCurrentDirectory: () => currentDirectory,
getNodeCount: () => getDiagnosticsProducingTypeChecker().getNodeCount(),
getIdentifierCount: () => getDiagnosticsProducingTypeChecker().getIdentifierCount(),
getSymbolCount: () => getDiagnosticsProducingTypeChecker().getSymbolCount(),
getTypeCount: () => getDiagnosticsProducingTypeChecker().getTypeCount(),
getInstantiationCount: () => getDiagnosticsProducingTypeChecker().getInstantiationCount(),
getRelationCacheSizes: () => getDiagnosticsProducingTypeChecker().getRelationCacheSizes(),
getNodeCount: () => getTypeChecker().getNodeCount(),
getIdentifierCount: () => getTypeChecker().getIdentifierCount(),
getSymbolCount: () => getTypeChecker().getSymbolCount(),
getTypeCount: () => getTypeChecker().getTypeCount(),
getInstantiationCount: () => getTypeChecker().getInstantiationCount(),
getRelationCacheSizes: () => getTypeChecker().getRelationCacheSizes(),
getFileProcessingDiagnostics: () => fileProcessingDiagnostics,
getResolvedTypeReferenceDirectives: () => resolvedTypeReferenceDirectives,
isSourceFileFromExternalLibrary,
isSourceFileDefaultLibrary,
dropDiagnosticsProducingTypeChecker,
getSourceFileFromReference,
getLibFileFromReference,
sourceFileToPackageName,
@@ -1980,16 +1977,8 @@ namespace ts {
}
}
function getDiagnosticsProducingTypeChecker() {
return diagnosticsProducingTypeChecker || (diagnosticsProducingTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ true));
}
function dropDiagnosticsProducingTypeChecker() {
diagnosticsProducingTypeChecker = undefined!;
}
function getTypeChecker() {
return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false));
return typeChecker || (typeChecker = createTypeChecker(program));
}
function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, transformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult {
@@ -2017,7 +2006,7 @@ namespace ts {
// This is because in the -out scenario all files need to be emitted, and therefore all
// files need to be type checked. And the way to specify that all files need to be type
// checked is to not pass the file to getEmitResolver.
const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver(outFile(options) ? undefined : sourceFile, cancellationToken);
const emitResolver = getTypeChecker().getEmitResolver(outFile(options) ? undefined : sourceFile, cancellationToken);
performance.mark("beforeEmit");
@@ -2121,15 +2110,7 @@ namespace ts {
if (e instanceof OperationCanceledException) {
// We were canceled while performing the operation. Because our type checker
// might be a bad state, we need to throw it away.
//
// Note: we are overly aggressive here. We do not actually *have* to throw away
// the "noDiagnosticsTypeChecker". However, for simplicity, i'd like to keep
// the lifetimes of these two TypeCheckers the same. Also, we generally only
// cancel when the user has made a change anyways. And, in that case, we (the
// program instance) will get thrown away anyways. So trying to keep one of
// these type checkers alive doesn't serve much purpose.
noDiagnosticsTypeChecker = undefined!;
diagnosticsProducingTypeChecker = undefined!;
typeChecker = undefined!;
}
throw e;
@@ -2153,7 +2134,7 @@ namespace ts {
return emptyArray;
}
const typeChecker = getDiagnosticsProducingTypeChecker();
const typeChecker = getTypeChecker();
Debug.assert(!!sourceFile.bindDiagnostics);
@@ -2209,7 +2190,7 @@ namespace ts {
function getSuggestionDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): readonly DiagnosticWithLocation[] {
return runWithCancellationToken(() => {
return getDiagnosticsProducingTypeChecker().getSuggestionDiagnostics(sourceFile, cancellationToken);
return getTypeChecker().getSuggestionDiagnostics(sourceFile, cancellationToken);
});
}
@@ -2446,7 +2427,7 @@ namespace ts {
function getDeclarationDiagnosticsForFileNoCache(sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined): readonly DiagnosticWithLocation[] {
return runWithCancellationToken(() => {
const resolver = getDiagnosticsProducingTypeChecker().getEmitResolver(sourceFile, cancellationToken);
const resolver = getTypeChecker().getEmitResolver(sourceFile, cancellationToken);
// Don't actually write any files since we're just getting diagnostics.
return ts.getDeclarationDiagnostics(getEmitHost(noop), resolver, sourceFile) || emptyArray;
});
@@ -2497,7 +2478,7 @@ namespace ts {
}
function getGlobalDiagnostics(): SortedReadonlyArray<Diagnostic> {
return rootNames.length ? sortAndDeduplicateDiagnostics(getDiagnosticsProducingTypeChecker().getGlobalDiagnostics().slice()) : emptyArray as any as SortedReadonlyArray<Diagnostic>;
return rootNames.length ? sortAndDeduplicateDiagnostics(getTypeChecker().getGlobalDiagnostics().slice()) : emptyArray as any as SortedReadonlyArray<Diagnostic>;
}
function getConfigFileParsingDiagnostics(): readonly Diagnostic[] {

View File

@@ -4016,11 +4016,6 @@ namespace ts {
/* @internal */ getCommonSourceDirectory(): string;
// For testing purposes only. Should not be used by any other consumers (including the
// language service).
/* @internal */ getDiagnosticsProducingTypeChecker(): TypeChecker;
/* @internal */ dropDiagnosticsProducingTypeChecker(): void;
/* @internal */ getCachedSemanticDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] | undefined;
/* @internal */ getClassifiableNames(): Set<__String>;

View File

@@ -717,7 +717,7 @@ namespace Harness {
// These types are equivalent, but depend on what order the compiler observed
// certain parts of the program.
const fullWalker = new TypeWriterWalker(program, /*fullTypeCheck*/ true, !!hasErrorBaseline);
const fullWalker = new TypeWriterWalker(program, !!hasErrorBaseline);
// Produce baselines. The first gives the types for all expressions.
// The second gives symbols for all identifiers.

View File

@@ -41,12 +41,10 @@ namespace Harness {
private checker: ts.TypeChecker;
constructor(private program: ts.Program, fullTypeCheck: boolean, private hadErrorBaseline: boolean) {
constructor(private program: ts.Program, private hadErrorBaseline: boolean) {
// Consider getting both the diagnostics checker and the non-diagnostics checker to verify
// they are consistent.
this.checker = fullTypeCheck
? program.getDiagnosticsProducingTypeChecker()
: program.getTypeChecker();
this.checker = program.getTypeChecker();
}
public *getSymbols(fileName: string): IterableIterator<TypeWriterSymbolResult> {

View File

@@ -13,7 +13,7 @@ namespace ts.codefix {
errorCodes,
getCodeActions: function getCodeActionsToAddMissingAsync(context) {
const { sourceFile, errorCode, cancellationToken, program, span } = context;
const diagnostic = find(program.getDiagnosticsProducingTypeChecker().getDiagnostics(sourceFile, cancellationToken), getIsMatchingAsyncError(span, errorCode));
const diagnostic = find(program.getTypeChecker().getDiagnostics(sourceFile, cancellationToken), getIsMatchingAsyncError(span, errorCode));
const directSpan = diagnostic && diagnostic.relatedInformation && find(diagnostic.relatedInformation, r => r.code === Diagnostics.Did_you_mean_to_mark_this_function_as_async.code) as TextSpan | undefined;
const decl = getFixableErrorSpanDeclaration(sourceFile, directSpan);

View File

@@ -93,7 +93,7 @@ namespace ts.codefix {
}
function isMissingAwaitError(sourceFile: SourceFile, errorCode: number, span: TextSpan, cancellationToken: CancellationToken, program: Program) {
const checker = program.getDiagnosticsProducingTypeChecker();
const checker = program.getTypeChecker();
const diagnostics = checker.getDiagnostics(sourceFile, cancellationToken);
return some(diagnostics, ({ start, length, relatedInformation, code }) =>
isNumber(start) && isNumber(length) && textSpansEqual({ start, length }, span) &&

View File

@@ -43,21 +43,6 @@ namespace ts.codefix {
function createClassElementsFromSymbol(symbol: Symbol) {
const memberElements: ClassElement[] = [];
// all instance members are stored in the "member" array of symbol
if (symbol.members) {
symbol.members.forEach((member, key) => {
if (key === "constructor" && member.valueDeclaration) {
// fn.prototype.constructor = fn
changes.delete(sourceFile, member.valueDeclaration.parent);
return;
}
const memberElement = createClassElement(member, /*modifiers*/ undefined);
if (memberElement) {
memberElements.push(...memberElement);
}
});
}
// all static members are stored in the "exports" array of symbol
if (symbol.exports) {
symbol.exports.forEach(member => {
@@ -71,21 +56,34 @@ namespace ts.codefix {
isObjectLiteralExpression(firstDeclaration.parent.right)
) {
const prototypes = firstDeclaration.parent.right;
const memberElement = createClassElement(prototypes.symbol, /** modifiers */ undefined);
if (memberElement) {
memberElements.push(...memberElement);
}
createClassElement(prototypes.symbol, /** modifiers */ undefined, memberElements);
}
}
else {
const memberElement = createClassElement(member, [factory.createToken(SyntaxKind.StaticKeyword)]);
if (memberElement) {
memberElements.push(...memberElement);
}
createClassElement(member, [factory.createToken(SyntaxKind.StaticKeyword)], memberElements);
}
});
}
// all instance members are stored in the "member" array of symbol (done last so instance members pulled from prototype assignments have priority)
if (symbol.members) {
symbol.members.forEach((member, key) => {
if (key === "constructor" && member.valueDeclaration) {
const prototypeAssignment = symbol.exports?.get("prototype" as __String)?.declarations?.[0]?.parent;
if (prototypeAssignment && isBinaryExpression(prototypeAssignment) && isObjectLiteralExpression(prototypeAssignment.right) && some(prototypeAssignment.right.properties, isConstructorAssignment)) {
// fn.prototype = { constructor: fn }
// Already deleted in `createClassElement` in first pass
}
else {
// fn.prototype.constructor = fn
changes.delete(sourceFile, member.valueDeclaration.parent);
}
return;
}
createClassElement(member, /*modifiers*/ undefined, memberElements);
});
}
return memberElements;
function shouldConvertDeclaration(_target: AccessExpression | ObjectLiteralExpression, source: Expression) {
@@ -109,19 +107,28 @@ namespace ts.codefix {
}
}
function createClassElement(symbol: Symbol, modifiers: Modifier[] | undefined): readonly ClassElement[] {
function createClassElement(symbol: Symbol, modifiers: Modifier[] | undefined, members: ClassElement[]): void {
// Right now the only thing we can convert are function expressions, which are marked as methods
// or { x: y } type prototype assignments, which are marked as ObjectLiteral
const members: ClassElement[] = [];
if (!(symbol.flags & SymbolFlags.Method) && !(symbol.flags & SymbolFlags.ObjectLiteral)) {
return members;
return;
}
const memberDeclaration = symbol.valueDeclaration as AccessExpression | ObjectLiteralExpression;
const assignmentBinaryExpression = memberDeclaration.parent as BinaryExpression;
const assignmentExpr = assignmentBinaryExpression.right;
if (!shouldConvertDeclaration(memberDeclaration, assignmentExpr)) {
return members;
return;
}
if (some(members, m => {
const name = getNameOfDeclaration(m);
if (name && isIdentifier(name) && idText(name) === symbolName(symbol)) {
return true; // class member already made for this name
}
return false;
})) {
return;
}
// delete the entire statement if this expression is the sole expression to take care of the semicolon at the end
@@ -132,7 +139,7 @@ namespace ts.codefix {
if (!assignmentExpr) {
members.push(factory.createPropertyDeclaration([], modifiers, symbol.name, /*questionToken*/ undefined,
/*type*/ undefined, /*initializer*/ undefined));
return members;
return;
}
// f.x = expr
@@ -140,52 +147,54 @@ namespace ts.codefix {
const quotePreference = getQuotePreference(sourceFile, preferences);
const name = tryGetPropertyName(memberDeclaration, compilerOptions, quotePreference);
if (name) {
return createFunctionLikeExpressionMember(members, assignmentExpr, name);
createFunctionLikeExpressionMember(members, assignmentExpr, name);
}
return members;
return;
}
// f.prototype = { ... }
else if (isObjectLiteralExpression(assignmentExpr)) {
return flatMap(
forEach(
assignmentExpr.properties,
property => {
if (isMethodDeclaration(property) || isGetOrSetAccessorDeclaration(property)) {
// MethodDeclaration and AccessorDeclaration can appear in a class directly
return members.concat(property);
members.push(property);
}
if (isPropertyAssignment(property) && isFunctionExpression(property.initializer)) {
return createFunctionLikeExpressionMember(members, property.initializer, property.name);
createFunctionLikeExpressionMember(members, property.initializer, property.name);
}
// Drop constructor assignments
if (isConstructorAssignment(property)) return members;
return [];
if (isConstructorAssignment(property)) return;
return;
}
);
return;
}
else {
// Don't try to declare members in JavaScript files
if (isSourceFileJS(sourceFile)) return members;
if (!isPropertyAccessExpression(memberDeclaration)) return members;
if (isSourceFileJS(sourceFile)) return;
if (!isPropertyAccessExpression(memberDeclaration)) return;
const prop = factory.createPropertyDeclaration(/*decorators*/ undefined, modifiers, memberDeclaration.name, /*questionToken*/ undefined, /*type*/ undefined, assignmentExpr);
copyLeadingComments(assignmentBinaryExpression.parent, prop, sourceFile);
members.push(prop);
return members;
return;
}
function createFunctionLikeExpressionMember(members: readonly ClassElement[], expression: FunctionExpression | ArrowFunction, name: PropertyName) {
function createFunctionLikeExpressionMember(members: ClassElement[], expression: FunctionExpression | ArrowFunction, name: PropertyName) {
if (isFunctionExpression(expression)) return createFunctionExpressionMember(members, expression, name);
else return createArrowFunctionExpressionMember(members, expression, name);
}
function createFunctionExpressionMember(members: readonly ClassElement[], functionExpression: FunctionExpression, name: PropertyName) {
function createFunctionExpressionMember(members: ClassElement[], functionExpression: FunctionExpression, name: PropertyName) {
const fullModifiers = concatenate(modifiers, getModifierKindFromSource(functionExpression, SyntaxKind.AsyncKeyword));
const method = factory.createMethodDeclaration(/*decorators*/ undefined, fullModifiers, /*asteriskToken*/ undefined, name, /*questionToken*/ undefined,
/*typeParameters*/ undefined, functionExpression.parameters, /*type*/ undefined, functionExpression.body);
copyLeadingComments(assignmentBinaryExpression, method, sourceFile);
return members.concat(method);
members.push(method);
return;
}
function createArrowFunctionExpressionMember(members: readonly ClassElement[], arrowFunction: ArrowFunction, name: PropertyName) {
function createArrowFunctionExpressionMember(members: ClassElement[], arrowFunction: ArrowFunction, name: PropertyName) {
const arrowFunctionBody = arrowFunction.body;
let bodyBlock: Block;
@@ -201,7 +210,7 @@ namespace ts.codefix {
const method = factory.createMethodDeclaration(/*decorators*/ undefined, fullModifiers, /*asteriskToken*/ undefined, name, /*questionToken*/ undefined,
/*typeParameters*/ undefined, arrowFunction.parameters, /*type*/ undefined, bodyBlock);
copyLeadingComments(assignmentBinaryExpression, method, sourceFile);
return members.concat(method);
members.push(method);
}
}
}

View File

@@ -179,13 +179,13 @@ namespace ts {
});
});
describe("unittests:: programApi:: Program.getDiagnosticsProducingTypeChecker / Program.getSemanticDiagnostics", () => {
describe("unittests:: programApi:: Program.getTypeChecker / Program.getSemanticDiagnostics", () => {
it("does not produce errors on `as const` it would not normally produce on the command line", () => {
const main = new documents.TextDocument("/main.ts", "0 as const");
const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main], cwd: "/" });
const program = createProgram(["/main.ts"], {}, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed }));
const typeChecker = program.getDiagnosticsProducingTypeChecker();
const typeChecker = program.getTypeChecker();
const sourceFile = program.getSourceFile("main.ts")!;
typeChecker.getTypeAtLocation(((sourceFile.statements[0] as ExpressionStatement).expression as AsExpression).type);
const diag = program.getSemanticDiagnostics();
@@ -199,7 +199,7 @@ namespace ts {
const program = createProgram(["/main.ts"], {}, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed }));
const sourceFile = program.getSourceFile("main.ts")!;
const typeChecker = program.getDiagnosticsProducingTypeChecker();
const typeChecker = program.getTypeChecker();
typeChecker.getSymbolAtLocation((sourceFile.statements[0] as ImportDeclaration).moduleSpecifier);
assert.isEmpty(program.getSemanticDiagnostics());
});