diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0753ebbc38885..22d21459373f3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -305,7 +305,7 @@ namespace ts { (preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly); } - export function createTypeChecker(host: TypeCheckerHost, produceDiagnostics: boolean): TypeChecker { + export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const getPackagesMap = memoize(() => { // A package name maps to true when we detect it has .d.ts files. // This is useful as an approximation of whether a package bundles its own types. @@ -322,6 +322,12 @@ namespace ts { return map; }); + let deferredDiagnosticsCallbacks: (() => void)[] = []; + + let addLazyDiagnostic = (arg: () => void) => { + deferredDiagnosticsCallbacks.push(arg); + }; + // Cancellation that controls whether or not we can cancel in the middle of type checking. // In general cancelling is *not* safe for the type checker. We might be in the middle of // computing something, and we will leave our internals in an inconsistent state. Callers @@ -705,8 +711,8 @@ namespace ts { // this call is done. cancellationToken = ct; - // Ensure file is type checked - checkSourceFile(file); + // Ensure file is type checked, with _eager_ diagnostic production, so identifiers are registered as potentially unused + checkSourceFileWithEagerDiagnostics(file); Debug.assert(!!(getNodeLinks(file).flags & NodeCheckFlags.TypeChecked)); diagnostics = addRange(diagnostics, suggestionDiagnostics.getDiagnostics(file.fileName)); @@ -2142,121 +2148,125 @@ namespace ts { } } if (!result) { - if (nameNotFoundMessage && produceDiagnostics) { - if (!errorLocation || - !checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg!) && // TODO: GH#18217 - !checkAndReportErrorForExtendingInterface(errorLocation) && - !checkAndReportErrorForUsingTypeAsNamespace(errorLocation, name, meaning) && - !checkAndReportErrorForExportingPrimitiveType(errorLocation, name) && - !checkAndReportErrorForUsingTypeAsValue(errorLocation, name, meaning) && - !checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation, name, meaning) && - !checkAndReportErrorForUsingValueAsType(errorLocation, name, meaning)) { - let suggestion: Symbol | undefined; - if (getSpellingSuggestions && suggestionCount < maximumSuggestionCount) { - suggestion = getSuggestedSymbolForNonexistentSymbol(originalLocation, name, meaning); - const isGlobalScopeAugmentationDeclaration = suggestion?.valueDeclaration && isAmbientModule(suggestion.valueDeclaration) && isGlobalScopeAugmentation(suggestion.valueDeclaration); - if (isGlobalScopeAugmentationDeclaration) { - suggestion = undefined; - } - if (suggestion) { - const suggestionName = symbolToString(suggestion); - const isUncheckedJS = isUncheckedJSSuggestion(originalLocation, suggestion, /*excludeClasses*/ false); - const message = meaning === SymbolFlags.Namespace || nameArg && typeof nameArg !== "string" && nodeIsSynthesized(nameArg) ? Diagnostics.Cannot_find_namespace_0_Did_you_mean_1 - : isUncheckedJS ? Diagnostics.Could_not_find_name_0_Did_you_mean_1 - : Diagnostics.Cannot_find_name_0_Did_you_mean_1; - const diagnostic = createError(errorLocation, message, diagnosticName(nameArg!), suggestionName); - addErrorOrSuggestion(!isUncheckedJS, diagnostic); - if (suggestion.valueDeclaration) { - addRelatedInfo( - diagnostic, - createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName) - ); + if (nameNotFoundMessage) { + addLazyDiagnostic(() => { + if (!errorLocation || + !checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg!) && // TODO: GH#18217 + !checkAndReportErrorForExtendingInterface(errorLocation) && + !checkAndReportErrorForUsingTypeAsNamespace(errorLocation, name, meaning) && + !checkAndReportErrorForExportingPrimitiveType(errorLocation, name) && + !checkAndReportErrorForUsingTypeAsValue(errorLocation, name, meaning) && + !checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation, name, meaning) && + !checkAndReportErrorForUsingValueAsType(errorLocation, name, meaning)) { + let suggestion: Symbol | undefined; + if (getSpellingSuggestions && suggestionCount < maximumSuggestionCount) { + suggestion = getSuggestedSymbolForNonexistentSymbol(originalLocation, name, meaning); + const isGlobalScopeAugmentationDeclaration = suggestion?.valueDeclaration && isAmbientModule(suggestion.valueDeclaration) && isGlobalScopeAugmentation(suggestion.valueDeclaration); + if (isGlobalScopeAugmentationDeclaration) { + suggestion = undefined; } - } - } - if (!suggestion) { - if (nameArg) { - const lib = getSuggestedLibForNonExistentName(nameArg); - if (lib) { - error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg), lib); + if (suggestion) { + const suggestionName = symbolToString(suggestion); + const isUncheckedJS = isUncheckedJSSuggestion(originalLocation, suggestion, /*excludeClasses*/ false); + const message = meaning === SymbolFlags.Namespace || nameArg && typeof nameArg !== "string" && nodeIsSynthesized(nameArg) ? Diagnostics.Cannot_find_namespace_0_Did_you_mean_1 + : isUncheckedJS ? Diagnostics.Could_not_find_name_0_Did_you_mean_1 + : Diagnostics.Cannot_find_name_0_Did_you_mean_1; + const diagnostic = createError(errorLocation, message, diagnosticName(nameArg!), suggestionName); + addErrorOrSuggestion(!isUncheckedJS, diagnostic); + if (suggestion.valueDeclaration) { + addRelatedInfo( + diagnostic, + createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName) + ); + } } - else { - error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg)); + } + if (!suggestion) { + if (nameArg) { + const lib = getSuggestedLibForNonExistentName(nameArg); + if (lib) { + error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg), lib); + } + else { + error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg)); + } } } + suggestionCount++; } - suggestionCount++; - } + }); } return undefined; } - // Perform extra checks only if error reporting was requested - if (nameNotFoundMessage && produceDiagnostics) { - if (propertyWithInvalidInitializer && !(getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields)) { - // We have a match, but the reference occurred within a property initializer and the identifier also binds - // to a local variable in the constructor where the code will be emitted. Note that this is actually allowed - // with ESNext+useDefineForClassFields because the scope semantics are different. - const propertyName = (propertyWithInvalidInitializer as PropertyDeclaration).name; - error(errorLocation, Diagnostics.Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor, - declarationNameToString(propertyName), diagnosticName(nameArg!)); - return undefined; - } - - // Only check for block-scoped variable if we have an error location and are looking for the - // name with variable meaning - // For example, - // declare module foo { - // interface bar {} - // } - // const foo/*1*/: foo/*2*/.bar; - // The foo at /*1*/ and /*2*/ will share same symbol with two meanings: - // block-scoped variable and namespace module. However, only when we - // try to resolve name in /*1*/ which is used in variable position, - // we want to check for block-scoped - if (errorLocation && - (meaning & SymbolFlags.BlockScopedVariable || - ((meaning & SymbolFlags.Class || meaning & SymbolFlags.Enum) && (meaning & SymbolFlags.Value) === SymbolFlags.Value))) { - const exportOrLocalSymbol = getExportSymbolOfValueSymbolIfExported(result); - if (exportOrLocalSymbol.flags & SymbolFlags.BlockScopedVariable || exportOrLocalSymbol.flags & SymbolFlags.Class || exportOrLocalSymbol.flags & SymbolFlags.Enum) { - checkResolvedBlockScopedVariable(exportOrLocalSymbol, errorLocation); - } - } - - // If we're in an external module, we can't reference value symbols created from UMD export declarations - if (result && isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value && !(originalLocation!.flags & NodeFlags.JSDoc)) { - const merged = getMergedSymbol(result); - if (length(merged.declarations) && every(merged.declarations, d => isNamespaceExportDeclaration(d) || isSourceFile(d) && !!d.symbol.globalExports)) { - errorOrSuggestion(!compilerOptions.allowUmdGlobalAccess, errorLocation!, Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead, unescapeLeadingUnderscores(name)); - } - } + if (propertyWithInvalidInitializer && !(getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields)) { + // We have a match, but the reference occurred within a property initializer and the identifier also binds + // to a local variable in the constructor where the code will be emitted. Note that this is actually allowed + // with ESNext+useDefineForClassFields because the scope semantics are different. + const propertyName = (propertyWithInvalidInitializer as PropertyDeclaration).name; + error(errorLocation, Diagnostics.Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor, + declarationNameToString(propertyName), diagnosticName(nameArg!)); + return undefined; + } - // If we're in a parameter initializer or binding name, we can't reference the values of the parameter whose initializer we're within or parameters to the right - if (result && associatedDeclarationForContainingInitializerOrBindingName && !withinDeferredContext && (meaning & SymbolFlags.Value) === SymbolFlags.Value) { - const candidate = getMergedSymbol(getLateBoundSymbol(result)); - const root = (getRootDeclaration(associatedDeclarationForContainingInitializerOrBindingName) as ParameterDeclaration); - // A parameter initializer or binding pattern initializer within a parameter cannot refer to itself - if (candidate === getSymbolOfNode(associatedDeclarationForContainingInitializerOrBindingName)) { - error(errorLocation, Diagnostics.Parameter_0_cannot_reference_itself, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name)); - } - // And it cannot refer to any declarations which come after it - else if (candidate.valueDeclaration && candidate.valueDeclaration.pos > associatedDeclarationForContainingInitializerOrBindingName.pos && root.parent.locals && lookup(root.parent.locals, candidate.escapedName, meaning) === candidate) { - error(errorLocation, Diagnostics.Parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name), declarationNameToString(errorLocation as Identifier)); - } - } - if (result && errorLocation && meaning & SymbolFlags.Value && result.flags & SymbolFlags.Alias && !(result.flags & SymbolFlags.Value) && !isValidTypeOnlyAliasUseSite(errorLocation)) { - const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(result); - if (typeOnlyDeclaration) { - const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier - ? Diagnostics._0_cannot_be_used_as_a_value_because_it_was_exported_using_export_type - : Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type; - const unescapedName = unescapeLeadingUnderscores(name); - addTypeOnlyDeclarationRelatedInfo( - error(errorLocation, message, unescapedName), - typeOnlyDeclaration, - unescapedName); + // Perform extra checks only if error reporting was requested + if (nameNotFoundMessage) { + addLazyDiagnostic(() => { + // Only check for block-scoped variable if we have an error location and are looking for the + // name with variable meaning + // For example, + // declare module foo { + // interface bar {} + // } + // const foo/*1*/: foo/*2*/.bar; + // The foo at /*1*/ and /*2*/ will share same symbol with two meanings: + // block-scoped variable and namespace module. However, only when we + // try to resolve name in /*1*/ which is used in variable position, + // we want to check for block-scoped + if (errorLocation && + (meaning & SymbolFlags.BlockScopedVariable || + ((meaning & SymbolFlags.Class || meaning & SymbolFlags.Enum) && (meaning & SymbolFlags.Value) === SymbolFlags.Value))) { + const exportOrLocalSymbol = getExportSymbolOfValueSymbolIfExported(result!); + if (exportOrLocalSymbol.flags & SymbolFlags.BlockScopedVariable || exportOrLocalSymbol.flags & SymbolFlags.Class || exportOrLocalSymbol.flags & SymbolFlags.Enum) { + checkResolvedBlockScopedVariable(exportOrLocalSymbol, errorLocation); + } + } + + // If we're in an external module, we can't reference value symbols created from UMD export declarations + if (result && isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value && !(originalLocation!.flags & NodeFlags.JSDoc)) { + const merged = getMergedSymbol(result); + if (length(merged.declarations) && every(merged.declarations, d => isNamespaceExportDeclaration(d) || isSourceFile(d) && !!d.symbol.globalExports)) { + errorOrSuggestion(!compilerOptions.allowUmdGlobalAccess, errorLocation!, Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead, unescapeLeadingUnderscores(name)); + } + } + + // If we're in a parameter initializer or binding name, we can't reference the values of the parameter whose initializer we're within or parameters to the right + if (result && associatedDeclarationForContainingInitializerOrBindingName && !withinDeferredContext && (meaning & SymbolFlags.Value) === SymbolFlags.Value) { + const candidate = getMergedSymbol(getLateBoundSymbol(result)); + const root = (getRootDeclaration(associatedDeclarationForContainingInitializerOrBindingName) as ParameterDeclaration); + // A parameter initializer or binding pattern initializer within a parameter cannot refer to itself + if (candidate === getSymbolOfNode(associatedDeclarationForContainingInitializerOrBindingName)) { + error(errorLocation, Diagnostics.Parameter_0_cannot_reference_itself, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name)); + } + // And it cannot refer to any declarations which come after it + else if (candidate.valueDeclaration && candidate.valueDeclaration.pos > associatedDeclarationForContainingInitializerOrBindingName.pos && root.parent.locals && lookup(root.parent.locals, candidate.escapedName, meaning) === candidate) { + error(errorLocation, Diagnostics.Parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name), declarationNameToString(errorLocation as Identifier)); + } + } + if (result && errorLocation && meaning & SymbolFlags.Value && result.flags & SymbolFlags.Alias && !(result.flags & SymbolFlags.Value) && !isValidTypeOnlyAliasUseSite(errorLocation)) { + const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(result); + if (typeOnlyDeclaration) { + const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier + ? Diagnostics._0_cannot_be_used_as_a_value_because_it_was_exported_using_export_type + : Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type; + const unescapedName = unescapeLeadingUnderscores(name); + addTypeOnlyDeclarationRelatedInfo( + error(errorLocation, message, unescapedName), + typeOnlyDeclaration, + unescapedName); + } } - } + }); } return result; } @@ -4095,9 +4105,7 @@ namespace ts { const result = new Type(checker, flags); typeCount++; result.id = typeCount; - if (produceDiagnostics) { // Only record types from one checker - tracing?.recordType(result); - } + tracing?.recordType(result); return result; } @@ -21659,12 +21667,14 @@ namespace ts { } function reportErrorsFromWidening(declaration: Declaration, type: Type, wideningKind?: WideningKind) { - if (produceDiagnostics && noImplicitAny && getObjectFlags(type) & ObjectFlags.ContainsWideningType && (!wideningKind || !getContextualSignatureForFunctionLikeDeclaration(declaration as FunctionLikeDeclaration))) { - // Report implicit any error within type if possible, otherwise report error on declaration - if (!reportWideningErrorsInType(type)) { - reportImplicitAny(declaration, type, wideningKind); + addLazyDiagnostic(() => { + if (noImplicitAny && getObjectFlags(type) & ObjectFlags.ContainsWideningType && (!wideningKind || !getContextualSignatureForFunctionLikeDeclaration(declaration as FunctionLikeDeclaration))) { + // Report implicit any error within type if possible, otherwise report error on declaration + if (!reportWideningErrorsInType(type)) { + reportImplicitAny(declaration, type, wideningKind); + } } - } + }); } function applyToParameterTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { @@ -30342,7 +30352,7 @@ namespace ts { const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; const isDecorator = node.kind === SyntaxKind.Decorator; const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); - const reportErrors = !candidatesOutArray && produceDiagnostics; + const reportErrors = !candidatesOutArray; let typeArguments: NodeArray | undefined; @@ -31777,12 +31787,14 @@ namespace ts { checkSourceElement(type); exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType)); const targetType = getTypeFromTypeNode(type); - if (produceDiagnostics && !isErrorType(targetType)) { - const widenedType = getWidenedType(exprType); - if (!isTypeComparableTo(targetType, widenedType)) { - checkTypeComparableTo(exprType, targetType, errNode, - Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first); - } + if (!isErrorType(targetType)) { + addLazyDiagnostic(() => { + const widenedType = getWidenedType(exprType); + if (!isTypeComparableTo(targetType, widenedType)) { + checkTypeComparableTo(exprType, targetType, errNode, + Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first); + } + }); } return targetType; } @@ -32604,53 +32616,54 @@ namespace ts { * * @param returnType - return type of the function, can be undefined if return type is not explicitly specified */ - function checkAllCodePathsInNonVoidFunctionReturnOrThrow(func: FunctionLikeDeclaration | MethodSignature, returnType: Type | undefined): void { - if (!produceDiagnostics) { - return; - } + function checkAllCodePathsInNonVoidFunctionReturnOrThrow(func: FunctionLikeDeclaration | MethodSignature, returnType: Type | undefined) { + addLazyDiagnostic(checkAllCodePathsInNonVoidFunctionReturnOrThrowDiagnostics); + return; - const functionFlags = getFunctionFlags(func); - const type = returnType && unwrapReturnType(returnType, functionFlags); + function checkAllCodePathsInNonVoidFunctionReturnOrThrowDiagnostics(): void { + const functionFlags = getFunctionFlags(func); + const type = returnType && unwrapReturnType(returnType, functionFlags); - // Functions with with an explicitly specified 'void' or 'any' return type don't need any return expressions. - if (type && maybeTypeOfKind(type, TypeFlags.Any | TypeFlags.Void)) { - return; - } + // Functions with with an explicitly specified 'void' or 'any' return type don't need any return expressions. + if (type && maybeTypeOfKind(type, TypeFlags.Any | TypeFlags.Void)) { + return; + } - // If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check. - // also if HasImplicitReturn flag is not set this means that all codepaths in function body end with return or throw - if (func.kind === SyntaxKind.MethodSignature || nodeIsMissing(func.body) || func.body!.kind !== SyntaxKind.Block || !functionHasImplicitReturn(func)) { - return; - } + // If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check. + // also if HasImplicitReturn flag is not set this means that all codepaths in function body end with return or throw + if (func.kind === SyntaxKind.MethodSignature || nodeIsMissing(func.body) || func.body!.kind !== SyntaxKind.Block || !functionHasImplicitReturn(func)) { + return; + } - const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn; - const errorNode = getEffectiveReturnTypeNode(func) || func; + const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn; + const errorNode = getEffectiveReturnTypeNode(func) || func; - if (type && type.flags & TypeFlags.Never) { - error(errorNode, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point); - } - else if (type && !hasExplicitReturn) { - // minimal check: function has syntactic return type annotation and no explicit return statements in the body - // this function does not conform to the specification. - error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); - } - else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) { - error(errorNode, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined); - } - else if (compilerOptions.noImplicitReturns) { - if (!type) { - // If return type annotation is omitted check if function has any explicit return statements. - // If it does not have any - its inferred return type is void - don't do any checks. - // Otherwise get inferred return type from function body and report error only if it is not void / anytype - if (!hasExplicitReturn) { - return; - } - const inferredReturnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func)); - if (isUnwrappedReturnTypeVoidOrAny(func, inferredReturnType)) { - return; + if (type && type.flags & TypeFlags.Never) { + error(errorNode, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point); + } + else if (type && !hasExplicitReturn) { + // minimal check: function has syntactic return type annotation and no explicit return statements in the body + // this function does not conform to the specification. + error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); + } + else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) { + error(errorNode, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined); + } + else if (compilerOptions.noImplicitReturns) { + if (!type) { + // If return type annotation is omitted check if function has any explicit return statements. + // If it does not have any - its inferred return type is void - don't do any checks. + // Otherwise get inferred return type from function body and report error only if it is not void / anytype + if (!hasExplicitReturn) { + return; + } + const inferredReturnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func)); + if (isUnwrappedReturnTypeVoidOrAny(func, inferredReturnType)) { + return; + } } + error(errorNode, Diagnostics.Not_all_code_paths_return_a_value); } - error(errorNode, Diagnostics.Not_all_code_paths_return_a_value); } } @@ -32934,52 +32947,54 @@ namespace ts { return undefinedWideningType; } - function checkAwaitExpression(node: AwaitExpression): Type { + function checkAwaitExpressionGrammar(node: AwaitExpression): void { // Grammar checking - if (produceDiagnostics) { - const container = getContainingFunctionOrClassStaticBlock(node); - if (container && isClassStaticBlockDeclaration(container)) { - error(node, Diagnostics.Await_expression_cannot_be_used_inside_a_class_static_block); - } - else if (!(node.flags & NodeFlags.AwaitContext)) { - if (isInTopLevelContext(node)) { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - let span: TextSpan | undefined; - if (!isEffectiveExternalModule(sourceFile, compilerOptions)) { - if (!span) span = getSpanOfTokenAtPosition(sourceFile, node.pos); - const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, - Diagnostics.await_expressions_are_only_allowed_at_the_top_level_of_a_file_when_that_file_is_a_module_but_this_file_has_no_imports_or_exports_Consider_adding_an_empty_export_to_make_this_file_a_module); - diagnostics.add(diagnostic); - } - if ((moduleKind !== ModuleKind.ES2022 && moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System && !(moduleKind === ModuleKind.NodeNext && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.ESNext)) || languageVersion < ScriptTarget.ES2017) { - span = getSpanOfTokenAtPosition(sourceFile, node.pos); - const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, - Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher); - diagnostics.add(diagnostic); - } + const container = getContainingFunctionOrClassStaticBlock(node); + if (container && isClassStaticBlockDeclaration(container)) { + error(node, Diagnostics.Await_expression_cannot_be_used_inside_a_class_static_block); + } + else if (!(node.flags & NodeFlags.AwaitContext)) { + if (isInTopLevelContext(node)) { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + let span: TextSpan | undefined; + if (!isEffectiveExternalModule(sourceFile, compilerOptions)) { + if (!span) span = getSpanOfTokenAtPosition(sourceFile, node.pos); + const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, + Diagnostics.await_expressions_are_only_allowed_at_the_top_level_of_a_file_when_that_file_is_a_module_but_this_file_has_no_imports_or_exports_Consider_adding_an_empty_export_to_make_this_file_a_module); + diagnostics.add(diagnostic); } - } - else { - // use of 'await' in non-async function - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - const span = getSpanOfTokenAtPosition(sourceFile, node.pos); - const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules); - if (container && container.kind !== SyntaxKind.Constructor && (getFunctionFlags(container) & FunctionFlags.Async) === 0) { - const relatedInfo = createDiagnosticForNode(container, Diagnostics.Did_you_mean_to_mark_this_function_as_async); - addRelatedInfo(diagnostic, relatedInfo); - } + if ((moduleKind !== ModuleKind.ES2022 && moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System && !(moduleKind === ModuleKind.NodeNext && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.ESNext)) || languageVersion < ScriptTarget.ES2017) { + span = getSpanOfTokenAtPosition(sourceFile, node.pos); + const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, + Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher); diagnostics.add(diagnostic); } } } - - if (isInParameterInitializerBeforeContainingFunction(node)) { - error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer); + else { + // use of 'await' in non-async function + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules); + if (container && container.kind !== SyntaxKind.Constructor && (getFunctionFlags(container) & FunctionFlags.Async) === 0) { + const relatedInfo = createDiagnosticForNode(container, Diagnostics.Did_you_mean_to_mark_this_function_as_async); + addRelatedInfo(diagnostic, relatedInfo); + } + diagnostics.add(diagnostic); + } } } + if (isInParameterInitializerBeforeContainingFunction(node)) { + error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer); + } + } + + function checkAwaitExpression(node: AwaitExpression): Type { + addLazyDiagnostic(() => checkAwaitExpressionGrammar(node)); + const operandType = checkExpression(node.expression); const awaitedType = checkAwaitedType(operandType, /*withAlias*/ true, node, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); if (awaitedType === operandType && !isErrorType(awaitedType) && !(operandType.flags & TypeFlags.AnyOrUnknown)) { @@ -33897,7 +33912,11 @@ namespace ts { } function checkAssignmentOperator(valueType: Type): void { - if (produceDiagnostics && isAssignmentOperator(operator)) { + if (isAssignmentOperator(operator)) { + addLazyDiagnostic(checkAssignmentOperatorWorker); + } + + function checkAssignmentOperatorWorker() { // TypeScript 1.0 spec (April 2014): 4.17 // An assignment of the form // VarExpr = ValueExpr @@ -34018,16 +34037,7 @@ namespace ts { } function checkYieldExpression(node: YieldExpression): Type { - // Grammar checking - if (produceDiagnostics) { - if (!(node.flags & NodeFlags.YieldContext)) { - grammarErrorOnFirstToken(node, Diagnostics.A_yield_expression_is_only_allowed_in_a_generator_body); - } - - if (isInParameterInitializerBeforeContainingFunction(node)) { - error(node, Diagnostics.yield_expressions_cannot_be_used_in_a_parameter_initializer); - } - } + addLazyDiagnostic(checkYieldExpressionGrammar); const func = getContainingFunction(node); if (!func) return anyType; @@ -34078,14 +34088,26 @@ namespace ts { let type = getContextualIterationType(IterationTypeKind.Next, func); if (!type) { type = anyType; - if (produceDiagnostics && noImplicitAny && !expressionResultIsUnused(node)) { - const contextualType = getContextualType(node); - if (!contextualType || isTypeAny(contextualType)) { - error(node, Diagnostics.yield_expression_implicitly_results_in_an_any_type_because_its_containing_generator_lacks_a_return_type_annotation); + addLazyDiagnostic(() => { + if (noImplicitAny && !expressionResultIsUnused(node)) { + const contextualType = getContextualType(node); + if (!contextualType || isTypeAny(contextualType)) { + error(node, Diagnostics.yield_expression_implicitly_results_in_an_any_type_because_its_containing_generator_lacks_a_return_type_annotation); + } } - } + }); } return type; + + function checkYieldExpressionGrammar() { + if (!(node.flags & NodeFlags.YieldContext)) { + grammarErrorOnFirstToken(node, Diagnostics.A_yield_expression_is_only_allowed_in_a_generator_body); + } + + if (isInParameterInitializerBeforeContainingFunction(node)) { + error(node, Diagnostics.yield_expressions_cannot_be_used_in_a_parameter_initializer); + } + } } function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { @@ -34723,9 +34745,7 @@ namespace ts { varianceTypeParameter = saveVarianceTypeParameter; } } - if (produceDiagnostics) { - checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0); - } + addLazyDiagnostic(() => checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0)); } function checkParameter(node: ParameterDeclaration) { @@ -34906,7 +34926,9 @@ namespace ts { checkSourceElement(node.type); } - if (produceDiagnostics) { + addLazyDiagnostic(checkSignatureDeclarationDiagnostics); + + function checkSignatureDeclarationDiagnostics() { checkCollisionWithArgumentsInGeneratedCode(node); const returnTypeNode = getEffectiveReturnTypeNode(node); if (noImplicitAny && !returnTypeNode) { @@ -35216,9 +35238,9 @@ namespace ts { return; } - if (!produceDiagnostics) { - return; - } + addLazyDiagnostic(checkConstructorDeclarationDiagnostics); + + return; function isInstancePropertyWithInitializerOrPrivateIdentifierProperty(n: Node): boolean { if (isPrivateIdentifierClassElementDeclaration(n)) { @@ -35229,59 +35251,61 @@ namespace ts { !!(n as PropertyDeclaration).initializer; } - // TS 1.0 spec (April 2014): 8.3.2 - // Constructors of classes with no extends clause may not contain super calls, whereas - // constructors of derived classes must contain at least one super call somewhere in their function body. - const containingClassDecl = node.parent as ClassDeclaration; - if (getClassExtendsHeritageElement(containingClassDecl)) { - captureLexicalThis(node.parent, containingClassDecl); - const classExtendsNull = classDeclarationExtendsNull(containingClassDecl); - const superCall = findFirstSuperCall(node.body!); - if (superCall) { - if (classExtendsNull) { - error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null); - } - - // A super call must be root-level in a constructor if both of the following are true: - // - The containing class is a derived class. - // - The constructor declares parameter properties - // or the containing class declares instance member variables with initializers. - - const superCallShouldBeRootLevel = - (getEmitScriptTarget(compilerOptions) !== ScriptTarget.ESNext || !useDefineForClassFields) && - (some((node.parent as ClassDeclaration).members, isInstancePropertyWithInitializerOrPrivateIdentifierProperty) || - some(node.parameters, p => hasSyntacticModifier(p, ModifierFlags.ParameterPropertyModifier))); - - if (superCallShouldBeRootLevel) { - // Until we have better flow analysis, it is an error to place the super call within any kind of block or conditional - // See GH #8277 - if (!superCallIsRootLevelInConstructor(superCall, node.body!)) { - error(superCall, Diagnostics.A_super_call_must_be_a_root_level_statement_within_a_constructor_of_a_derived_class_that_contains_initialized_properties_parameter_properties_or_private_identifiers); - } - // Skip past any prologue directives to check statements for referring to 'super' or 'this' before a super call - else { - let superCallStatement: ExpressionStatement | undefined; + function checkConstructorDeclarationDiagnostics() { + // TS 1.0 spec (April 2014): 8.3.2 + // Constructors of classes with no extends clause may not contain super calls, whereas + // constructors of derived classes must contain at least one super call somewhere in their function body. + const containingClassDecl = node.parent as ClassDeclaration; + if (getClassExtendsHeritageElement(containingClassDecl)) { + captureLexicalThis(node.parent, containingClassDecl); + const classExtendsNull = classDeclarationExtendsNull(containingClassDecl); + const superCall = findFirstSuperCall(node.body!); + if (superCall) { + if (classExtendsNull) { + error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null); + } - for (const statement of node.body!.statements) { - if (isExpressionStatement(statement) && isSuperCall(skipOuterExpressions(statement.expression))) { - superCallStatement = statement; - break; - } - if (!isPrologueDirective(statement) && nodeImmediatelyReferencesSuperOrThis(statement)) { - break; - } - } + // A super call must be root-level in a constructor if both of the following are true: + // - The containing class is a derived class. + // - The constructor declares parameter properties + // or the containing class declares instance member variables with initializers. + const superCallShouldBeRootLevel = + (getEmitScriptTarget(compilerOptions) !== ScriptTarget.ESNext || !useDefineForClassFields) && + (some((node.parent as ClassDeclaration).members, isInstancePropertyWithInitializerOrPrivateIdentifierProperty) || + some(node.parameters, p => hasSyntacticModifier(p, ModifierFlags.ParameterPropertyModifier))); + + if (superCallShouldBeRootLevel) { // Until we have better flow analysis, it is an error to place the super call within any kind of block or conditional // See GH #8277 - if (superCallStatement === undefined) { - error(node, Diagnostics.A_super_call_must_be_the_first_statement_in_the_constructor_to_refer_to_super_or_this_when_a_derived_class_contains_initialized_properties_parameter_properties_or_private_identifiers); + if (!superCallIsRootLevelInConstructor(superCall, node.body!)) { + error(superCall, Diagnostics.A_super_call_must_be_a_root_level_statement_within_a_constructor_of_a_derived_class_that_contains_initialized_properties_parameter_properties_or_private_identifiers); + } + // Skip past any prologue directives to check statements for referring to 'super' or 'this' before a super call + else { + let superCallStatement: ExpressionStatement | undefined; + + for (const statement of node.body!.statements) { + if (isExpressionStatement(statement) && isSuperCall(skipOuterExpressions(statement.expression))) { + superCallStatement = statement; + break; + } + if (!isPrologueDirective(statement) && nodeImmediatelyReferencesSuperOrThis(statement)) { + break; + } + } + + // Until we have better flow analysis, it is an error to place the super call within any kind of block or conditional + // See GH #8277 + if (superCallStatement === undefined) { + error(node, Diagnostics.A_super_call_must_be_the_first_statement_in_the_constructor_to_refer_to_super_or_this_when_a_derived_class_contains_initialized_properties_parameter_properties_or_private_identifiers); + } } } } - } - else if (!classExtendsNull) { - error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call); + else if (!classExtendsNull) { + error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call); + } } } } @@ -35304,7 +35328,11 @@ namespace ts { } function checkAccessorDeclaration(node: AccessorDeclaration) { - if (produceDiagnostics) { + addLazyDiagnostic(checkAccessorDeclarationDiagnostics); + checkSourceElement(node.body); + setNodeLinksForPrivateIdentifierScope(node); + + function checkAccessorDeclarationDiagnostics() { // Grammar checking accessors if (!checkGrammarFunctionLikeDeclaration(node) && !checkGrammarAccessor(node)) checkGrammarComputedPropertyName(node.name); @@ -35356,8 +35384,6 @@ namespace ts { checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); } } - checkSourceElement(node.body); - setNodeLinksForPrivateIdentifierScope(node); } function checkMissingDeclaration(node: Node) { @@ -35410,11 +35436,13 @@ namespace ts { forEach(node.typeArguments, checkSourceElement); const type = getTypeFromTypeReference(node); if (!isErrorType(type)) { - if (node.typeArguments && produceDiagnostics) { - const typeParameters = getTypeParametersForTypeReference(node); - if (typeParameters) { - checkTypeArgumentConstraints(node, typeParameters); - } + if (node.typeArguments) { + addLazyDiagnostic(() => { + const typeParameters = getTypeParametersForTypeReference(node); + if (typeParameters) { + checkTypeArgumentConstraints(node, typeParameters); + } + }); } const symbol = getNodeLinks(node).resolvedSymbol; if (symbol) { @@ -35447,7 +35475,9 @@ namespace ts { function checkTypeLiteral(node: TypeLiteralNode) { forEach(node.members, checkSourceElement); - if (produceDiagnostics) { + addLazyDiagnostic(checkTypeLiteralDiagnostics); + + function checkTypeLiteralDiagnostics() { const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); checkIndexConstraints(type, type.symbol); checkTypeForDuplicateIndexSignatures(node); @@ -35658,10 +35688,10 @@ namespace ts { } function checkFunctionOrConstructorSymbol(symbol: Symbol): void { - if (!produceDiagnostics) { - return; - } + addLazyDiagnostic(() => checkFunctionOrConstructorSymbolWorker(symbol)); + } + function checkFunctionOrConstructorSymbolWorker(symbol: Symbol): void { function getCanonicalOverload(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined): Declaration { // Consider the canonical set of flags to be the flags of the bodyDeclaration or the first declaration // Error on all deviations from this canonical set of flags @@ -35909,10 +35939,10 @@ namespace ts { } function checkExportsOnMergedDeclarations(node: Declaration): void { - if (!produceDiagnostics) { - return; - } + addLazyDiagnostic(() => checkExportsOnMergedDeclarationsWorker(node)); + } + function checkExportsOnMergedDeclarationsWorker(node: Declaration): void { // if localSymbol is defined on node then node itself is exported - check is required let symbol = node.localSymbol; if (!symbol) { @@ -36640,7 +36670,9 @@ namespace ts { } function checkFunctionDeclaration(node: FunctionDeclaration): void { - if (produceDiagnostics) { + addLazyDiagnostic(checkFunctionDeclarationDiagnostics); + + function checkFunctionDeclarationDiagnostics() { checkFunctionOrMethodDeclaration(node); checkGrammarForGenerator(node); checkCollisionsForDeclarationName(node, node.name); @@ -36679,10 +36711,14 @@ namespace ts { } function checkJSDocFunctionType(node: JSDocFunctionType): void { - if (produceDiagnostics && !node.type && !isJSDocConstructSignature(node)) { - reportImplicitAny(node, anyType); - } + addLazyDiagnostic(checkJSDocFunctionTypeImplicitAny); checkSignatureDeclaration(node); + + function checkJSDocFunctionTypeImplicitAny() { + if (!node.type && !isJSDocConstructSignature(node)) { + reportImplicitAny(node, anyType); + } + } } function checkJSDocImplementsTag(node: JSDocImplementsTag): void { @@ -36778,20 +36814,7 @@ namespace ts { checkSourceElement(body); checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, getReturnTypeFromAnnotation(node)); - if (produceDiagnostics && !getEffectiveReturnTypeNode(node)) { - // Report an implicit any error if there is no body, no explicit return type, and node is not a private method - // in an ambient context - if (nodeIsMissing(body) && !isPrivateWithinAmbient(node)) { - reportImplicitAny(node, anyType); - } - - if (functionFlags & FunctionFlags.Generator && nodeIsPresent(body)) { - // A generator with a body and no type annotation can still cause errors. It can error if the - // yielded values have no common supertype, or it can give an implicit any error if it has no - // yielded values. The only way to trigger these errors is to try checking its return type. - getReturnTypeOfSignature(getSignatureFromDeclaration(node)); - } - } + addLazyDiagnostic(checkFunctionOrMethodDeclarationDiagnostics); // A js function declaration can have a @type tag instead of a return type node, but that type must have a call signature if (isInJSFile(node)) { @@ -36800,11 +36823,30 @@ namespace ts { error(typeTag.typeExpression.type, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature); } } + + function checkFunctionOrMethodDeclarationDiagnostics() { + if (!getEffectiveReturnTypeNode(node)) { + // Report an implicit any error if there is no body, no explicit return type, and node is not a private method + // in an ambient context + if (nodeIsMissing(body) && !isPrivateWithinAmbient(node)) { + reportImplicitAny(node, anyType); + } + + if (functionFlags & FunctionFlags.Generator && nodeIsPresent(body)) { + // A generator with a body and no type annotation can still cause errors. It can error if the + // yielded values have no common supertype, or it can give an implicit any error if it has no + // yielded values. The only way to trigger these errors is to try checking its return type. + getReturnTypeOfSignature(getSignatureFromDeclaration(node)); + } + } + } } function registerForUnusedIdentifiersCheck(node: PotentiallyUnusedIdentifier): void { - // May be in a call such as getTypeOfNode that happened to call this. But potentiallyUnusedIdentifiers is only defined in the scope of `checkSourceFile`. - if (produceDiagnostics) { + addLazyDiagnostic(registerForUnusedIdentifiersCheckDiagnostics); + + function registerForUnusedIdentifiersCheckDiagnostics() { + // May be in a call such as getTypeOfNode that happened to call this. But potentiallyUnusedIdentifiers is only defined in the scope of `checkSourceFile`. const sourceFile = getSourceFileOfNode(node); let potentiallyUnusedIdentifiers = allPotentiallyUnusedIdentifiers.get(sourceFile.path); if (!potentiallyUnusedIdentifiers) { @@ -38779,26 +38821,32 @@ namespace ts { } } - if (produceDiagnostics && clause.kind === SyntaxKind.CaseClause) { - // TypeScript 1.0 spec (April 2014): 5.9 - // In a 'switch' statement, each 'case' expression must be of a type that is comparable - // to or from the type of the 'switch' expression. - let caseType = checkExpression(clause.expression); - const caseIsLiteral = isLiteralType(caseType); - let comparedExpressionType = expressionType; - if (!caseIsLiteral || !expressionIsLiteral) { - caseType = caseIsLiteral ? getBaseTypeOfLiteralType(caseType) : caseType; - comparedExpressionType = getBaseTypeOfLiteralType(expressionType); - } - if (!isTypeEqualityComparableTo(comparedExpressionType, caseType)) { - // expressionType is not comparable to caseType, try the reversed check and report errors if it fails - checkTypeComparableTo(caseType, comparedExpressionType, clause.expression, /*headMessage*/ undefined); - } + if (clause.kind === SyntaxKind.CaseClause) { + addLazyDiagnostic(createLazyCaseClauseDiagnostics(clause)); } forEach(clause.statements, checkSourceElement); if (compilerOptions.noFallthroughCasesInSwitch && clause.fallthroughFlowNode && isReachableFlowNode(clause.fallthroughFlowNode)) { error(clause, Diagnostics.Fallthrough_case_in_switch); } + + function createLazyCaseClauseDiagnostics(clause: CaseClause) { + return () => { + // TypeScript 1.0 spec (April 2014): 5.9 + // In a 'switch' statement, each 'case' expression must be of a type that is comparable + // to or from the type of the 'switch' expression. + let caseType = checkExpression(clause.expression); + const caseIsLiteral = isLiteralType(caseType); + let comparedExpressionType = expressionType; + if (!caseIsLiteral || !expressionIsLiteral) { + caseType = caseIsLiteral ? getBaseTypeOfLiteralType(caseType) : caseType; + comparedExpressionType = getBaseTypeOfLiteralType(expressionType); + } + if (!isTypeEqualityComparableTo(comparedExpressionType, caseType)) { + // expressionType is not comparable to caseType, try the reversed check and report errors if it fails + checkTypeComparableTo(caseType, comparedExpressionType, clause.expression, /*headMessage*/ undefined); + } + }; + } }); if (node.caseBlock.locals) { registerForUnusedIdentifiersCheck(node.caseBlock); @@ -39023,27 +39071,31 @@ namespace ts { * Check each type parameter and check that type parameters have no duplicate type parameter declarations */ function checkTypeParameters(typeParameterDeclarations: readonly TypeParameterDeclaration[] | undefined) { + let seenDefault = false; if (typeParameterDeclarations) { - let seenDefault = false; for (let i = 0; i < typeParameterDeclarations.length; i++) { const node = typeParameterDeclarations[i]; checkTypeParameter(node); - if (produceDiagnostics) { - if (node.default) { - seenDefault = true; - checkTypeParametersNotReferenced(node.default, typeParameterDeclarations, i); - } - else if (seenDefault) { - error(node, Diagnostics.Required_type_parameters_may_not_follow_optional_type_parameters); - } - for (let j = 0; j < i; j++) { - if (typeParameterDeclarations[j].symbol === node.symbol) { - error(node.name, Diagnostics.Duplicate_identifier_0, declarationNameToString(node.name)); - } + addLazyDiagnostic(createCheckTypeParameterDiagnostic(node, i)); + } + } + + function createCheckTypeParameterDiagnostic(node: TypeParameterDeclaration, i: number) { + return () => { + if (node.default) { + seenDefault = true; + checkTypeParametersNotReferenced(node.default, typeParameterDeclarations!, i); + } + else if (seenDefault) { + error(node, Diagnostics.Required_type_parameters_may_not_follow_optional_type_parameters); + } + for (let j = 0; j < i; j++) { + if (typeParameterDeclarations![j].symbol === node.symbol) { + error(node.name, Diagnostics.Duplicate_identifier_0, declarationNameToString(node.name)); } } - } + }; } } @@ -39193,50 +39245,52 @@ namespace ts { } const baseTypes = getBaseTypes(type); - if (baseTypes.length && produceDiagnostics) { - const baseType = baseTypes[0]; - const baseConstructorType = getBaseConstructorTypeOfClass(type); - const staticBaseType = getApparentType(baseConstructorType); - checkBaseTypeAccessibility(staticBaseType, baseTypeNode); - checkSourceElement(baseTypeNode.expression); - if (some(baseTypeNode.typeArguments)) { - forEach(baseTypeNode.typeArguments, checkSourceElement); - for (const constructor of getConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode)) { - if (!checkTypeArgumentConstraints(baseTypeNode, constructor.typeParameters!)) { - break; + if (baseTypes.length) { + addLazyDiagnostic(() => { + const baseType = baseTypes[0]; + const baseConstructorType = getBaseConstructorTypeOfClass(type); + const staticBaseType = getApparentType(baseConstructorType); + checkBaseTypeAccessibility(staticBaseType, baseTypeNode); + checkSourceElement(baseTypeNode.expression); + if (some(baseTypeNode.typeArguments)) { + forEach(baseTypeNode.typeArguments, checkSourceElement); + for (const constructor of getConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode)) { + if (!checkTypeArgumentConstraints(baseTypeNode, constructor.typeParameters!)) { + break; + } } } - } - const baseWithThis = getTypeWithThisArgument(baseType, type.thisType); - if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { - issueMemberSpecificError(node, typeWithThis, baseWithThis, Diagnostics.Class_0_incorrectly_extends_base_class_1); - } - else { - // Report static side error only when instance type is assignable - checkTypeAssignableTo(staticType, getTypeWithoutSignatures(staticBaseType), node.name || node, - Diagnostics.Class_static_side_0_incorrectly_extends_base_class_static_side_1); - } - if (baseConstructorType.flags & TypeFlags.TypeVariable) { - if (!isMixinConstructorType(staticType)) { - error(node.name || node, Diagnostics.A_mixin_class_must_have_a_constructor_with_a_single_rest_parameter_of_type_any); + const baseWithThis = getTypeWithThisArgument(baseType, type.thisType); + if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { + issueMemberSpecificError(node, typeWithThis, baseWithThis, Diagnostics.Class_0_incorrectly_extends_base_class_1); } else { - const constructSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); - if (constructSignatures.some(signature => signature.flags & SignatureFlags.Abstract) && !hasSyntacticModifier(node, ModifierFlags.Abstract)) { - error(node.name || node, Diagnostics.A_mixin_class_that_extends_from_a_type_variable_containing_an_abstract_construct_signature_must_also_be_declared_abstract); + // Report static side error only when instance type is assignable + checkTypeAssignableTo(staticType, getTypeWithoutSignatures(staticBaseType), node.name || node, + Diagnostics.Class_static_side_0_incorrectly_extends_base_class_static_side_1); + } + if (baseConstructorType.flags & TypeFlags.TypeVariable) { + if (!isMixinConstructorType(staticType)) { + error(node.name || node, Diagnostics.A_mixin_class_must_have_a_constructor_with_a_single_rest_parameter_of_type_any); + } + else { + const constructSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); + if (constructSignatures.some(signature => signature.flags & SignatureFlags.Abstract) && !hasSyntacticModifier(node, ModifierFlags.Abstract)) { + error(node.name || node, Diagnostics.A_mixin_class_that_extends_from_a_type_variable_containing_an_abstract_construct_signature_must_also_be_declared_abstract); + } } } - } - if (!(staticBaseType.symbol && staticBaseType.symbol.flags & SymbolFlags.Class) && !(baseConstructorType.flags & TypeFlags.TypeVariable)) { - // When the static base type is a "class-like" constructor function (but not actually a class), we verify - // that all instantiated base constructor signatures return the same type. - const constructors = getInstantiatedConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode); - if (forEach(constructors, sig => !isJSConstructor(sig.declaration) && !isTypeIdenticalTo(getReturnTypeOfSignature(sig), baseType))) { - error(baseTypeNode.expression, Diagnostics.Base_constructors_must_all_have_the_same_return_type); + if (!(staticBaseType.symbol && staticBaseType.symbol.flags & SymbolFlags.Class) && !(baseConstructorType.flags & TypeFlags.TypeVariable)) { + // When the static base type is a "class-like" constructor function (but not actually a class), we verify + // that all instantiated base constructor signatures return the same type. + const constructors = getInstantiatedConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode); + if (forEach(constructors, sig => !isJSConstructor(sig.declaration) && !isTypeIdenticalTo(getReturnTypeOfSignature(sig), baseType))) { + error(baseTypeNode.expression, Diagnostics.Base_constructors_must_all_have_the_same_return_type); + } } - } - checkKindsOfPropertyMemberOverrides(type, baseType); + checkKindsOfPropertyMemberOverrides(type, baseType); + }); } } @@ -39249,31 +39303,35 @@ namespace ts { error(typeRefNode.expression, Diagnostics.A_class_can_only_implement_an_identifier_Slashqualified_name_with_optional_type_arguments); } checkTypeReferenceNode(typeRefNode); - if (produceDiagnostics) { - const t = getReducedType(getTypeFromTypeNode(typeRefNode)); - if (!isErrorType(t)) { - if (isValidBaseType(t)) { - const genericDiag = t.symbol && t.symbol.flags & SymbolFlags.Class ? - Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass : - Diagnostics.Class_0_incorrectly_implements_interface_1; - const baseWithThis = getTypeWithThisArgument(t, type.thisType); - if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { - issueMemberSpecificError(node, typeWithThis, baseWithThis, genericDiag); - } - } - else { - error(typeRefNode, Diagnostics.A_class_can_only_implement_an_object_type_or_intersection_of_object_types_with_statically_known_members); - } - } - } + addLazyDiagnostic(createImplementsDiagnostics(typeRefNode)); } } - if (produceDiagnostics) { + addLazyDiagnostic(() => { checkIndexConstraints(type, symbol); checkIndexConstraints(staticType, symbol, /*isStaticIndex*/ true); checkTypeForDuplicateIndexSignatures(node); checkPropertyInitialization(node); + }); + + function createImplementsDiagnostics(typeRefNode: ExpressionWithTypeArguments) { + return () => { + const t = getReducedType(getTypeFromTypeNode(typeRefNode)); + if (!isErrorType(t)) { + if (isValidBaseType(t)) { + const genericDiag = t.symbol && t.symbol.flags & SymbolFlags.Class ? + Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass : + Diagnostics.Class_0_incorrectly_implements_interface_1; + const baseWithThis = getTypeWithThisArgument(t, type.thisType); + if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { + issueMemberSpecificError(node, typeWithThis, baseWithThis, genericDiag); + } + } + else { + error(typeRefNode, Diagnostics.A_class_can_only_implement_an_object_type_or_intersection_of_object_types_with_statically_known_members); + } + } + }; } } @@ -39805,12 +39863,13 @@ namespace ts { return !(getFalsyFlags(flowType) & TypeFlags.Undefined); } + function checkInterfaceDeclaration(node: InterfaceDeclaration) { // Grammar checking if (!checkGrammarDecoratorsAndModifiers(node)) checkGrammarInterfaceDeclaration(node); checkTypeParameters(node.typeParameters); - if (produceDiagnostics) { + addLazyDiagnostic(() => { checkTypeNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0); checkExportsOnMergedDeclarations(node); @@ -39831,7 +39890,7 @@ namespace ts { } } checkObjectTypeForDuplicateDeclarations(node); - } + }); forEach(getInterfaceBaseTypeNodes(node), heritageElement => { if (!isEntityNameExpression(heritageElement.expression) || isOptionalChain(heritageElement.expression)) { error(heritageElement.expression, Diagnostics.An_interface_can_only_extend_an_identifier_Slashqualified_name_with_optional_type_arguments); @@ -39841,10 +39900,10 @@ namespace ts { forEach(node.members, checkSourceElement); - if (produceDiagnostics) { + addLazyDiagnostic(() => { checkTypeForDuplicateIndexSignatures(node); registerForUnusedIdentifiersCheck(node); - } + }); } function checkTypeAliasDeclaration(node: TypeAliasDeclaration) { @@ -40041,10 +40100,10 @@ namespace ts { } function checkEnumDeclaration(node: EnumDeclaration) { - if (!produceDiagnostics) { - return; - } + addLazyDiagnostic(() => checkEnumDeclarationWorker(node)); + } + function checkEnumDeclarationWorker(node: EnumDeclaration) { // Grammar checking checkGrammarDecoratorsAndModifiers(node); @@ -40133,7 +40192,16 @@ namespace ts { } function checkModuleDeclaration(node: ModuleDeclaration) { - if (produceDiagnostics) { + if (node.body) { + checkSourceElement(node.body); + if (!isGlobalScopeAugmentation(node)) { + registerForUnusedIdentifiersCheck(node); + } + } + + addLazyDiagnostic(checkModuleDeclarationDiagnostics); + + function checkModuleDeclarationDiagnostics() { // Grammar checking const isGlobalAugmentation = isGlobalScopeAugmentation(node); const inAmbientContext = node.flags & NodeFlags.Ambient; @@ -40222,13 +40290,6 @@ namespace ts { } } } - - if (node.body) { - checkSourceElement(node.body); - if (!isGlobalScopeAugmentation(node)) { - registerForUnusedIdentifiersCheck(node); - } - } } function checkModuleAugmentationElement(node: Node, isGlobalAugmentation: boolean): void { @@ -41229,13 +41290,16 @@ namespace ts { registerForUnusedIdentifiersCheck(node); } - if (!node.isDeclarationFile && (compilerOptions.noUnusedLocals || compilerOptions.noUnusedParameters)) { - checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(node), (containingNode, kind, diag) => { - if (!containsParseError(containingNode) && unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { - diagnostics.add(diag); - } - }); - } + addLazyDiagnostic(() => { + // This relies on the results of other lazy diagnostics, so must be computed after them + if (!node.isDeclarationFile && (compilerOptions.noUnusedLocals || compilerOptions.noUnusedParameters)) { + checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(node), (containingNode, kind, diag) => { + if (!containsParseError(containingNode) && unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { + diagnostics.add(diag); + } + }); + } + }); if (compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && !node.isDeclarationFile && @@ -41285,16 +41349,37 @@ namespace ts { } } + function ensurePendingDiagnosticWorkComplete() { + // Invoke any existing lazy diagnostics to add them, clear the backlog of diagnostics + for (const cb of deferredDiagnosticsCallbacks) { + cb(); + } + deferredDiagnosticsCallbacks = []; + } + + function checkSourceFileWithEagerDiagnostics(sourceFile: SourceFile) { + ensurePendingDiagnosticWorkComplete(); + // then setup diagnostics for immediate invocation (as we are about to collect them, and + // this avoids the overhead of longer-lived callbacks we don't need to allocate) + // This also serves to make the shift to possibly lazy diagnostics transparent to serial command-line scenarios + // (as in those cases, all the diagnostics will still be computed as the appropriate place in the tree, + // thus much more likely retaining the same union ordering as before we had lazy diagnostics) + const oldAddLazyDiagnostics = addLazyDiagnostic; + addLazyDiagnostic = cb => cb(); + checkSourceFile(sourceFile); + addLazyDiagnostic = oldAddLazyDiagnostics; + } + function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] { - throwIfNonDiagnosticsProducing(); if (sourceFile) { + ensurePendingDiagnosticWorkComplete(); // Some global diagnostics are deferred until they are needed and // may not be reported in the first call to getGlobalDiagnostics. // We should catch these changes and report them. const previousGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); const previousGlobalDiagnosticsSize = previousGlobalDiagnostics.length; - checkSourceFile(sourceFile); + checkSourceFileWithEagerDiagnostics(sourceFile); const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName); const currentGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); @@ -41315,21 +41400,15 @@ namespace ts { // Global diagnostics are always added when a file is not provided to // getDiagnostics - forEach(host.getSourceFiles(), checkSourceFile); + forEach(host.getSourceFiles(), checkSourceFileWithEagerDiagnostics); return diagnostics.getDiagnostics(); } function getGlobalDiagnostics(): Diagnostic[] { - throwIfNonDiagnosticsProducing(); + ensurePendingDiagnosticWorkComplete(); return diagnostics.getGlobalDiagnostics(); } - function throwIfNonDiagnosticsProducing() { - if (!produceDiagnostics) { - throw new Error("Trying to get diagnostics from a type checker that does not produce them."); - } - } - // Language service support function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 35b27a0263736..1fddfb58cc178 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -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(); let fileReasons = createMultiMap(); @@ -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 { - return rootNames.length ? sortAndDeduplicateDiagnostics(getDiagnosticsProducingTypeChecker().getGlobalDiagnostics().slice()) : emptyArray as any as SortedReadonlyArray; + return rootNames.length ? sortAndDeduplicateDiagnostics(getTypeChecker().getGlobalDiagnostics().slice()) : emptyArray as any as SortedReadonlyArray; } function getConfigFileParsingDiagnostics(): readonly Diagnostic[] { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0f17e9fef860c..1046c79dd3a16 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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>; diff --git a/src/harness/harnessIO.ts b/src/harness/harnessIO.ts index 99da90f44c32a..558aca6bfd31c 100644 --- a/src/harness/harnessIO.ts +++ b/src/harness/harnessIO.ts @@ -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. diff --git a/src/harness/typeWriter.ts b/src/harness/typeWriter.ts index e7f8e8d28b1cb..3de4f3962e69a 100644 --- a/src/harness/typeWriter.ts +++ b/src/harness/typeWriter.ts @@ -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 { diff --git a/src/services/codefixes/addMissingAsync.ts b/src/services/codefixes/addMissingAsync.ts index bb076588a4540..de12496f86528 100644 --- a/src/services/codefixes/addMissingAsync.ts +++ b/src/services/codefixes/addMissingAsync.ts @@ -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); diff --git a/src/services/codefixes/addMissingAwait.ts b/src/services/codefixes/addMissingAwait.ts index 3853a52b7249f..1a9110227e504 100644 --- a/src/services/codefixes/addMissingAwait.ts +++ b/src/services/codefixes/addMissingAwait.ts @@ -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) && diff --git a/src/services/codefixes/convertFunctionToEs6Class.ts b/src/services/codefixes/convertFunctionToEs6Class.ts index 8a20f68840d4c..352fbbb5bc160 100644 --- a/src/services/codefixes/convertFunctionToEs6Class.ts +++ b/src/services/codefixes/convertFunctionToEs6Class.ts @@ -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,18 +56,31 @@ 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); }); } @@ -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); } } } diff --git a/src/testRunner/unittests/programApi.ts b/src/testRunner/unittests/programApi.ts index 359dff1c03434..e15ec0bf434d0 100644 --- a/src/testRunner/unittests/programApi.ts +++ b/src/testRunner/unittests/programApi.ts @@ -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()); }); diff --git a/tests/baselines/reference/findAllRefs_importType_js.1.baseline.jsonc b/tests/baselines/reference/findAllRefs_importType_js.1.baseline.jsonc index 072f0ac1ab936..194d24ed3ed52 100644 --- a/tests/baselines/reference/findAllRefs_importType_js.1.baseline.jsonc +++ b/tests/baselines/reference/findAllRefs_importType_js.1.baseline.jsonc @@ -15,7 +15,7 @@ "containerName": "", "fileName": "/a.js", "kind": "local class", - "name": "(local class) C", + "name": "(local class) C\nmodule C", "textSpan": { "start": 23, "length": 1 @@ -37,6 +37,22 @@ "text": " ", "kind": "space" }, + { + "text": "C", + "kind": "className" + }, + { + "text": "\n", + "kind": "lineBreak" + }, + { + "text": "module", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, { "text": "C", "kind": "className" diff --git a/tests/cases/fourslash/convertFunctionToEs6Class1.ts b/tests/cases/fourslash/convertFunctionToEs6Class1.ts index a0046fc4e9088..c99a2efe7d0a5 100644 --- a/tests/cases/fourslash/convertFunctionToEs6Class1.ts +++ b/tests/cases/fourslash/convertFunctionToEs6Class1.ts @@ -21,10 +21,10 @@ verify.codeFix({ newFileContent: `class foo { constructor() { } - instanceMethod1() { return "this is name"; } - instanceMethod2() { return "this is name"; } static staticMethod1() { return "this is static name"; } static staticMethod2() { return "this is static name"; } + instanceMethod1() { return "this is name"; } + instanceMethod2() { return "this is name"; } } foo.prototype.instanceProp1 = "hello"; foo.prototype.instanceProp2 = undefined; diff --git a/tests/cases/fourslash/convertFunctionToEs6Class2.ts b/tests/cases/fourslash/convertFunctionToEs6Class2.ts index 5326c30eea496..f8395a8484d29 100644 --- a/tests/cases/fourslash/convertFunctionToEs6Class2.ts +++ b/tests/cases/fourslash/convertFunctionToEs6Class2.ts @@ -16,10 +16,10 @@ verify.codeFix({ newFileContent: `class foo { constructor() { } - instanceMethod1() { return "this is name"; } - instanceMethod2() { return "this is name"; } static staticMethod1() { return "this is static name"; } static staticMethod2() { return "this is static name"; } + instanceMethod1() { return "this is name"; } + instanceMethod2() { return "this is name"; } } foo.instanceProp1 = "hello"; foo.instanceProp2 = undefined; diff --git a/tests/cases/fourslash/convertFunctionToEs6Class3.ts b/tests/cases/fourslash/convertFunctionToEs6Class3.ts index d88c4b80a8ddb..8b4bbd7f22b58 100644 --- a/tests/cases/fourslash/convertFunctionToEs6Class3.ts +++ b/tests/cases/fourslash/convertFunctionToEs6Class3.ts @@ -17,10 +17,10 @@ verify.codeFix({ `var bar = 10; class foo { constructor() { } - instanceMethod1() { return "this is name"; } - instanceMethod2() { return "this is name"; } static staticMethod1() { return "this is static name"; } static staticMethod2() { return "this is static name"; } + instanceMethod1() { return "this is name"; } + instanceMethod2() { return "this is name"; } } foo.prototype.instanceProp1 = "hello"; foo.prototype.instanceProp2 = undefined; diff --git a/tests/cases/fourslash/convertFunctionToEs6Class_asyncMethods.ts b/tests/cases/fourslash/convertFunctionToEs6Class_asyncMethods.ts index bb8f8c229621f..ab5bcb61ab435 100644 --- a/tests/cases/fourslash/convertFunctionToEs6Class_asyncMethods.ts +++ b/tests/cases/fourslash/convertFunctionToEs6Class_asyncMethods.ts @@ -18,10 +18,10 @@ verify.codeFix({ `export class MyClass { constructor() { } - async foo() { + static async bar() { await Promise.resolve(); } - static async bar() { + async foo() { await Promise.resolve(); } }