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:
overlookmotel 2021-02-19 01:36:34 +00:00 committed by GitHub
parent a940c0984f
commit 792672ec60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 391 additions and 2 deletions

View File

@ -0,0 +1,4 @@
let x;
const a = {
[x.y?.z]() {}
};

View File

@ -0,0 +1,3 @@
{
"plugins": [["proposal-optional-chaining", { "loose": true }]]
}

View File

@ -0,0 +1,7 @@
var _x$y;
let x;
const a = {
[(_x$y = x.y) == null ? void 0 : _x$y.z]() {}
};

View File

@ -0,0 +1,4 @@
let x;
const a = {
[x.y?.z]() {}
};

View File

@ -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]() {}
};

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -0,0 +1,8 @@
let a = "outside";
const obj = {
[a]() {
let a = "inside";
return a;
}
};

View File

@ -0,0 +1,3 @@
{
"plugins": ["./plugin"]
}

View File

@ -0,0 +1,8 @@
let z = "outside";
const obj = {
[z]() {
let a = "inside";
return a;
}
};

View File

@ -0,0 +1,9 @@
module.exports = function() {
return {
visitor: {
Program(path) {
path.scope.rename("a", "z");
}
}
};
};

View File

@ -0,0 +1,8 @@
let a = "outside";
class C {
[a]() {
let a = "inside";
return a;
}
}

View File

@ -0,0 +1,3 @@
{
"plugins": ["./plugin"]
}

View File

@ -0,0 +1,9 @@
let z = "outside";
class C {
[z]() {
let a = "inside";
return a;
}
}

View File

@ -0,0 +1,9 @@
module.exports = function() {
return {
visitor: {
Program(path) {
path.scope.rename("a", "z");
}
}
};
};

View File

@ -0,0 +1,7 @@
let a = "outside";
const obj = {
[a](a = "inside") {
return a;
}
};

View File

@ -0,0 +1,3 @@
{
"plugins": ["./plugin"]
}

View File

@ -0,0 +1,7 @@
let z = "outside";
const obj = {
[z](a = "inside") {
return a;
}
};

View File

@ -0,0 +1,9 @@
module.exports = function() {
return {
visitor: {
Program(path) {
path.scope.rename("a", "z");
}
}
};
};

View File

@ -0,0 +1,7 @@
let a = "outside";
class C {
[a](a = "inside") {
return a;
}
}

View File

@ -0,0 +1,3 @@
{
"plugins": ["./plugin"]
}

View File

@ -0,0 +1,8 @@
let z = "outside";
class C {
[z](a = "inside") {
return a;
}
}

View File

@ -0,0 +1,9 @@
module.exports = function() {
return {
visitor: {
Program(path) {
path.scope.rename("a", "z");
}
}
};
};

View File

@ -0,0 +1,8 @@
let a = "outside";
const obj = {
[(() => a)()]() {
let a = "inside";
return a;
}
};

View File

@ -0,0 +1,3 @@
{
"plugins": ["./plugin"]
}

View File

@ -0,0 +1,8 @@
let z = "outside";
const obj = {
[(() => z)()]() {
let a = "inside";
return a;
}
};

View File

@ -0,0 +1,9 @@
module.exports = function() {
return {
visitor: {
Program(path) {
path.scope.rename("a", "z");
}
}
};
};

View File

@ -0,0 +1,8 @@
let a = "outside";
class C {
[(() => a)()]() {
let a = "inside";
return a;
}
}

View File

@ -0,0 +1,3 @@
{
"plugins": ["./plugin"]
}

View File

@ -0,0 +1,9 @@
let z = "outside";
class C {
[(() => z)()]() {
let a = "inside";
return a;
}
}

View File

@ -0,0 +1,9 @@
module.exports = function() {
return {
visitor: {
Program(path) {
path.scope.rename("a", "z");
}
}
};
};

View File

@ -0,0 +1,15 @@
let a = "outside";
const obj = {
get [
{
get [a]() {
let a = "inside";
return a;
}
}.outside
]() {
let a = "middle";
return a;
}
};

View File

@ -0,0 +1,3 @@
{
"plugins": ["./plugin"]
}

View File

@ -0,0 +1,14 @@
let z = "outside";
const obj = {
get [{
get [z]() {
let a = "inside";
return a;
}
}.outside]() {
let a = "middle";
return a;
}
};

View File

@ -0,0 +1,9 @@
module.exports = function() {
return {
visitor: {
Program(path) {
path.scope.rename("a", "z");
}
}
};
};

View 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;
}
}

View File

@ -0,0 +1,3 @@
{
"plugins": ["./plugin"]
}

View 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;
}
}

View File

@ -0,0 +1,9 @@
module.exports = function() {
return {
visitor: {
Program(path) {
path.scope.rename("a", "z");
}
}
};
};

View File

@ -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",