diff --git a/packages/babel-traverse/src/path/context.js b/packages/babel-traverse/src/path/context.js index faa5e9d3f1..47c8f831f4 100644 --- a/packages/babel-traverse/src/path/context.js +++ b/packages/babel-traverse/src/path/context.js @@ -67,7 +67,12 @@ export function visit(): boolean { return false; } - if (this.call("enter") || this.shouldSkip) { + // Note: We need to check "this.shouldSkip" twice because + // the visitor can set it to true. Usually .shouldSkip is false + // before calling the enter visitor, but it can be true in case of + // a requeued node (e.g. by .replaceWith()) that is then marked + // with .skip(). + if (this.shouldSkip || this.call("enter") || this.shouldSkip) { this.debug("Skip..."); return this.shouldStop; } @@ -233,6 +238,14 @@ export function setKey(key) { export function requeue(pathToQueue = this) { if (pathToQueue.removed) return; + // TODO: Uncomment in Babel 8. If a path is skipped, and then replaced with a + // new one, the new one shouldn't probably be skipped. + // Note that this currently causes an infinite loop because of + // packages/babel-plugin-transform-block-scoping/src/tdz.js#L52-L59 + // (b5b8055cc00756f94bf71deb45f288738520ee3c) + // + // pathToQueue.shouldSkip = false; + // TODO(loganfsmyth): This should be switched back to queue in parent contexts // automatically once #2892 and #4135 have been resolved. See #4140. // let contexts = this._getQueueContexts(); diff --git a/packages/babel-traverse/test/traverse.js b/packages/babel-traverse/test/traverse.js index a4b391c0b2..316c2e12c0 100644 --- a/packages/babel-traverse/test/traverse.js +++ b/packages/babel-traverse/test/traverse.js @@ -1,6 +1,7 @@ import cloneDeep from "lodash/cloneDeep"; import traverse from "../lib"; import { parse } from "@babel/parser"; +import * as t from "@babel/types"; describe("traverse", function() { const code = ` @@ -174,4 +175,45 @@ describe("traverse", function() { expect(p).not.toBe(scopes[i]); }); }); + + describe("path.skip()", function() { + it("replaced paths can be skipped", function() { + const ast = parse("id"); + + let skipped; + traverse(ast, { + noScope: true, + Identifier(path) { + path.replaceWith(t.numericLiteral(0)); + path.skip(); + skipped = true; + }, + NumericLiteral() { + skipped = false; + }, + }); + + expect(skipped).toBe(true); + }); + + // Skipped: see the comment in the `NodePath.requque` method. + it.skip("skipped and requeued paths should be visited", function() { + const ast = parse("id"); + + let visited = false; + traverse(ast, { + noScope: true, + Identifier(path) { + path.replaceWith(t.numericLiteral(0)); + path.skip(); + path.requeue(); + }, + NumericLiteral() { + visited = true; + }, + }); + + expect(visited).toBe(true); + }); + }); });