diff --git a/packages/babel-traverse/src/path/context.ts b/packages/babel-traverse/src/path/context.ts index 3a9aa5620cf3..44fd54bee868 100644 --- a/packages/babel-traverse/src/path/context.ts +++ b/packages/babel-traverse/src/path/context.ts @@ -119,6 +119,10 @@ export function setScope(this: NodePath) { if (this.opts && this.opts.noScope) return; let path = this.parentPath; + + // Skip method scope if is computed method key + if (this.key === "key" && path.isMethod()) path = path.parentPath; + let target; while (path && !target) { if (path.opts && path.opts.noScope) return; diff --git a/packages/babel-traverse/src/scope/index.ts b/packages/babel-traverse/src/scope/index.ts index e592fc1e2fc7..169f8a585e98 100644 --- a/packages/babel-traverse/src/scope/index.ts +++ b/packages/babel-traverse/src/scope/index.ts @@ -359,7 +359,16 @@ export default class Scope { static contextVariables = ["arguments", "undefined", "Infinity", "NaN"]; get parent() { - const parent = this.path.findParent(p => p.isScope()); + let parent, + path = this.path; + do { + // Skip method scope if coming from inside computed key + const isKey = path.key === "key"; + path = path.parentPath; + if (isKey && path.isMethod()) path = path.parentPath; + if (path && path.isScope()) parent = path; + } while (path && !parent); + return parent?.scope; } diff --git a/packages/babel-traverse/test/scope.js b/packages/babel-traverse/test/scope.js index ed0bbd9d8302..67590ac396f5 100644 --- a/packages/babel-traverse/test/scope.js +++ b/packages/babel-traverse/test/scope.js @@ -168,6 +168,74 @@ describe("scope", () => { }); }); + describe("computed method key", () => { + describe("should not have visibility of declarations inside method body", () => { + it("when path is computed key", () => { + expect( + getPath(`var a = "outside"; ({ [a]() { let a = "inside" } })`) + .get("body.1.expression.properties.0.key") + .scope.getBinding("a").path.node.init.value, + ).toBe("outside"); + + expect( + getPath( + `var a = "outside"; class foo { [a]() { let a = "inside" } }`, + ) + .get("body.1.body.body.0.key") + .scope.getBinding("a").path.node.init.value, + ).toBe("outside"); + }); + + it("when path is in nested scope which is computed key", () => { + expect( + getPath(`var a = "outside"; ({ [() => a]() { let a = "inside" } })`) + .get("body.1.expression.properties.0.key.body") + .scope.getBinding("a").path.node.init.value, + ).toBe("outside"); + + expect( + getPath( + `var a = "outside"; class foo { [() => a]() { let a = "inside" } }`, + ) + .get("body.1.body.body.0.key.body") + .scope.getBinding("a").path.node.init.value, + ).toBe("outside"); + }); + + it("when path is in nested scope within computed key", () => { + expect( + getPath( + `var a = "outside"; ({ [(() => a)() + ""]() { let a = "inside" } })`, + ) + .get("body.1.expression.properties.0.key.left.callee.body") + .scope.getBinding("a").path.node.init.value, + ).toBe("outside"); + + expect( + getPath( + `var a = "outside"; class foo { [(() => a)() + ""]() { let a = "inside" } }`, + ) + .get("body.1.body.body.0.key.left.callee.body") + .scope.getBinding("a").path.node.init.value, + ).toBe("outside"); + }); + }); + + it("should not have visibility on parameter bindings", () => { + expect( + getPath(`var a = "outside"; ({ [a](a = "inside") {} })`) + .get("body.1.expression.properties.0.key") + .scope.getBinding("a").path.node.init.value, + ).toBe("outside"); + + expect( + getPath(`var a = "outside"; class foo { [a](a = "inside") {} }`) + .get("body.1.body.body.0.key") + .scope.getBinding("a").path.node.init.value, + ).toBe("outside"); + }); + }); + it("variable declaration", function () { expect(getPath("var foo = null;").scope.getBinding("foo").path.type).toBe( "VariableDeclarator",