From 026fba8ff17c153efff0e8f382dc0e058b361905 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Mon, 24 Apr 2023 02:29:03 +0800 Subject: [PATCH] add support for ts2154 in the compiler --- src/compiler/binder.ts | 6 +- src/compiler/checker.ts | 148 ++++++++++++------ src/compiler/diagnosticMessages.json | 8 + src/compiler/factory/nodeFactory.ts | 27 +++- src/compiler/factory/nodeTests.ts | 5 + src/compiler/factory/utilities.ts | 8 +- src/compiler/parser.ts | 57 +++++-- src/compiler/transformers/declarations.ts | 3 +- .../transformers/module/esnextAnd2015.ts | 130 ++++++++++++++- src/compiler/transformers/module/module.ts | 26 +-- src/compiler/transformers/module/system.ts | 17 +- src/compiler/transformers/utilities.ts | 33 ++-- src/compiler/types.ts | 33 ++-- src/compiler/utilities.ts | 30 +++- src/compiler/utilitiesPublic.ts | 4 +- src/compiler/visitorPublic.ts | 7 +- .../codefixes/fixInvalidImportSyntax.ts | 3 +- 17 files changed, 418 insertions(+), 127 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index f54a3e0666f00..4eb2b2ad6e347 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -243,6 +243,8 @@ import { ModifierFlags, ModuleBlock, ModuleDeclaration, + moduleExportNameText, + moduleExportNameTextEscaped, Mutable, NamespaceExportDeclaration, Node, @@ -432,7 +434,7 @@ function getModuleInstanceStateForAliasTarget(specifier: ExportSpecifier, visite const statements = p.statements; let found: ModuleInstanceState | undefined; for (const statement of statements) { - if (nodeHasName(statement, name)) { + if (nodeHasName(statement, moduleExportNameText(name))) { if (!statement.parent) { setParent(statement, p); setParentRecursive(statement, /*incremental*/ false); @@ -738,7 +740,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { function declareSymbol(symbolTable: SymbolTable, parent: Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean, isComputedName?: boolean): Symbol { Debug.assert(isComputedName || !hasDynamicName(node)); - const isDefaultExport = hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node) && node.name.escapedText === "default"; + const isDefaultExport = hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node) && moduleExportNameTextEscaped(node.name) === "default"; // The exported symbol for an export default function/class node is always named "default" const name = isComputedName ? InternalSymbolName.Computed diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 81cb730122da7..f5de2aaab35b2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -622,6 +622,7 @@ import { isModifier, isModuleBlock, isModuleDeclaration, + isModuleExportName, isModuleExportsAccessExpression, isModuleIdentifier, isModuleOrEnumDeclaration, @@ -826,6 +827,9 @@ import { modifierToFlag, ModuleBlock, ModuleDeclaration, + ModuleExportName, + moduleExportNameText, + moduleExportNameTextEscaped, ModuleInstanceState, ModuleKind, ModuleResolutionKind, @@ -3342,7 +3346,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (lastLocation && lastLocation === (location as ExportSpecifier).propertyName && (location as ExportSpecifier).parent.parent.moduleSpecifier) { - location = location.parent.parent.parent; + // skip to to parent ModuleBlock or SourceFile. + location = (location as ExportSpecifier).parent.parent.parent; } break; } @@ -3415,7 +3420,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let suggestion: Symbol | undefined; let suggestedLib: string | undefined; // Report missing lib first - if (nameArg) { + if (nameArg) { suggestedLib = getSuggestedLibForNonExistentName(nameArg); if (suggestedLib) { error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg), suggestedLib); @@ -3928,7 +3933,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { : Diagnostics._0_was_imported_here; // TODO: how to get name for export *? - const name = typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration ? "*" : unescapeLeadingUnderscores(typeOnlyDeclaration.name.escapedText); + const name = typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration ? "*" : moduleExportNameText(typeOnlyDeclaration.name); addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name)); } } @@ -4140,12 +4145,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } - function getExportOfModule(symbol: Symbol, name: Identifier, specifier: Declaration, dontResolveAlias: boolean): Symbol | undefined { + function getExportOfModule(symbol: Symbol, name: ModuleExportName, specifier: Declaration, dontResolveAlias: boolean): Symbol | undefined { if (symbol.flags & SymbolFlags.Module) { - const exportSymbol = getExportsOfSymbol(symbol).get(name.escapedText); + const escapedName = moduleExportNameTextEscaped(name); + const exportSymbol = getExportsOfSymbol(symbol).get(escapedName); const resolved = resolveSymbol(exportSymbol, dontResolveAlias); - const exportStarDeclaration = getSymbolLinks(symbol).typeOnlyExportStarMap?.get(name.escapedText); - markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false, exportStarDeclaration, name.escapedText); + const exportStarDeclaration = getSymbolLinks(symbol).typeOnlyExportStarMap?.get(escapedName); + markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false, exportStarDeclaration, escapedName); return resolved; } } @@ -4163,13 +4169,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const moduleSpecifier = getExternalModuleRequireArgument(node) || (node as ImportDeclaration | ExportDeclaration).moduleSpecifier!; const moduleSymbol = resolveExternalModuleName(node, moduleSpecifier)!; // TODO: GH#18217 const name = !isPropertyAccessExpression(specifier) && specifier.propertyName || specifier.name; - if (!isIdentifier(name)) { + if (!isModuleExportName(name)) { return undefined; } - const suppressInteropError = name.escapedText === InternalSymbolName.Default && allowSyntheticDefaultImports; + const escapedName = moduleExportNameTextEscaped(name); + const suppressInteropError = escapedName === InternalSymbolName.Default && allowSyntheticDefaultImports; const targetSymbol = resolveESModuleSymbol(moduleSymbol, moduleSpecifier, /*dontResolveAlias*/ false, suppressInteropError); if (targetSymbol) { - if (name.escapedText) { + if (escapedName) { if (isShorthandAmbientModuleSymbol(moduleSymbol)) { return moduleSymbol; } @@ -4177,16 +4184,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let symbolFromVariable: Symbol | undefined; // First check if module was specified with "export=". If so, get the member from the resolved type if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports.get(InternalSymbolName.ExportEquals)) { - symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), name.escapedText, /*skipObjectFunctionPropertyAugment*/ true); + symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), escapedName, /*skipObjectFunctionPropertyAugment*/ true); } else { - symbolFromVariable = getPropertyOfVariable(targetSymbol, name.escapedText); + symbolFromVariable = getPropertyOfVariable(targetSymbol, escapedName); } // if symbolFromVariable is export - get its final target symbolFromVariable = resolveSymbol(symbolFromVariable, dontResolveAlias); let symbolFromModule = getExportOfModule(targetSymbol, name, specifier, dontResolveAlias); - if (symbolFromModule === undefined && name.escapedText === InternalSymbolName.Default) { + if (symbolFromModule === undefined && escapedName === InternalSymbolName.Default) { const file = moduleSymbol.declarations?.find(isSourceFile); if (isOnlyImportedAsDefault(moduleSpecifier) || canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, moduleSpecifier)) { symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); @@ -4204,7 +4211,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function errorNoModuleMemberSymbol(moduleSymbol: Symbol, targetSymbol: Symbol, node: Node, name: Identifier) { + function errorNoModuleMemberSymbol(moduleSymbol: Symbol, targetSymbol: Symbol, node: Node, name: ModuleExportName) { const moduleName = getFullyQualifiedName(moduleSymbol, node); const declarationName = declarationNameToString(name); const suggestion = getSuggestedSymbolForNonexistentModule(name, targetSymbol); @@ -4232,8 +4239,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function reportNonExportedMember(node: Node, name: Identifier, declarationName: string, moduleSymbol: Symbol, moduleName: string): void { - const localSymbol = tryCast(moduleSymbol.valueDeclaration, canHaveLocals)?.locals?.get(name.escapedText); + function reportNonExportedMember(node: Node, name: ModuleExportName, declarationName: string, moduleSymbol: Symbol, moduleName: string): void { + // for import { "ident" as T } we can provide meaningful error message. (ident is defined locally) + // for import { "not ident" as T } we will still look for the locals of the target module symbol, but + // since it's impossible to define a local ident with invalid name, we will use the normal message "Module_0_has_no_exported_member_1" + const localSymbol = tryCast(moduleSymbol.valueDeclaration, canHaveLocals)?.locals?.get(moduleExportNameTextEscaped(name)); const exports = moduleSymbol.exports; if (localSymbol) { const exportedEqualsSymbol = exports?.get(InternalSymbolName.ExportEquals); @@ -4257,7 +4267,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function reportInvalidImportEqualsExportMember(node: Node, name: Identifier, declarationName: string, moduleName: string) { + function reportInvalidImportEqualsExportMember(node: Node, name: ModuleExportName, declarationName: string, moduleName: string) { if (moduleKind >= ModuleKind.ES2015) { const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_a_default_import : Diagnostics._0_can_only_be_imported_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; @@ -4278,7 +4288,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getTargetOfImportSpecifier(node: ImportSpecifier | BindingElement, dontResolveAlias: boolean): Symbol | undefined { - if (isImportSpecifier(node) && idText(node.propertyName || node.name) === InternalSymbolName.Default) { + if (isImportSpecifier(node) && moduleExportNameText(node.propertyName || node.name) === InternalSymbolName.Default) { const specifier = getModuleSpecifierForImportOrExport(node); const moduleSymbol = specifier && resolveExternalModuleName(node, specifier); if (moduleSymbol) { @@ -4311,16 +4321,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) { - if (idText(node.propertyName || node.name) === InternalSymbolName.Default) { + if (moduleExportNameText(node.propertyName || node.name) === InternalSymbolName.Default) { const specifier = getModuleSpecifierForImportOrExport(node); const moduleSymbol = specifier && resolveExternalModuleName(node, specifier); if (moduleSymbol) { return getTargetofModuleDefault(moduleSymbol, node, !!dontResolveAlias); } } - const resolved = node.parent.parent.moduleSpecifier ? - getExternalModuleMember(node.parent.parent, node, dontResolveAlias) : - resolveEntityName(node.propertyName || node.name, meaning, /*ignoreErrors*/ false, dontResolveAlias); + const exported = node.propertyName || node.name; + let resolved: Symbol | undefined; + if (node.parent.parent.moduleSpecifier) { + resolved = getExternalModuleMember(node.parent.parent, node, dontResolveAlias); + } + // skip resolution for grammar error export { "x" } + if (exported.kind !== SyntaxKind.StringLiteral) { + resolved = resolveEntityName(exported, meaning, /*ignoreErrors*/ false, dontResolveAlias); + } markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); return resolved; } @@ -8547,7 +8563,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!e.propertyName) { // export {name} - look thru `statements` for `name`, and if all results can take an `export` modifier, do so and filter it const indices = indicesOf(statements); - const associatedIndices = filter(indices, i => nodeHasName(statements[i], e.name)); + const associatedIndices = filter(indices, i => { + if (e.name.kind === SyntaxKind.StringLiteral) return false; + return nodeHasName(statements[i], e.name); + }); if (length(associatedIndices) && every(associatedIndices, i => canHaveExportModifier(statements[i]))) { for (const index of associatedIndices) { statements[index] = addExportModifier(statements[index] as Extract); @@ -8727,7 +8746,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { factory.createExportDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, alias, localName)]) + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, alias, factory.createModuleExportName(localName, languageVersion))]) ), ModifierFlags.None ); @@ -8764,7 +8783,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { factory.createExportDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, name, localName)]) + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, factory.createModuleExportName(name, languageVersion), factory.createModuleExportName(localName, languageVersion))]) ), ModifierFlags.None ); @@ -8823,7 +8842,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { addResult(factory.createExportDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, getInternalSymbolName(symbol, symbolName), symbolName)]) + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, factory.createModuleExportName(getInternalSymbolName(symbol, symbolName), languageVersion), factory.createModuleExportName(symbolName, languageVersion))]) ), ModifierFlags.None); } } @@ -8961,7 +8980,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); includePrivateSymbol(target || s); const targetName = target ? getInternalSymbolName(target, unescapeLeadingUnderscores(target.escapedName)) : localName; - return factory.createExportSpecifier(/*isTypeOnly*/ false, name === targetName ? undefined : targetName, name); + return factory.createExportSpecifier(/*isTypeOnly*/ false, factory.createModuleExportName(name === targetName ? undefined : targetName, languageVersion), factory.createModuleExportName(name, languageVersion)); })) )]); addResult(factory.createModuleDeclaration( @@ -9189,7 +9208,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getSomeTargetNameFromDeclarations(declarations: Declaration[] | undefined) { return firstDefined(declarations, d => { if (isImportSpecifier(d) || isExportSpecifier(d)) { - return idText(d.propertyName || d.name); + return moduleExportNameText(d.propertyName || d.name); } if (isBinaryExpression(d) || isExportAssignment(d)) { const expression = isExportAssignment(d) ? d.expression : d.right; @@ -9393,7 +9412,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { addResult(factory.createExportDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, localName !== targetName ? targetName : undefined, localName)]), + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, factory.createModuleExportName(localName !== targetName ? targetName : undefined, languageVersion), factory.createModuleExportName(localName, languageVersion))]), specifier ), ModifierFlags.None); } @@ -10050,9 +10069,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function collectLinkedAliases(node: Identifier, setVisibility?: boolean): Node[] | undefined { + function collectLinkedAliases(node: ModuleExportName, setVisibility?: boolean): Node[] | undefined { let exportSymbol: Symbol | undefined; - if (node.parent && node.parent.kind === SyntaxKind.ExportAssignment) { + if (node.parent?.kind === SyntaxKind.ExportAssignment && node.kind !== SyntaxKind.StringLiteral) { exportSymbol = resolveName(node, node.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, node, /*isUse*/ false); } else if (node.parent.kind === SyntaxKind.ExportSpecifier) { @@ -31847,8 +31866,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return symbolResult && symbolName(symbolResult); } - function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: Symbol): Symbol | undefined { - return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember); + function getSuggestedSymbolForNonexistentModule(name: ModuleExportName, targetModule: Symbol): Symbol | undefined { + return targetModule.exports && getSpellingSuggestionForName(moduleExportNameText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember); } function getSuggestionForNonexistentExport(name: Identifier, targetModule: Symbol): string | undefined { @@ -44041,7 +44060,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { Debug.assert(node.kind !== SyntaxKind.NamespaceExport); if (node.kind === SyntaxKind.ExportSpecifier) { const diag = error(errorNode, Diagnostics.Types_cannot_appear_in_export_declarations_in_JavaScript_files); - const alreadyExportedSymbol = getSourceFileOfNode(node).symbol?.exports?.get((node.propertyName || node.name).escapedText); + const exported = node.propertyName || node.name; + const alreadyExportedSymbol = getSourceFileOfNode(node).symbol?.exports?.get(moduleExportNameTextEscaped(exported)); if (alreadyExportedSymbol === target) { const exportingDeclaration = alreadyExportedSymbol.declarations?.find(isJSDocNode); if (exportingDeclaration) { @@ -44099,7 +44119,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { : compilerOptions.verbatimModuleSyntax ? Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_verbatimModuleSyntax_is_enabled : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled; - const name = idText(node.kind === SyntaxKind.ImportSpecifier ? node.propertyName || node.name : node.name); + const name = moduleExportNameText(node.kind === SyntaxKind.ImportSpecifier ? node.propertyName || node.name : node.name); addTypeOnlyDeclarationRelatedInfo( error(node, message, name), isType ? undefined : typeOnlyAlias, @@ -44116,7 +44136,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // The exception is that `import type { A } from './a'; export { A }` is allowed // because single-file analysis can determine that the export should be dropped. if (compilerOptions.verbatimModuleSyntax || getSourceFileOfNode(typeOnlyAlias) !== getSourceFileOfNode(node)) { - const name = idText(node.propertyName || node.name); + + const exported = node.propertyName || node.name; + const name = moduleExportNameText(exported); const diagnostic = isType ? error(node, Diagnostics.Re_exporting_a_type_when_0_is_enabled_requires_using_export_type, isolatedModulesLikeFlagName) : error(node, Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_re_exported_using_a_type_only_re_export_when_1_is_enabled, name, isolatedModulesLikeFlagName); @@ -44181,7 +44203,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkCollisionsForDeclarationName(node, node.name); checkAliasSymbol(node); if (node.kind === SyntaxKind.ImportSpecifier && - idText(node.propertyName || node.name) === "default" && + moduleExportNameText(node.propertyName || node.name) === "default" && getESModuleInterop(compilerOptions) && moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); @@ -44352,8 +44374,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function checkGrammarExportDeclaration(node: ExportDeclaration): boolean { - if (node.isTypeOnly && node.exportClause?.kind === SyntaxKind.NamedExports) { - return checkGrammarNamedImportsOrExports(node.exportClause); + if (node.exportClause?.kind === SyntaxKind.NamedExports) { + return checkGrammarNamedImportsOrExports(node.exportClause, node.isTypeOnly, !node.moduleSpecifier); + } + if ( + (moduleKind === ModuleKind.ES2015 || moduleKind === ModuleKind.ES2020) && + node.exportClause?.kind === SyntaxKind.NamespaceExport && + node.exportClause.name.kind === SyntaxKind.StringLiteral && + !isIdentifierText(node.exportClause.name.text, languageVersion) + ) { + grammarErrorOnNode(node.exportClause.name, Diagnostics.String_literal_module_export_names_are_not_allowed_when_the_module_option_is_set_to_es2020_or_lower); + return true; } return false; } @@ -44416,6 +44447,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } if (!node.parent.parent.moduleSpecifier) { const exportedName = node.propertyName || node.name; + if (exportedName.kind === SyntaxKind.StringLiteral) { + // export { "x" } or export { "x" as name } is a syntax error. reported in grammar check. + return; + } // find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases) const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); @@ -44436,7 +44471,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (getESModuleInterop(compilerOptions) && moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && - idText(node.propertyName || node.name) === "default") { + moduleExportNameText(node.propertyName || node.name) === "default") { checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); } } @@ -45735,9 +45770,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { /** Returns the target of an export specifier without following aliases */ function getExportSpecifierLocalTargetSymbol(node: ExportSpecifier | Identifier): Symbol | undefined { if (isExportSpecifier(node)) { - return node.parent.parent.moduleSpecifier ? - getExternalModuleMember(node.parent.parent, node) : - resolveEntityName(node.propertyName || node.name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); + if (node.parent.parent.moduleSpecifier) { + return getExternalModuleMember(node.parent.parent, node); + } + const exported = node.propertyName || node.name; + // skip resolution for grammar error export { "" as X }. + if (exported.kind === SyntaxKind.StringLiteral) return undefined; + return resolveEntityName(exported, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); } else { return resolveEntityName(node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); @@ -48718,21 +48757,36 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (node.isTypeOnly && node.name && node.namedBindings) { return grammarErrorOnNode(node, Diagnostics.A_type_only_import_can_specify_a_default_import_or_named_bindings_but_not_both); } - if (node.isTypeOnly && node.namedBindings?.kind === SyntaxKind.NamedImports) { - return checkGrammarNamedImportsOrExports(node.namedBindings); + if (node.namedBindings?.kind === SyntaxKind.NamedImports) { + return checkGrammarNamedImportsOrExports(node.namedBindings, node.isTypeOnly, /*isExportLocal*/ false); } return false; } - function checkGrammarNamedImportsOrExports(namedBindings: NamedImportsOrExports): boolean { + function checkGrammarNamedImportsOrExports(namedBindings: NamedImportsOrExports, isTypeOnly: boolean, isExportLocal: boolean): boolean { + const checkString = moduleKind === ModuleKind.ES2015 || moduleKind === ModuleKind.ES2020; + if (!isTypeOnly && !isExportLocal && !checkString) return false; return !!forEach(namedBindings.elements, specifier => { - if (specifier.isTypeOnly) { + if (isTypeOnly && specifier.isTypeOnly) { return grammarErrorOnFirstToken( specifier, specifier.kind === SyntaxKind.ImportSpecifier ? Diagnostics.The_type_modifier_cannot_be_used_on_a_named_import_when_import_type_is_used_on_its_import_statement : Diagnostics.The_type_modifier_cannot_be_used_on_a_named_export_when_export_type_is_used_on_its_export_statement); } + const exported = specifier.propertyName || specifier.name; + if (isExportLocal && exported.kind === SyntaxKind.StringLiteral) { + return grammarErrorOnNode(exported, Diagnostics.String_literal_module_export_names_must_be_followed_by_a_from_clause); + } + + if (checkString) { + if (specifier.name.kind === SyntaxKind.StringLiteral && !isIdentifierText(specifier.name.text, languageVersion)) { + return grammarErrorOnNode(specifier.name, Diagnostics.String_literal_module_export_names_are_not_allowed_when_the_module_option_is_set_to_es2020_or_lower); + } + if (specifier.propertyName?.kind === SyntaxKind.StringLiteral && !isIdentifierText(specifier.propertyName.text, languageVersion)) { + return grammarErrorOnNode(specifier.propertyName, Diagnostics.String_literal_module_export_names_are_not_allowed_when_the_module_option_is_set_to_es2020_or_lower); + } + } }); } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 27787a6fa9e60..508f4323b54f1 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1609,6 +1609,10 @@ "category": "Error", "code": 1490 }, + "String literal module export names are not allowed when the 'module' option is set to 'es2020' or lower.": { + "category": "Error", + "code": 1491 + }, "The types of '{0}' are incompatible between these types.": { "category": "Error", @@ -1667,6 +1671,10 @@ "category": "Message", "code": 2212 }, + "String literal module export names must be followed by a 'from' clause.": { + "category": "Error", + "code": 2213 + }, "Duplicate identifier '{0}'.": { "category": "Error", diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index c3d2e25f38e35..ab08ec9d30f61 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -174,6 +174,7 @@ import { isHoistedFunction, isHoistedVariableStatement, isIdentifier, + isIdentifierText, isImportDeclaration, isImportEqualsDeclaration, isImportKeyword, @@ -312,6 +313,7 @@ import { ModuleBlock, ModuleBody, ModuleDeclaration, + ModuleExportName, ModuleKind, ModuleName, ModuleReference, @@ -982,6 +984,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode get createLogicalNot() { return getPrefixUnaryCreateFunction(SyntaxKind.ExclamationToken); }, get createPostfixIncrement() { return getPostfixUnaryCreateFunction(SyntaxKind.PlusPlusToken); }, get createPostfixDecrement() { return getPostfixUnaryCreateFunction(SyntaxKind.MinusMinusToken); }, + createModuleExportName, // Compound nodes createImmediatelyInvokedFunctionExpression, @@ -4711,7 +4714,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode } // @api - function createNamespaceExport(name: Identifier): NamespaceExport { + function createNamespaceExport(name: ModuleExportName): NamespaceExport { const node = createBaseDeclaration(SyntaxKind.NamespaceExport); node.name = name; node.transformFlags |= @@ -4722,7 +4725,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode } // @api - function updateNamespaceExport(node: NamespaceExport, name: Identifier) { + function updateNamespaceExport(node: NamespaceExport, name: ModuleExportName) { return node.name !== name ? update(createNamespaceExport(name), node) : node; @@ -4745,7 +4748,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode } // @api - function createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { + function createImportSpecifier(isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: Identifier) { const node = createBaseDeclaration(SyntaxKind.ImportSpecifier); node.isTypeOnly = isTypeOnly; node.propertyName = propertyName; @@ -4758,7 +4761,15 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode } // @api - function updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { + function createModuleExportName(name: string, languageVersion: ScriptTarget): ModuleExportName; + function createModuleExportName(name: string | undefined, languageVersion: ScriptTarget): ModuleExportName | undefined; + function createModuleExportName(name: string | undefined, languageVersion: ScriptTarget): ModuleExportName | undefined { + if (name === undefined) return undefined; + return isIdentifierText(name, languageVersion) ? createIdentifier(name) : createStringLiteral(name); + } + + // @api + function updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: Identifier) { return node.isTypeOnly !== isTypeOnly || node.propertyName !== propertyName || node.name !== name @@ -4866,11 +4877,11 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode } // @api - function createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier) { + function createExportSpecifier(isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: ModuleExportName) { const node = createBaseNode(SyntaxKind.ExportSpecifier); node.isTypeOnly = isTypeOnly; - node.propertyName = asName(propertyName); - node.name = asName(name); + node.propertyName = propertyName; + node.name = name; node.transformFlags |= propagateChildFlags(node.propertyName) | propagateChildFlags(node.name); @@ -4881,7 +4892,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode } // @api - function updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { + function updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: ModuleExportName) { return node.isTypeOnly !== isTypeOnly || node.propertyName !== propertyName || node.name !== name diff --git a/src/compiler/factory/nodeTests.ts b/src/compiler/factory/nodeTests.ts index ffc8c4a44839d..f8d000f32a8e1 100644 --- a/src/compiler/factory/nodeTests.ts +++ b/src/compiler/factory/nodeTests.ts @@ -143,6 +143,7 @@ import { MissingDeclaration, ModuleBlock, ModuleDeclaration, + ModuleExportName, NamedExports, NamedImports, NamedTupleMember, @@ -321,6 +322,10 @@ export function isPrivateIdentifier(node: Node): node is PrivateIdentifier { return node.kind === SyntaxKind.PrivateIdentifier; } +export function isModuleExportName(node: Node): node is ModuleExportName { + return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.StringLiteral; +} + // Reserved Words /** @internal */ diff --git a/src/compiler/factory/utilities.ts b/src/compiler/factory/utilities.ts index 2e2721eadcaaf..7cc936efde63c 100644 --- a/src/compiler/factory/utilities.ts +++ b/src/compiler/factory/utilities.ts @@ -792,7 +792,9 @@ export function getOrCreateExternalHelpersModuleNameIfNeeded(factory: NodeFactor } /** - * Get the name of that target module from an import or export declaration + * Get the name of that target module from an import or export declaration. + * + * This is only used in AMD and SystemJS emit. * * @internal */ @@ -800,7 +802,9 @@ export function getLocalNameForExternalImport(factory: NodeFactory, node: Import const namespaceDeclaration = getNamespaceDeclarationNode(node); if (namespaceDeclaration && !isDefaultImport(node) && !isExportNamespaceAsDefaultDeclaration(node)) { const name = namespaceDeclaration.name; - return isGeneratedIdentifier(name) ? name : factory.createIdentifier(getSourceTextOfNodeFromSourceFile(sourceFile, name) || idText(name)); + if (isGeneratedIdentifier(name)) return name; + if (name.kind === SyntaxKind.StringLiteral) return factory.getGeneratedNameForNode(name); + return factory.createIdentifier(getSourceTextOfNodeFromSourceFile(sourceFile, name) || idText(name)); } if (node.kind === SyntaxKind.ImportDeclaration && node.importClause) { return factory.getGeneratedNameForNode(node); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index b97245f84e096..ffe8c23d38299 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -253,6 +253,7 @@ import { modifiersToFlags, ModuleBlock, ModuleDeclaration, + ModuleExportName, ModuleKind, Mutable, NamedExportBindings, @@ -2867,7 +2868,7 @@ namespace Parser { case ParsingContext.HeritageClauses: return isHeritageClause(); case ParsingContext.ImportOrExportSpecifiers: - return tokenIsIdentifierOrKeyword(token()); + return token() === SyntaxKind.StringLiteral || tokenIsIdentifierOrKeyword(token()); case ParsingContext.JsxAttributes: return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.OpenBraceToken; case ParsingContext.JsxChildren: @@ -8331,20 +8332,24 @@ namespace Parser { function parseImportOrExportSpecifier(kind: SyntaxKind): ImportOrExportSpecifier { const pos = getNodePos(); + // ModuleExportName: + // Identifier + // StringLiteral // ImportSpecifier: // BindingIdentifier - // IdentifierName as BindingIdentifier + // ModuleExportName as BindingIdentifier // ExportSpecifier: - // IdentifierName - // IdentifierName as IdentifierName + // ModuleExportName + // ModuleExportName as ModuleExportName let checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); let checkIdentifierStart = scanner.getTokenStart(); let checkIdentifierEnd = scanner.getTokenEnd(); let isTypeOnly = false; - let propertyName: Identifier | undefined; + let propertyName: ModuleExportName | undefined; let canParseAsKeyword = true; - let name = parseIdentifierName(); - if (name.escapedText === "type") { + let mustParseAsKeyword = false; + let name = parseModuleExportName(parseIdentifierName); + if (name.kind === SyntaxKind.Identifier && name.escapedText === "type") { // If the first token of an import specifier is 'type', there are a lot of possibilities, // especially if we see 'as' afterwards: // @@ -8352,6 +8357,7 @@ namespace Parser { // import { type as } from "mod"; - isTypeOnly: true, name: as // import { type as as } from "mod"; - isTypeOnly: false, name: as, propertyName: type // import { type as as as } from "mod"; - isTypeOnly: true, name: as, propertyName: as + // export { type as as "s" } from "mod";- isTypeOnly: true, name: "s", propertyName: as if (token() === SyntaxKind.AsKeyword) { // { type as ...? } const firstAs = parseIdentifierName(); @@ -8360,9 +8366,10 @@ namespace Parser { const secondAs = parseIdentifierName(); if (tokenIsIdentifierOrKeyword(token())) { // { type as as something } + // { type as as "something" } (only in exports) isTypeOnly = true; propertyName = firstAs; - name = parseNameWithKeywordCheck(); + name = parseModuleExportNameOnlyForExports(); canParseAsKeyword = false; } else { @@ -8376,7 +8383,7 @@ namespace Parser { // { type as something } propertyName = name; canParseAsKeyword = false; - name = parseNameWithKeywordCheck(); + name = parseModuleExportNameOnlyForExports(); } else { // { type as } @@ -8384,23 +8391,35 @@ namespace Parser { name = firstAs; } } + // export { type "x" } + // import { type "x" as ... } + else if (token() === SyntaxKind.StringLiteral) { + isTypeOnly = true; + if (kind === SyntaxKind.ImportSpecifier) mustParseAsKeyword = true; + name = parseModuleExportName(parseNameWithKeywordCheck); + } else if (tokenIsIdentifierOrKeyword(token())) { // { type something ...? } isTypeOnly = true; name = parseNameWithKeywordCheck(); } } + // import { "x" as ... } + else if (kind === SyntaxKind.ImportSpecifier && name.kind === SyntaxKind.StringLiteral) { + mustParseAsKeyword = true; + } - if (canParseAsKeyword && token() === SyntaxKind.AsKeyword) { + if (mustParseAsKeyword || (canParseAsKeyword && token() === SyntaxKind.AsKeyword)) { propertyName = name; parseExpected(SyntaxKind.AsKeyword); - name = parseNameWithKeywordCheck(); + name = parseModuleExportNameOnlyForExports(); } if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) { parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected); } + if (kind === SyntaxKind.ImportSpecifier) Debug.assert(name.kind === SyntaxKind.Identifier); const node = kind === SyntaxKind.ImportSpecifier - ? factory.createImportSpecifier(isTypeOnly, propertyName, name) + ? factory.createImportSpecifier(isTypeOnly, propertyName, name as Identifier) : factory.createExportSpecifier(isTypeOnly, propertyName, name); return finishNode(node, pos); @@ -8410,10 +8429,22 @@ namespace Parser { checkIdentifierEnd = scanner.getTokenEnd(); return parseIdentifierName(); } + function parseModuleExportNameOnlyForExports() { + if (kind === SyntaxKind.ImportSpecifier) return parseNameWithKeywordCheck(); + return parseModuleExportName(parseNameWithKeywordCheck); + } + function parseModuleExportName(parser: () => Identifier): ModuleExportName { + if (token() === SyntaxKind.StringLiteral) return parseStringLiteral(); + return parser(); + } + function parseStringLiteral(): StringLiteral { + // TODO: the spec requires it pass IsStringWellFormedUnicode + return parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral; + } } function parseNamespaceExport(pos: number): NamespaceExport { - return finishNode(factory.createNamespaceExport(parseIdentifierName()), pos); + return finishNode(factory.createNamespaceExport(token() === SyntaxKind.StringLiteral ? parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral : parseIdentifierName()), pos); } function parseExportDeclaration(pos: number, hasJSDoc: boolean, modifiers: NodeArray | undefined): ExportDeclaration { diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 4be05fd79eb06..d0e9ef4c90b62 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -59,6 +59,7 @@ import { getDirectoryPath, getEffectiveBaseTypeNode, getEffectiveModifierFlags, + getEmitScriptTarget, getExternalModuleImportEqualsDeclarationExpression, getExternalModuleNameFromDeclaration, getFirstConstructorWithBody, @@ -1520,7 +1521,7 @@ export function transformDeclarations(context: TransformationContext) { /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports(map(exportMappings, ([gen, exp]) => { - return factory.createExportSpecifier(/*isTypeOnly*/ false, gen, exp); + return factory.createExportSpecifier(/*isTypeOnly*/ false, gen, factory.createModuleExportName(exp, getEmitScriptTarget(context.getCompilerOptions()))); })) )); } diff --git a/src/compiler/transformers/module/esnextAnd2015.ts b/src/compiler/transformers/module/esnextAnd2015.ts index 879544063f9f0..01991d0f69aac 100644 --- a/src/compiler/transformers/module/esnextAnd2015.ts +++ b/src/compiler/transformers/module/esnextAnd2015.ts @@ -10,6 +10,7 @@ import { EmitHint, ExportAssignment, ExportDeclaration, + ExportSpecifier, Expression, GeneratedIdentifierFlags, getEmitFlags, @@ -22,17 +23,21 @@ import { idText, ImportDeclaration, ImportEqualsDeclaration, + ImportSpecifier, insertStatementsAfterCustomPrologue, isExportNamespaceAsDefaultDeclaration, isExternalModule, isExternalModuleImportEqualsDeclaration, isExternalModuleIndicator, isIdentifier, + isIdentifierText, isNamespaceExport, isSourceFile, isStatement, ModifierFlags, + ModuleExportName, ModuleKind, + NamedImports, Node, NodeFlags, ScriptTarget, @@ -119,6 +124,8 @@ export function transformECMAScriptModule(context: TransformationContext): (x: S function visitor(node: Node): VisitResult { switch (node.kind) { + case SyntaxKind.ImportDeclaration: + return visitImportDeclaration(node as ImportDeclaration); case SyntaxKind.ImportEqualsDeclaration: // Though an error in es2020 modules, in node-flavor es2020 modules, we can helpfully transform this to a synthetic `require` call // To give easy access to a synchronous `require` in node-flavor esm. We do the transform even in scenarios where we error, but `import.meta.url` @@ -185,6 +192,73 @@ export function transformECMAScriptModule(context: TransformationContext): (x: S return factory.createCallExpression(factory.cloneNode(name), /*typeArguments*/ undefined, args); } + /** + * Visits an ImportDeclaration node. + * + * @param node The node to visit. + */ + function visitImportDeclaration(node: ImportDeclaration): VisitResult { + if (compilerOptions.module !== ModuleKind.ES2015 && compilerOptions.module !== ModuleKind.ES2020) return node; + if (node.importClause?.namedBindings?.kind !== SyntaxKind.NamedImports) return node; + + const clause = node.importClause; + const substitutions: ImportSpecifier[] = []; + let needUpdate = false; + const bindings = clause.namedBindings as NamedImports; + const nextImports: ImportSpecifier[] = []; + for (const element of bindings.elements) { + if (element.propertyName?.kind === SyntaxKind.StringLiteral) { + const id = tryConvertModuleExportNameToIdentifier(element.propertyName); + if (id) { + needUpdate = true; + nextImports.push(factory.updateImportSpecifier(element, element.isTypeOnly, id, element.name)); + } + else { + substitutions.push(element); + } + } + else nextImports.push(element); + } + if (!needUpdate && !substitutions.length) return node; + node = factory.updateImportDeclaration( + node, + node.modifiers, + factory.updateImportClause(clause, clause.isTypeOnly, clause.name, factory.updateNamedImports(bindings, nextImports)), + node.moduleSpecifier, + node.assertClause + ); + if (!substitutions.length) return node; + const nsImportName = factory.getGeneratedNameForNode(node); + for (const element of substitutions) { + // TODO: this does not support ESM live binding. Not worth to introduce so much node substitute code for this? + context.addInitializationStatement( + factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + [factory.createVariableDeclaration( + element.name, + /*exclamationToken*/ undefined, + /*type*/ undefined, + factory.createElementAccessExpression( + nsImportName, + element.propertyName! + ) + )], + NodeFlags.Const + ) + ) + ); + } + const nsImport = factory.createImportDeclaration( + node.modifiers, + factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamespaceImport(nsImportName)), + node.moduleSpecifier, + node.assertClause + ); + if (!clause.name && nextImports.length === 0) return nsImport; + return [node, nsImport]; + } + /** * Visits an ImportEqualsDeclaration node. * @@ -226,7 +300,7 @@ export function transformECMAScriptModule(context: TransformationContext): (x: S statements = append(statements, factory.createExportDeclaration( /*modifiers*/ undefined, node.isTypeOnly, - factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, idText(node.name))]) + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, node.name)]) )); } return statements; @@ -237,7 +311,61 @@ export function transformECMAScriptModule(context: TransformationContext): (x: S return node.isExportEquals ? undefined : node; } + function tryConvertModuleExportNameToIdentifier(node: ModuleExportName): Identifier | undefined { + if (node.kind === SyntaxKind.Identifier) return node; + if (!isIdentifierText(node.text, languageVersion)) return undefined; + const id = factory.createIdentifier(node.text); + setOriginalNode(id, node); + return id; + } + function visitExportDeclaration(node: ExportDeclaration) { + // transform StringLiteral to Identifier when it's possible. + if (compilerOptions.module === ModuleKind.ES2015 || compilerOptions.module === ModuleKind.ES2020) { + const clause = node.exportClause; + if (clause?.kind === SyntaxKind.NamespaceExport && clause.name.kind === SyntaxKind.StringLiteral) { + const id = tryConvertModuleExportNameToIdentifier(clause.name); + if (id) { + node = factory.updateExportDeclaration( + node, + node.modifiers, + node.isTypeOnly, + factory.updateNamespaceExport(clause, id), + node.moduleSpecifier, + node.assertClause + ); + } + else return undefined; + } + if (clause?.kind === SyntaxKind.NamedExports) { + const nextExports: ExportSpecifier[] = []; + let needUpdate = false; + for (const element of clause.elements) { + const exported = element.propertyName || element.name; + // ill-formed + if (exported.kind === SyntaxKind.StringLiteral && !node.moduleSpecifier) return node; + const prop = element.propertyName && tryConvertModuleExportNameToIdentifier(element.propertyName); + const name = tryConvertModuleExportNameToIdentifier(element.name); + if (element.name !== name || prop !== element.propertyName) { + needUpdate = true; + if ((element.propertyName && !prop) || !name) continue; + nextExports.push(factory.updateExportSpecifier(element, element.isTypeOnly, prop, name || element.name)); + } + else nextExports.push(element); + } + if (needUpdate) { + node = factory.updateExportDeclaration( + node, + node.modifiers, + node.isTypeOnly, + factory.updateNamedExports(clause, nextExports), + node.moduleSpecifier, + node.assertClause + ); + } + } + } + // `export * as ns` only needs to be transformed in ES2015 if (compilerOptions.module !== undefined && compilerOptions.module > ModuleKind.ES2015) { return node; diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 38b7eee2d3ba3..812a8f7868add 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -13,6 +13,7 @@ import { chainBundle, ClassDeclaration, collectExternalModuleInfo, + createElementAccessOrPropertyAccessExpression, Debug, Declaration, DestructuringAssignment, @@ -104,6 +105,8 @@ import { mapDefined, Modifier, ModifierFlags, + ModuleExportName, + moduleExportNameText, ModuleKind, Node, NodeArray, @@ -247,7 +250,7 @@ export function transformModule(context: TransformationContext): (x: SourceFile factory.createExpressionStatement( reduceLeft( currentModuleInfo.exportedNames!.slice(i, i + chunkSize), - (prev, nextId) => factory.createAssignment(factory.createPropertyAccessExpression(factory.createIdentifier("exports"), factory.createIdentifier(idText(nextId))), prev), + (prev, nextId) => factory.createAssignment(createElementAccessOrPropertyAccessExpression(factory.createIdentifier("exports"), nextId), prev), factory.createVoidZero() as Expression ) ) @@ -574,7 +577,7 @@ export function transformModule(context: TransformationContext): (x: SourceFile append(statements, createUnderscoreUnderscoreESModule()); } if (length(currentModuleInfo.exportedNames)) { - append(statements, factory.createExpressionStatement(reduceLeft(currentModuleInfo.exportedNames, (prev, nextId) => factory.createAssignment(factory.createPropertyAccessExpression(factory.createIdentifier("exports"), factory.createIdentifier(idText(nextId))), prev), factory.createVoidZero() as Expression))); + append(statements, factory.createExpressionStatement(reduceLeft(currentModuleInfo.exportedNames, (prev, nextId) => factory.createAssignment(createElementAccessOrPropertyAccessExpression(factory.createIdentifier("exports"), nextId), prev), factory.createVoidZero() as Expression))); } // Visit each statement of the module body. @@ -1288,8 +1291,8 @@ export function transformModule(context: TransformationContext): (x: SourceFile const exportNeedsImportDefault = !!getESModuleInterop(compilerOptions) && !(getInternalEmitFlags(node) & InternalEmitFlags.NeverApplyImportHelper) && - idText(specifier.propertyName || specifier.name) === "default"; - const exportedValue = factory.createPropertyAccessExpression( + moduleExportNameText(specifier.propertyName || specifier.name) === "default"; + const exportedValue = createElementAccessOrPropertyAccessExpression( exportNeedsImportDefault ? emitHelpers().createImportDefaultHelper(generatedName) : generatedName, specifier.propertyName || specifier.name); statements.push( @@ -1320,7 +1323,10 @@ export function transformModule(context: TransformationContext): (x: SourceFile getHelperExpressionForExport(node, moduleKind !== ModuleKind.AMD ? createRequireCall(node) : isExportNamespaceAsDefaultDeclaration(node) ? generatedName : - factory.createIdentifier(idText(node.exportClause.name))) + node.exportClause.name.kind === SyntaxKind.Identifier ? + factory.createIdentifier(idText(node.exportClause.name)) : + factory.getGeneratedNameForNode(node.exportClause.name) + ) ) ), node @@ -1725,7 +1731,7 @@ export function transformModule(context: TransformationContext): (x: SourceFile * @param location The location to use for source maps and comments for the export. * @param allowComments Whether to allow comments on the export. */ - function appendExportStatement(statements: Statement[] | undefined, exportName: Identifier, expression: Expression, location?: TextRange, allowComments?: boolean, liveBinding?: boolean): Statement[] | undefined { + function appendExportStatement(statements: Statement[] | undefined, exportName: ModuleExportName, expression: Expression, location?: TextRange, allowComments?: boolean, liveBinding?: boolean): Statement[] | undefined { statements = append(statements, createExportStatement(exportName, expression, location, allowComments, liveBinding)); return statements; } @@ -1767,7 +1773,7 @@ export function transformModule(context: TransformationContext): (x: SourceFile * @param location The location to use for source maps and comments for the export. * @param allowComments An optional value indicating whether to emit comments for the statement. */ - function createExportStatement(name: Identifier, value: Expression, location?: TextRange, allowComments?: boolean, liveBinding?: boolean) { + function createExportStatement(name: ModuleExportName, value: Expression, location?: TextRange, allowComments?: boolean, liveBinding?: boolean) { const statement = setTextRange(factory.createExpressionStatement(createExportExpression(name, value, /*location*/ undefined, liveBinding)), location); startOnNewLine(statement); if (!allowComments) { @@ -1784,7 +1790,7 @@ export function transformModule(context: TransformationContext): (x: SourceFile * @param value The exported value. * @param location The location to use for source maps and comments for the export. */ - function createExportExpression(name: Identifier, value: Expression, location?: TextRange, liveBinding?: boolean) { + function createExportExpression(name: ModuleExportName, value: Expression, location?: TextRange, liveBinding?: boolean) { return setTextRange( liveBinding && languageVersion !== ScriptTarget.ES3 ? factory.createCallExpression( factory.createPropertyAccessExpression( @@ -1809,7 +1815,7 @@ export function transformModule(context: TransformationContext): (x: SourceFile ]) ] ) : factory.createAssignment( - factory.createPropertyAccessExpression( + createElementAccessOrPropertyAccessExpression( factory.createIdentifier("exports"), factory.cloneNode(name) ), @@ -2008,7 +2014,7 @@ export function transformModule(context: TransformationContext): (x: SourceFile else if (isImportSpecifier(importDeclaration)) { const name = importDeclaration.propertyName || importDeclaration.name; return setTextRange( - factory.createPropertyAccessExpression( + createElementAccessOrPropertyAccessExpression( factory.getGeneratedNameForNode(importDeclaration.parent?.parent?.parent || importDeclaration), factory.cloneNode(name) ), diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index f87307b5f3115..ca3648c3076c7 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -12,6 +12,7 @@ import { chainBundle, ClassDeclaration, collectExternalModuleInfo, + createElementAccessOrPropertyAccessExpression, Debug, Declaration, DefaultClause, @@ -88,6 +89,8 @@ import { map, MetaProperty, ModifierFlags, + moduleExportNameText, + moduleExportNameTextEscaped, moveEmitHelpers, Node, NodeFlags, @@ -451,7 +454,7 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc const exportedNames: ObjectLiteralElementLike[] = []; if (moduleInfo.exportedNames) { for (const exportedLocalName of moduleInfo.exportedNames) { - if (exportedLocalName.escapedText === "default") { + if (moduleExportNameTextEscaped(exportedLocalName) === "default") { continue; } @@ -625,10 +628,10 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc for (const e of entry.exportClause.elements) { properties.push( factory.createPropertyAssignment( - factory.createStringLiteral(idText(e.name)), + factory.createStringLiteral(moduleExportNameText(e.name)), factory.createElementAccessExpression( parameterName, - factory.createStringLiteral(idText(e.propertyName || e.name)) + factory.createStringLiteral(moduleExportNameText(e.propertyName || e.name)) ) ) ); @@ -651,7 +654,7 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc exportFunction, /*typeArguments*/ undefined, [ - factory.createStringLiteral(idText(entry.exportClause.name)), + factory.createStringLiteral(moduleExportNameText(entry.exportClause.name)), parameterName ] ) @@ -1116,7 +1119,7 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc const exportSpecifiers = moduleInfo.exportSpecifiers.get(idText(name)); if (exportSpecifiers) { for (const exportSpecifier of exportSpecifiers) { - if (exportSpecifier.name.escapedText !== excludeName) { + if (moduleExportNameTextEscaped(exportSpecifier.name) !== excludeName) { statements = appendExportStatement(statements, exportSpecifier.name, name); } } @@ -1788,7 +1791,7 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc return setTextRange( factory.createPropertyAssignment( factory.cloneNode(name), - factory.createPropertyAccessExpression( + createElementAccessOrPropertyAccessExpression( factory.getGeneratedNameForNode(importDeclaration.parent?.parent?.parent || importDeclaration), factory.cloneNode(importDeclaration.propertyName || importDeclaration.name) ), @@ -1854,7 +1857,7 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc } else if (isImportSpecifier(importDeclaration)) { return setTextRange( - factory.createPropertyAccessExpression( + createElementAccessOrPropertyAccessExpression( factory.getGeneratedNameForNode(importDeclaration.parent?.parent?.parent || importDeclaration), factory.cloneNode(importDeclaration.propertyName || importDeclaration.name) ), diff --git a/src/compiler/transformers/utilities.ts b/src/compiler/transformers/utilities.ts index c094a8dc0e76c..af2e1bf4bb1a9 100644 --- a/src/compiler/transformers/utilities.ts +++ b/src/compiler/transformers/utilities.ts @@ -66,6 +66,9 @@ import { map, MethodDeclaration, ModifierFlags, + ModuleExportName, + moduleExportNameText, + moduleExportNameTextEscaped, NamedImportBindings, NamespaceExport, Node, @@ -99,7 +102,7 @@ export interface ExternalModuleInfo { externalHelpersImportDeclaration: ImportDeclaration | undefined; // import of external helpers exportSpecifiers: Map; // file-local export specifiers by name (no reexports) exportedBindings: Identifier[][]; // exported names of local declarations - exportedNames: Identifier[] | undefined; // all exported names in the module, both local and reexported + exportedNames: ModuleExportName[] | undefined; // all exported names in the module, both local and reexported exportEquals: ExportAssignment | undefined; // an export= declaration if one was present hasExportStarsToExportValues: boolean; // whether this module contains export* } @@ -111,7 +114,7 @@ function containsDefaultReference(node: NamedImportBindings | undefined) { } function isNamedDefaultReference(e: ImportSpecifier): boolean { - return e.propertyName !== undefined && e.propertyName.escapedText === InternalSymbolName.Default; + return e.propertyName !== undefined && moduleExportNameTextEscaped(e.propertyName) === InternalSymbolName.Default; } /** @internal */ @@ -164,7 +167,7 @@ export function collectExternalModuleInfo(context: TransformationContext, source const exportSpecifiers = createMultiMap(); const exportedBindings: Identifier[][] = []; const uniqueExports = new Map(); - let exportedNames: Identifier[] | undefined; + let exportedNames: ModuleExportName[] | undefined; let hasExportDefault = false; let exportEquals: ExportAssignment | undefined; let hasExportStarsToExportValues = false; @@ -211,9 +214,10 @@ export function collectExternalModuleInfo(context: TransformationContext, source } else { const name = ((node as ExportDeclaration).exportClause as NamespaceExport).name; - if (!uniqueExports.get(idText(name))) { + const nameText = moduleExportNameText(name); + if (!uniqueExports.get(nameText)) { multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), name); - uniqueExports.set(idText(name), true); + uniqueExports.set(nameText, true); exportedNames = append(exportedNames, name); } // we use the same helpers for `export * as ns` as we do for `import * as ns` @@ -295,27 +299,30 @@ export function collectExternalModuleInfo(context: TransformationContext, source function addExportedNamesForExportDeclaration(node: ExportDeclaration) { for (const specifier of cast(node.exportClause, isNamedExports).elements) { - if (!uniqueExports.get(idText(specifier.name))) { + if (!uniqueExports.get(moduleExportNameText(specifier.name))) { const name = specifier.propertyName || specifier.name; - if (!node.moduleSpecifier) { + if (!node.moduleSpecifier && name.kind === SyntaxKind.Identifier) { exportSpecifiers.add(idText(name), specifier); } - const decl = resolver.getReferencedImportDeclaration(name) - || resolver.getReferencedValueDeclaration(name); + // export { "x" as ... } cannot be resolved locally. + if (name.kind === SyntaxKind.Identifier) { + const decl = resolver.getReferencedImportDeclaration(name) + || resolver.getReferencedValueDeclaration(name); - if (decl) { - multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(decl), specifier.name); + if (decl) { + multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(decl), specifier.name); + } } - uniqueExports.set(idText(specifier.name), true); + uniqueExports.set(moduleExportNameText(specifier.name), true); exportedNames = append(exportedNames, specifier.name); } } } } -function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, uniqueExports: Map, exportedNames: Identifier[] | undefined, exportedBindings: Identifier[][]) { +function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, uniqueExports: Map, exportedNames: ModuleExportName[] | undefined, exportedBindings: Identifier[][]) { if (isBindingPattern(decl.name)) { for (const element of decl.name.elements) { if (!isOmittedExpression(element)) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a651f955078f6..b9759b0370251 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3734,7 +3734,7 @@ export interface NamespaceImport extends NamedDeclaration { export interface NamespaceExport extends NamedDeclaration { readonly kind: SyntaxKind.NamespaceExport; readonly parent: ExportDeclaration; - readonly name: Identifier + readonly name: ModuleExportName; } export interface NamespaceExportDeclaration extends DeclarationStatement, JSDocContainer { @@ -3774,7 +3774,7 @@ export type NamedImportsOrExports = NamedImports | NamedExports; export interface ImportSpecifier extends NamedDeclaration { readonly kind: SyntaxKind.ImportSpecifier; readonly parent: NamedImports; - readonly propertyName?: Identifier; // Name preceding "as" keyword (or undefined when "as" is absent) + readonly propertyName?: ModuleExportName; // Name preceding "as" keyword (or undefined when "as" is absent) readonly name: Identifier; // Declared name readonly isTypeOnly: boolean; } @@ -3783,10 +3783,11 @@ export interface ExportSpecifier extends NamedDeclaration, JSDocContainer { readonly kind: SyntaxKind.ExportSpecifier; readonly parent: NamedExports; readonly isTypeOnly: boolean; - readonly propertyName?: Identifier; // Name preceding "as" keyword (or undefined when "as" is absent) - readonly name: Identifier; // Declared name + readonly propertyName?: ModuleExportName; // Name preceding "as" keyword (or undefined when "as" is absent) + readonly name: ModuleExportName; // Declared name } +export type ModuleExportName = Identifier | StringLiteral; export type ImportOrExportSpecifier = | ImportSpecifier | ExportSpecifier @@ -5678,7 +5679,7 @@ export interface EmitResolver { getNodeCheckFlags(node: Node): NodeCheckFlags; isDeclarationVisible(node: Declaration | AnyImportSyntax): boolean; isLateBound(node: Declaration): node is LateBoundDeclaration; - collectLinkedAliases(node: Identifier, setVisibility?: boolean): Node[] | undefined; + collectLinkedAliases(node: ModuleExportName, setVisibility?: boolean): Node[] | undefined; isImplementationOfOverload(node: SignatureDeclaration): boolean | undefined; isRequiredInitializedParameter(node: ParameterDeclaration): boolean; isOptionalUninitializedParameterProperty(node: ParameterDeclaration): boolean; @@ -5946,6 +5947,8 @@ export interface ReverseMappedSymbol extends TransientSymbol { links: ReverseMappedSymbolLinks; } +// All names should starts with __, since it's possible to declare an arbitrary symbol name with +// export { ident as "this" } export const enum InternalSymbolName { Call = "__call", // Call signatures Constructor = "__constructor", // Constructor implementations @@ -5961,9 +5964,9 @@ export const enum InternalSymbolName { Function = "__function", // Unnamed function expression Computed = "__computed", // Computed property name declaration with dynamic name Resolving = "__resolving__", // Indicator symbol used to mark partially resolved type aliases - ExportEquals = "export=", // Export assignment symbol - Default = "default", // Default export symbol (technically not wholly internal, but included here for usability) - This = "this", + ExportEquals = "__export=", // Export assignment symbol + Default = "__default", // Default export symbol (technically not wholly internal, but included here for usability) + This = "__this", } /** @@ -8607,20 +8610,20 @@ export interface NodeFactory { updateImportTypeAssertionContainer(node: ImportTypeAssertionContainer, clause: AssertClause, multiLine?: boolean): ImportTypeAssertionContainer; createNamespaceImport(name: Identifier): NamespaceImport; updateNamespaceImport(node: NamespaceImport, name: Identifier): NamespaceImport; - createNamespaceExport(name: Identifier): NamespaceExport; - updateNamespaceExport(node: NamespaceExport, name: Identifier): NamespaceExport; + createNamespaceExport(name: ModuleExportName): NamespaceExport; + updateNamespaceExport(node: NamespaceExport, name: ModuleExportName): NamespaceExport; createNamedImports(elements: readonly ImportSpecifier[]): NamedImports; updateNamedImports(node: NamedImports, elements: readonly ImportSpecifier[]): NamedImports; - createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier; - updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier; + createImportSpecifier(isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: Identifier): ImportSpecifier; + updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: Identifier): ImportSpecifier; createExportAssignment(modifiers: readonly ModifierLike[] | undefined, isExportEquals: boolean | undefined, expression: Expression): ExportAssignment; updateExportAssignment(node: ExportAssignment, modifiers: readonly ModifierLike[] | undefined, expression: Expression): ExportAssignment; createExportDeclaration(modifiers: readonly ModifierLike[] | undefined, isTypeOnly: boolean, exportClause: NamedExportBindings | undefined, moduleSpecifier?: Expression, assertClause?: AssertClause): ExportDeclaration; updateExportDeclaration(node: ExportDeclaration, modifiers: readonly ModifierLike[] | undefined, isTypeOnly: boolean, exportClause: NamedExportBindings | undefined, moduleSpecifier: Expression | undefined, assertClause: AssertClause | undefined): ExportDeclaration; createNamedExports(elements: readonly ExportSpecifier[]): NamedExports; updateNamedExports(node: NamedExports, elements: readonly ExportSpecifier[]): NamedExports; - createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier): ExportSpecifier; - updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier): ExportSpecifier; + createExportSpecifier(isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: ModuleExportName): ExportSpecifier; + updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: ModuleExportName): ExportSpecifier; /** @internal */ createMissingDeclaration(): MissingDeclaration; // @@ -8852,6 +8855,8 @@ export interface NodeFactory { createLogicalNot(operand: Expression): PrefixUnaryExpression; createPostfixIncrement(operand: Expression): PostfixUnaryExpression; createPostfixDecrement(operand: Expression): PostfixUnaryExpression; + createModuleExportName(name: string, languageVersion: ScriptTarget): ModuleExportName; + createModuleExportName(name: string | undefined, languageVersion: ScriptTarget): ModuleExportName | undefined; // // Compound Nodes diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index bc6882e5fbb20..bd196dc513449 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -390,6 +390,7 @@ import { ModuleBlock, ModuleDeclaration, ModuleDetectionKind, + ModuleExportName, ModuleKind, ModuleResolutionKind, moduleResolutionOptionDeclarations, @@ -1173,7 +1174,7 @@ function isJSDocTypeExpressionOrChild(node: Node): boolean { /** @internal */ export function isExportNamespaceAsDefaultDeclaration(node: Node): boolean { - return !!(isExportDeclaration(node) && node.exportClause && isNamespaceExport(node.exportClause) && node.exportClause.name.escapedText === "default"); + return !!(isExportDeclaration(node) && node.exportClause && isNamespaceExport(node.exportClause) && moduleExportNameTextEscaped(node.exportClause.name) === "default"); } /** @internal */ @@ -2009,7 +2010,7 @@ export function forEachEnclosingBlockScopeContainer(node: Node, cb: (container: // Computed property names will just be emitted as "[]", where is the source // text of the expression in the computed property. /** @internal */ -export function declarationNameToString(name: DeclarationName | QualifiedName | undefined) { +export function declarationNameToString(name: DeclarationName | QualifiedName | ModuleExportName | undefined) { return !name || getFullWidth(name) === 0 ? "(Missing)" : getTextOfNode(name); } @@ -3837,6 +3838,12 @@ export function getElementOrPropertyAccessName(node: AccessExpression): __String return undefined; } +/** @internal */ +export function createElementAccessOrPropertyAccessExpression(lhs: Expression, rhs: Expression) { + if (isMemberName(rhs)) return factory.createPropertyAccessExpression(lhs, rhs); + return factory.createElementAccessExpression(lhs, rhs); +} + /** @internal */ export function getAssignmentDeclarationPropertyAccessKind(lhs: AccessExpression): AssignmentDeclarationKind { if (lhs.expression.kind === SyntaxKind.ThisKeyword) { @@ -3987,6 +3994,10 @@ export function getExternalModuleName(node: AnyImportOrReExport | ImportTypeNode } /** @internal */ +export function getNamespaceDeclarationNode(node: ImportDeclaration): NamespaceImport | undefined; +export function getNamespaceDeclarationNode(node: ImportEqualsDeclaration): ImportEqualsDeclaration; +export function getNamespaceDeclarationNode(node: ExportDeclaration): NamespaceExport | undefined; +export function getNamespaceDeclarationNode(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): ImportEqualsDeclaration | NamespaceImport | NamespaceExport | undefined; export function getNamespaceDeclarationNode(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): ImportEqualsDeclaration | NamespaceImport | NamespaceExport | undefined { switch (node.kind) { case SyntaxKind.ImportDeclaration: @@ -10207,3 +10218,18 @@ export function getTextOfJsxNamespacedName(node: JsxNamespacedName) { export function intrinsicTagNameToString(node: Identifier | JsxNamespacedName) { return isIdentifier(node) ? idText(node) : getTextOfJsxNamespacedName(node); } + +/** @internal */ +export function moduleExportNameText(node: ModuleExportName): string { + if (node.kind === SyntaxKind.StringLiteral) return node.text; + return idText(node); +} +/** @internal */ +export function moduleExportNameTextEscaped(node: ModuleExportName): __String { + if (node.kind === SyntaxKind.StringLiteral) return escapeLeadingUnderscores(node.text); + return node.escapedText; +} +export function isModuleExportNameStringLiteral(node: __String | ModuleExportName | undefined): node is StringLiteral { + if (!node) return false; + return typeof node !== "string" && node.kind === SyntaxKind.StringLiteral; +} diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index 3767bcc3f1118..13349d297b500 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -851,8 +851,8 @@ function getDeclarationIdentifier(node: Declaration | Expression): Identifier | } /** @internal */ -export function nodeHasName(statement: Node, name: Identifier) { - if (isNamedDeclaration(statement) && isIdentifier(statement.name) && idText(statement.name as Identifier) === idText(name)) { +export function nodeHasName(statement: Node, name: Identifier | string) { + if (isNamedDeclaration(statement) && isIdentifier(statement.name) && idText(statement.name) === (typeof name === "string" ? name : idText(name))) { return true; } if (isVariableStatement(statement) && some(statement.declarationList.declarations, d => nodeHasName(d, name))) { diff --git a/src/compiler/visitorPublic.ts b/src/compiler/visitorPublic.ts index 08475d5c0ae7a..08aeaa8961bf7 100644 --- a/src/compiler/visitorPublic.ts +++ b/src/compiler/visitorPublic.ts @@ -59,6 +59,7 @@ import { isModifier, isModifierLike, isModuleBody, + isModuleExportName, isModuleName, isModuleReference, isNamedExportBindings, @@ -1355,7 +1356,7 @@ const visitEachChildTable: VisitEachChildTable = { [SyntaxKind.NamespaceExport]: function visitEachChildOfNamespaceExport(node, visitor, context, _nodesVisitor, nodeVisitor, _tokenVisitor) { return context.factory.updateNamespaceExport(node, - Debug.checkDefined(nodeVisitor(node.name, visitor, isIdentifier))); + Debug.checkDefined(nodeVisitor(node.name, visitor, isModuleExportName))); }, [SyntaxKind.NamedImports]: function visitEachChildOfNamedImports(node, visitor, context, nodesVisitor, _nodeVisitor, _tokenVisitor) { @@ -1366,7 +1367,7 @@ const visitEachChildTable: VisitEachChildTable = { [SyntaxKind.ImportSpecifier]: function visitEachChildOfImportSpecifier(node, visitor, context, _nodesVisitor, nodeVisitor, _tokenVisitor) { return context.factory.updateImportSpecifier(node, node.isTypeOnly, - nodeVisitor(node.propertyName, visitor, isIdentifier), + nodeVisitor(node.propertyName, visitor, isModuleExportName), Debug.checkDefined(nodeVisitor(node.name, visitor, isIdentifier))); }, @@ -1393,7 +1394,7 @@ const visitEachChildTable: VisitEachChildTable = { [SyntaxKind.ExportSpecifier]: function visitEachChildOfExportSpecifier(node, visitor, context, _nodesVisitor, nodeVisitor, _tokenVisitor) { return context.factory.updateExportSpecifier(node, node.isTypeOnly, - nodeVisitor(node.propertyName, visitor, isIdentifier), + nodeVisitor(node.propertyName, visitor, isModuleExportName), Debug.checkDefined(nodeVisitor(node.name, visitor, isIdentifier))); }, diff --git a/src/services/codefixes/fixInvalidImportSyntax.ts b/src/services/codefixes/fixInvalidImportSyntax.ts index bae3f2d2c0f16..1124d10a305a0 100644 --- a/src/services/codefixes/fixInvalidImportSyntax.ts +++ b/src/services/codefixes/fixInvalidImportSyntax.ts @@ -18,7 +18,6 @@ import { isTransientSymbol, makeImport, ModuleKind, - NamespaceImport, NewExpression, Node, SourceFile, @@ -34,7 +33,7 @@ const fixName = "invalidImportSyntax"; function getCodeFixesForImportDeclaration(context: CodeFixContext, node: ImportDeclaration): CodeFixAction[] { const sourceFile = getSourceFileOfNode(node); - const namespace = getNamespaceDeclarationNode(node) as NamespaceImport; + const namespace = getNamespaceDeclarationNode(node)!; const opts = context.program.getCompilerOptions(); const variations: CodeFixAction[] = [];