diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/in-method-key-loose/input.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/in-method-key-loose/input.js new file mode 100644 index 000000000000..62117a1d29bf --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/in-method-key-loose/input.js @@ -0,0 +1,4 @@ +let x; +const a = { + [x.y?.z]() {} +}; diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/in-method-key-loose/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/in-method-key-loose/options.json new file mode 100644 index 000000000000..39ea3f99c7ff --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/in-method-key-loose/options.json @@ -0,0 +1,3 @@ +{ + "plugins": [["proposal-optional-chaining", { "loose": true }]] +} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/in-method-key-loose/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/in-method-key-loose/output.js new file mode 100644 index 000000000000..c73c6141bb2a --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/in-method-key-loose/output.js @@ -0,0 +1,7 @@ +var _x$y; + +let x; +const a = { + [(_x$y = x.y) == null ? void 0 : _x$y.z]() {} + +}; diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/in-method-key/input.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/in-method-key/input.js new file mode 100644 index 000000000000..62117a1d29bf --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/in-method-key/input.js @@ -0,0 +1,4 @@ +let x; +const a = { + [x.y?.z]() {} +}; diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/in-method-key/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/in-method-key/output.js new file mode 100644 index 000000000000..73c0e55449fc --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/in-method-key/output.js @@ -0,0 +1,7 @@ +var _x$y; + +let x; +const a = { + [(_x$y = x.y) === null || _x$y === void 0 ? void 0 : _x$y.z]() {} + +}; 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/src/scope/lib/renamer.ts b/packages/babel-traverse/src/scope/lib/renamer.ts index 28f753ae597c..e9d207d4d44a 100644 --- a/packages/babel-traverse/src/scope/lib/renamer.ts +++ b/packages/babel-traverse/src/scope/lib/renamer.ts @@ -17,7 +17,7 @@ const renameVisitor: Visitor = { state.binding.identifier, ) ) { - path.skip(); + skipAllButComputedMethodKey(path); } }, @@ -142,3 +142,18 @@ export default class Renamer { } } } + +function skipAllButComputedMethodKey(path) { + // If the path isn't method with computed key, just skip everything. + if (!path.isMethod() || !path.node.computed) { + path.skip(); + return; + } + + // So it's a method with a computed key. Make sure to skip every other key the + // traversal would visit. + const keys = t.VISITOR_KEYS[path.type]; + for (const key of keys) { + if (key !== "key") path.skipKey(key); + } +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-1/input.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-1/input.js new file mode 100644 index 000000000000..a1b30b3c28d9 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-1/input.js @@ -0,0 +1,8 @@ +let a = "outside"; + +const obj = { + [a]() { + let a = "inside"; + return a; + } +}; diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-1/options.json b/packages/babel-traverse/test/fixtures/rename/method-computed-key-1/options.json new file mode 100644 index 000000000000..14af0e5feac8 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-1/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["./plugin"] +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-1/output.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-1/output.js new file mode 100644 index 000000000000..87382cf31cf9 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-1/output.js @@ -0,0 +1,8 @@ +let z = "outside"; +const obj = { + [z]() { + let a = "inside"; + return a; + } + +}; diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-1/plugin.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-1/plugin.js new file mode 100644 index 000000000000..a364c62a4747 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-1/plugin.js @@ -0,0 +1,9 @@ +module.exports = function() { + return { + visitor: { + Program(path) { + path.scope.rename("a", "z"); + } + } + }; +}; diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-2/input.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-2/input.js new file mode 100644 index 000000000000..c921a639fb16 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-2/input.js @@ -0,0 +1,8 @@ +let a = "outside"; + +class C { + [a]() { + let a = "inside"; + return a; + } +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-2/options.json b/packages/babel-traverse/test/fixtures/rename/method-computed-key-2/options.json new file mode 100644 index 000000000000..14af0e5feac8 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-2/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["./plugin"] +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-2/output.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-2/output.js new file mode 100644 index 000000000000..70441057c388 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-2/output.js @@ -0,0 +1,9 @@ +let z = "outside"; + +class C { + [z]() { + let a = "inside"; + return a; + } + +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-2/plugin.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-2/plugin.js new file mode 100644 index 000000000000..a364c62a4747 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-2/plugin.js @@ -0,0 +1,9 @@ +module.exports = function() { + return { + visitor: { + Program(path) { + path.scope.rename("a", "z"); + } + } + }; +}; diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-3/input.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-3/input.js new file mode 100644 index 000000000000..851ca77ca048 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-3/input.js @@ -0,0 +1,7 @@ +let a = "outside"; + +const obj = { + [a](a = "inside") { + return a; + } +}; diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-3/options.json b/packages/babel-traverse/test/fixtures/rename/method-computed-key-3/options.json new file mode 100644 index 000000000000..14af0e5feac8 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-3/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["./plugin"] +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-3/output.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-3/output.js new file mode 100644 index 000000000000..1b7cdff1dd4a --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-3/output.js @@ -0,0 +1,7 @@ +let z = "outside"; +const obj = { + [z](a = "inside") { + return a; + } + +}; diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-3/plugin.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-3/plugin.js new file mode 100644 index 000000000000..a364c62a4747 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-3/plugin.js @@ -0,0 +1,9 @@ +module.exports = function() { + return { + visitor: { + Program(path) { + path.scope.rename("a", "z"); + } + } + }; +}; diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-4/input.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-4/input.js new file mode 100644 index 000000000000..7c063d89323d --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-4/input.js @@ -0,0 +1,7 @@ +let a = "outside"; + +class C { + [a](a = "inside") { + return a; + } +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-4/options.json b/packages/babel-traverse/test/fixtures/rename/method-computed-key-4/options.json new file mode 100644 index 000000000000..14af0e5feac8 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-4/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["./plugin"] +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-4/output.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-4/output.js new file mode 100644 index 000000000000..10d6576f6fb5 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-4/output.js @@ -0,0 +1,8 @@ +let z = "outside"; + +class C { + [z](a = "inside") { + return a; + } + +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-4/plugin.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-4/plugin.js new file mode 100644 index 000000000000..a364c62a4747 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-4/plugin.js @@ -0,0 +1,9 @@ +module.exports = function() { + return { + visitor: { + Program(path) { + path.scope.rename("a", "z"); + } + } + }; +}; diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-5/input.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-5/input.js new file mode 100644 index 000000000000..e4209826a56c --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-5/input.js @@ -0,0 +1,8 @@ +let a = "outside"; + +const obj = { + [(() => a)()]() { + let a = "inside"; + return a; + } +}; diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-5/options.json b/packages/babel-traverse/test/fixtures/rename/method-computed-key-5/options.json new file mode 100644 index 000000000000..14af0e5feac8 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-5/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["./plugin"] +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-5/output.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-5/output.js new file mode 100644 index 000000000000..5097fbcda760 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-5/output.js @@ -0,0 +1,8 @@ +let z = "outside"; +const obj = { + [(() => z)()]() { + let a = "inside"; + return a; + } + +}; diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-5/plugin.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-5/plugin.js new file mode 100644 index 000000000000..a364c62a4747 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-5/plugin.js @@ -0,0 +1,9 @@ +module.exports = function() { + return { + visitor: { + Program(path) { + path.scope.rename("a", "z"); + } + } + }; +}; diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-6/input.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-6/input.js new file mode 100644 index 000000000000..e4cc34a35a85 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-6/input.js @@ -0,0 +1,8 @@ +let a = "outside"; + +class C { + [(() => a)()]() { + let a = "inside"; + return a; + } +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-6/options.json b/packages/babel-traverse/test/fixtures/rename/method-computed-key-6/options.json new file mode 100644 index 000000000000..14af0e5feac8 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-6/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["./plugin"] +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-6/output.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-6/output.js new file mode 100644 index 000000000000..113244b17111 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-6/output.js @@ -0,0 +1,9 @@ +let z = "outside"; + +class C { + [(() => z)()]() { + let a = "inside"; + return a; + } + +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-6/plugin.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-6/plugin.js new file mode 100644 index 000000000000..a364c62a4747 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-6/plugin.js @@ -0,0 +1,9 @@ +module.exports = function() { + return { + visitor: { + Program(path) { + path.scope.rename("a", "z"); + } + } + }; +}; diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-7/input.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-7/input.js new file mode 100644 index 000000000000..f54c05e2df28 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-7/input.js @@ -0,0 +1,15 @@ +let a = "outside"; + +const obj = { + get [ + { + get [a]() { + let a = "inside"; + return a; + } + }.outside + ]() { + let a = "middle"; + return a; + } +}; diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-7/options.json b/packages/babel-traverse/test/fixtures/rename/method-computed-key-7/options.json new file mode 100644 index 000000000000..14af0e5feac8 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-7/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["./plugin"] +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-7/output.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-7/output.js new file mode 100644 index 000000000000..f7266d7c5006 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-7/output.js @@ -0,0 +1,14 @@ +let z = "outside"; +const obj = { + get [{ + get [z]() { + let a = "inside"; + return a; + } + + }.outside]() { + let a = "middle"; + return a; + } + +}; diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-7/plugin.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-7/plugin.js new file mode 100644 index 000000000000..a364c62a4747 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-7/plugin.js @@ -0,0 +1,9 @@ +module.exports = function() { + return { + visitor: { + Program(path) { + path.scope.rename("a", "z"); + } + } + }; +}; diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-8/input.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-8/input.js new file mode 100644 index 000000000000..91735d36ad34 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-8/input.js @@ -0,0 +1,15 @@ +let a = "outside"; + +class C { + static get [ + class D { + static get [a]() { + let a = "inside"; + return a; + } + }.outside + ]() { + let a = "middle"; + return a; + } +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-8/options.json b/packages/babel-traverse/test/fixtures/rename/method-computed-key-8/options.json new file mode 100644 index 000000000000..14af0e5feac8 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-8/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["./plugin"] +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-8/output.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-8/output.js new file mode 100644 index 000000000000..86efee20b9e0 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-8/output.js @@ -0,0 +1,15 @@ +let z = "outside"; + +class C { + static get [class D { + static get [z]() { + let a = "inside"; + return a; + } + + }.outside]() { + let a = "middle"; + return a; + } + +} diff --git a/packages/babel-traverse/test/fixtures/rename/method-computed-key-8/plugin.js b/packages/babel-traverse/test/fixtures/rename/method-computed-key-8/plugin.js new file mode 100644 index 000000000000..a364c62a4747 --- /dev/null +++ b/packages/babel-traverse/test/fixtures/rename/method-computed-key-8/plugin.js @@ -0,0 +1,9 @@ +module.exports = function() { + return { + visitor: { + Program(path) { + path.scope.rename("a", "z"); + } + } + }; +}; diff --git a/packages/babel-traverse/test/scope.js b/packages/babel-traverse/test/scope.js index ed0bbd9d8302..7f704ffb620c 100644 --- a/packages/babel-traverse/test/scope.js +++ b/packages/babel-traverse/test/scope.js @@ -168,6 +168,92 @@ 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("when path is in nested within another computed key", () => { + expect( + getPath( + `var a = "outside"; ({ get [ { get [a]() { let a = "inside"; return a; } }.outside ]() { let a = "middle"; return a; } })`, + ) + .get("body.1.expression.properties.0.key.object.properties.0.key") + .scope.getBinding("a").path.node.init.value, + ).toBe("outside"); + + expect( + getPath( + `var a = "outside"; class foo { static get [ class { static get [a]() { let a = "inside"; return a; } }.outside ]() { let a = "middle"; return a; } }`, + ) + .get("body.1.body.body.0.key.object.body.body.0.key") + .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",