Skip to content

Commit 65a6bca

Browse files
[babel 8] Use NodePath#hub as part of the paths cache key (#15759)
1 parent b23b0eb commit 65a6bca

File tree

12 files changed

+121
-23
lines changed

12 files changed

+121
-23
lines changed

.github/workflows/e2e-tests-breaking.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ jobs:
5050
# https://github.com/facebook/metro/blob/29bb5f2ad3319ba8f4764c3993aa85c15f59af23/packages/metro-source-map/src/generateFunctionMap.js#L182
5151
# react-native
5252
- prettier
53-
- angular-cli
53+
# disabled due to https://github.com/babel/babel/pull/15759, because
54+
# angular pins the @babel/core dependency
55+
# - angular-cli
5456
steps:
5557
- name: Get yarn1 cache directory path
5658
id: yarn1-cache-dir-path

packages/babel-core/src/transformation/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,11 @@ function* transformFile(file: File, pluginPasses: PluginPasses): Handler<void> {
118118
passes,
119119
file.opts.wrapPluginVisitorMethod,
120120
);
121-
traverse(file.ast, visitor, file.scope);
121+
if (process.env.BABEL_8_BREAKING) {
122+
traverse(file.ast.program, visitor, file.scope, null, file.path, true);
123+
} else {
124+
traverse(file.ast, visitor, file.scope);
125+
}
122126

123127
for (const [plugin, pass] of passPairs) {
124128
const fn = plugin.post;

packages/babel-traverse/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"globals": "condition:BABEL_8_BREAKING ? ^13.5.0 : ^11.1.0"
2929
},
3030
"devDependencies": {
31+
"@babel/core": "workspace:^",
3132
"@babel/helper-plugin-test-runner": "workspace:^"
3233
},
3334
"engines": {
Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import type { Node } from "@babel/types";
22
import type NodePath from "./path";
33
import type Scope from "./scope";
4+
import type { HubInterface } from "./hub";
45

5-
export let path: WeakMap<Node, Map<Node, NodePath>> = new WeakMap();
6+
let pathsCache: WeakMap<
7+
HubInterface | typeof nullHub,
8+
WeakMap<Node, Map<Node, NodePath>>
9+
> = new WeakMap();
10+
export { pathsCache as path };
611
export let scope: WeakMap<Node, Scope> = new WeakMap();
712

813
export function clear() {
@@ -11,9 +16,39 @@ export function clear() {
1116
}
1217

1318
export function clearPath() {
14-
path = new WeakMap();
19+
pathsCache = new WeakMap();
1520
}
1621

1722
export function clearScope() {
1823
scope = new WeakMap();
1924
}
25+
26+
// NodePath#hub can be null, but it's not a valid weakmap key because it
27+
// cannot be collected by GC. Use an object, knowing tht it will not be
28+
// collected anyway. It's not a memory leak because pathsCache.get(nullHub)
29+
// is itself a weakmap, so its entries can still be collected.
30+
const nullHub = Object.freeze({} as const);
31+
32+
export function getCachedPaths(hub: HubInterface | null, parent: Node) {
33+
if (!process.env.BABEL_8_BREAKING) {
34+
// Only use Hub as part of the cache key in Babel 8, because it is a
35+
// breaking change (it causes incompatibilities with older `@babel/core`
36+
// versions: see https://github.com/babel/babel/pull/15759)
37+
hub = null;
38+
}
39+
return pathsCache.get(hub ?? nullHub)?.get(parent);
40+
}
41+
42+
export function getOrCreateCachedPaths(hub: HubInterface | null, parent: Node) {
43+
if (!process.env.BABEL_8_BREAKING) {
44+
hub = null;
45+
}
46+
47+
let parents = pathsCache.get(hub ?? nullHub);
48+
if (!parents) pathsCache.set(hub ?? nullHub, (parents = new WeakMap()));
49+
50+
let paths = parents.get(parent);
51+
if (!paths) parents.set(parent, (paths = new Map()));
52+
53+
return paths;
54+
}

packages/babel-traverse/src/index.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ function traverse<S>(
3636
scope: Scope | undefined,
3737
state: S,
3838
parentPath?: NodePath,
39+
visitSelf?: boolean,
3940
): void;
4041

4142
function traverse(
@@ -44,6 +45,7 @@ function traverse(
4445
scope?: Scope,
4546
state?: any,
4647
parentPath?: NodePath,
48+
visitSelf?: boolean,
4749
): void;
4850

4951
function traverse<Options extends TraverseOptions>(
@@ -53,6 +55,7 @@ function traverse<Options extends TraverseOptions>(
5355
scope?: Scope,
5456
state?: any,
5557
parentPath?: NodePath,
58+
visitSelf?: boolean,
5659
) {
5760
if (!parent) return;
5861

@@ -66,13 +69,25 @@ function traverse<Options extends TraverseOptions>(
6669
}
6770
}
6871

72+
if (!parentPath && visitSelf) {
73+
throw new Error("visitSelf can only be used when providing a NodePath.");
74+
}
75+
6976
if (!VISITOR_KEYS[parent.type]) {
7077
return;
7178
}
7279

7380
visitors.explode(opts as Visitor);
7481

75-
traverseNode(parent, opts as ExplodedVisitor, scope, state, parentPath);
82+
traverseNode(
83+
parent,
84+
opts as ExplodedVisitor,
85+
scope,
86+
state,
87+
parentPath,
88+
/* skipKeys */ null,
89+
visitSelf,
90+
);
7691
}
7792

7893
export default traverse;
@@ -100,8 +115,6 @@ traverse.node = function (
100115

101116
traverse.clearNode = function (node: t.Node, opts?: RemovePropertiesOptions) {
102117
removeProperties(node, opts);
103-
104-
cache.path.delete(node);
105118
};
106119

107120
traverse.removeProperties = function (

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { Visitor } from "../types";
88
import Scope from "../scope";
99
import { validate } from "@babel/types";
1010
import * as t from "@babel/types";
11-
import { path as pathCache } from "../cache";
11+
import * as cache from "../cache";
1212
import generator from "@babel/generator";
1313

1414
// NodePath is split across many files.
@@ -92,11 +92,7 @@ class NodePath<T extends t.Node = t.Node> {
9292
// @ts-expect-error key must present in container
9393
container[key];
9494

95-
let paths = pathCache.get(parent);
96-
if (!paths) {
97-
paths = new Map();
98-
pathCache.set(parent, paths);
99-
}
95+
const paths = cache.getOrCreateCachedPaths(hub, parent);
10096

10197
let path = paths.get(targetNode);
10298
if (!path) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// This file contains methods that modify the path/node in some ways.
22

3-
import { path as pathCache } from "../cache";
3+
import { getCachedPaths } from "../cache";
44
import PathHoister from "./lib/hoister";
55
import NodePath from "./index";
66
import {
@@ -279,7 +279,7 @@ export function updateSiblingKeys(
279279
) {
280280
if (!this.parent) return;
281281

282-
const paths = pathCache.get(this.parent);
282+
const paths = getCachedPaths(this.hub, this.parent) || ([] as never[]);
283283

284284
for (const [, path] of paths) {
285285
if (typeof path.key === "number" && path.key >= fromIndex) {

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// This file contains methods responsible for removing a node.
22

33
import { hooks } from "./lib/removal-hooks";
4-
import { path as pathCache } from "../cache";
4+
import { getCachedPaths } from "../cache";
55
import type NodePath from "./index";
66
import { REMOVED, SHOULD_SKIP } from "./index";
77

@@ -46,7 +46,9 @@ export function _remove(this: NodePath) {
4646
export function _markRemoved(this: NodePath) {
4747
// this.shouldSkip = true; this.removed = true;
4848
this._traverseFlags |= SHOULD_SKIP | REMOVED;
49-
if (this.parent) pathCache.get(this.parent).delete(this.node);
49+
if (this.parent) {
50+
getCachedPaths(this.hub, this.parent).delete(this.node);
51+
}
5052
this.node = null;
5153
}
5254

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { codeFrameColumns } from "@babel/code-frame";
44
import traverse from "../index";
55
import NodePath from "./index";
6-
import { path as pathCache } from "../cache";
6+
import { getCachedPaths } from "../cache";
77
import { parse } from "@babel/parser";
88
import {
99
FUNCTION_TYPES,
@@ -47,7 +47,7 @@ export function replaceWithMultiple(
4747
nodes = this._verifyNodeList(nodes);
4848
inheritLeadingComments(nodes[0], this.node);
4949
inheritTrailingComments(nodes[nodes.length - 1], this.node);
50-
pathCache.get(this.parent)?.delete(this.node);
50+
getCachedPaths(this.hub, this.parent)?.delete(this.node);
5151
this.node =
5252
// @ts-expect-error this.key must present in this.container
5353
this.container[this.key] = null;
@@ -210,7 +210,7 @@ export function _replaceWith(this: NodePath, node: t.Node) {
210210
}
211211

212212
this.debug(`Replace with ${node?.type}`);
213-
pathCache.get(this.parent)?.set(node, this).delete(this.node);
213+
getCachedPaths(this.hub, this.parent)?.set(node, this).delete(this.node);
214214

215215
this.node =
216216
// @ts-expect-error this.key must present in this.container

packages/babel-traverse/src/traverse-node.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,19 @@ export function traverseNode<S = unknown>(
2424
state?: any,
2525
path?: NodePath,
2626
skipKeys?: Record<string, boolean>,
27+
visitSelf?: boolean,
2728
): boolean {
2829
const keys = VISITOR_KEYS[node.type];
2930
if (!keys) return false;
3031

3132
const context = new TraversalContext(scope, opts, state, path);
33+
if (visitSelf) {
34+
if (skipKeys?.[path.parentKey]) return false;
35+
return context.visitQueue([path]);
36+
}
37+
3238
for (const key of keys) {
33-
if (skipKeys && skipKeys[key]) continue;
39+
if (skipKeys?.[key]) continue;
3440
if (context.visit(node, key)) {
3541
return true;
3642
}

0 commit comments

Comments
 (0)