Skip to content

Commit ab0073c

Browse files
committed
Support arbitrary module namespace identifier names
1 parent 2a37eb2 commit ab0073c

27 files changed

+601
-154
lines changed

src/compiler/binder.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ import {
243243
ModifierFlags,
244244
ModuleBlock,
245245
ModuleDeclaration,
246+
moduleExportNameText,
247+
moduleExportNameTextEscaped,
246248
Mutable,
247249
NamespaceExportDeclaration,
248250
Node,
@@ -432,7 +434,7 @@ function getModuleInstanceStateForAliasTarget(specifier: ExportSpecifier, visite
432434
const statements = p.statements;
433435
let found: ModuleInstanceState | undefined;
434436
for (const statement of statements) {
435-
if (nodeHasName(statement, name)) {
437+
if (nodeHasName(statement, moduleExportNameText(name))) {
436438
if (!statement.parent) {
437439
setParent(statement, p);
438440
setParentRecursive(statement, /*incremental*/ false);
@@ -738,7 +740,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
738740
function declareSymbol(symbolTable: SymbolTable, parent: Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean, isComputedName?: boolean): Symbol {
739741
Debug.assert(isComputedName || !hasDynamicName(node));
740742

741-
const isDefaultExport = hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node) && node.name.escapedText === "default";
743+
const isDefaultExport = hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node) && moduleExportNameTextEscaped(node.name) === "default";
742744

743745
// The exported symbol for an export default function/class node is always named "default"
744746
const name = isComputedName ? InternalSymbolName.Computed

src/compiler/checker.ts

Lines changed: 101 additions & 47 deletions
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,10 @@
16011601
"category": "Error",
16021602
"code": 1490
16031603
},
1604+
"String literal module export names are not allowed when the 'module' option is set to 'es2020' or lower.": {
1605+
"category": "Error",
1606+
"code": 1491
1607+
},
16041608

16051609
"The types of '{0}' are incompatible between these types.": {
16061610
"category": "Error",
@@ -1659,6 +1663,10 @@
16591663
"category": "Message",
16601664
"code": 2212
16611665
},
1666+
"String literal module export names must be followed by a 'from' clause.": {
1667+
"category": "Error",
1668+
"code": 2213
1669+
},
16621670

16631671
"Duplicate identifier '{0}'.": {
16641672
"category": "Error",

src/compiler/factory/nodeFactory.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ import {
175175
isHoistedFunction,
176176
isHoistedVariableStatement,
177177
isIdentifier,
178+
isIdentifierText,
178179
isImportDeclaration,
179180
isImportEqualsDeclaration,
180181
isImportKeyword,
@@ -313,6 +314,7 @@ import {
313314
ModuleBlock,
314315
ModuleBody,
315316
ModuleDeclaration,
317+
ModuleExportName,
316318
ModuleKind,
317319
ModuleName,
318320
ModuleReference,
@@ -983,6 +985,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
983985
get createLogicalNot() { return getPrefixUnaryCreateFunction(SyntaxKind.ExclamationToken); },
984986
get createPostfixIncrement() { return getPostfixUnaryCreateFunction(SyntaxKind.PlusPlusToken); },
985987
get createPostfixDecrement() { return getPostfixUnaryCreateFunction(SyntaxKind.MinusMinusToken); },
988+
createModuleExportName,
986989

987990
// Compound nodes
988991
createImmediatelyInvokedFunctionExpression,
@@ -4713,7 +4716,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
47134716
}
47144717

47154718
// @api
4716-
function createNamespaceExport(name: Identifier): NamespaceExport {
4719+
function createNamespaceExport(name: ModuleExportName): NamespaceExport {
47174720
const node = createBaseDeclaration<NamespaceExport>(SyntaxKind.NamespaceExport);
47184721
node.name = name;
47194722
node.transformFlags |=
@@ -4724,7 +4727,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
47244727
}
47254728

47264729
// @api
4727-
function updateNamespaceExport(node: NamespaceExport, name: Identifier) {
4730+
function updateNamespaceExport(node: NamespaceExport, name: ModuleExportName) {
47284731
return node.name !== name
47294732
? update(createNamespaceExport(name), node)
47304733
: node;
@@ -4747,7 +4750,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
47474750
}
47484751

47494752
// @api
4750-
function createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) {
4753+
function createImportSpecifier(isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: Identifier) {
47514754
const node = createBaseDeclaration<ImportSpecifier>(SyntaxKind.ImportSpecifier);
47524755
node.isTypeOnly = isTypeOnly;
47534756
node.propertyName = propertyName;
@@ -4760,7 +4763,15 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
47604763
}
47614764

47624765
// @api
4763-
function updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) {
4766+
function createModuleExportName(name: string, languageVersion: ScriptTarget): ModuleExportName;
4767+
function createModuleExportName(name: string | undefined, languageVersion: ScriptTarget): ModuleExportName | undefined;
4768+
function createModuleExportName(name: string | undefined, languageVersion: ScriptTarget): ModuleExportName | undefined {
4769+
if (name === undefined) return undefined;
4770+
return isIdentifierText(name, languageVersion) ? createIdentifier(name) : createStringLiteral(name);
4771+
}
4772+
4773+
// @api
4774+
function updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: Identifier) {
47644775
return node.isTypeOnly !== isTypeOnly
47654776
|| node.propertyName !== propertyName
47664777
|| node.name !== name
@@ -4868,11 +4879,11 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
48684879
}
48694880

48704881
// @api
4871-
function createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier) {
4882+
function createExportSpecifier(isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: ModuleExportName) {
48724883
const node = createBaseNode<ExportSpecifier>(SyntaxKind.ExportSpecifier);
48734884
node.isTypeOnly = isTypeOnly;
4874-
node.propertyName = asName(propertyName);
4875-
node.name = asName(name);
4885+
node.propertyName = propertyName;
4886+
node.name = name;
48764887
node.transformFlags |=
48774888
propagateChildFlags(node.propertyName) |
48784889
propagateChildFlags(node.name);
@@ -4883,7 +4894,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
48834894
}
48844895

48854896
// @api
4886-
function updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) {
4897+
function updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: ModuleExportName) {
48874898
return node.isTypeOnly !== isTypeOnly
48884899
|| node.propertyName !== propertyName
48894900
|| node.name !== name

src/compiler/factory/nodeTests.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ import {
143143
MissingDeclaration,
144144
ModuleBlock,
145145
ModuleDeclaration,
146+
ModuleExportName,
146147
NamedExports,
147148
NamedImports,
148149
NamedTupleMember,
@@ -321,6 +322,10 @@ export function isPrivateIdentifier(node: Node): node is PrivateIdentifier {
321322
return node.kind === SyntaxKind.PrivateIdentifier;
322323
}
323324

325+
export function isModuleExportName(node: Node): node is ModuleExportName {
326+
return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.StringLiteral;
327+
}
328+
324329
// Reserved Words
325330

326331
/** @internal */

src/compiler/factory/utilities.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -792,15 +792,19 @@ export function getOrCreateExternalHelpersModuleNameIfNeeded(factory: NodeFactor
792792
}
793793

794794
/**
795-
* Get the name of that target module from an import or export declaration
795+
* Get the name of that target module from an import or export declaration.
796+
*
797+
* This is only used in AMD and SystemJS emit.
796798
*
797799
* @internal
798800
*/
799801
export function getLocalNameForExternalImport(factory: NodeFactory, node: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration, sourceFile: SourceFile): Identifier | undefined {
800802
const namespaceDeclaration = getNamespaceDeclarationNode(node);
801803
if (namespaceDeclaration && !isDefaultImport(node) && !isExportNamespaceAsDefaultDeclaration(node)) {
802804
const name = namespaceDeclaration.name;
803-
return isGeneratedIdentifier(name) ? name : factory.createIdentifier(getSourceTextOfNodeFromSourceFile(sourceFile, name) || idText(name));
805+
if (isGeneratedIdentifier(name)) return name;
806+
if (name.kind === SyntaxKind.StringLiteral) return factory.getGeneratedNameForNode(name);
807+
return factory.createIdentifier(getSourceTextOfNodeFromSourceFile(sourceFile, name) || idText(name));
804808
}
805809
if (node.kind === SyntaxKind.ImportDeclaration && node.importClause) {
806810
return factory.getGeneratedNameForNode(node);

src/compiler/parser.ts

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ import {
253253
modifiersToFlags,
254254
ModuleBlock,
255255
ModuleDeclaration,
256+
ModuleExportName,
256257
ModuleKind,
257258
Mutable,
258259
NamedExportBindings,
@@ -2869,7 +2870,7 @@ namespace Parser {
28692870
case ParsingContext.HeritageClauses:
28702871
return isHeritageClause();
28712872
case ParsingContext.ImportOrExportSpecifiers:
2872-
return tokenIsIdentifierOrKeyword(token());
2873+
return token() === SyntaxKind.StringLiteral || tokenIsIdentifierOrKeyword(token());
28732874
case ParsingContext.JsxAttributes:
28742875
return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.OpenBraceToken;
28752876
case ParsingContext.JsxChildren:
@@ -8347,27 +8348,32 @@ namespace Parser {
83478348

83488349
function parseImportOrExportSpecifier(kind: SyntaxKind): ImportOrExportSpecifier {
83498350
const pos = getNodePos();
8351+
// ModuleExportName:
8352+
// Identifier
8353+
// StringLiteral
83508354
// ImportSpecifier:
83518355
// BindingIdentifier
8352-
// IdentifierName as BindingIdentifier
8356+
// ModuleExportName as BindingIdentifier
83538357
// ExportSpecifier:
8354-
// IdentifierName
8355-
// IdentifierName as IdentifierName
8358+
// ModuleExportName
8359+
// ModuleExportName as ModuleExportName
83568360
let checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier();
83578361
let checkIdentifierStart = scanner.getTokenStart();
83588362
let checkIdentifierEnd = scanner.getTokenEnd();
83598363
let isTypeOnly = false;
8360-
let propertyName: Identifier | undefined;
8364+
let propertyName: ModuleExportName | undefined;
83618365
let canParseAsKeyword = true;
8362-
let name = parseIdentifierName();
8363-
if (name.escapedText === "type") {
8366+
let mustParseAsKeyword = false;
8367+
let name = parseModuleExportName(parseIdentifierName);
8368+
if (name.kind === SyntaxKind.Identifier && name.escapedText === "type") {
83648369
// If the first token of an import specifier is 'type', there are a lot of possibilities,
83658370
// especially if we see 'as' afterwards:
83668371
//
83678372
// import { type } from "mod"; - isTypeOnly: false, name: type
83688373
// import { type as } from "mod"; - isTypeOnly: true, name: as
83698374
// import { type as as } from "mod"; - isTypeOnly: false, name: as, propertyName: type
83708375
// import { type as as as } from "mod"; - isTypeOnly: true, name: as, propertyName: as
8376+
// export { type as as "s" } from "mod";- isTypeOnly: true, name: "s", propertyName: as
83718377
if (token() === SyntaxKind.AsKeyword) {
83728378
// { type as ...? }
83738379
const firstAs = parseIdentifierName();
@@ -8376,9 +8382,10 @@ namespace Parser {
83768382
const secondAs = parseIdentifierName();
83778383
if (tokenIsIdentifierOrKeyword(token())) {
83788384
// { type as as something }
8385+
// { type as as "something" } (only in exports)
83798386
isTypeOnly = true;
83808387
propertyName = firstAs;
8381-
name = parseNameWithKeywordCheck();
8388+
name = parseModuleExportNameOnlyForExports();
83828389
canParseAsKeyword = false;
83838390
}
83848391
else {
@@ -8392,31 +8399,43 @@ namespace Parser {
83928399
// { type as something }
83938400
propertyName = name;
83948401
canParseAsKeyword = false;
8395-
name = parseNameWithKeywordCheck();
8402+
name = parseModuleExportNameOnlyForExports();
83968403
}
83978404
else {
83988405
// { type as }
83998406
isTypeOnly = true;
84008407
name = firstAs;
84018408
}
84028409
}
8410+
// export { type "x" }
8411+
// import { type "x" as ... }
8412+
else if (token() === SyntaxKind.StringLiteral) {
8413+
isTypeOnly = true;
8414+
if (kind === SyntaxKind.ImportSpecifier) mustParseAsKeyword = true;
8415+
name = parseModuleExportName(parseNameWithKeywordCheck);
8416+
}
84038417
else if (tokenIsIdentifierOrKeyword(token())) {
84048418
// { type something ...? }
84058419
isTypeOnly = true;
84068420
name = parseNameWithKeywordCheck();
84078421
}
84088422
}
8423+
// import { "x" as ... }
8424+
else if (kind === SyntaxKind.ImportSpecifier && name.kind === SyntaxKind.StringLiteral) {
8425+
mustParseAsKeyword = true;
8426+
}
84098427

8410-
if (canParseAsKeyword && token() === SyntaxKind.AsKeyword) {
8428+
if (mustParseAsKeyword || (canParseAsKeyword && token() === SyntaxKind.AsKeyword)) {
84118429
propertyName = name;
84128430
parseExpected(SyntaxKind.AsKeyword);
8413-
name = parseNameWithKeywordCheck();
8431+
name = parseModuleExportNameOnlyForExports();
84148432
}
84158433
if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) {
84168434
parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected);
84178435
}
8436+
if (kind === SyntaxKind.ImportSpecifier) Debug.assert(name.kind === SyntaxKind.Identifier);
84188437
const node = kind === SyntaxKind.ImportSpecifier
8419-
? factory.createImportSpecifier(isTypeOnly, propertyName, name)
8438+
? factory.createImportSpecifier(isTypeOnly, propertyName, name as Identifier)
84208439
: factory.createExportSpecifier(isTypeOnly, propertyName, name);
84218440
return finishNode(node, pos);
84228441

@@ -8426,10 +8445,22 @@ namespace Parser {
84268445
checkIdentifierEnd = scanner.getTokenEnd();
84278446
return parseIdentifierName();
84288447
}
8448+
function parseModuleExportNameOnlyForExports() {
8449+
if (kind === SyntaxKind.ImportSpecifier) return parseNameWithKeywordCheck();
8450+
return parseModuleExportName(parseNameWithKeywordCheck);
8451+
}
8452+
function parseModuleExportName(parser: () => Identifier): ModuleExportName {
8453+
if (token() === SyntaxKind.StringLiteral) return parseStringLiteral();
8454+
return parser();
8455+
}
8456+
function parseStringLiteral(): StringLiteral {
8457+
// TODO: the spec requires it pass IsStringWellFormedUnicode
8458+
return parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral;
8459+
}
84298460
}
84308461

84318462
function parseNamespaceExport(pos: number): NamespaceExport {
8432-
return finishNode(factory.createNamespaceExport(parseIdentifierName()), pos);
8463+
return finishNode(factory.createNamespaceExport(token() === SyntaxKind.StringLiteral ? parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral : parseIdentifierName()), pos);
84338464
}
84348465

84358466
function parseExportDeclaration(pos: number, hasJSDoc: boolean, modifiers: NodeArray<ModifierLike> | undefined): ExportDeclaration {

src/compiler/transformers/declarations.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import {
5959
getDirectoryPath,
6060
getEffectiveBaseTypeNode,
6161
getEffectiveModifierFlags,
62+
getEmitScriptTarget,
6263
getExternalModuleImportEqualsDeclarationExpression,
6364
getExternalModuleNameFromDeclaration,
6465
getFirstConstructorWithBody,
@@ -1520,7 +1521,7 @@ export function transformDeclarations(context: TransformationContext) {
15201521
/*modifiers*/ undefined,
15211522
/*isTypeOnly*/ false,
15221523
factory.createNamedExports(map(exportMappings, ([gen, exp]) => {
1523-
return factory.createExportSpecifier(/*isTypeOnly*/ false, gen, exp);
1524+
return factory.createExportSpecifier(/*isTypeOnly*/ false, gen, factory.createModuleExportName(exp, getEmitScriptTarget(context.getCompilerOptions())));
15241525
}))
15251526
));
15261527
}

0 commit comments

Comments
 (0)