Skip to content

Commit 7385eae

Browse files
authored
[Babel 8] Improve scope information collection performance (#17043)
1 parent ea5ad5f commit 7385eae

File tree

7 files changed

+147
-16
lines changed

7 files changed

+147
-16
lines changed

packages/babel-traverse/src/context.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ export default class TraversalContext<S = unknown> {
127127
visitIndex++;
128128
resync.call(path);
129129

130+
// this path no longer belongs to the tree
131+
if (process.env.BABEL_8_BREAKING && path.key === null) continue;
132+
130133
if (
131134
path.contexts.length === 0 ||
132135
path.contexts[path.contexts.length - 1] !== this
@@ -138,7 +141,7 @@ export default class TraversalContext<S = unknown> {
138141
}
139142

140143
// this path no longer belongs to the tree
141-
if (path.key === null) continue;
144+
if (!process.env.BABEL_8_BREAKING && path.key === null) continue;
142145

143146
// ensure we don't visit the same node twice
144147
const { node } = path;
@@ -160,6 +163,7 @@ export default class TraversalContext<S = unknown> {
160163

161164
// pop contexts
162165
for (let i = 0; i < visitIndex; i++) {
166+
if (process.env.BABEL_8_BREAKING && queue[i].key === null) continue;
163167
popContext.call(queue[i]);
164168
}
165169

packages/babel-traverse/src/path/context.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export function stop(this: NodePath) {
134134
}
135135

136136
export function setScope(this: NodePath) {
137-
if (this.opts?.noScope) return;
137+
if (!process.env.BABEL_8_BREAKING && this.opts?.noScope) return;
138138

139139
let path = this.parentPath;
140140

@@ -150,7 +150,7 @@ export function setScope(this: NodePath) {
150150

151151
let target;
152152
while (path && !target) {
153-
if (path.opts?.noScope) return;
153+
if (!process.env.BABEL_8_BREAKING && path.opts?.noScope) return;
154154

155155
target = path.scope;
156156
path = path.parentPath;

packages/babel-traverse/src/path/lib/virtual-types.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import type * as t from "@babel/types";
22

33
export interface VirtualTypeAliases {
44
BindingIdentifier: t.Identifier;
5-
BlockScoped: t.Node;
5+
BlockScoped:
6+
| t.FunctionDeclaration
7+
| t.ClassDeclaration
8+
| t.VariableDeclaration;
69
ExistentialTypeParam: t.ExistsTypeAnnotation;
710
Expression: t.Expression;
811
Flow: t.Flow | t.ImportDeclaration | t.ExportDeclaration | t.ImportSpecifier;
@@ -42,7 +45,11 @@ export const Scope: VirtualTypeMapping = ["Scopable", "Pattern"] as const;
4245

4346
export const Referenced: VirtualTypeMapping = null;
4447

45-
export const BlockScoped: VirtualTypeMapping = null;
48+
export const BlockScoped: VirtualTypeMapping = [
49+
"FunctionDeclaration",
50+
"ClassDeclaration",
51+
"VariableDeclaration",
52+
] as const;
4653

4754
export const Var: VirtualTypeMapping = ["VariableDeclaration"];
4855

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Renamer from "./lib/renamer.ts";
22
import type NodePath from "../path/index.ts";
33
import traverse from "../index.ts";
4+
import traverseForScope from "./traverseForScope.ts";
45
import Binding from "./binding.ts";
56
import type { BindingKind } from "./binding.ts";
67
import globalsBuiltinLower from "@babel/helper-globals/data/builtin-lower.json" with { type: "json" };
@@ -1011,6 +1012,10 @@ class Scope {
10111012
crawl() {
10121013
const path = this.path;
10131014

1015+
if (process.env.BABEL_8_BREAKING && path.opts?.noScope) {
1016+
return;
1017+
}
1018+
10141019
resetScope(this);
10151020
this.data = Object.create(null);
10161021

@@ -1042,17 +1047,18 @@ class Scope {
10421047
// traverse does not visit the root node, here we explicitly collect
10431048
// root node binding info when the root is not a Program.
10441049
if (path.type !== "Program") {
1045-
for (const visit of scopeVisitor.enter) {
1046-
visit.call(state, path, state);
1047-
}
10481050
const typeVisitors = scopeVisitor[path.type];
10491051
if (typeVisitors) {
10501052
for (const visit of typeVisitors.enter) {
10511053
visit.call(state, path, state);
10521054
}
10531055
}
10541056
}
1055-
path.traverse(scopeVisitor, state);
1057+
if (process.env.BABEL_8_BREAKING) {
1058+
traverseForScope(path, scopeVisitor, state);
1059+
} else {
1060+
path.traverse(scopeVisitor, state);
1061+
}
10561062
this.crawling = false;
10571063

10581064
// register assignments
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { VISITOR_KEYS } from "@babel/types";
2+
import type * as t from "@babel/types";
3+
import type { HubInterface, Visitor } from "../index.ts";
4+
import { NodePath } from "../index.ts";
5+
import { explode } from "../visitors.ts";
6+
import { setScope } from "../path/context.ts";
7+
8+
export default function traverseForScope(
9+
path: NodePath,
10+
visitors: Visitor,
11+
state: any,
12+
) {
13+
const exploded = explode(visitors);
14+
15+
if (exploded.enter || exploded.exit) {
16+
throw new Error("Should not be used with enter/exit visitors.");
17+
}
18+
19+
_traverse(
20+
path.parentPath,
21+
path.parent,
22+
path.node,
23+
path.container,
24+
path.key,
25+
path.listKey,
26+
path.hub,
27+
path,
28+
);
29+
30+
function _traverse(
31+
parentPath: NodePath,
32+
parent: t.Node,
33+
node: t.Node,
34+
container: t.Node | t.Node[],
35+
key: string | number,
36+
listKey: string,
37+
hub?: HubInterface,
38+
inPath?: NodePath,
39+
) {
40+
if (!node) {
41+
return;
42+
}
43+
44+
const path =
45+
inPath ||
46+
NodePath.get({
47+
hub,
48+
parentPath,
49+
parent,
50+
container,
51+
listKey,
52+
key,
53+
});
54+
55+
setScope.call(path);
56+
57+
const visitor = exploded[node.type];
58+
if (visitor) {
59+
if (visitor.enter) {
60+
for (const visit of visitor.enter) {
61+
visit.call(state, path, state);
62+
}
63+
}
64+
if (visitor.exit) {
65+
for (const visit of visitor.exit) {
66+
visit.call(state, path, state);
67+
}
68+
}
69+
}
70+
if (path.shouldSkip) {
71+
return;
72+
}
73+
74+
const keys = VISITOR_KEYS[node.type];
75+
if (!keys?.length) {
76+
return;
77+
}
78+
79+
for (const key of keys) {
80+
// @ts-expect-error key must present in node
81+
const prop = node[key];
82+
if (!prop) continue;
83+
if (Array.isArray(prop)) {
84+
for (let i = 0; i < prop.length; i++) {
85+
const value = prop[i];
86+
_traverse(path, node, value, prop, i, key);
87+
}
88+
} else {
89+
_traverse(path, node, prop, node, key, null);
90+
}
91+
}
92+
}
93+
}

packages/babel-traverse/src/visitors.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,9 @@ function explode$1<S>(visitor: Visitor<S>): ExplodedVisitor<S> {
104104
const types = virtualTypes[nodeType];
105105
if (types !== null) {
106106
for (const type of types) {
107-
// merge the visitor if necessary or just put it back in
108-
if (visitor[type]) {
109-
mergePair(visitor[type], fns);
110-
} else {
111-
// @ts-expect-error Expression produces too complex union
112-
visitor[type] = fns;
113-
}
107+
// @ts-expect-error Expression produces too complex union
108+
visitor[type] ??= {};
109+
mergePair(visitor[type], fns);
114110
}
115111
} else {
116112
mergePair(visitor, fns);

packages/babel-traverse/test/scope.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,31 @@ describe("scope", () => {
291291
).toBe("outside");
292292
});
293293

294+
it.each([
295+
`switch (a) { default: let a = "inside" }`,
296+
`class foo { @a m() { var a = "inside"; } }`,
297+
`class foo { [a]() { var a = "inside" } }`,
298+
])("scope hasGlobal with crawl: `%s`", function (code) {
299+
let hasGlobal;
300+
traverse(
301+
parse(code, {
302+
plugins: [["decorators", { version: "2025-03" }]],
303+
}),
304+
{
305+
Identifier(path) {
306+
if (
307+
path.node.name === "a" &&
308+
path.parent.type !== "VariableDeclarator" &&
309+
path.parent.type !== "AssignmentPattern"
310+
) {
311+
hasGlobal = path.scope.hasGlobal("a");
312+
}
313+
},
314+
},
315+
);
316+
expect(hasGlobal).toBe(true);
317+
});
318+
294319
it("variable declaration", function () {
295320
expect(getPath("var foo = null;").scope.getBinding("foo").path.type).toBe(
296321
"VariableDeclarator",

0 commit comments

Comments
 (0)