Skip to content

Commit 07bd000

Browse files
authored
Improve getBindingIdentifiers (#16544)
* Add more getBindingIdentifiers tests * fix: do not return class id from getOuterBindingId * update expression should not introduce new bindings * fix: register class expression binding from its id node * defer the fix to Babel 8 * remove unused FlowIgnore comment
1 parent ec0c62a commit 07bd000

File tree

3 files changed

+212
-12
lines changed

3 files changed

+212
-12
lines changed

packages/babel-traverse/src/scope/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ const collectorVisitor: Visitor<CollectVisitorState> = {
369369
// @ts-expect-error Fixme: document symbol ast properties
370370
!path.get("id").node[NOT_LOCAL_BINDING]
371371
) {
372-
path.scope.registerBinding("local", path);
372+
path.scope.registerBinding("local", path.get("id"), path);
373373
}
374374
},
375375
TSTypeAnnotation(path) {

packages/babel-types/src/retrievers/getBindingIdentifiers.ts

+14-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import {
22
isExportDeclaration,
33
isIdentifier,
4+
isClassExpression,
45
isDeclaration,
56
isFunctionDeclaration,
67
isFunctionExpression,
78
isExportAllDeclaration,
89
isAssignmentExpression,
910
isUnaryExpression,
11+
isUpdateExpression,
1012
} from "../validators/generated/index.ts";
1113
import type * as t from "../index.ts";
1214

@@ -51,20 +53,18 @@ function getBindingIdentifiers(
5153

5254
if (
5355
newBindingsOnly &&
54-
// These two nodes do not introduce _new_ bindings, but they are included
56+
// These nodes do not introduce _new_ bindings, but they are included
5557
// in getBindingIdentifiers.keys for backwards compatibility.
5658
// TODO(@nicolo-ribaudo): Check if we can remove them from .keys in a
5759
// backward-compatible way, and if not what we need to do to remove them
5860
// in Babel 8.
59-
(isAssignmentExpression(id) || isUnaryExpression(id))
61+
(isAssignmentExpression(id) ||
62+
isUnaryExpression(id) ||
63+
isUpdateExpression(id))
6064
) {
6165
continue;
6266
}
6367

64-
const keys =
65-
// @ts-expect-error getBindingIdentifiers.keys do not cover all AST types
66-
getBindingIdentifiers.keys[id.type];
67-
6868
if (isIdentifier(id)) {
6969
if (duplicates) {
7070
const _ids = (ids[id.name] = ids[id.name] || []);
@@ -88,11 +88,18 @@ function getBindingIdentifiers(
8888
continue;
8989
}
9090

91-
if (isFunctionExpression(id)) {
91+
if (
92+
isFunctionExpression(id) ||
93+
(process.env.BABEL_8_BREAKING && isClassExpression(id))
94+
) {
9295
continue;
9396
}
9497
}
9598

99+
const keys =
100+
// @ts-expect-error getBindingIdentifiers.keys do not cover all AST types
101+
getBindingIdentifiers.keys[id.type];
102+
96103
if (keys) {
97104
for (let i = 0; i < keys.length; i++) {
98105
const key = keys[i];
@@ -105,8 +112,6 @@ function getBindingIdentifiers(
105112
}
106113
}
107114
}
108-
109-
// $FlowIssue Object.create() seems broken
110115
return ids;
111116
}
112117

packages/babel-types/test/retrievers.js

+197-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import {
2+
describeBabel7,
3+
describeBabel8,
4+
} from "../../../scripts/repo-utils/index.cjs";
15
import * as t from "../lib/index.js";
26
import { parse } from "@babel/parser";
37

@@ -15,8 +19,28 @@ describe("retrievers", function () {
1519
],
1620
[
1721
"function declarations",
18-
getBody("var foo = 1; function bar() { var baz = 2; }"),
19-
["bar", "foo"],
22+
getBody("function bar() { var baz = 2; }"),
23+
["bar"],
24+
],
25+
[
26+
"function declarations with parameters",
27+
getBody(
28+
"function f(a, { b }, c = 1, [{ _: [ d ], ...e }]) { var baz = 2; }",
29+
),
30+
["f", "a", "c", "b", "e", "d"],
31+
],
32+
[
33+
"function expressions with parameters",
34+
getBody(
35+
"(function f(a, { b }, c = 1, [{ _: [ d ], ...e }]) { var baz = 2; })",
36+
)[0].expression,
37+
["f", "a", "c", "b", "e", "d"],
38+
],
39+
["class declarations", getBody("class C { a(b) { let c } }")[0], ["C"]],
40+
[
41+
"class expressions",
42+
getBody("(class C { a(b) { let c } })")[0].expression,
43+
["C"],
2044
],
2145
[
2246
"object methods",
@@ -33,11 +57,28 @@ describe("retrievers", function () {
3357
getBody("(class { #a(b) { let c } })")[0].expression.body.body,
3458
["b"],
3559
],
60+
[
61+
"for-in statement",
62+
getBody("for ([{ _: [ d ], ...e }] in rhs);"),
63+
["e", "d"],
64+
],
65+
[
66+
"for-of statement",
67+
getBody("for ([{ _: [ d ], ...e }] of rhs);"),
68+
["e", "d"],
69+
],
70+
["catch clause", getBody("try { } catch (e) {}")[0].handler, ["e"]],
71+
["labeled statement", getBody("label: x"), ["label"]],
3672
[
3773
"export named declarations",
3874
getBody("export const foo = 'foo';"),
3975
["foo"],
4076
],
77+
[
78+
"export function declarations",
79+
getBody("export function foo() {}"),
80+
["foo"],
81+
],
4182
[
4283
"export default class declarations",
4384
getBody("export default class foo {}"),
@@ -69,11 +110,165 @@ describe("retrievers", function () {
69110
getBody("var [ a, ...{ b, ...c } ] = {}"),
70111
["a", "b", "c"],
71112
],
113+
["unary expression", getBody("void x")[0].expression, ["x"]],
72114
["update expression", getBody("++x")[0].expression, ["x"]],
73115
["assignment expression", getBody("x ??= 1")[0].expression, ["x"]],
74116
])("%s", (_, program, bindingNames) => {
75117
const ids = t.getBindingIdentifiers(program);
76118
expect(Object.keys(ids)).toEqual(bindingNames);
77119
});
78120
});
121+
describe("getBindingIdentifiers(%, /* duplicates */ true)", function () {
122+
it.each([
123+
["variable declarations", getBody("var a = 1, a = 2"), { a: 2 }],
124+
[
125+
"function declarations with parameters",
126+
getBody("function f(f) { var f = 1; }"),
127+
{ f: 2 },
128+
],
129+
])("%s", (_, program, expected) => {
130+
const ids = t.getBindingIdentifiers(program, true);
131+
for (const name of Object.keys(ids)) {
132+
ids[name] = Array.isArray(ids[name]) ? ids[name].length : 1;
133+
}
134+
expect(ids).toEqual(expected);
135+
});
136+
});
137+
describe("getOuterBindingIdentifiers", function () {
138+
it.each([
139+
[
140+
"variable declarations",
141+
getBody("var a = 1; let b = 2; const c = 3;"),
142+
["a", "b", "c"],
143+
],
144+
[
145+
"function declarations",
146+
getBody("function bar() { var baz = 2; }"),
147+
["bar"],
148+
],
149+
[
150+
"function declarations with parameters",
151+
getBody(
152+
"function f(a, { b }, c = 1, [{ _: [ d ], ...e }]) { var baz = 2; }",
153+
),
154+
["f"],
155+
],
156+
[
157+
"function expressions with parameters",
158+
getBody(
159+
"(function f(a, { b }, c = 1, [{ _: [ d ], ...e }]) { var baz = 2; })",
160+
)[0].expression,
161+
[],
162+
],
163+
["class declarations", getBody("class C { a(b) { let c } }")[0], ["C"]],
164+
[
165+
"object methods",
166+
getBody("({ a(b) { let c } })")[0].expression.properties[0],
167+
["b"],
168+
],
169+
[
170+
"class methods",
171+
getBody("(class { a(b) { let c } })")[0].expression.body.body,
172+
["b"],
173+
],
174+
[
175+
"class private methods",
176+
getBody("(class { #a(b) { let c } })")[0].expression.body.body,
177+
["b"],
178+
],
179+
[
180+
"for-in statement",
181+
getBody("for ([{ _: [ d ], ...e }] in rhs);"),
182+
["e", "d"],
183+
],
184+
[
185+
"for-of statement",
186+
getBody("for ([{ _: [ d ], ...e }] of rhs);"),
187+
["e", "d"],
188+
],
189+
["catch clause", getBody("try { } catch (e) {}")[0].handler, ["e"]],
190+
["labeled statement", getBody("label: x"), ["label"]],
191+
[
192+
"export named declarations",
193+
getBody("export const foo = 'foo';"),
194+
["foo"],
195+
],
196+
[
197+
"export function declarations",
198+
getBody("export function foo() {}"),
199+
["foo"],
200+
],
201+
[
202+
"export default class declarations",
203+
getBody("export default class foo {}"),
204+
["foo"],
205+
],
206+
[
207+
"export default referenced identifiers",
208+
getBody("export default foo"),
209+
[],
210+
],
211+
["export all declarations", getBody("export * from 'x'"), []],
212+
[
213+
"export all as namespace declarations",
214+
getBody("export * as ns from 'x'"),
215+
[], // exported bindings are not associated with declarations
216+
],
217+
[
218+
"export namespace specifiers",
219+
getBody("export * as ns from 'x'")[0].specifiers,
220+
["ns"],
221+
],
222+
[
223+
"object patterns",
224+
getBody("const { a, b: { ...c } = { d } } = {}"),
225+
["a", "c"],
226+
],
227+
[
228+
"array patterns",
229+
getBody("var [ a, ...{ b, ...c } ] = {}"),
230+
["a", "b", "c"],
231+
],
232+
["unary expression", getBody("void x")[0].expression, ["x"]],
233+
["update expression", getBody("++x")[0].expression, ["x"]],
234+
["assignment expression", getBody("x ??= 1")[0].expression, ["x"]],
235+
])("%s", (_, program, bindingNames) => {
236+
const ids = t.getOuterBindingIdentifiers(program);
237+
expect(Object.keys(ids)).toEqual(bindingNames);
238+
});
239+
});
240+
describeBabel7("getOuterBindingIdentifiers - Babel 7", function () {
241+
it.each([
242+
[
243+
"class expressions",
244+
getBody("(class C { a(b) { let c } })")[0].expression,
245+
["C"],
246+
],
247+
])("%s", (_, program, bindingNames) => {
248+
const ids = t.getOuterBindingIdentifiers(program);
249+
expect(Object.keys(ids)).toEqual(bindingNames);
250+
});
251+
});
252+
describeBabel8("getOuterBindingIdentifiers - Babel 8", function () {
253+
it.each([
254+
[
255+
"class expressions",
256+
getBody("(class C { a(b) { let c } })")[0].expression,
257+
[],
258+
],
259+
])("%s", (_, program, bindingNames) => {
260+
const ids = t.getOuterBindingIdentifiers(program);
261+
expect(Object.keys(ids)).toEqual(bindingNames);
262+
});
263+
});
264+
describe("getBindingIdentifiers(%, false, false, /* newBindingsOnly */ true)", function () {
265+
it.each([
266+
["unary expression", getBody("void x")[0].expression, []],
267+
["update expression", getBody("++x")[0].expression, []],
268+
["assignment expression", getBody("x ??= 1")[0].expression, []],
269+
])("%s", (_, program, bindingNames) => {
270+
const ids = t.getBindingIdentifiers(program, false, false, true);
271+
expect(Object.keys(ids)).toEqual(bindingNames);
272+
});
273+
});
79274
});

0 commit comments

Comments
 (0)