Skip to content

Commit f2347e4

Browse files
Inject IIFE when variables shadow binding in rest param (#14736)
1 parent d307173 commit f2347e4

File tree

9 files changed

+231
-94
lines changed

9 files changed

+231
-94
lines changed

packages/babel-plugin-transform-parameters/src/params.ts

Lines changed: 13 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { template, types as t } from "@babel/core";
2-
import type { NodePath, Scope, Visitor } from "@babel/traverse";
2+
import type { NodePath } from "@babel/traverse";
3+
4+
import {
5+
iifeVisitor,
6+
collectShadowedParamsNames,
7+
buildScopeIIFE,
8+
} from "./shadow-utils";
39

410
const buildDefaultParam = template.statement(`
511
let VARIABLE_NAME =
@@ -23,34 +29,6 @@ const buildSafeArgumentsAccess = template.statement(`
2329
let $0 = arguments.length > $1 ? arguments[$1] : undefined;
2430
`);
2531

26-
const iifeVisitor: Visitor<State> = {
27-
"ReferencedIdentifier|BindingIdentifier"(
28-
path: NodePath<t.Identifier>,
29-
state,
30-
) {
31-
const { scope, node } = path;
32-
const { name } = node;
33-
34-
if (
35-
name === "eval" ||
36-
(scope.getBinding(name) === state.scope.parent.getBinding(name) &&
37-
state.scope.hasOwnBinding(name))
38-
) {
39-
state.needsOuterBinding = true;
40-
path.stop();
41-
}
42-
},
43-
// type annotations don't use or introduce "real" bindings
44-
"TypeAnnotation|TSTypeAnnotation|TypeParameterDeclaration|TSTypeParameterDeclaration":
45-
(path: NodePath) => path.skip(),
46-
};
47-
48-
type State = {
49-
stop: boolean;
50-
needsOuterBinding: boolean;
51-
scope: Scope;
52-
};
53-
5432
// last 2 parameters are optional -- they are used by proposal-object-rest-spread/src/index.js
5533
export default function convertFunctionParams(
5634
path: NodePath<t.Function>,
@@ -69,53 +47,17 @@ export default function convertFunctionParams(
6947

7048
const { node, scope } = path;
7149

72-
const state = {
73-
stop: false,
74-
needsOuterBinding: false,
75-
scope,
76-
};
77-
7850
const body = [];
7951
const shadowedParams = new Set<string>();
8052

8153
for (const param of params) {
82-
for (const name of Object.keys(param.getBindingIdentifiers())) {
83-
const constantViolations = scope.bindings[name]?.constantViolations;
84-
if (constantViolations) {
85-
for (const redeclarator of constantViolations) {
86-
const node = redeclarator.node;
87-
// If a constant violation is a var or a function declaration,
88-
// we first check to see if it's a var without an init.
89-
// If so, we remove that declarator.
90-
// Otherwise, we have to wrap it in an IIFE.
91-
switch (node.type) {
92-
case "VariableDeclarator": {
93-
if (node.init === null) {
94-
const declaration = redeclarator.parentPath;
95-
// The following uninitialized var declarators should not be removed
96-
// for (var x in {})
97-
// for (var x;;)
98-
if (
99-
!declaration.parentPath.isFor() ||
100-
declaration.parentPath.get("body") === declaration
101-
) {
102-
redeclarator.remove();
103-
break;
104-
}
105-
}
106-
107-
shadowedParams.add(name);
108-
break;
109-
}
110-
case "FunctionDeclaration":
111-
shadowedParams.add(name);
112-
break;
113-
}
114-
}
115-
}
116-
}
54+
collectShadowedParamsNames(param, scope, shadowedParams);
11755
}
11856

57+
const state = {
58+
needsOuterBinding: false,
59+
scope,
60+
};
11961
if (shadowedParams.size === 0) {
12062
for (const param of params) {
12163
if (!param.isIdentifier()) param.traverse(iifeVisitor, state);
@@ -213,12 +155,7 @@ export default function convertFunctionParams(
213155
path.ensureBlock();
214156

215157
if (state.needsOuterBinding || shadowedParams.size > 0) {
216-
body.push(
217-
buildScopeIIFE(
218-
shadowedParams,
219-
(path.get("body") as NodePath<t.BlockStatement>).node,
220-
),
221-
);
158+
body.push(buildScopeIIFE(shadowedParams, path.node.body));
222159

223160
path.set("body", t.blockStatement(body as t.Statement[]));
224161

@@ -244,18 +181,3 @@ export default function convertFunctionParams(
244181

245182
return true;
246183
}
247-
248-
function buildScopeIIFE(shadowedParams: Set<string>, body: t.BlockStatement) {
249-
const args = [];
250-
const params = [];
251-
252-
for (const name of shadowedParams) {
253-
// We create them twice; the other option is to use t.cloneNode
254-
args.push(t.identifier(name));
255-
params.push(t.identifier(name));
256-
}
257-
258-
return t.returnStatement(
259-
t.callExpression(t.arrowFunctionExpression(params, body), args),
260-
);
261-
}

packages/babel-plugin-transform-parameters/src/rest.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { template, types as t } from "@babel/core";
22
import type { NodePath, Visitor } from "@babel/traverse";
33

4+
import {
5+
iifeVisitor,
6+
collectShadowedParamsNames,
7+
buildScopeIIFE,
8+
} from "./shadow-utils";
9+
410
const buildRest = template.statement(`
511
for (var LEN = ARGUMENTS.length,
612
ARRAY = new Array(ARRAY_LEN),
@@ -292,9 +298,35 @@ export default function convertFunctionRest(path: NodePath<t.Function>) {
292298
const { node, scope } = path;
293299
if (!hasRest(node)) return false;
294300

295-
let rest = (node.params.pop() as t.RestElement).argument as
296-
| t.Pattern
297-
| t.Identifier;
301+
const restPath = path.get(
302+
`params.${node.params.length - 1}.argument`,
303+
) as NodePath<t.Pattern | t.Identifier>;
304+
305+
if (!restPath.isIdentifier()) {
306+
const shadowedParams = new Set<string>();
307+
collectShadowedParamsNames(restPath, path.scope, shadowedParams);
308+
309+
let needsIIFE = shadowedParams.size > 0;
310+
if (!needsIIFE) {
311+
const state = {
312+
needsOuterBinding: false,
313+
scope,
314+
};
315+
restPath.traverse(iifeVisitor, state);
316+
needsIIFE = state.needsOuterBinding;
317+
}
318+
319+
if (needsIIFE) {
320+
path.ensureBlock();
321+
path.set(
322+
"body",
323+
t.blockStatement([buildScopeIIFE(shadowedParams, path.node.body)]),
324+
);
325+
}
326+
}
327+
328+
let rest = restPath.node;
329+
node.params.pop(); // This returns 'rest'
298330

299331
if (t.isPattern(rest)) {
300332
const pattern = rest;
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { types as t } from "@babel/core";
2+
import type { NodePath, Scope, Visitor } from "@babel/traverse";
3+
4+
type State = {
5+
needsOuterBinding: boolean;
6+
scope: Scope;
7+
};
8+
9+
export const iifeVisitor: Visitor<State> = {
10+
"ReferencedIdentifier|BindingIdentifier"(
11+
path: NodePath<t.Identifier>,
12+
state,
13+
) {
14+
const { scope, node } = path;
15+
const { name } = node;
16+
17+
if (
18+
name === "eval" ||
19+
(scope.getBinding(name) === state.scope.parent.getBinding(name) &&
20+
state.scope.hasOwnBinding(name))
21+
) {
22+
state.needsOuterBinding = true;
23+
path.stop();
24+
}
25+
},
26+
// type annotations don't use or introduce "real" bindings
27+
"TypeAnnotation|TSTypeAnnotation|TypeParameterDeclaration|TSTypeParameterDeclaration":
28+
(path: NodePath) => path.skip(),
29+
};
30+
31+
export function collectShadowedParamsNames(
32+
param: NodePath<t.Function["params"][number]>,
33+
functionScope: Scope,
34+
shadowedParams: Set<string>,
35+
) {
36+
for (const name of Object.keys(param.getBindingIdentifiers())) {
37+
const constantViolations = functionScope.bindings[name]?.constantViolations;
38+
if (constantViolations) {
39+
for (const redeclarator of constantViolations) {
40+
const node = redeclarator.node;
41+
// If a constant violation is a var or a function declaration,
42+
// we first check to see if it's a var without an init.
43+
// If so, we remove that declarator.
44+
// Otherwise, we have to wrap it in an IIFE.
45+
switch (node.type) {
46+
case "VariableDeclarator": {
47+
if (node.init === null) {
48+
const declaration = redeclarator.parentPath;
49+
// The following uninitialized var declarators should not be removed
50+
// for (var x in {})
51+
// for (var x;;)
52+
if (
53+
!declaration.parentPath.isFor() ||
54+
declaration.parentPath.get("body") === declaration
55+
) {
56+
redeclarator.remove();
57+
break;
58+
}
59+
}
60+
61+
shadowedParams.add(name);
62+
break;
63+
}
64+
case "FunctionDeclaration":
65+
shadowedParams.add(name);
66+
break;
67+
}
68+
}
69+
}
70+
}
71+
}
72+
73+
export function buildScopeIIFE(
74+
shadowedParams: Set<string>,
75+
body: t.BlockStatement,
76+
) {
77+
const args = [];
78+
const params = [];
79+
80+
for (const name of shadowedParams) {
81+
// We create them twice; the other option is to use t.cloneNode
82+
args.push(t.identifier(name));
83+
params.push(t.identifier(name));
84+
}
85+
86+
return t.returnStatement(
87+
t.callExpression(t.arrowFunctionExpression(params, body), args),
88+
);
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function f(...{ length: x = 0, y = x }) {
2+
var x;
3+
return x;
4+
}
5+
6+
function g(...{ length: x = 0, y = x }) {
7+
var x = 1;
8+
return x;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
function f() {
2+
for (var _len = arguments.length, _ref = new Array(_len), _key = 0; _key < _len; _key++) {
3+
_ref[_key] = arguments[_key];
4+
}
5+
6+
var _ref$length = _ref.length,
7+
x = _ref$length === void 0 ? 0 : _ref$length,
8+
_ref$y = _ref.y,
9+
y = _ref$y === void 0 ? x : _ref$y;
10+
return x;
11+
}
12+
13+
function g() {
14+
for (var _len2 = arguments.length, _ref2 = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
15+
_ref2[_key2] = arguments[_key2];
16+
}
17+
18+
var _ref2$length = _ref2.length,
19+
x = _ref2$length === void 0 ? 0 : _ref2$length,
20+
_ref2$y = _ref2.y,
21+
y = _ref2$y === void 0 ? x : _ref2$y;
22+
return function (x) {
23+
var x = 1;
24+
return x;
25+
}(x);
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function f(...[x, y = x]) {
2+
var x;
3+
return x;
4+
}
5+
6+
function g(...[x, y = x]) {
7+
var x = 1;
8+
return x;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
function f() {
2+
for (var _len = arguments.length, _ref = new Array(_len), _key = 0; _key < _len; _key++) {
3+
_ref[_key] = arguments[_key];
4+
}
5+
6+
var x = _ref[0],
7+
_ref$ = _ref[1],
8+
y = _ref$ === void 0 ? x : _ref$;
9+
return x;
10+
}
11+
12+
function g() {
13+
for (var _len2 = arguments.length, _ref2 = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
14+
_ref2[_key2] = arguments[_key2];
15+
}
16+
17+
var x = _ref2[0],
18+
_ref2$ = _ref2[1],
19+
y = _ref2$ === void 0 ? x : _ref2$;
20+
return function (x) {
21+
var x = 1;
22+
return x;
23+
}(x);
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function f(...x) {
2+
var x;
3+
return x;
4+
}
5+
6+
function g(...x) {
7+
var x = 1;
8+
return x;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
function f() {
2+
for (var _len = arguments.length, x = new Array(_len), _key = 0; _key < _len; _key++) {
3+
x[_key] = arguments[_key];
4+
}
5+
6+
var x;
7+
return x;
8+
}
9+
10+
function g() {
11+
for (var _len2 = arguments.length, x = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
12+
x[_key2] = arguments[_key2];
13+
}
14+
15+
var x = 1;
16+
return x;
17+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy