Skip to content

Commit b1de75f

Browse files
nullableVoidPtrliuxingbaoyunicolo-ribaudo
authored
Refactor visitors merging (#15702)
Co-authored-by: liuxingbaoyu <[email protected]> Co-authored-by: Nicolò Ribaudo <[email protected]>
1 parent 4986833 commit b1de75f

File tree

4 files changed

+157
-33
lines changed

4 files changed

+157
-33
lines changed

packages/babel-core/src/config/validation/plugins.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@ const VALIDATORS: ValidatorSet = {
3838
function assertVisitorMap(loc: OptionPath, value: unknown): Visitor {
3939
const obj = assertObject(loc, value);
4040
if (obj) {
41-
Object.keys(obj).forEach(prop => assertVisitorHandler(prop, obj[prop]));
41+
Object.keys(obj).forEach(prop => {
42+
if (prop !== "_exploded" && prop !== "_verified") {
43+
assertVisitorHandler(prop, obj[prop]);
44+
}
45+
});
4246

4347
if (obj.enter || obj.exit) {
4448
throw new Error(
@@ -54,7 +58,7 @@ function assertVisitorMap(loc: OptionPath, value: unknown): Visitor {
5458
function assertVisitorHandler(
5559
key: string,
5660
value: unknown,
57-
): VisitorHandler | void {
61+
): asserts value is VisitorHandler {
5862
if (value && typeof value === "object") {
5963
Object.keys(value).forEach((handler: string) => {
6064
if (handler !== "enter" && handler !== "exit") {
@@ -66,8 +70,6 @@ function assertVisitorHandler(
6670
} else if (typeof value !== "function") {
6771
throw new Error(`.visitor["${key}"] must be a function`);
6872
}
69-
70-
return value as any;
7173
}
7274

7375
type VisitorHandler =

packages/babel-traverse/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ type VisitNodeObject<S, P extends t.Node> = {
88
[K in VisitPhase]?: VisitNodeFunction<S, P>;
99
};
1010

11-
type ExplVisitNode<S, P extends t.Node> = {
11+
export type ExplVisitNode<S, P extends t.Node> = {
1212
[K in VisitPhase]?: VisitNodeFunction<S, P>[];
1313
};
1414

packages/babel-traverse/src/visitors.ts

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as virtualTypes from "./path/lib/virtual-types";
2+
import type { Node } from "@babel/types";
23
import {
34
DEPRECATED_KEYS,
45
DEPRECATED_ALIASES,
@@ -7,11 +8,17 @@ import {
78
__internal__deprecationWarning as deprecationWarning,
89
} from "@babel/types";
910
import type { ExplodedVisitor, NodePath, Visitor } from "./index";
11+
import type { ExplVisitNode, VisitNodeFunction, VisitPhase } from "./types";
1012

1113
type VIRTUAL_TYPES = keyof typeof virtualTypes;
1214
function isVirtualType(type: string): type is VIRTUAL_TYPES {
1315
return type in virtualTypes;
1416
}
17+
export type VisitWrapper<S = any> = (
18+
stateName: string | undefined,
19+
visitorType: VisitPhase,
20+
callback: VisitNodeFunction<S, Node>,
21+
) => VisitNodeFunction<S, Node>;
1522

1623
export function isExplodedVisitor(
1724
visitor: Visitor,
@@ -29,7 +36,6 @@ export function isExplodedVisitor(
2936
* * `Identifier() { ... }` -> `Identifier: { enter() { ... } }`
3037
* * `"Identifier|NumericLiteral": { ... }` -> `Identifier: { ... }, NumericLiteral: { ... }`
3138
* * Aliases in `@babel/types`: e.g. `Property: { ... }` -> `ObjectProperty: { ... }, ClassProperty: { ... }`
32-
*
3339
* Other normalizations are:
3440
* * Visitors of virtual types are wrapped, so that they are only visited when
3541
* their dynamic check passes
@@ -213,51 +219,67 @@ function validateVisitorMethods(
213219
}
214220
}
215221

216-
export function merge<State>(visitors: Visitor<State>[]): Visitor<State>;
222+
export function merge<State>(
223+
visitors: Visitor<State>[],
224+
): ExplodedVisitor<State>;
217225
export function merge(
218226
visitors: Visitor<unknown>[],
219227
states?: any[],
220228
wrapper?: Function | null,
221-
): Visitor<unknown>;
229+
): ExplodedVisitor<unknown>;
222230
export function merge(
223231
visitors: any[],
224232
states: any[] = [],
225-
wrapper?: Function | null,
226-
) {
227-
const rootVisitor: Visitor = {};
233+
wrapper?: VisitWrapper | null,
234+
): ExplodedVisitor {
235+
// @ts-expect-error don't bother with internal flags so it can work with earlier @babel/core validations
236+
const mergedVisitor: ExplodedVisitor = {};
228237

229238
for (let i = 0; i < visitors.length; i++) {
230-
const visitor = visitors[i];
239+
const visitor = explode(visitors[i]);
231240
const state = states[i];
232241

233-
explode(visitor);
242+
let topVisitor: ExplVisitNode<unknown, Node> = visitor;
243+
if (state || wrapper) {
244+
topVisitor = wrapWithStateOrWrapper(topVisitor, state, wrapper);
245+
}
246+
mergePair(mergedVisitor, topVisitor);
247+
248+
for (const key of Object.keys(visitor) as (keyof ExplodedVisitor)[]) {
249+
if (shouldIgnoreKey(key)) continue;
234250

235-
for (const type of Object.keys(visitor) as (keyof Visitor)[]) {
236-
let visitorType = visitor[type];
251+
let typeVisitor = visitor[key];
237252

238253
// if we have state or wrapper then overload the callbacks to take it
239254
if (state || wrapper) {
240-
visitorType = wrapWithStateOrWrapper(visitorType, state, wrapper);
255+
typeVisitor = wrapWithStateOrWrapper(typeVisitor, state, wrapper);
241256
}
242257

243-
// @ts-expect-error: Expression produces a union type that is too complex to represent.
244-
const nodeVisitor = (rootVisitor[type] ||= {});
245-
mergePair(nodeVisitor, visitorType);
258+
const nodeVisitor = (mergedVisitor[key] ||= {});
259+
mergePair(nodeVisitor, typeVisitor);
246260
}
247261
}
248262

249-
return rootVisitor;
263+
if (process.env.BABEL_8_BREAKING) {
264+
return {
265+
...mergedVisitor,
266+
_exploded: true,
267+
_verified: true,
268+
};
269+
}
270+
271+
return mergedVisitor;
250272
}
251273

252274
function wrapWithStateOrWrapper<State>(
253-
oldVisitor: Visitor<State>,
254-
state: State,
255-
wrapper?: Function | null,
256-
) {
257-
const newVisitor: Visitor = {};
275+
oldVisitor: ExplVisitNode<State, Node>,
276+
state: State | null,
277+
wrapper?: VisitWrapper<State> | null,
278+
): ExplVisitNode<State, Node> {
279+
const newVisitor: ExplVisitNode<State, Node> = {};
258280

259-
for (const key of Object.keys(oldVisitor) as (keyof Visitor<State>)[]) {
260-
let fns = oldVisitor[key];
281+
for (const phase of ["enter", "exit"] as VisitPhase[]) {
282+
let fns = oldVisitor[phase];
261283

262284
// not an enter/exit array of callbacks
263285
if (!Array.isArray(fns)) continue;
@@ -272,8 +294,8 @@ function wrapWithStateOrWrapper<State>(
272294
}
273295

274296
if (wrapper) {
275-
// @ts-expect-error Fixme: document state.key
276-
newFn = wrapper(state.key, key, newFn);
297+
// @ts-expect-error Fixme: actually PluginPass.key (aka pluginAlias)?
298+
newFn = wrapper(state?.key, phase, newFn);
277299
}
278300

279301
// Override toString in case this function is printed, we want to print the wrapped function, same as we do in `wrapCheck`
@@ -284,8 +306,7 @@ function wrapWithStateOrWrapper<State>(
284306
return newFn;
285307
});
286308

287-
// @ts-expect-error: Expression produces a union type that is too complex to represent.
288-
newVisitor[key] = fns;
309+
newVisitor[phase] = fns;
289310
}
290311

291312
return newVisitor;
@@ -321,6 +342,7 @@ function wrapCheck(nodeType: VIRTUAL_TYPES, fn: Function) {
321342
function shouldIgnoreKey(
322343
key: string,
323344
): key is
345+
| `_${string}`
324346
| "enter"
325347
| "exit"
326348
| "shouldSkip"
@@ -348,8 +370,15 @@ function shouldIgnoreKey(
348370
return false;
349371
}
350372

373+
/*
374+
function mergePair(
375+
dest: ExplVisitNode<unknown, Node>,
376+
src: ExplVisitNode<unknown, Node>,
377+
);
378+
*/
351379
function mergePair(dest: any, src: any) {
352-
for (const key of Object.keys(src)) {
353-
dest[key] = [].concat(dest[key] || [], src[key]);
380+
for (const phase of ["enter", "exit"] as VisitPhase[]) {
381+
if (!src[phase]) continue;
382+
dest[phase] = [].concat(dest[phase] || [], src[phase]);
354383
}
355384
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { parse } from "@babel/parser";
2+
3+
import _traverse, { visitors } from "../lib/index.js";
4+
const traverse = _traverse.default || _traverse;
5+
6+
describe("visitors", () => {
7+
describe("merge", () => {
8+
(process.env.BABEL_8_BREAKING ? it : it.skip)(
9+
"should set `_verified` and `_exploded` to `true` if merging catch-all visitors",
10+
() => {
11+
const visitor = visitors.merge([{ enter() {} }, { enter() {} }]);
12+
expect(visitor._verified).toBe(true);
13+
expect(visitor._exploded).toBe(true);
14+
},
15+
);
16+
17+
it("should work when merging node type visitors", () => {
18+
const ast = parse("1");
19+
const visitor = visitors.merge([
20+
{ ArrayExpression() {} },
21+
{ ArrayExpression() {} },
22+
]);
23+
traverse(ast, visitor);
24+
expect(visitor).toMatchInlineSnapshot(`
25+
Object {
26+
"ArrayExpression": Object {
27+
"enter": Array [
28+
[Function],
29+
[Function],
30+
],
31+
},
32+
"_exploded": true,
33+
"_verified": true,
34+
}
35+
`);
36+
});
37+
38+
it("enter", () => {
39+
const ast = parse("1");
40+
const visitor = visitors.merge([{ enter() {} }, { enter() {} }]);
41+
traverse(ast, visitor);
42+
expect(visitor).toMatchInlineSnapshot(`
43+
Object {
44+
"_exploded": true,
45+
"_verified": true,
46+
"enter": Array [
47+
[Function],
48+
[Function],
49+
],
50+
}
51+
`);
52+
});
53+
54+
it("enter with states", () => {
55+
const ast = parse("1");
56+
const visitor = visitors.merge(
57+
[{ enter() {} }, { enter() {} }],
58+
[{}, {}],
59+
);
60+
traverse(ast, visitor);
61+
expect(visitor).toMatchInlineSnapshot(`
62+
Object {
63+
"_exploded": true,
64+
"_verified": true,
65+
"enter": Array [
66+
[Function],
67+
[Function],
68+
],
69+
}
70+
`);
71+
});
72+
73+
it("enter with wrapper", () => {
74+
const ast = parse("1");
75+
const visitor = visitors.merge(
76+
[{ enter() {} }, { enter() {} }],
77+
[{}, {}],
78+
(stateKey, key, fn) => fn,
79+
);
80+
traverse(ast, visitor);
81+
expect(visitor).toMatchInlineSnapshot(`
82+
Object {
83+
"_exploded": true,
84+
"_verified": true,
85+
"enter": Array [
86+
[Function],
87+
[Function],
88+
],
89+
}
90+
`);
91+
});
92+
});
93+
});

0 commit comments

Comments
 (0)