Skip to content

Commit d842911

Browse files
Fix .parentPath after rename in SwitchCase (#15287)
1 parent 87e48e7 commit d842911

3 files changed

Lines changed: 41 additions & 9 deletions

File tree

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,14 @@ export default class Scope {
457457
traverse(node: t.Node | t.Node[], opts?: TraverseOptions, state?: any): void;
458458
/**
459459
* Traverse node with current scope and path.
460+
*
461+
* !!! WARNING !!!
462+
* This method assumes that `this.path` is the NodePath representing `node`.
463+
* After running the traversal, the `.parentPath` of the NodePaths
464+
* corresponding to `node`'s children will be set to `this.path`.
465+
*
466+
* There is no good reason to use this method, since the only safe way to use
467+
* it is equivalent to `scope.path.traverse(opts, state)`.
460468
*/
461469
traverse<S>(node: any, opts: any, state?: S) {
462470
traverse(node, opts, this, state, this.path);

packages/babel-traverse/src/scope/lib/renamer.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import splitExportDeclaration from "@babel/helper-split-export-declaration";
33
import * as t from "@babel/types";
44
import type { NodePath, Visitor } from "../..";
55
import { requeueComputedKeyAndDecorators } from "@babel/helper-environment-visitor";
6+
import { traverseNode } from "../../traverse-node";
7+
import { explode } from "../../visitors";
68

79
const renameVisitor: Visitor<Renamer> = {
810
ReferencedIdentifier({ node }, state) {
@@ -111,6 +113,7 @@ export default class Renamer {
111113
// );
112114
}
113115

116+
// TODO(Babel 8): Remove this `block` parameter. It's not needed anywhere.
114117
rename(block?: t.Pattern | t.Scopable) {
115118
const { binding, oldName, newName } = this;
116119
const { scope, path } = binding;
@@ -130,15 +133,16 @@ export default class Renamer {
130133
}
131134
}
132135

133-
const blockToTraverse = block || scope.block;
134-
if (blockToTraverse?.type === "SwitchStatement") {
135-
// discriminant is not part of current scope, should be skipped.
136-
blockToTraverse.cases.forEach(c => {
137-
scope.traverse(c, renameVisitor, this);
138-
});
139-
} else {
140-
scope.traverse(blockToTraverse, renameVisitor, this);
141-
}
136+
traverseNode(
137+
block || scope.block,
138+
explode(renameVisitor),
139+
scope,
140+
this,
141+
scope.path,
142+
// When blockToTraverse is a SwitchStatement, the discriminant
143+
// is not part of the current scope and thus should be skipped.
144+
{ discriminant: true },
145+
);
142146

143147
if (!block) {
144148
scope.removeOwnBinding(oldName);

packages/babel-traverse/test/scope.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,4 +1019,24 @@ describe("scope", () => {
10191019
expect(program.scope.hasOwnBinding("ref")).toBe(true);
10201020
});
10211021
});
1022+
1023+
describe("rename", () => {
1024+
it(".parentPath after renaming variable in switch", () => {
1025+
const program = getPath(`
1026+
switch (x) {
1027+
case y:
1028+
let a;
1029+
}
1030+
`);
1031+
program.traverse({
1032+
VariableDeclaration(path) {
1033+
if (path.node.declarations[0].id.name !== "a") return;
1034+
1035+
expect(path.parentPath.type).toBe("SwitchCase");
1036+
path.scope.rename("a");
1037+
expect(path.parentPath.type).toBe("SwitchCase");
1038+
},
1039+
});
1040+
});
1041+
});
10221042
});

0 commit comments

Comments
 (0)