- 
                Notifications
    You must be signed in to change notification settings 
- Fork 13.1k
Synthesize namespace records for proper esm interop #19675
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
          
     Merged
      
      
    
  
     Merged
                    Changes from all commits
      Commits
    
    
            Show all changes
          
          
            32 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      213146d
              
                Integrate importStar and importDefault helpers
              
              
                weswigham b5a7ef9
              
                Accept baselines
              
              
                weswigham 8abc907
              
                Support dynamic imports, write helpers for umd module (and amd is pos…
              
              
                weswigham 2d0c8d3
              
                Accept baselines
              
              
                weswigham a725916
              
                Support AMD, use same helper initialization as is normal
              
              
                weswigham e910603
              
                update typechecker to have errors on called imported namespaces and g…
              
              
                weswigham e84b051
              
                Overhaul allowSyntheticDefaultExports to be safer
              
              
                weswigham f23d41c
              
                Put the new behavior behind a flag
              
              
                weswigham ebd4c03
              
                Merge branch 'master' into module-nodejs
              
              
                weswigham 3b33df0
              
                Rename strictESM to ESMInterop
              
              
                weswigham 7ff11bb
              
                Merge branch 'master' into module-nodejs
              
              
                weswigham bcafdba
              
                ESMInterop -> ESModuleInterop, make default for tsc --init
              
              
                weswigham eaf2fc5
              
                Merge branch 'master' into module-nodejs
              
              
                weswigham 29c731c
              
                Rename ESMInterop -> ESModuleInterop in module.ts, add emit test (sin…
              
              
                weswigham 4350caf
              
                Merge branch 'master' into module-nodejs
              
              
                weswigham e20a548
              
                Remove erroneous semicolons from helper
              
              
                weswigham 578141d
              
                Merge branch 'master' into module-nodejs
              
              
                weswigham dfe5675
              
                Reword diagnostic
              
              
                weswigham 8c6c313
              
                Change style
              
              
                weswigham 3d996d7
              
                Edit followup diagnostic
              
              
                weswigham 2f9c240
              
                Add secondary quickfix for call sites, tests forthcoming
              
              
                weswigham 2361f37
              
                Add synth default to namespace import type, enhance quickfix
              
              
                weswigham 357996b
              
                Pair of spare tests for good measure
              
              
                weswigham a682476
              
                Merge branch 'master' into module-nodejs
              
              
                weswigham 6e9e874
              
                Fix typos in diagnostic message
              
              
                weswigham a654b82
              
                Improve comment clarity
              
              
                weswigham ef6faf1
              
                Actually accept the updated changes to the esmodule interop description
              
              
                weswigham a8233b3
              
                ESModule -> esModule
              
              
                weswigham f4f4e84
              
                Use find and not forEach
              
              
                weswigham 386c54d
              
                Use guard
              
              
                weswigham 2663db7
              
                Rely on implicit falsiness of Result.False
              
              
                weswigham 8068e2e
              
                These should have been emit flags
              
              
                weswigham File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
|  | @@ -1464,6 +1464,43 @@ namespace ts { | |
| return getSymbolOfPartOfRightHandSideOfImportEquals(<EntityName>node.moduleReference, dontResolveAlias); | ||
| } | ||
|  | ||
| function resolveExportByName(moduleSymbol: Symbol, name: __String, dontResolveAlias: boolean) { | ||
| const exportValue = moduleSymbol.exports.get(InternalSymbolName.ExportEquals); | ||
| return exportValue | ||
| ? getPropertyOfType(getTypeOfSymbol(exportValue), name) | ||
| : resolveSymbol(moduleSymbol.exports.get(name), dontResolveAlias); | ||
| } | ||
|  | ||
| function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean) { | ||
| if (!allowSyntheticDefaultImports) { | ||
| return false; | ||
| } | ||
| // Declaration files (and ambient modules) | ||
| if (!file || file.isDeclarationFile) { | ||
| // Definitely cannot have a synthetic default if they have a default member specified | ||
| if (resolveExportByName(moduleSymbol, InternalSymbolName.Default, dontResolveAlias)) { | ||
| return false; | ||
| } | ||
| // It _might_ still be incorrect to assume there is no __esModule marker on the import at runtime, even if there is no `default` member | ||
| // So we check a bit more, | ||
| if (resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), dontResolveAlias)) { | ||
| // If there is an `__esModule` specified in the declaration (meaning someone explicitly added it or wrote it in their code), | ||
| // it definitely is a module and does not have a synthetic default | ||
| return false; | ||
| } | ||
| // There are _many_ declaration files not written with esmodules in mind that still get compiled into a format with __esModule set | ||
| There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we considering an  | ||
| // Meaning there may be no default at runtime - however to be on the permissive side, we allow access to a synthetic default member | ||
| // as there is no marker to indicate if the accompanying JS has `__esModule` or not, or is even native esm | ||
| return true; | ||
| } | ||
| // TypeScript files never have a synthetic default (as they are always emitted with an __esModule marker) _unless_ they contain an export= statement | ||
| if (!isSourceFileJavaScript(file)) { | ||
| return hasExportAssignmentSymbol(moduleSymbol); | ||
| } | ||
| // JS files have a synthetic default if they do not contain ES2015+ module syntax (export = is not valid in js) _and_ do not have an __esModule marker | ||
| return !file.externalModuleIndicator && !resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), dontResolveAlias); | ||
| } | ||
|  | ||
| function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): Symbol { | ||
| const moduleSymbol = resolveExternalModuleName(node, (<ImportDeclaration>node.parent).moduleSpecifier); | ||
|  | ||
|  | @@ -1473,16 +1510,16 @@ namespace ts { | |
| exportDefaultSymbol = moduleSymbol; | ||
| } | ||
| else { | ||
| const exportValue = moduleSymbol.exports.get("export=" as __String); | ||
| exportDefaultSymbol = exportValue | ||
| ? getPropertyOfType(getTypeOfSymbol(exportValue), InternalSymbolName.Default) | ||
| : resolveSymbol(moduleSymbol.exports.get(InternalSymbolName.Default), dontResolveAlias); | ||
| exportDefaultSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, dontResolveAlias); | ||
| } | ||
|  | ||
| if (!exportDefaultSymbol && !allowSyntheticDefaultImports) { | ||
| const file = find(moduleSymbol.declarations, isSourceFile); | ||
| const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias); | ||
| if (!exportDefaultSymbol && !hasSyntheticDefault) { | ||
| error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol)); | ||
| } | ||
| else if (!exportDefaultSymbol && allowSyntheticDefaultImports) { | ||
| else if (!exportDefaultSymbol && hasSyntheticDefault) { | ||
| // per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present | ||
| return resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); | ||
| } | ||
| return exportDefaultSymbol; | ||
|  | @@ -1882,8 +1919,40 @@ namespace ts { | |
| // combine other declarations with the module or variable (e.g. a class/module, function/module, interface/variable). | ||
| function resolveESModuleSymbol(moduleSymbol: Symbol, moduleReferenceExpression: Expression, dontResolveAlias: boolean): Symbol { | ||
| const symbol = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias); | ||
| if (!dontResolveAlias && symbol && !(symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable))) { | ||
| error(moduleReferenceExpression, Diagnostics.Module_0_resolves_to_a_non_module_entity_and_cannot_be_imported_using_this_construct, symbolToString(moduleSymbol)); | ||
| if (!dontResolveAlias && symbol) { | ||
| if (!(symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable))) { | ||
| error(moduleReferenceExpression, Diagnostics.Module_0_resolves_to_a_non_module_entity_and_cannot_be_imported_using_this_construct, symbolToString(moduleSymbol)); | ||
| return symbol; | ||
| } | ||
| if (compilerOptions.esModuleInterop) { | ||
| const referenceParent = moduleReferenceExpression.parent; | ||
| if ( | ||
| (isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) || | ||
| isImportCall(referenceParent) | ||
| ) { | ||
| const type = getTypeOfSymbol(symbol); | ||
| let sigs = getSignaturesOfStructuredType(type, SignatureKind.Call); | ||
| if (!sigs || !sigs.length) { | ||
| sigs = getSignaturesOfStructuredType(type, SignatureKind.Construct); | ||
| } | ||
| if (sigs && sigs.length) { | ||
| const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol); | ||
| // Create a new symbol which has the module's type less the call and construct signatures | ||
| const result = createSymbol(symbol.flags, symbol.escapedName); | ||
| result.declarations = symbol.declarations ? symbol.declarations.slice() : []; | ||
| result.parent = symbol.parent; | ||
| result.target = symbol; | ||
| result.originatingImport = referenceParent; | ||
| if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; | ||
| if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; | ||
| if (symbol.members) result.members = cloneMap(symbol.members); | ||
| if (symbol.exports) result.exports = cloneMap(symbol.exports); | ||
| const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above | ||
| result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.stringIndexInfo, resolvedModuleType.numberIndexInfo); | ||
| return result; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return symbol; | ||
| } | ||
|  | @@ -9401,6 +9470,17 @@ namespace ts { | |
|  | ||
| diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode, errorInfo)); | ||
| } | ||
| // Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement | ||
| if (headMessage && errorNode && !result && source.symbol) { | ||
| const links = getSymbolLinks(source.symbol); | ||
| if (links.originatingImport && !isImportCall(links.originatingImport)) { | ||
| const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target), target, relation, /*errorNode*/ undefined); | ||
| if (helpfulRetry) { | ||
| // Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import | ||
| diagnostics.add(createDiagnosticForNode(links.originatingImport, Diagnostics.A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime)); | ||
| } | ||
| } | ||
| } | ||
| return result !== Ternary.False; | ||
|  | ||
| function reportError(message: DiagnosticMessage, arg0?: string, arg1?: string, arg2?: string): void { | ||
|  | @@ -17256,7 +17336,7 @@ namespace ts { | |
| error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); | ||
| } | ||
| else { | ||
| error(node, Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, typeToString(apparentType)); | ||
| invocationError(node, apparentType, SignatureKind.Call); | ||
| } | ||
| return resolveErrorCall(node); | ||
| } | ||
|  | @@ -17346,7 +17426,7 @@ namespace ts { | |
| return signature; | ||
| } | ||
|  | ||
| error(node, Diagnostics.Cannot_use_new_with_an_expression_whose_type_lacks_a_call_or_construct_signature); | ||
| invocationError(node, expressionType, SignatureKind.Construct); | ||
| return resolveErrorCall(node); | ||
| } | ||
|  | ||
|  | @@ -17393,6 +17473,28 @@ namespace ts { | |
| return true; | ||
| } | ||
|  | ||
| function invocationError(node: Node, apparentType: Type, kind: SignatureKind) { | ||
| error(node, kind === SignatureKind.Call | ||
| ? Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures | ||
| : Diagnostics.Cannot_use_new_with_an_expression_whose_type_lacks_a_call_or_construct_signature | ||
| , typeToString(apparentType)); | ||
| invocationErrorRecovery(apparentType, kind); | ||
| } | ||
|  | ||
| function invocationErrorRecovery(apparentType: Type, kind: SignatureKind) { | ||
| if (!apparentType.symbol) { | ||
| return; | ||
| } | ||
| const importNode = getSymbolLinks(apparentType.symbol).originatingImport; | ||
| // Create a diagnostic on the originating import if possible onto which we can attach a quickfix | ||
| // An import call expression cannot be rewritten into another form to correct the error - the only solution is to use `.default` at the use-site | ||
| if (importNode && !isImportCall(importNode)) { | ||
| const sigs = getSignaturesOfType(getTypeOfSymbol(getSymbolLinks(apparentType.symbol).target), kind); | ||
| if (!sigs || !sigs.length) return; | ||
| error(importNode, Diagnostics.A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime); | ||
| } | ||
| } | ||
|  | ||
| function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[]): Signature { | ||
| const tagType = checkExpression(node.tag); | ||
| const apparentType = getApparentType(tagType); | ||
|  | @@ -17410,7 +17512,7 @@ namespace ts { | |
| } | ||
|  | ||
| if (!callSignatures.length) { | ||
| error(node, Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, typeToString(apparentType)); | ||
| invocationError(node, apparentType, SignatureKind.Call); | ||
| return resolveErrorCall(node); | ||
| } | ||
|  | ||
|  | @@ -17467,6 +17569,7 @@ namespace ts { | |
| errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, typeToString(apparentType)); | ||
| errorInfo = chainDiagnosticMessages(errorInfo, headMessage); | ||
| diagnostics.add(createDiagnosticForNodeFromMessageChain(node, errorInfo)); | ||
| invocationErrorRecovery(apparentType, SignatureKind.Call); | ||
| return resolveErrorCall(node); | ||
| } | ||
|  | ||
|  | @@ -17721,25 +17824,27 @@ namespace ts { | |
| if (moduleSymbol) { | ||
| const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontRecursivelyResolve*/ true); | ||
| if (esModuleSymbol) { | ||
| return createPromiseReturnType(node, getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol)); | ||
| return createPromiseReturnType(node, getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol)); | ||
| } | ||
| } | ||
| return createPromiseReturnType(node, anyType); | ||
| } | ||
|  | ||
| function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol): Type { | ||
| function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol, originalSymbol: Symbol): Type { | ||
| if (allowSyntheticDefaultImports && type && type !== unknownType) { | ||
| const synthType = type as SyntheticDefaultModuleType; | ||
| if (!synthType.syntheticType) { | ||
| if (!getPropertyOfType(type, InternalSymbolName.Default)) { | ||
| const file = find(originalSymbol.declarations, isSourceFile); | ||
| const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false); | ||
| if (hasSyntheticDefault) { | ||
| const memberTable = createSymbolTable(); | ||
| const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); | ||
| newSymbol.target = resolveSymbol(symbol); | ||
| memberTable.set(InternalSymbolName.Default, newSymbol); | ||
| const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); | ||
| const defaultContainingObject = createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); | ||
| anonymousSymbol.type = defaultContainingObject; | ||
| synthType.syntheticType = getIntersectionType([type, defaultContainingObject]); | ||
| synthType.syntheticType = (type.flags & TypeFlags.StructuredType && type.symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable)) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*propegatedFlags*/ 0) : defaultContainingObject; | ||
| } | ||
| else { | ||
| synthType.syntheticType = type; | ||
|  | ||
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is a breaking change to
--allowSyntheticDefaultImports, thought i doubt anyone would run into this, we should document it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated