fix: forward stop signal to parent path (#14105)

This commit is contained in:
Huáng Jùnliàng 2022-01-06 12:38:07 -05:00 committed by GitHub
parent d158a48945
commit b9ba4f9de8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 108 additions and 14 deletions

View File

@ -1,4 +1,3 @@
import TraversalContext from "./context";
import * as visitors from "./visitors"; import * as visitors from "./visitors";
import { VISITOR_KEYS, removeProperties, traverseFast } from "@babel/types"; import { VISITOR_KEYS, removeProperties, traverseFast } from "@babel/types";
import type * as t from "@babel/types"; import type * as t from "@babel/types";
@ -6,6 +5,7 @@ import * as cache from "./cache";
import type NodePath from "./path"; import type NodePath from "./path";
import type { default as Scope, Binding } from "./scope"; import type { default as Scope, Binding } from "./scope";
import type { Visitor } from "./types"; import type { Visitor } from "./types";
import { traverseNode } from "./traverse-node";
export type { Visitor, Binding }; export type { Visitor, Binding };
export { default as NodePath } from "./path"; export { default as NodePath } from "./path";
@ -64,7 +64,7 @@ function traverse(
visitors.explode(opts); visitors.explode(opts);
traverse.node(parent, opts, scope, state, parentPath); traverseNode(parent, opts, scope, state, parentPath);
} }
export default traverse; export default traverse;
@ -82,17 +82,11 @@ traverse.node = function (
opts: TraverseOptions, opts: TraverseOptions,
scope?: Scope, scope?: Scope,
state?: any, state?: any,
parentPath?: NodePath, path?: NodePath,
skipKeys?, skipKeys?: string[],
) { ) {
const keys = VISITOR_KEYS[node.type]; traverseNode(node, opts, scope, state, path, skipKeys);
if (!keys) return; // traverse.node always returns undefined
const context = new TraversalContext(scope, opts, state, parentPath);
for (const key of keys) {
if (skipKeys && skipKeys[key]) continue;
if (context.visit(node, key)) return;
}
}; };
traverse.clearNode = function (node: t.Node, opts?) { traverse.clearNode = function (node: t.Node, opts?) {

View File

@ -1,6 +1,6 @@
// This file contains methods responsible for maintaining a TraversalContext. // This file contains methods responsible for maintaining a TraversalContext.
import traverse from "../index"; import { traverseNode } from "../traverse-node";
import { SHOULD_SKIP, SHOULD_STOP } from "./index"; import { SHOULD_SKIP, SHOULD_STOP } from "./index";
import type TraversalContext from "../context"; import type TraversalContext from "../context";
import type NodePath from "./index"; import type NodePath from "./index";
@ -95,7 +95,7 @@ export function visit(this: NodePath): boolean {
restoreContext(this, currentContext); restoreContext(this, currentContext);
this.debug("Recursing into..."); this.debug("Recursing into...");
traverse.node( this.shouldStop = traverseNode(
this.node, this.node,
this.opts, this.opts,
this.scope, this.scope,

View File

@ -0,0 +1,40 @@
import TraversalContext from "./context";
import type { TraverseOptions } from "./index";
import type NodePath from "./path";
import type Scope from "./scope";
import type * as t from "@babel/types";
import { VISITOR_KEYS } from "@babel/types";
/**
* Traverse the children of given node
* @param {Node} node
* @param {TraverseOptions} opts The traverse options used to create a new traversal context
* @param {scope} scope A traversal scope used to create a new traversal context. When opts.noScope is true, scope should not be provided
* @param {any} state A user data storage provided as the second callback argument for traversal visitors
* @param {NodePath} path A NodePath of given node
* @param {string[]} skipKeys A list of key names that should be skipped during traversal. The skipKeys are applied to every descendants
* @returns {boolean} Whether the traversal stops early
* @note This function does not visit the given `node`.
*/
export function traverseNode(
node: t.Node,
opts: TraverseOptions,
scope?: Scope,
state?: any,
path?: NodePath,
skipKeys?: string[],
): boolean {
const keys = VISITOR_KEYS[node.type];
if (!keys) return false;
const context = new TraversalContext(scope, opts, state, path);
for (const key of keys) {
if (skipKeys && skipKeys[key]) continue;
if (context.visit(node, key)) {
return true;
}
}
return false;
}

View File

@ -277,4 +277,64 @@ describe("traverse", function () {
expect(blockStatementVisitedCounter).toBe(1); expect(blockStatementVisitedCounter).toBe(1);
}); });
}); });
describe("path.stop()", () => {
it("should stop the traversal when a grand child is stopped", () => {
const ast = parse("f;g;");
let visitedCounter = 0;
traverse(ast, {
noScope: true,
Identifier(path) {
visitedCounter += 1;
path.stop();
},
});
expect(visitedCounter).toBe(1);
});
it("can be reverted in the exit listener of the parent whose child is stopped", () => {
const ast = parse("f;g;");
let visitedCounter = 0;
traverse(ast, {
noScope: true,
Identifier(path) {
visitedCounter += 1;
path.stop();
},
ExpressionStatement: {
exit(path) {
path.shouldStop = false;
path.shouldSkip = false;
},
},
});
expect(visitedCounter).toBe(2);
});
it("should not affect root traversal", () => {
const ast = parse("f;g;");
let visitedCounter = 0;
let programShouldStop;
traverse(ast, {
noScope: true,
Program(path) {
path.traverse({
noScope: true,
Identifier(path) {
visitedCounter += 1;
path.stop();
},
});
programShouldStop = path.shouldStop;
},
});
expect(visitedCounter).toBe(1);
expect(programShouldStop).toBe(false);
});
});
}); });