Fix scope of computed method keys (#12812)
* Fix scope of computed method keys * Test for nested computed keys * Fix scope.rename with computed method keys * Optional chaining tests
This commit is contained in:
parent
a940c0984f
commit
792672ec60
@ -0,0 +1,4 @@
|
||||
let x;
|
||||
const a = {
|
||||
[x.y?.z]() {}
|
||||
};
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": [["proposal-optional-chaining", { "loose": true }]]
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
var _x$y;
|
||||
|
||||
let x;
|
||||
const a = {
|
||||
[(_x$y = x.y) == null ? void 0 : _x$y.z]() {}
|
||||
|
||||
};
|
||||
@ -0,0 +1,4 @@
|
||||
let x;
|
||||
const a = {
|
||||
[x.y?.z]() {}
|
||||
};
|
||||
@ -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]() {}
|
||||
|
||||
};
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ const renameVisitor: Visitor<Renamer> = {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
8
packages/babel-traverse/test/fixtures/rename/method-computed-key-1/input.js
vendored
Normal file
8
packages/babel-traverse/test/fixtures/rename/method-computed-key-1/input.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
let a = "outside";
|
||||
|
||||
const obj = {
|
||||
[a]() {
|
||||
let a = "inside";
|
||||
return a;
|
||||
}
|
||||
};
|
||||
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-1/options.json
vendored
Normal file
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-1/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["./plugin"]
|
||||
}
|
||||
8
packages/babel-traverse/test/fixtures/rename/method-computed-key-1/output.js
vendored
Normal file
8
packages/babel-traverse/test/fixtures/rename/method-computed-key-1/output.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
let z = "outside";
|
||||
const obj = {
|
||||
[z]() {
|
||||
let a = "inside";
|
||||
return a;
|
||||
}
|
||||
|
||||
};
|
||||
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-1/plugin.js
vendored
Normal file
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-1/plugin.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = function() {
|
||||
return {
|
||||
visitor: {
|
||||
Program(path) {
|
||||
path.scope.rename("a", "z");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
8
packages/babel-traverse/test/fixtures/rename/method-computed-key-2/input.js
vendored
Normal file
8
packages/babel-traverse/test/fixtures/rename/method-computed-key-2/input.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
let a = "outside";
|
||||
|
||||
class C {
|
||||
[a]() {
|
||||
let a = "inside";
|
||||
return a;
|
||||
}
|
||||
}
|
||||
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-2/options.json
vendored
Normal file
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-2/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["./plugin"]
|
||||
}
|
||||
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-2/output.js
vendored
Normal file
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-2/output.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
let z = "outside";
|
||||
|
||||
class C {
|
||||
[z]() {
|
||||
let a = "inside";
|
||||
return a;
|
||||
}
|
||||
|
||||
}
|
||||
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-2/plugin.js
vendored
Normal file
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-2/plugin.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = function() {
|
||||
return {
|
||||
visitor: {
|
||||
Program(path) {
|
||||
path.scope.rename("a", "z");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
7
packages/babel-traverse/test/fixtures/rename/method-computed-key-3/input.js
vendored
Normal file
7
packages/babel-traverse/test/fixtures/rename/method-computed-key-3/input.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
let a = "outside";
|
||||
|
||||
const obj = {
|
||||
[a](a = "inside") {
|
||||
return a;
|
||||
}
|
||||
};
|
||||
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-3/options.json
vendored
Normal file
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-3/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["./plugin"]
|
||||
}
|
||||
7
packages/babel-traverse/test/fixtures/rename/method-computed-key-3/output.js
vendored
Normal file
7
packages/babel-traverse/test/fixtures/rename/method-computed-key-3/output.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
let z = "outside";
|
||||
const obj = {
|
||||
[z](a = "inside") {
|
||||
return a;
|
||||
}
|
||||
|
||||
};
|
||||
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-3/plugin.js
vendored
Normal file
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-3/plugin.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = function() {
|
||||
return {
|
||||
visitor: {
|
||||
Program(path) {
|
||||
path.scope.rename("a", "z");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
7
packages/babel-traverse/test/fixtures/rename/method-computed-key-4/input.js
vendored
Normal file
7
packages/babel-traverse/test/fixtures/rename/method-computed-key-4/input.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
let a = "outside";
|
||||
|
||||
class C {
|
||||
[a](a = "inside") {
|
||||
return a;
|
||||
}
|
||||
}
|
||||
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-4/options.json
vendored
Normal file
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-4/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["./plugin"]
|
||||
}
|
||||
8
packages/babel-traverse/test/fixtures/rename/method-computed-key-4/output.js
vendored
Normal file
8
packages/babel-traverse/test/fixtures/rename/method-computed-key-4/output.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
let z = "outside";
|
||||
|
||||
class C {
|
||||
[z](a = "inside") {
|
||||
return a;
|
||||
}
|
||||
|
||||
}
|
||||
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-4/plugin.js
vendored
Normal file
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-4/plugin.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = function() {
|
||||
return {
|
||||
visitor: {
|
||||
Program(path) {
|
||||
path.scope.rename("a", "z");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
8
packages/babel-traverse/test/fixtures/rename/method-computed-key-5/input.js
vendored
Normal file
8
packages/babel-traverse/test/fixtures/rename/method-computed-key-5/input.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
let a = "outside";
|
||||
|
||||
const obj = {
|
||||
[(() => a)()]() {
|
||||
let a = "inside";
|
||||
return a;
|
||||
}
|
||||
};
|
||||
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-5/options.json
vendored
Normal file
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-5/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["./plugin"]
|
||||
}
|
||||
8
packages/babel-traverse/test/fixtures/rename/method-computed-key-5/output.js
vendored
Normal file
8
packages/babel-traverse/test/fixtures/rename/method-computed-key-5/output.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
let z = "outside";
|
||||
const obj = {
|
||||
[(() => z)()]() {
|
||||
let a = "inside";
|
||||
return a;
|
||||
}
|
||||
|
||||
};
|
||||
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-5/plugin.js
vendored
Normal file
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-5/plugin.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = function() {
|
||||
return {
|
||||
visitor: {
|
||||
Program(path) {
|
||||
path.scope.rename("a", "z");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
8
packages/babel-traverse/test/fixtures/rename/method-computed-key-6/input.js
vendored
Normal file
8
packages/babel-traverse/test/fixtures/rename/method-computed-key-6/input.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
let a = "outside";
|
||||
|
||||
class C {
|
||||
[(() => a)()]() {
|
||||
let a = "inside";
|
||||
return a;
|
||||
}
|
||||
}
|
||||
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-6/options.json
vendored
Normal file
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-6/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["./plugin"]
|
||||
}
|
||||
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-6/output.js
vendored
Normal file
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-6/output.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
let z = "outside";
|
||||
|
||||
class C {
|
||||
[(() => z)()]() {
|
||||
let a = "inside";
|
||||
return a;
|
||||
}
|
||||
|
||||
}
|
||||
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-6/plugin.js
vendored
Normal file
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-6/plugin.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = function() {
|
||||
return {
|
||||
visitor: {
|
||||
Program(path) {
|
||||
path.scope.rename("a", "z");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
15
packages/babel-traverse/test/fixtures/rename/method-computed-key-7/input.js
vendored
Normal file
15
packages/babel-traverse/test/fixtures/rename/method-computed-key-7/input.js
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
let a = "outside";
|
||||
|
||||
const obj = {
|
||||
get [
|
||||
{
|
||||
get [a]() {
|
||||
let a = "inside";
|
||||
return a;
|
||||
}
|
||||
}.outside
|
||||
]() {
|
||||
let a = "middle";
|
||||
return a;
|
||||
}
|
||||
};
|
||||
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-7/options.json
vendored
Normal file
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-7/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["./plugin"]
|
||||
}
|
||||
14
packages/babel-traverse/test/fixtures/rename/method-computed-key-7/output.js
vendored
Normal file
14
packages/babel-traverse/test/fixtures/rename/method-computed-key-7/output.js
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
let z = "outside";
|
||||
const obj = {
|
||||
get [{
|
||||
get [z]() {
|
||||
let a = "inside";
|
||||
return a;
|
||||
}
|
||||
|
||||
}.outside]() {
|
||||
let a = "middle";
|
||||
return a;
|
||||
}
|
||||
|
||||
};
|
||||
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-7/plugin.js
vendored
Normal file
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-7/plugin.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = function() {
|
||||
return {
|
||||
visitor: {
|
||||
Program(path) {
|
||||
path.scope.rename("a", "z");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
15
packages/babel-traverse/test/fixtures/rename/method-computed-key-8/input.js
vendored
Normal file
15
packages/babel-traverse/test/fixtures/rename/method-computed-key-8/input.js
vendored
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-8/options.json
vendored
Normal file
3
packages/babel-traverse/test/fixtures/rename/method-computed-key-8/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["./plugin"]
|
||||
}
|
||||
15
packages/babel-traverse/test/fixtures/rename/method-computed-key-8/output.js
vendored
Normal file
15
packages/babel-traverse/test/fixtures/rename/method-computed-key-8/output.js
vendored
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-8/plugin.js
vendored
Normal file
9
packages/babel-traverse/test/fixtures/rename/method-computed-key-8/plugin.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = function() {
|
||||
return {
|
||||
visitor: {
|
||||
Program(path) {
|
||||
path.scope.rename("a", "z");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user