Skip to content

Commit 410c9ac

Browse files
[systemjs] Fix nested let/const shadowing imported bindings (#14057)
Co-authored-by: Nicolò Ribaudo <nicolo.ribaudo@gmail.com>
1 parent 01380a6 commit 410c9ac

8 files changed

Lines changed: 119 additions & 20 deletions

File tree

packages/babel-plugin-transform-modules-systemjs/src/index.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { template, types as t } from "@babel/core";
44
import { getImportSource } from "babel-plugin-dynamic-import-node/utils";
55
import { rewriteThis, getModuleName } from "@babel/helper-module-transforms";
66
import { isIdentifierName } from "@babel/helper-validator-identifier";
7+
import type { NodePath } from "@babel/traverse";
78

8-
const buildTemplate = template(`
9+
const buildTemplate = template.statement(`
910
SYSTEM_REGISTER(MODULE_NAME, SOURCES, function (EXPORT_IDENTIFIER, CONTEXT_IDENTIFIER) {
1011
"use strict";
1112
BEFORE_BODY;
@@ -16,7 +17,7 @@ const buildTemplate = template(`
1617
});
1718
`);
1819

19-
const buildExportAll = template(`
20+
const buildExportAll = template.statement(`
2021
for (var KEY in TARGET) {
2122
if (KEY !== "default" && KEY !== "__esModule") EXPORT_OBJ[KEY] = TARGET[KEY];
2223
}
@@ -286,7 +287,7 @@ export default declare((api, options) => {
286287
rewriteThis(path);
287288
}
288289
},
289-
exit(path, state: PluginState) {
290+
exit(path: NodePath<t.Program>, state: PluginState) {
290291
const scope = path.scope;
291292
const exportIdent = scope.generateUid("export");
292293
const { contextIdent, stringSpecifiers } = state;
@@ -332,7 +333,7 @@ export default declare((api, options) => {
332333
const exportNames = [];
333334
const exportValues = [];
334335

335-
const body: Array<any> = path.get("body");
336+
const body = path.get("body");
336337

337338
for (const path of body) {
338339
if (path.isFunctionDeclaration()) {
@@ -349,6 +350,10 @@ export default declare((api, options) => {
349350
),
350351
),
351352
);
353+
} else if (path.isVariableDeclaration()) {
354+
// Convert top-level variable declarations to "var",
355+
// because they must be hoisted
356+
path.node.kind = "var";
352357
} else if (path.isImportDeclaration()) {
353358
const source = path.node.source.value;
354359
pushModule(source, "imports", path.node.specifiers);
@@ -362,8 +367,8 @@ export default declare((api, options) => {
362367
path.remove();
363368
} else if (path.isExportDefaultDeclaration()) {
364369
const declar = path.get("declaration");
365-
const id = declar.node.id;
366370
if (declar.isClassDeclaration()) {
371+
const id = declar.node.id;
367372
if (id) {
368373
exportNames.push("default");
369374
exportValues.push(scope.buildUndefinedNode());
@@ -384,6 +389,7 @@ export default declare((api, options) => {
384389
removedPaths.push(path);
385390
}
386391
} else if (declar.isFunctionDeclaration()) {
392+
const id = declar.node.id;
387393
if (id) {
388394
beforeBody.push(declar.node);
389395
exportNames.push("default");
@@ -403,15 +409,15 @@ export default declare((api, options) => {
403409
if (declar.node) {
404410
path.replaceWith(declar);
405411

406-
if (path.isFunction()) {
412+
if (declar.isFunction()) {
407413
const node = declar.node;
408414
const name = node.id.name;
409415
addExportName(name, name);
410416
beforeBody.push(node);
411417
exportNames.push(name);
412418
exportValues.push(t.cloneNode(node.id));
413419
removedPaths.push(path);
414-
} else if (path.isClass()) {
420+
} else if (declar.isClass()) {
415421
const name = declar.node.id.name;
416422
exportNames.push(name);
417423
exportValues.push(scope.buildUndefinedNode());
@@ -427,6 +433,11 @@ export default declare((api, options) => {
427433
);
428434
addExportName(name, name);
429435
} else {
436+
if (declar.isVariableDeclaration()) {
437+
// Convert top-level variable declarations to "var",
438+
// because they must be hoisted
439+
declar.node.kind = "var";
440+
}
430441
for (const name of Object.keys(
431442
declar.getBindingIdentifiers(),
432443
)) {
@@ -443,7 +454,10 @@ export default declare((api, options) => {
443454
const nodes = [];
444455

445456
for (const specifier of specifiers) {
457+
// @ts-expect-error This isn't an "export ... from" declaration
458+
// because path.node.source is falsy, so the local specifier exists.
446459
const { local, exported } = specifier;
460+
447461
const binding = scope.getBinding(local.name);
448462
const exportedName = getExportSpecifierName(
449463
exported,
@@ -565,19 +579,15 @@ export default declare((api, options) => {
565579
// @ts-expect-error todo(flow->ts): do not reuse variables
566580
if (moduleName) moduleName = t.stringLiteral(moduleName);
567581

568-
hoistVariables(
569-
path,
570-
(id, name, hasInit) => {
571-
variableIds.push(id);
572-
if (!hasInit && name in exportMap) {
573-
for (const exported of exportMap[name]) {
574-
exportNames.push(exported);
575-
exportValues.push(scope.buildUndefinedNode());
576-
}
582+
hoistVariables(path, (id, name, hasInit) => {
583+
variableIds.push(id);
584+
if (!hasInit && name in exportMap) {
585+
for (const exported of exportMap[name]) {
586+
exportNames.push(exported);
587+
exportValues.push(scope.buildUndefinedNode());
577588
}
578-
},
579-
null,
580-
);
589+
}
590+
});
581591

582592
if (variableIds.length) {
583593
beforeBody.unshift(
@@ -620,6 +630,7 @@ export default declare((api, options) => {
620630
Function(path) {
621631
path.skip();
622632
},
633+
// @ts-expect-error - todo: add noScope to type definitions
623634
noScope: true,
624635
});
625636

packages/babel-plugin-transform-modules-systemjs/test/fixtures/systemjs/export-fn-decl/output.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ System.register([], function (_export, _context) {
1515
_export("testProp", testProp = 'test property');
1616
}
1717
};
18-
});
18+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
let l_foo = 1;
2+
const c_foo = 2;
3+
4+
{ let l_foo, l_bar; const c_foo = 3; const c_bar = 4; }
5+
6+
export { l_foo, c_foo };
7+
8+
export let l_bar = 4;
9+
export const c_bar = 6;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
System.register([], function (_export, _context) {
2+
"use strict";
3+
4+
var l_foo, c_foo, l_bar, c_bar;
5+
return {
6+
setters: [],
7+
execute: function () {
8+
_export("l_foo", l_foo = 1);
9+
10+
_export("c_foo", c_foo = 2);
11+
12+
{
13+
let l_foo, l_bar;
14+
const c_foo = 3;
15+
const c_bar = 4;
16+
}
17+
18+
_export("l_bar", l_bar = 4);
19+
20+
_export("c_bar", c_bar = 6);
21+
}
22+
};
23+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { x } from './x.js';
2+
3+
if (true) {
4+
const { x } = { x: 1 };
5+
console.log(x);
6+
}
7+
8+
new (class extends x {})();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
System.register(["./x.js"], function (_export, _context) {
2+
"use strict";
3+
4+
var x;
5+
return {
6+
setters: [function (_xJs) {
7+
x = _xJs.x;
8+
}],
9+
execute: function () {
10+
if (true) {
11+
const {
12+
x
13+
} = {
14+
x: 1
15+
};
16+
console.log(x);
17+
}
18+
19+
new class extends x {}();
20+
}
21+
};
22+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { x } from './x.js';
2+
3+
if (true) {
4+
const x = 1;
5+
console.log(x);
6+
}
7+
8+
new (class extends x {})();
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
System.register(["./x.js"], function (_export, _context) {
2+
"use strict";
3+
4+
var x;
5+
return {
6+
setters: [function (_xJs) {
7+
x = _xJs.x;
8+
}],
9+
execute: function () {
10+
if (true) {
11+
const x = 1;
12+
console.log(x);
13+
}
14+
15+
new class extends x {}();
16+
}
17+
};
18+
});

0 commit comments

Comments
 (0)