mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-24 11:43:18 -05:00
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:
File diff suppressed because it is too large
Load Diff
@@ -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[] {
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) &&
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user