Skip to content

Commit 821b1d8

Browse files
authored
Looser import/export elision blocking in visitElidableStatement (#57223)
1 parent 104b0a6 commit 821b1d8

File tree

3 files changed

+114
-6
lines changed

3 files changed

+114
-6
lines changed

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

+63-6
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,10 @@ import {
8686
isComputedPropertyName,
8787
isDecorator,
8888
isElementAccessExpression,
89+
isEntityName,
8990
isEnumConst,
91+
isExportAssignment,
92+
isExportDeclaration,
9093
isExportOrDefaultModifier,
9194
isExportSpecifier,
9295
isExpression,
@@ -96,6 +99,8 @@ import {
9699
isHeritageClause,
97100
isIdentifier,
98101
isImportClause,
102+
isImportDeclaration,
103+
isImportEqualsDeclaration,
99104
isImportSpecifier,
100105
isInJSFile,
101106
isInstantiatedModule,
@@ -425,13 +430,65 @@ export function transformTypeScript(context: TransformationContext) {
425430
}
426431
}
427432

428-
function visitElidableStatement(node: ImportDeclaration | ImportEqualsDeclaration | ExportAssignment | ExportDeclaration): VisitResult<Node | undefined> {
433+
/**
434+
* Determines whether import/export elision is blocked for this statement.
435+
*
436+
* @description
437+
* We generally block import/export elision if the statement was modified by a `before` custom
438+
* transform, although we will continue to allow it if the statement hasn't replaced a node of a different kind and
439+
* as long as the local bindings for the declarations are unchanged.
440+
*/
441+
function isElisionBlocked(node: ImportDeclaration | ImportEqualsDeclaration | ExportAssignment | ExportDeclaration) {
429442
const parsed = getParseTreeNode(node);
430-
if (parsed !== node) {
431-
// If the node has been transformed by a `before` transformer, perform no ellision on it
432-
// As the type information we would attempt to lookup to perform ellision is potentially unavailable for the synthesized nodes
433-
// We do not reuse `visitorWorker`, as the ellidable statement syntax kinds are technically unrecognized by the switch-case in `visitTypeScript`,
434-
// and will trigger debug failures when debug verbosity is turned up
443+
if (parsed === node || isExportAssignment(node)) {
444+
return false;
445+
}
446+
447+
if (!parsed || parsed.kind !== node.kind) {
448+
// no longer safe to elide as the declaration was replaced with a node of a different kind
449+
return true;
450+
}
451+
452+
switch (node.kind) {
453+
case SyntaxKind.ImportDeclaration:
454+
Debug.assertNode(parsed, isImportDeclaration);
455+
if (node.importClause !== parsed.importClause) {
456+
return true; // no longer safe to elide as the import clause has changed
457+
}
458+
if (node.attributes !== parsed.attributes) {
459+
return true; // no longer safe to elide as the import attributes have changed
460+
}
461+
break;
462+
case SyntaxKind.ImportEqualsDeclaration:
463+
Debug.assertNode(parsed, isImportEqualsDeclaration);
464+
if (node.name !== parsed.name) {
465+
return true; // no longer safe to elide as local binding has changed
466+
}
467+
if (node.isTypeOnly !== parsed.isTypeOnly) {
468+
return true; // no longer safe to elide as `type` modifier has changed
469+
}
470+
if (node.moduleReference !== parsed.moduleReference && (isEntityName(node.moduleReference) || isEntityName(parsed.moduleReference))) {
471+
return true; // no longer safe to elide as EntityName reference has changed.
472+
}
473+
break;
474+
case SyntaxKind.ExportDeclaration:
475+
Debug.assertNode(parsed, isExportDeclaration);
476+
if (node.exportClause !== parsed.exportClause) {
477+
return true; // no longer safe to elide as the export clause has changed
478+
}
479+
if (node.attributes !== parsed.attributes) {
480+
return true; // no longer safe to elide as the export attributes have changed
481+
}
482+
break;
483+
}
484+
485+
return false;
486+
}
487+
488+
function visitElidableStatement(node: ImportDeclaration | ImportEqualsDeclaration | ExportAssignment | ExportDeclaration): VisitResult<Node | undefined> {
489+
if (isElisionBlocked(node)) {
490+
// We do not reuse `visitorWorker`, as the ellidable statement syntax kinds are technically unrecognized by
491+
// the switch-case in `visitTypeScript`, and will trigger debug failures when debug verbosity is turned up.
435492
if (node.transformFlags & TransformFlags.ContainsTypeScript) {
436493
// This node contains TypeScript, so we should visit its children.
437494
return visitEachChild(node, visitor, context);

Diff for: src/testRunner/unittests/customTransforms.ts

+45
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,49 @@ describe("unittests:: customTransforms", () => {
164164
},
165165
],
166166
}, { sourceMap: true, outFile: "source.js" });
167+
168+
emitsCorrectly("importDeclarationBeforeTransformElision", [
169+
{
170+
file: "a.ts",
171+
text: "export type A = string;",
172+
},
173+
{
174+
file: "index.ts",
175+
text: "import { A } from './a.js';\nexport { A } from './a.js';",
176+
},
177+
], {
178+
before: [
179+
context => {
180+
const { factory } = context;
181+
return (s: ts.SourceFile) => ts.visitEachChild(s, visitor, context);
182+
183+
function visitor(node: ts.Node): ts.Node {
184+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
185+
return factory.updateImportDeclaration(
186+
node,
187+
node.modifiers,
188+
node.importClause,
189+
factory.createStringLiteral(node.moduleSpecifier.text),
190+
node.attributes,
191+
);
192+
}
193+
194+
if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
195+
return factory.updateExportDeclaration(
196+
node,
197+
node.modifiers,
198+
node.isTypeOnly,
199+
node.exportClause,
200+
factory.createStringLiteral(node.moduleSpecifier.text),
201+
node.attributes,
202+
);
203+
}
204+
return node;
205+
}
206+
},
207+
],
208+
}, {
209+
target: ts.ScriptTarget.ESNext,
210+
module: ts.ModuleKind.ESNext,
211+
});
167212
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// [a.js]
2+
export {};
3+
4+
5+
// [index.js]
6+
export {};

0 commit comments

Comments
 (0)