Skip to content

Commit 8d62e2f

Browse files
evanwrbuckton
andauthored
Implement "Arbitrary Module Namespace Identifiers" (microsoft#58640)
Co-authored-by: Ron Buckton <[email protected]>
1 parent 112e860 commit 8d62e2f

File tree

93 files changed

+9016
-186
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+9016
-186
lines changed

src/compiler/binder.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ import {
251251
ModifierFlags,
252252
ModuleBlock,
253253
ModuleDeclaration,
254+
moduleExportNameIsDefault,
254255
Mutable,
255256
NamespaceExportDeclaration,
256257
Node,
@@ -433,6 +434,9 @@ function getModuleInstanceStateWorker(node: Node, visited: Map<number, ModuleIns
433434

434435
function getModuleInstanceStateForAliasTarget(specifier: ExportSpecifier, visited: Map<number, ModuleInstanceState | undefined>) {
435436
const name = specifier.propertyName || specifier.name;
437+
if (name.kind !== SyntaxKind.Identifier) {
438+
return ModuleInstanceState.Instantiated; // Skip for invalid syntax like this: export { "x" }
439+
}
436440
let p: Node | undefined = specifier.parent;
437441
while (p) {
438442
if (isBlock(p) || isModuleBlock(p) || isSourceFile(p)) {
@@ -759,7 +763,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
759763
function declareSymbol(symbolTable: SymbolTable, parent: Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean, isComputedName?: boolean): Symbol {
760764
Debug.assert(isComputedName || !hasDynamicName(node));
761765

762-
const isDefaultExport = hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node) && node.name.escapedText === "default";
766+
const isDefaultExport = hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node) && moduleExportNameIsDefault(node.name);
763767

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

src/compiler/checker.ts

Lines changed: 87 additions & 44 deletions
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8325,5 +8325,9 @@
83258325
"Enum member following a non-literal numeric member must have an initializer when 'isolatedModules' is enabled.": {
83268326
"category": "Error",
83278327
"code": 18056
8328+
},
8329+
"String literal import and export names are not supported when the '--module' flag is set to 'es2015' or 'es2020'.": {
8330+
"category": "Error",
8331+
"code": 18057
83288332
}
83298333
}

src/compiler/factory/nodeFactory.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ import {
309309
ModuleBlock,
310310
ModuleBody,
311311
ModuleDeclaration,
312+
ModuleExportName,
312313
ModuleName,
313314
ModuleReference,
314315
Mutable,
@@ -4842,7 +4843,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
48424843
}
48434844

48444845
// @api
4845-
function createNamespaceExport(name: Identifier): NamespaceExport {
4846+
function createNamespaceExport(name: ModuleExportName): NamespaceExport {
48464847
const node = createBaseDeclaration<NamespaceExport>(SyntaxKind.NamespaceExport);
48474848
node.name = name;
48484849
node.transformFlags |= propagateChildFlags(node.name) |
@@ -4852,7 +4853,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
48524853
}
48534854

48544855
// @api
4855-
function updateNamespaceExport(node: NamespaceExport, name: Identifier) {
4856+
function updateNamespaceExport(node: NamespaceExport, name: ModuleExportName) {
48564857
return node.name !== name
48574858
? update(createNamespaceExport(name), node)
48584859
: node;
@@ -4875,7 +4876,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
48754876
}
48764877

48774878
// @api
4878-
function createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) {
4879+
function createImportSpecifier(isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: Identifier) {
48794880
const node = createBaseDeclaration<ImportSpecifier>(SyntaxKind.ImportSpecifier);
48804881
node.isTypeOnly = isTypeOnly;
48814882
node.propertyName = propertyName;
@@ -4887,7 +4888,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
48874888
}
48884889

48894890
// @api
4890-
function updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) {
4891+
function updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: Identifier) {
48914892
return node.isTypeOnly !== isTypeOnly
48924893
|| node.propertyName !== propertyName
48934894
|| node.name !== name
@@ -4994,7 +4995,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
49944995
}
49954996

49964997
// @api
4997-
function createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier) {
4998+
function createExportSpecifier(isTypeOnly: boolean, propertyName: string | ModuleExportName | undefined, name: string | ModuleExportName) {
49984999
const node = createBaseNode<ExportSpecifier>(SyntaxKind.ExportSpecifier);
49995000
node.isTypeOnly = isTypeOnly;
50005001
node.propertyName = asName(propertyName);
@@ -5008,7 +5009,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
50085009
}
50095010

50105011
// @api
5011-
function updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) {
5012+
function updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: ModuleExportName | undefined, name: ModuleExportName) {
50125013
return node.isTypeOnly !== isTypeOnly
50135014
|| node.propertyName !== propertyName
50145015
|| node.name !== name

src/compiler/factory/nodeTests.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ import {
146146
MissingDeclaration,
147147
ModuleBlock,
148148
ModuleDeclaration,
149+
ModuleExportName,
149150
NamedExports,
150151
NamedImports,
151152
NamedTupleMember,
@@ -898,6 +899,10 @@ export function isExportSpecifier(node: Node): node is ExportSpecifier {
898899
return node.kind === SyntaxKind.ExportSpecifier;
899900
}
900901

902+
export function isModuleExportName(node: Node): node is ModuleExportName {
903+
return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.StringLiteral;
904+
}
905+
901906
export function isMissingDeclaration(node: Node): node is MissingDeclaration {
902907
return node.kind === SyntaxKind.MissingDeclaration;
903908
}

src/compiler/factory/utilities.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,9 @@ export function getLocalNameForExternalImport(factory: NodeFactory, node: Import
802802
const namespaceDeclaration = getNamespaceDeclarationNode(node);
803803
if (namespaceDeclaration && !isDefaultImport(node) && !isExportNamespaceAsDefaultDeclaration(node)) {
804804
const name = namespaceDeclaration.name;
805+
if (name.kind === SyntaxKind.StringLiteral) {
806+
return factory.getGeneratedNameForNode(node);
807+
}
805808
return isGeneratedIdentifier(name) ? name : factory.createIdentifier(getSourceTextOfNodeFromSourceFile(sourceFile, name) || idText(name));
806809
}
807810
if (node.kind === SyntaxKind.ImportDeclaration && node.importClause) {

src/compiler/parser.ts

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ import {
256256
modifiersToFlags,
257257
ModuleBlock,
258258
ModuleDeclaration,
259+
ModuleExportName,
259260
ModuleKind,
260261
Mutable,
261262
NamedExportBindings,
@@ -2906,6 +2907,9 @@ namespace Parser {
29062907
if (token() === SyntaxKind.FromKeyword && lookAhead(nextTokenIsStringLiteral)) {
29072908
return false;
29082909
}
2910+
if (token() === SyntaxKind.StringLiteral) {
2911+
return true; // For "arbitrary module namespace identifiers"
2912+
}
29092913
return tokenIsIdentifierOrKeyword(token());
29102914
case ParsingContext.JsxAttributes:
29112915
return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.OpenBraceToken;
@@ -8504,6 +8508,14 @@ namespace Parser {
85048508
return finishNode(factory.createNamespaceImport(name), pos);
85058509
}
85068510

8511+
function canParseModuleExportName(): boolean {
8512+
return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.StringLiteral;
8513+
}
8514+
8515+
function parseModuleExportName(parseName: () => Identifier): ModuleExportName {
8516+
return token() === SyntaxKind.StringLiteral ? parseLiteralNode() as StringLiteral : parseName();
8517+
}
8518+
85078519
function parseNamedImportsOrExports(kind: SyntaxKind.NamedImports): NamedImports;
85088520
function parseNamedImportsOrExports(kind: SyntaxKind.NamedExports): NamedExports;
85098521
function parseNamedImportsOrExports(kind: SyntaxKind): NamedImportsOrExports {
@@ -8536,18 +8548,18 @@ namespace Parser {
85368548
const pos = getNodePos();
85378549
// ImportSpecifier:
85388550
// BindingIdentifier
8539-
// IdentifierName as BindingIdentifier
8551+
// ModuleExportName as BindingIdentifier
85408552
// ExportSpecifier:
8541-
// IdentifierName
8542-
// IdentifierName as IdentifierName
8553+
// ModuleExportName
8554+
// ModuleExportName as ModuleExportName
85438555
let checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier();
85448556
let checkIdentifierStart = scanner.getTokenStart();
85458557
let checkIdentifierEnd = scanner.getTokenEnd();
85468558
let isTypeOnly = false;
8547-
let propertyName: Identifier | undefined;
8559+
let propertyName: ModuleExportName | undefined;
85488560
let canParseAsKeyword = true;
8549-
let name = parseIdentifierName();
8550-
if (name.escapedText === "type") {
8561+
let name = parseModuleExportName(parseIdentifierName);
8562+
if (name.kind === SyntaxKind.Identifier && name.escapedText === "type") {
85518563
// If the first token of an import specifier is 'type', there are a lot of possibilities,
85528564
// especially if we see 'as' afterwards:
85538565
//
@@ -8561,11 +8573,12 @@ namespace Parser {
85618573
if (token() === SyntaxKind.AsKeyword) {
85628574
// { type as as ...? }
85638575
const secondAs = parseIdentifierName();
8564-
if (tokenIsIdentifierOrKeyword(token())) {
8576+
if (canParseModuleExportName()) {
85658577
// { type as as something }
8578+
// { type as as "something" }
85668579
isTypeOnly = true;
85678580
propertyName = firstAs;
8568-
name = parseNameWithKeywordCheck();
8581+
name = parseModuleExportName(parseNameWithKeywordCheck);
85698582
canParseAsKeyword = false;
85708583
}
85718584
else {
@@ -8575,35 +8588,44 @@ namespace Parser {
85758588
canParseAsKeyword = false;
85768589
}
85778590
}
8578-
else if (tokenIsIdentifierOrKeyword(token())) {
8591+
else if (canParseModuleExportName()) {
85798592
// { type as something }
8593+
// { type as "something" }
85808594
propertyName = name;
85818595
canParseAsKeyword = false;
8582-
name = parseNameWithKeywordCheck();
8596+
name = parseModuleExportName(parseNameWithKeywordCheck);
85838597
}
85848598
else {
85858599
// { type as }
85868600
isTypeOnly = true;
85878601
name = firstAs;
85888602
}
85898603
}
8590-
else if (tokenIsIdentifierOrKeyword(token())) {
8604+
else if (canParseModuleExportName()) {
85918605
// { type something ...? }
8606+
// { type "something" ...? }
85928607
isTypeOnly = true;
8593-
name = parseNameWithKeywordCheck();
8608+
name = parseModuleExportName(parseNameWithKeywordCheck);
85948609
}
85958610
}
85968611

85978612
if (canParseAsKeyword && token() === SyntaxKind.AsKeyword) {
85988613
propertyName = name;
85998614
parseExpected(SyntaxKind.AsKeyword);
8600-
name = parseNameWithKeywordCheck();
8615+
name = parseModuleExportName(parseNameWithKeywordCheck);
86018616
}
8602-
if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) {
8603-
parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected);
8617+
if (kind === SyntaxKind.ImportSpecifier) {
8618+
if (name.kind !== SyntaxKind.Identifier) {
8619+
// ImportSpecifier casts "name" to Identifier below, so make sure it's an identifier
8620+
parseErrorAt(skipTrivia(sourceText, name.pos), name.end, Diagnostics.Identifier_expected);
8621+
name = setTextRangePosEnd(createMissingNode<Identifier>(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false), name.pos, name.pos);
8622+
}
8623+
else if (checkIdentifierIsKeyword) {
8624+
parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected);
8625+
}
86048626
}
86058627
const node = kind === SyntaxKind.ImportSpecifier
8606-
? factory.createImportSpecifier(isTypeOnly, propertyName, name)
8628+
? factory.createImportSpecifier(isTypeOnly, propertyName, name as Identifier)
86078629
: factory.createExportSpecifier(isTypeOnly, propertyName, name);
86088630
return finishNode(node, pos);
86098631

@@ -8616,7 +8638,7 @@ namespace Parser {
86168638
}
86178639

86188640
function parseNamespaceExport(pos: number): NamespaceExport {
8619-
return finishNode(factory.createNamespaceExport(parseIdentifierName()), pos);
8641+
return finishNode(factory.createNamespaceExport(parseModuleExportName(parseIdentifierName)), pos);
86208642
}
86218643

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

0 commit comments

Comments
 (0)