-
Notifications
You must be signed in to change notification settings - Fork 12.4k
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
Synthesize namespace records for proper esm interop #19675
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 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 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 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 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 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 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