Skip to content

Commit b788c09

Browse files
committed
Fix for namespace exports as well and add evaluation tests
1 parent 0bf051b commit b788c09

13 files changed

+369
-112
lines changed

Diff for: src/compiler/transformers/module/module.ts

+42-48
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,6 @@
11
/*@internal*/
22
namespace ts {
33

4-
const enum ImportOrExportBindingReferenceKind {
5-
None,
6-
ImportedHelper,
7-
TopLevelExportBinding,
8-
ImportClause,
9-
ImportSpecifier,
10-
}
11-
12-
type ImportOrExportBindingReferenceResult =
13-
| { kind: ImportOrExportBindingReferenceKind.None, node: undefined }
14-
| { kind: ImportOrExportBindingReferenceKind.ImportedHelper, node: Identifier }
15-
| { kind: ImportOrExportBindingReferenceKind.TopLevelExportBinding, node: undefined }
16-
| { kind: ImportOrExportBindingReferenceKind.ImportClause, node: ImportClause }
17-
| { kind: ImportOrExportBindingReferenceKind.ImportSpecifier, node: ImportSpecifier };
18-
19-
const noReferenceResult: ImportOrExportBindingReferenceResult = { kind: ImportOrExportBindingReferenceKind.None, node: undefined };
20-
const topLevelExportReferenceResult: ImportOrExportBindingReferenceResult = { kind: ImportOrExportBindingReferenceKind.TopLevelExportBinding, node: undefined };
21-
224
export function transformModule(context: TransformationContext) {
235
interface AsynchronousDependencies {
246
aliasedModuleNames: Expression[];
@@ -67,7 +49,7 @@ namespace ts {
6749
let currentModuleInfo: ExternalModuleInfo; // The ExternalModuleInfo for the current file.
6850
let noSubstitution: boolean[]; // Set of nodes for which substitution rules should be ignored.
6951
let needUMDDynamicImportHelper: boolean;
70-
let bindingReferenceCache: ESMap<Node, ImportOrExportBindingReferenceResult> | undefined;
52+
let bindingReferenceCache: ESMap<Node, Identifier | SourceFile | ImportClause | ImportSpecifier | undefined> | undefined;
7153

7254
return chainBundle(context, transformSourceFile);
7355

@@ -1776,49 +1758,61 @@ namespace ts {
17761758
return node;
17771759
}
17781760

1779-
function getImportOrExportBindingReferenceWorker(node: Identifier): ImportOrExportBindingReferenceResult {
1761+
/**
1762+
* For an Identifier, gets the import or export binding that it references.
1763+
* @returns One of the following:
1764+
* - An `Identifier` if node references an external helpers module (i.e., `tslib`).
1765+
* - A `SourceFile` if the node references an export in the file.
1766+
* - An `ImportClause` or `ImportSpecifier` if the node references an import binding.
1767+
* - Otherwise, `undefined`.
1768+
*/
1769+
function getImportOrExportBindingReferenceWorker(node: Identifier): Identifier | SourceFile | ImportClause | ImportSpecifier | undefined {
17801770
if (getEmitFlags(node) & EmitFlags.HelperName) {
17811771
const externalHelpersModuleName = getExternalHelpersModuleName(currentSourceFile);
17821772
if (externalHelpersModuleName) {
1783-
return { kind: ImportOrExportBindingReferenceKind.ImportedHelper, node: externalHelpersModuleName };
1773+
return externalHelpersModuleName;
17841774
}
17851775
}
17861776
else if (!(isGeneratedIdentifier(node) && !(node.autoGenerateFlags & GeneratedIdentifierFlags.AllowNameSubstitution)) && !isLocalName(node)) {
17871777
const exportContainer = resolver.getReferencedExportContainer(node, isExportName(node));
17881778
if (exportContainer?.kind === SyntaxKind.SourceFile) {
1789-
return topLevelExportReferenceResult;
1779+
return exportContainer;
17901780
}
17911781
const importDeclaration = resolver.getReferencedImportDeclaration(node);
1792-
if (importDeclaration) {
1793-
if (isImportClause(importDeclaration)) return { kind: ImportOrExportBindingReferenceKind.ImportClause, node: importDeclaration };
1794-
if (isImportSpecifier(importDeclaration)) return { kind: ImportOrExportBindingReferenceKind.ImportSpecifier, node: importDeclaration };
1782+
if (importDeclaration && (isImportClause(importDeclaration) || isImportSpecifier(importDeclaration))) {
1783+
return importDeclaration;
17951784
}
17961785
}
1797-
return noReferenceResult;
1786+
return undefined;
17981787
}
17991788

1800-
function getImportOrExportBindingReference(node: Identifier, removeEntry: boolean): ImportOrExportBindingReferenceResult {
1801-
bindingReferenceCache ||= new Map();
1802-
let result = bindingReferenceCache.get(node);
1803-
if (!result) {
1789+
/**
1790+
* For an Identifier, gets the import or export binding that it references.
1791+
* @param removeEntry When `false`, the result is cached to avoid recomputing the result in a later substitution.
1792+
* When `true`, any cached result for the node is removed.
1793+
* @returns One of the following:
1794+
* - An `Identifier` if node references an external helpers module (i.e., `tslib`).
1795+
* - A `SourceFile` if the node references an export in the file.
1796+
* - An `ImportClause` or `ImportSpecifier` if the node references an import binding.
1797+
* - Otherwise, `undefined`.
1798+
*/
1799+
function getImportOrExportBindingReference(node: Identifier, removeEntry: boolean): Identifier | SourceFile | ImportClause | ImportSpecifier | undefined {
1800+
let result = bindingReferenceCache?.get(node);
1801+
if (!result && !bindingReferenceCache?.has(node)) {
18041802
result = getImportOrExportBindingReferenceWorker(node);
18051803
if (!removeEntry) {
1806-
switch (result.kind) {
1807-
case ImportOrExportBindingReferenceKind.ImportedHelper:
1808-
case ImportOrExportBindingReferenceKind.ImportClause:
1809-
case ImportOrExportBindingReferenceKind.ImportSpecifier:
1810-
bindingReferenceCache.set(node, result);
1811-
}
1804+
bindingReferenceCache ||= new Map();
1805+
bindingReferenceCache.set(node, result);
18121806
}
18131807
}
18141808
else if (removeEntry) {
1815-
bindingReferenceCache.delete(node);
1809+
bindingReferenceCache?.delete(node);
18161810
}
18171811
return result;
18181812
}
18191813

18201814
function substituteCallExpression(node: CallExpression) {
1821-
if (isIdentifier(node.expression) && getImportOrExportBindingReference(node.expression, /*removeEntry*/ false).kind !== ImportOrExportBindingReferenceKind.None) {
1815+
if (isIdentifier(node.expression) && getImportOrExportBindingReference(node.expression, /*removeEntry*/ false)) {
18221816
return isCallChain(node) ?
18231817
factory.updateCallChain(node,
18241818
setTextRange(factory.createComma(factory.createNumericLiteral(0), node.expression), node.expression),
@@ -1834,7 +1828,7 @@ namespace ts {
18341828
}
18351829

18361830
function substituteTaggedTemplateExpression(node: TaggedTemplateExpression) {
1837-
if (isIdentifier(node.tag) && getImportOrExportBindingReference(node.tag, /*removeEntry*/ false).kind !== ImportOrExportBindingReferenceKind.None) {
1831+
if (isIdentifier(node.tag) && getImportOrExportBindingReference(node.tag, /*removeEntry*/ false)) {
18381832
return factory.updateTaggedTemplateExpression(
18391833
node,
18401834
setTextRange(factory.createComma(factory.createNumericLiteral(0), node.tag), node.tag),
@@ -1852,30 +1846,30 @@ namespace ts {
18521846
*/
18531847
function substituteExpressionIdentifier(node: Identifier): Expression {
18541848
const result = getImportOrExportBindingReference(node, /*removeEntry*/ true);
1855-
switch (result.kind) {
1856-
case ImportOrExportBindingReferenceKind.ImportedHelper:
1857-
return factory.createPropertyAccessExpression(result.node, node);
1858-
case ImportOrExportBindingReferenceKind.TopLevelExportBinding:
1849+
switch (result?.kind) {
1850+
case SyntaxKind.Identifier: // tslib import
1851+
return factory.createPropertyAccessExpression(result, node);
1852+
case SyntaxKind.SourceFile: // top-level export
18591853
return setTextRange(
18601854
factory.createPropertyAccessExpression(
18611855
factory.createIdentifier("exports"),
18621856
factory.cloneNode(node)
18631857
),
18641858
/*location*/ node
18651859
);
1866-
case ImportOrExportBindingReferenceKind.ImportClause:
1860+
case SyntaxKind.ImportClause:
18671861
return setTextRange(
18681862
factory.createPropertyAccessExpression(
1869-
factory.getGeneratedNameForNode(result.node.parent),
1863+
factory.getGeneratedNameForNode(result.parent),
18701864
factory.createIdentifier("default")
18711865
),
18721866
/*location*/ node
18731867
);
1874-
case ImportOrExportBindingReferenceKind.ImportSpecifier:
1875-
const name = result.node.propertyName || result.node.name;
1868+
case SyntaxKind.ImportSpecifier:
1869+
const name = result.propertyName || result.name;
18761870
return setTextRange(
18771871
factory.createPropertyAccessExpression(
1878-
factory.getGeneratedNameForNode(result.node.parent?.parent?.parent || result.node),
1872+
factory.getGeneratedNameForNode(result.parent?.parent?.parent || result),
18791873
factory.cloneNode(name)
18801874
),
18811875
/*location*/ node

Diff for: src/compiler/transformers/ts.ts

+62-7
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,11 @@ namespace ts {
5656
context.onSubstituteNode = onSubstituteNode;
5757

5858
// Enable substitution for property/element access to emit const enum values.
59+
context.enableSubstitution(SyntaxKind.Identifier);
5960
context.enableSubstitution(SyntaxKind.PropertyAccessExpression);
6061
context.enableSubstitution(SyntaxKind.ElementAccessExpression);
62+
context.enableSubstitution(SyntaxKind.CallExpression);
63+
context.enableSubstitution(SyntaxKind.TaggedTemplateExpression);
6164

6265
// These variables contain state that changes as we descend into the tree.
6366
let currentSourceFile: SourceFile;
@@ -73,6 +76,7 @@ namespace ts {
7376
* They are persisted between each SourceFile transformation and should not be reset.
7477
*/
7578
let enabledSubstitutions: TypeScriptSubstitutionFlags;
79+
let bindingReferrenceCache: ESMap<Node, ModuleDeclaration | EnumDeclaration | undefined> | undefined;
7680

7781
/**
7882
* A map that keeps track of aliases created for classes with decorators to avoid issues
@@ -3275,6 +3279,10 @@ namespace ts {
32753279
return substitutePropertyAccessExpression(<PropertyAccessExpression>node);
32763280
case SyntaxKind.ElementAccessExpression:
32773281
return substituteElementAccessExpression(<ElementAccessExpression>node);
3282+
case SyntaxKind.CallExpression:
3283+
return substituteCallExpression(<CallExpression>node);
3284+
case SyntaxKind.TaggedTemplateExpression:
3285+
return substituteTaggedTemplateExpression(<TaggedTemplateExpression>node);
32783286
}
32793287

32803288
return node;
@@ -3310,7 +3318,7 @@ namespace ts {
33103318
return undefined;
33113319
}
33123320

3313-
function trySubstituteNamespaceExportedName(node: Identifier): Expression | undefined {
3321+
function getExportBindingReferenceWorker(node: Identifier) {
33143322
// If this is explicitly a local name, do not substitute.
33153323
if (enabledSubstitutions & applicableSubstitutions && !isGeneratedIdentifier(node) && !isLocalName(node)) {
33163324
// If we are nested within a namespace declaration, we may need to qualifiy
@@ -3320,18 +3328,65 @@ namespace ts {
33203328
const substitute =
33213329
(applicableSubstitutions & TypeScriptSubstitutionFlags.NamespaceExports && container.kind === SyntaxKind.ModuleDeclaration) ||
33223330
(applicableSubstitutions & TypeScriptSubstitutionFlags.NonQualifiedEnumMembers && container.kind === SyntaxKind.EnumDeclaration);
3323-
if (substitute) {
3324-
return setTextRange(
3325-
factory.createPropertyAccessExpression(factory.getGeneratedNameForNode(container), node),
3326-
/*location*/ node
3327-
);
3328-
}
3331+
if (substitute) return container;
33293332
}
33303333
}
3334+
return undefined;
3335+
}
33313336

3337+
function getExportBindingReference(node: Identifier, removeEntry: boolean) {
3338+
let result = bindingReferrenceCache?.get(node);
3339+
if (!result) {
3340+
result = getExportBindingReferenceWorker(node);
3341+
if (!removeEntry) {
3342+
bindingReferrenceCache ||= new Map();
3343+
bindingReferrenceCache.set(node, result);
3344+
}
3345+
}
3346+
else if (removeEntry) {
3347+
bindingReferrenceCache?.delete(node);
3348+
}
3349+
return result;
3350+
}
3351+
3352+
function trySubstituteNamespaceExportedName(node: Identifier): Expression | undefined {
3353+
const container = getExportBindingReference(node, /*removeEntry*/ true);
3354+
if (container) {
3355+
return setTextRange(
3356+
factory.createPropertyAccessExpression(factory.getGeneratedNameForNode(container), node),
3357+
/*location*/ node
3358+
);
3359+
}
33323360
return undefined;
33333361
}
33343362

3363+
function substituteCallExpression(node: CallExpression) {
3364+
if (isIdentifier(node.expression) && getExportBindingReference(node.expression, /*removeEntry*/ false)) {
3365+
return isCallChain(node) ?
3366+
factory.updateCallChain(node,
3367+
setTextRange(factory.createComma(factory.createNumericLiteral(0), node.expression), node.expression),
3368+
node.questionDotToken,
3369+
/*typeArguments*/ undefined,
3370+
node.arguments) :
3371+
factory.updateCallExpression(node,
3372+
setTextRange(factory.createComma(factory.createNumericLiteral(0), node.expression), node.expression),
3373+
/*typeArguments*/ undefined,
3374+
node.arguments);
3375+
}
3376+
return node;
3377+
}
3378+
3379+
function substituteTaggedTemplateExpression(node: TaggedTemplateExpression) {
3380+
if (isIdentifier(node.tag) && getExportBindingReference(node.tag, /*removeEntry*/ false)) {
3381+
return factory.updateTaggedTemplateExpression(
3382+
node,
3383+
setTextRange(factory.createComma(factory.createNumericLiteral(0), node.tag), node.tag),
3384+
/*typeArguments*/ undefined,
3385+
node.template);
3386+
}
3387+
return node;
3388+
}
3389+
33353390
function substitutePropertyAccessExpression(node: PropertyAccessExpression) {
33363391
return substituteConstantValue(node);
33373392
}

0 commit comments

Comments
 (0)