Skip to content

Commit 27c4aae

Browse files
committed
initial support for Normative: Arbitrary module namespace identifier names
1 parent 80832a8 commit 27c4aae

File tree

18 files changed

+204
-114
lines changed

18 files changed

+204
-114
lines changed

src/compiler/binder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ namespace ts {
115115
const statements = p.statements;
116116
let found: ModuleInstanceState | undefined;
117117
for (const statement of statements) {
118-
if (nodeHasName(statement, name)) {
118+
if (nodeHasName(statement, moduleExportNameText(name))) {
119119
if (!statement.parent) {
120120
setParent(statement, p);
121121
setParentRecursive(statement, /*incremental*/ false);
@@ -413,7 +413,7 @@ namespace ts {
413413
function declareSymbol(symbolTable: SymbolTable, parent: Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean, isComputedName?: boolean): Symbol {
414414
Debug.assert(isComputedName || !hasDynamicName(node));
415415

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

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

src/compiler/checker.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1826,8 +1826,8 @@ namespace ts {
18261826
nameArg: __String | Identifier | undefined,
18271827
isUse: boolean,
18281828
excludeGlobals = false,
1829-
getSpellingSuggstions = true): Symbol | undefined {
1830-
return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSpellingSuggstions, getSymbol);
1829+
getSpellingSuggestions = true): Symbol | undefined {
1830+
return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSpellingSuggestions, getSymbol);
18311831
}
18321832

18331833
function resolveNameHelper(
@@ -2683,7 +2683,7 @@ namespace ts {
26832683
? Diagnostics._0_was_exported_here
26842684
: Diagnostics._0_was_imported_here;
26852685

2686-
const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name.escapedText);
2686+
const name = moduleExportNameText(typeOnlyDeclaration.name);
26872687
addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name));
26882688
}
26892689
}
@@ -3305,7 +3305,7 @@ namespace ts {
33053305
/**
33063306
* Resolves a qualified name and any involved aliases.
33073307
*/
3308-
function resolveEntityName(name: EntityNameOrEntityNameExpression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean, location?: Node): Symbol | undefined {
3308+
function resolveEntityName(name: EntityNameOrEntityNameExpression | ModuleExportName, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean, location?: Node): Symbol | undefined {
33093309
if (nodeIsMissing(name)) {
33103310
return undefined;
33113311
}
@@ -3390,6 +3390,11 @@ namespace ts {
33903390
return undefined;
33913391
}
33923392
}
3393+
else if (name.kind === SyntaxKind.StringLiteral) {
3394+
const message = Diagnostics.Cannot_find_name_0;
3395+
symbol = getMergedSymbol(resolveName(location || name, moduleExportNameTextEscaped(name), meaning, ignoreErrors ? undefined : message, moduleExportNameTextEscaped(name), /*isUse*/ true, /** excludeGlobals */true));
3396+
if (!symbol) return undefined;
3397+
}
33933398
else {
33943399
throw Debug.assertNever(name, "Unknown entity name kind.");
33953400
}
@@ -6952,7 +6957,7 @@ namespace ts {
69526957
if (!e.propertyName) {
69536958
// export {name} - look thru `statements` for `name`, and if all results can take an `export` modifier, do so and filter it
69546959
const indices = indicesOf(statements);
6955-
const associatedIndices = filter(indices, i => nodeHasName(statements[i], e.name));
6960+
const associatedIndices = filter(indices, i => nodeHasName(statements[i], moduleExportNameText(e.name)));
69566961
if (length(associatedIndices) && every(associatedIndices, i => canHaveExportModifier(statements[i]))) {
69576962
for (const index of associatedIndices) {
69586963
statements[index] = addExportModifier(statements[index] as Extract<HasModifiers, Statement>);
@@ -7615,7 +7620,7 @@ namespace ts {
76157620
function getSomeTargetNameFromDeclarations(declarations: Declaration[] | undefined) {
76167621
return firstDefined(declarations, d => {
76177622
if (isImportSpecifier(d) || isExportSpecifier(d)) {
7618-
return idText(d.propertyName || d.name);
7623+
return moduleExportNameText(d.propertyName || d.name);
76197624
}
76207625
if (isBinaryExpression(d) || isExportAssignment(d)) {
76217626
const expression = isExportAssignment(d) ? d.expression : d.right;
@@ -8513,10 +8518,10 @@ namespace ts {
85138518
}
85148519
}
85158520

8516-
function collectLinkedAliases(node: Identifier, setVisibility?: boolean): Node[] | undefined {
8521+
function collectLinkedAliases(node: ModuleExportName, setVisibility?: boolean): Node[] | undefined {
85178522
let exportSymbol: Symbol | undefined;
85188523
if (node.parent && node.parent.kind === SyntaxKind.ExportAssignment) {
8519-
exportSymbol = resolveName(node, node.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, node, /*isUse*/ false);
8524+
exportSymbol = resolveName(node, moduleExportNameTextEscaped(node), SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, moduleExportNameTextEscaped(node), /*isUse*/ false);
85208525
}
85218526
else if (node.parent.kind === SyntaxKind.ExportSpecifier) {
85228527
exportSymbol = getTargetOfExportSpecifier(node.parent as ExportSpecifier, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias);
@@ -40863,7 +40868,7 @@ namespace ts {
4086340868
const message = isType
4086440869
? Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled
4086540870
: Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled;
40866-
const name = idText(node.kind === SyntaxKind.ImportSpecifier ? node.propertyName || node.name : node.name);
40871+
const name = moduleExportNameText(node.kind === SyntaxKind.ImportSpecifier ? node.propertyName || node.name : node.name);
4086740872
addTypeOnlyDeclarationRelatedInfo(
4086840873
error(node, message, name),
4086940874
isType ? undefined : typeOnlyAlias,
@@ -40883,7 +40888,7 @@ namespace ts {
4088340888
const message = isType
4088440889
? Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type
4088540890
: Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_re_exported_using_a_type_only_re_export_when_isolatedModules_is_enabled;
40886-
const name = idText(node.propertyName || node.name);
40891+
const name = moduleExportNameText(node.propertyName || node.name);
4088740892
addTypeOnlyDeclarationRelatedInfo(
4088840893
error(node, message, name),
4088940894
isType ? undefined : typeOnlyAlias,
@@ -40941,7 +40946,7 @@ namespace ts {
4094140946
checkCollisionsForDeclarationName(node, node.name);
4094240947
checkAliasSymbol(node);
4094340948
if (node.kind === SyntaxKind.ImportSpecifier &&
40944-
idText(node.propertyName || node.name) === "default" &&
40949+
moduleExportNameTextEscaped(node.propertyName || node.name) === "default" &&
4094540950
getESModuleInterop(compilerOptions) &&
4094640951
moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) {
4094740952
checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault);
@@ -41178,10 +41183,10 @@ namespace ts {
4117841183
if (!node.parent.parent.moduleSpecifier) {
4117941184
const exportedName = node.propertyName || node.name;
4118041185
// find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases)
41181-
const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias,
41186+
const symbol = resolveName(exportedName, moduleExportNameTextEscaped(exportedName), SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias,
4118241187
/*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true);
4118341188
if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) {
41184-
error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName));
41189+
error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, moduleExportNameText(exportedName));
4118541190
}
4118641191
else {
4118741192
markExportAsReferenced(node);
@@ -41195,7 +41200,7 @@ namespace ts {
4119541200
if (getESModuleInterop(compilerOptions) &&
4119641201
moduleKind !== ModuleKind.System &&
4119741202
(moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) &&
41198-
idText(node.propertyName || node.name) === "default") {
41203+
moduleExportNameTextEscaped(node.propertyName || node.name) === "default") {
4119941204
checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault);
4120041205
}
4120141206
}

src/compiler/factory/nodeTests.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,11 @@ namespace ts {
637637
return node.kind === SyntaxKind.ExportSpecifier;
638638
}
639639

640+
/** @internal */
641+
export function isModuleExportName(node: Node): node is ModuleExportName {
642+
return isIdentifier(node) || isStringLiteral(node);
643+
}
644+
640645
export function isMissingDeclaration(node: Node): node is MissingDeclaration {
641646
return node.kind === SyntaxKind.MissingDeclaration;
642647
}

src/compiler/parser.ts

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2209,7 +2209,7 @@ namespace ts {
22092209
case ParsingContext.HeritageClauses:
22102210
return isHeritageClause();
22112211
case ParsingContext.ImportOrExportSpecifiers:
2212-
return tokenIsIdentifierOrKeyword(token());
2212+
return token() === SyntaxKind.StringLiteral || tokenIsIdentifierOrKeyword(token());
22132213
case ParsingContext.JsxAttributes:
22142214
return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.OpenBraceToken;
22152215
case ParsingContext.JsxChildren:
@@ -7540,36 +7540,44 @@ namespace ts {
75407540

75417541
function parseExportSpecifier() {
75427542
const hasJSDoc = hasPrecedingJSDocComment();
7543-
return withJSDoc(parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier) as ExportSpecifier, hasJSDoc);
7543+
return withJSDoc(parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier), hasJSDoc);
75447544
}
75457545

75467546
function parseImportSpecifier() {
7547-
return parseImportOrExportSpecifier(SyntaxKind.ImportSpecifier) as ImportSpecifier;
7547+
return parseImportOrExportSpecifier(SyntaxKind.ImportSpecifier);
75487548
}
75497549

7550-
function parseImportOrExportSpecifier(kind: SyntaxKind): ImportOrExportSpecifier {
7550+
function parseImportOrExportSpecifier(kind: SyntaxKind.ImportSpecifier): ImportSpecifier;
7551+
function parseImportOrExportSpecifier(kind: SyntaxKind.ExportSpecifier): ExportSpecifier;
7552+
function parseImportOrExportSpecifier(kind: SyntaxKind.ImportSpecifier | SyntaxKind.ExportSpecifier): ImportOrExportSpecifier {
75517553
const pos = getNodePos();
7554+
// ModuleExportName:
7555+
// Identifier
7556+
// StringLiteral
75527557
// ImportSpecifier:
75537558
// BindingIdentifier
7554-
// IdentifierName as BindingIdentifier
7559+
// ModuleExportName as BindingIdentifier
75557560
// ExportSpecifier:
7556-
// IdentifierName
7557-
// IdentifierName as IdentifierName
7561+
// ModuleExportName
7562+
// ModuleExportName as ModuleExportName
75587563
let checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier();
75597564
let checkIdentifierStart = scanner.getTokenPos();
75607565
let checkIdentifierEnd = scanner.getTextPos();
75617566
let isTypeOnly = false;
7562-
let propertyName: Identifier | undefined;
7567+
let propertyName: ModuleExportName | undefined;
75637568
let canParseAsKeyword = true;
7564-
let name = parseIdentifierName();
7565-
if (name.escapedText === "type") {
7569+
let mustParseAsKeyword = false;
7570+
let name = parseModuleExportName(parseIdentifierName);
7571+
7572+
if (name.kind === SyntaxKind.Identifier && name.escapedText === "type") {
75667573
// If the first token of an import specifier is 'type', there are a lot of possibilities,
75677574
// especially if we see 'as' afterwards:
75687575
//
75697576
// import { type } from "mod"; - isTypeOnly: false, name: type
75707577
// import { type as } from "mod"; - isTypeOnly: true, name: as
75717578
// import { type as as } from "mod"; - isTypeOnly: false, name: as, propertyName: type
75727579
// import { type as as as } from "mod"; - isTypeOnly: true, name: as, propertyName: as
7580+
// export { type as as "s" } from "mod";- isTypeOnly: true, name: "s", propertyName: as
75737581
if (token() === SyntaxKind.AsKeyword) {
75747582
// { type as ...? }
75757583
const firstAs = parseIdentifierName();
@@ -7578,9 +7586,10 @@ namespace ts {
75787586
const secondAs = parseIdentifierName();
75797587
if (tokenIsIdentifierOrKeyword(token())) {
75807588
// { type as as something }
7589+
// { type as as "something" } (only in exports)
75817590
isTypeOnly = true;
75827591
propertyName = firstAs;
7583-
name = parseNameWithKeywordCheck();
7592+
name = parseModuleExportNameOnlyForExports();
75847593
canParseAsKeyword = false;
75857594
}
75867595
else {
@@ -7594,31 +7603,42 @@ namespace ts {
75947603
// { type as something }
75957604
propertyName = name;
75967605
canParseAsKeyword = false;
7597-
name = parseNameWithKeywordCheck();
7606+
name = parseModuleExportNameOnlyForExports();
75987607
}
75997608
else {
76007609
// { type as }
76017610
isTypeOnly = true;
76027611
name = firstAs;
76037612
}
76047613
}
7614+
// import { type "x" as y } from "mod";
7615+
else if (token() === SyntaxKind.StringLiteral) {
7616+
// { type "x" ...? }
7617+
isTypeOnly = true;
7618+
if (kind === SyntaxKind.ImportSpecifier) mustParseAsKeyword = true;
7619+
name = parseModuleExportName(parseNameWithKeywordCheck);
7620+
}
76057621
else if (tokenIsIdentifierOrKeyword(token())) {
76067622
// { type something ...? }
76077623
isTypeOnly = true;
76087624
name = parseNameWithKeywordCheck();
76097625
}
76107626
}
7627+
else if (kind === SyntaxKind.ImportSpecifier && name.kind === SyntaxKind.StringLiteral) {
7628+
mustParseAsKeyword = true;
7629+
}
76117630

7612-
if (canParseAsKeyword && token() === SyntaxKind.AsKeyword) {
7631+
if ((canParseAsKeyword && token() === SyntaxKind.AsKeyword) || mustParseAsKeyword) {
76137632
propertyName = name;
76147633
parseExpected(SyntaxKind.AsKeyword);
7615-
name = parseNameWithKeywordCheck();
7634+
name = parseModuleExportNameOnlyForExports();
76167635
}
76177636
if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) {
76187637
parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected);
76197638
}
7639+
if (kind === SyntaxKind.ImportSpecifier) Debug.assertNode(name, isIdentifier);
76207640
const node = kind === SyntaxKind.ImportSpecifier
7621-
? factory.createImportSpecifier(isTypeOnly, propertyName, name)
7641+
? factory.createImportSpecifier(isTypeOnly, propertyName, name as Identifier)
76227642
: factory.createExportSpecifier(isTypeOnly, propertyName, name);
76237643
return finishNode(node, pos);
76247644

@@ -7628,6 +7648,19 @@ namespace ts {
76287648
checkIdentifierEnd = scanner.getTextPos();
76297649
return parseIdentifierName();
76307650
}
7651+
7652+
function parseModuleExportNameOnlyForExports() {
7653+
if (kind === SyntaxKind.ImportSpecifier) return parseNameWithKeywordCheck();
7654+
return parseModuleExportName(parseNameWithKeywordCheck);
7655+
}
7656+
function parseModuleExportName(parser: () => Identifier): ModuleExportName {
7657+
if (token() === SyntaxKind.StringLiteral) return parseStringLiteral();
7658+
return parser();
7659+
}
7660+
7661+
function parseStringLiteral(): StringLiteral {
7662+
return parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral;
7663+
}
76317664
}
76327665

76337666
function parseNamespaceExport(pos: number): NamespaceExport {

0 commit comments

Comments
 (0)