Skip to content

Commit 2435a73

Browse files
committed
Normative: Arbitrary module namespace identifier names
1 parent 8aa9c3b commit 2435a73

29 files changed

+635
-173
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: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ import {
120120
ImportClause,
121121
ImportDeclaration,
122122
ImportEqualsDeclaration,
123-
ImportOrExportSpecifier,
124123
ImportSpecifier,
125124
ImportTypeAssertionContainer,
126125
ImportTypeNode,
@@ -253,6 +252,7 @@ import {
253252
modifiersToFlags,
254253
ModuleBlock,
255254
ModuleDeclaration,
255+
ModuleExportName,
256256
ModuleKind,
257257
Mutable,
258258
NamedExportBindings,
@@ -2874,7 +2874,7 @@ namespace Parser {
28742874
if (token() === SyntaxKind.FromKeyword && lookAhead(nextTokenIsStringLiteral)) {
28752875
return false;
28762876
}
2877-
return tokenIsIdentifierOrKeyword(token());
2877+
return token() === SyntaxKind.StringLiteral || tokenIsIdentifierOrKeyword(token());
28782878
case ParsingContext.JsxAttributes:
28792879
return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.OpenBraceToken;
28802880
case ParsingContext.JsxChildren:
@@ -8357,29 +8357,34 @@ namespace Parser {
83578357
return parseImportOrExportSpecifier(SyntaxKind.ImportSpecifier) as ImportSpecifier;
83588358
}
83598359

8360-
function parseImportOrExportSpecifier(kind: SyntaxKind): ImportOrExportSpecifier {
8360+
function parseImportOrExportSpecifier(kind: SyntaxKind) {
83618361
const pos = getNodePos();
8362+
// ModuleExportName:
8363+
// Identifier
8364+
// StringLiteral
83628365
// ImportSpecifier:
83638366
// BindingIdentifier
8364-
// IdentifierName as BindingIdentifier
8367+
// ModuleExportName as BindingIdentifier
83658368
// ExportSpecifier:
8366-
// IdentifierName
8367-
// IdentifierName as IdentifierName
8369+
// ModuleExportName
8370+
// ModuleExportName as ModuleExportName
83688371
let checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier();
83698372
let checkIdentifierStart = scanner.getTokenStart();
83708373
let checkIdentifierEnd = scanner.getTokenEnd();
83718374
let isTypeOnly = false;
8372-
let propertyName: Identifier | undefined;
8375+
let propertyName: ModuleExportName | undefined;
83738376
let canParseAsKeyword = true;
8374-
let name = parseIdentifierName();
8375-
if (name.escapedText === "type") {
8377+
let mustParseAsKeyword = false;
8378+
let name = parseModuleExportName(parseIdentifierName);
8379+
if (name.kind === SyntaxKind.Identifier && name.escapedText === "type") {
83768380
// If the first token of an import specifier is 'type', there are a lot of possibilities,
83778381
// especially if we see 'as' afterwards:
83788382
//
83798383
// import { type } from "mod"; - isTypeOnly: false, name: type
83808384
// import { type as } from "mod"; - isTypeOnly: true, name: as
83818385
// import { type as as } from "mod"; - isTypeOnly: false, name: as, propertyName: type
83828386
// import { type as as as } from "mod"; - isTypeOnly: true, name: as, propertyName: as
8387+
// export { type as as "s" } from "mod";- isTypeOnly: true, name: "s", propertyName: as
83838388
if (token() === SyntaxKind.AsKeyword) {
83848389
// { type as ...? }
83858390
const firstAs = parseIdentifierName();
@@ -8388,9 +8393,10 @@ namespace Parser {
83888393
const secondAs = parseIdentifierName();
83898394
if (tokenIsIdentifierOrKeyword(token())) {
83908395
// { type as as something }
8396+
// { type as as "something" } (only in exports)
83918397
isTypeOnly = true;
83928398
propertyName = firstAs;
8393-
name = parseNameWithKeywordCheck();
8399+
name = parseModuleExportNameOnlyForExports();
83948400
canParseAsKeyword = false;
83958401
}
83968402
else {
@@ -8404,31 +8410,43 @@ namespace Parser {
84048410
// { type as something }
84058411
propertyName = name;
84068412
canParseAsKeyword = false;
8407-
name = parseNameWithKeywordCheck();
8413+
name = parseModuleExportNameOnlyForExports();
84088414
}
84098415
else {
84108416
// { type as }
84118417
isTypeOnly = true;
84128418
name = firstAs;
84138419
}
84148420
}
8421+
// export { type "x" }
8422+
// import { type "x" as ... }
8423+
else if (token() === SyntaxKind.StringLiteral) {
8424+
isTypeOnly = true;
8425+
if (kind === SyntaxKind.ImportSpecifier) mustParseAsKeyword = true;
8426+
name = parseModuleExportName(parseNameWithKeywordCheck);
8427+
}
84158428
else if (tokenIsIdentifierOrKeyword(token())) {
84168429
// { type something ...? }
84178430
isTypeOnly = true;
84188431
name = parseNameWithKeywordCheck();
84198432
}
84208433
}
8434+
// import { "x" as ... }
8435+
else if (kind === SyntaxKind.ImportSpecifier && name.kind === SyntaxKind.StringLiteral) {
8436+
mustParseAsKeyword = true;
8437+
}
84218438

8422-
if (canParseAsKeyword && token() === SyntaxKind.AsKeyword) {
8439+
if (mustParseAsKeyword || (canParseAsKeyword && token() === SyntaxKind.AsKeyword)) {
84238440
propertyName = name;
84248441
parseExpected(SyntaxKind.AsKeyword);
8425-
name = parseNameWithKeywordCheck();
8442+
name = parseModuleExportNameOnlyForExports();
84268443
}
84278444
if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) {
84288445
parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected);
84298446
}
8447+
if (kind === SyntaxKind.ImportSpecifier) Debug.assert(name.kind === SyntaxKind.Identifier);
84308448
const node = kind === SyntaxKind.ImportSpecifier
8431-
? factory.createImportSpecifier(isTypeOnly, propertyName, name)
8449+
? factory.createImportSpecifier(isTypeOnly, propertyName, name as Identifier)
84328450
: factory.createExportSpecifier(isTypeOnly, propertyName, name);
84338451
return finishNode(node, pos);
84348452

@@ -8438,10 +8456,22 @@ namespace Parser {
84388456
checkIdentifierEnd = scanner.getTokenEnd();
84398457
return parseIdentifierName();
84408458
}
8459+
function parseModuleExportNameOnlyForExports() {
8460+
if (kind === SyntaxKind.ImportSpecifier) return parseNameWithKeywordCheck();
8461+
return parseModuleExportName(parseNameWithKeywordCheck);
8462+
}
8463+
function parseModuleExportName(parser: () => Identifier): ModuleExportName {
8464+
if (token() === SyntaxKind.StringLiteral) return parseStringLiteral();
8465+
return parser();
8466+
}
8467+
function parseStringLiteral(): StringLiteral {
8468+
// TODO: the spec requires it pass IsStringWellFormedUnicode
8469+
return parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral;
8470+
}
84418471
}
84428472

84438473
function parseNamespaceExport(pos: number): NamespaceExport {
8444-
return finishNode(factory.createNamespaceExport(parseIdentifierName()), pos);
8474+
return finishNode(factory.createNamespaceExport(token() === SyntaxKind.StringLiteral ? parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral : parseIdentifierName()), pos);
84458475
}
84468476

84478477
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)