Mark FOO in "var { x: FOO }˝ as a binding, not as a reference (#9492)
This commit is contained in:
parent
43eed1ac92
commit
5c8cc0d536
@ -2,6 +2,17 @@ import { declare } from "@babel/helper-plugin-utils";
|
|||||||
import syntaxObjectRestSpread from "@babel/plugin-syntax-object-rest-spread";
|
import syntaxObjectRestSpread from "@babel/plugin-syntax-object-rest-spread";
|
||||||
import { types as t } from "@babel/core";
|
import { types as t } from "@babel/core";
|
||||||
|
|
||||||
|
// TODO: Remove in Babel 8
|
||||||
|
// @babel/types <=7.3.3 counts FOO as referenced in var { x: FOO }.
|
||||||
|
// We need to detect this bug to know if "unused" means 0 or 1 references.
|
||||||
|
const ZERO_REFS = (() => {
|
||||||
|
const node = t.identifier("a");
|
||||||
|
const property = t.objectProperty(t.identifier("key"), node);
|
||||||
|
const pattern = t.objectPattern([property]);
|
||||||
|
|
||||||
|
return t.isReferenced(node, property, pattern) ? 1 : 0;
|
||||||
|
})();
|
||||||
|
|
||||||
export default declare((api, opts) => {
|
export default declare((api, opts) => {
|
||||||
api.assertVersion(7);
|
api.assertVersion(7);
|
||||||
|
|
||||||
@ -98,7 +109,7 @@ export default declare((api, opts) => {
|
|||||||
Object.keys(bindings).forEach(bindingName => {
|
Object.keys(bindings).forEach(bindingName => {
|
||||||
const bindingParentPath = bindings[bindingName].parentPath;
|
const bindingParentPath = bindings[bindingName].parentPath;
|
||||||
if (
|
if (
|
||||||
path.scope.getBinding(bindingName).references > 1 ||
|
path.scope.getBinding(bindingName).references > ZERO_REFS ||
|
||||||
!bindingParentPath.isObjectProperty()
|
!bindingParentPath.isObjectProperty()
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -18,24 +18,32 @@ _baz.Baz = (44, function () {
|
|||||||
throw new Error('"' + "Baz" + '" is read-only.');
|
throw new Error('"' + "Baz" + '" is read-only.');
|
||||||
}());
|
}());
|
||||||
({
|
({
|
||||||
Foo: _foo.default
|
Foo
|
||||||
} = {});
|
} = ({}, function () {
|
||||||
|
throw new Error('"' + "Foo" + '" is read-only.');
|
||||||
|
}()));
|
||||||
({
|
({
|
||||||
Bar
|
Bar
|
||||||
} = ({}, function () {
|
} = ({}, function () {
|
||||||
throw new Error('"' + "Bar" + '" is read-only.');
|
throw new Error('"' + "Bar" + '" is read-only.');
|
||||||
}()));
|
}()));
|
||||||
({
|
({
|
||||||
Baz: _baz.Baz
|
Baz
|
||||||
} = {});
|
} = ({}, function () {
|
||||||
|
throw new Error('"' + "Baz" + '" is read-only.');
|
||||||
|
}()));
|
||||||
({
|
({
|
||||||
prop: _foo.default
|
prop: Foo
|
||||||
} = {});
|
} = ({}, function () {
|
||||||
|
throw new Error('"' + "Foo" + '" is read-only.');
|
||||||
|
}()));
|
||||||
({
|
({
|
||||||
prop: Bar
|
prop: Bar
|
||||||
} = ({}, function () {
|
} = ({}, function () {
|
||||||
throw new Error('"' + "Bar" + '" is read-only.');
|
throw new Error('"' + "Bar" + '" is read-only.');
|
||||||
}()));
|
}()));
|
||||||
({
|
({
|
||||||
prop: _baz.Baz
|
prop: Baz
|
||||||
} = {});
|
} = ({}, function () {
|
||||||
|
throw new Error('"' + "Baz" + '" is read-only.');
|
||||||
|
}()));
|
||||||
|
|||||||
@ -4,7 +4,8 @@ import * as t from "@babel/types";
|
|||||||
|
|
||||||
export const ReferencedIdentifier = {
|
export const ReferencedIdentifier = {
|
||||||
types: ["Identifier", "JSXIdentifier"],
|
types: ["Identifier", "JSXIdentifier"],
|
||||||
checkPath({ node, parent }: NodePath, opts?: Object): boolean {
|
checkPath(path: NodePath, opts?: Object): boolean {
|
||||||
|
const { node, parent } = path;
|
||||||
if (!t.isIdentifier(node, opts) && !t.isJSXMemberExpression(parent, opts)) {
|
if (!t.isIdentifier(node, opts) && !t.isJSXMemberExpression(parent, opts)) {
|
||||||
if (t.isJSXIdentifier(node, opts)) {
|
if (t.isJSXIdentifier(node, opts)) {
|
||||||
if (react.isCompatTag(node.name)) return false;
|
if (react.isCompatTag(node.name)) return false;
|
||||||
@ -15,7 +16,7 @@ export const ReferencedIdentifier = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check if node is referenced
|
// check if node is referenced
|
||||||
return t.isReferenced(node, parent);
|
return t.isReferenced(node, parent, path.parentPath.parent);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,8 +29,10 @@ export const ReferencedMemberExpression = {
|
|||||||
|
|
||||||
export const BindingIdentifier = {
|
export const BindingIdentifier = {
|
||||||
types: ["Identifier"],
|
types: ["Identifier"],
|
||||||
checkPath({ node, parent }: NodePath): boolean {
|
checkPath(path: NodePath): boolean {
|
||||||
return t.isIdentifier(node) && t.isBinding(node, parent);
|
const { node, parent } = path;
|
||||||
|
const grandparent = path.parentPath.parent;
|
||||||
|
return t.isIdentifier(node) && t.isBinding(node, parent, grandparent);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,22 @@ import getBindingIdentifiers from "../retrievers/getBindingIdentifiers";
|
|||||||
/**
|
/**
|
||||||
* Check if the input `node` is a binding identifier.
|
* Check if the input `node` is a binding identifier.
|
||||||
*/
|
*/
|
||||||
export default function isBinding(node: Object, parent: Object): boolean {
|
export default function isBinding(
|
||||||
|
node: Object,
|
||||||
|
parent: Object,
|
||||||
|
grandparent?: Object,
|
||||||
|
): boolean {
|
||||||
|
if (
|
||||||
|
grandparent &&
|
||||||
|
node.type === "Identifier" &&
|
||||||
|
parent.type === "ObjectProperty" &&
|
||||||
|
grandparent.type === "ObjectExpression"
|
||||||
|
) {
|
||||||
|
// We need to special-case this, because getBindingIdentifiers
|
||||||
|
// has an ObjectProperty->value entry for destructuring patterns.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const keys = getBindingIdentifiers.keys[parent.type];
|
const keys = getBindingIdentifiers.keys[parent.type];
|
||||||
if (keys) {
|
if (keys) {
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
|||||||
@ -2,7 +2,11 @@
|
|||||||
/**
|
/**
|
||||||
* Check if the input `node` is a reference to a bound variable.
|
* Check if the input `node` is a reference to a bound variable.
|
||||||
*/
|
*/
|
||||||
export default function isReferenced(node: Object, parent: Object): boolean {
|
export default function isReferenced(
|
||||||
|
node: Object,
|
||||||
|
parent: Object,
|
||||||
|
grandparent?: Object,
|
||||||
|
): boolean {
|
||||||
switch (parent.type) {
|
switch (parent.type) {
|
||||||
// yes: PARENT[NODE]
|
// yes: PARENT[NODE]
|
||||||
// yes: NODE.child
|
// yes: NODE.child
|
||||||
@ -37,6 +41,7 @@ export default function isReferenced(node: Object, parent: Object): boolean {
|
|||||||
// yes: { [NODE]: "" }
|
// yes: { [NODE]: "" }
|
||||||
// no: { NODE: "" }
|
// no: { NODE: "" }
|
||||||
// depends: { NODE }
|
// depends: { NODE }
|
||||||
|
// depends: { key: NODE }
|
||||||
case "ObjectProperty":
|
case "ObjectProperty":
|
||||||
// no: class { NODE = value; }
|
// no: class { NODE = value; }
|
||||||
// yes: class { [NODE] = value; }
|
// yes: class { [NODE] = value; }
|
||||||
@ -51,7 +56,10 @@ export default function isReferenced(node: Object, parent: Object): boolean {
|
|||||||
if (parent.key === node) {
|
if (parent.key === node) {
|
||||||
return !!parent.computed;
|
return !!parent.computed;
|
||||||
}
|
}
|
||||||
return parent.value === node;
|
if (parent.value === node) {
|
||||||
|
return !grandparent || grandparent.type !== "ObjectPattern";
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
// no: class NODE {}
|
// no: class NODE {}
|
||||||
// yes: class Foo extends NODE {}
|
// yes: class Foo extends NODE {}
|
||||||
|
|||||||
@ -121,6 +121,40 @@ describe("validators", function() {
|
|||||||
|
|
||||||
expect(t.isReferenced(node, parent)).toBe(true);
|
expect(t.isReferenced(node, parent)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns true if node id a value of ObjectProperty of an expression", function() {
|
||||||
|
const node = t.identifier("a");
|
||||||
|
const parent = t.objectProperty(t.identifier("key"), node);
|
||||||
|
const grandparent = t.objectExpression([parent]);
|
||||||
|
|
||||||
|
expect(t.isReferenced(node, parent, grandparent)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns false if node id a value of ObjectProperty of a pattern", function() {
|
||||||
|
const node = t.identifier("a");
|
||||||
|
const parent = t.objectProperty(t.identifier("key"), node);
|
||||||
|
const grandparent = t.objectPattern([parent]);
|
||||||
|
|
||||||
|
expect(t.isReferenced(node, parent, grandparent)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isBinding", function() {
|
||||||
|
it("returns false if node id a value of ObjectProperty of an expression", function() {
|
||||||
|
const node = t.identifier("a");
|
||||||
|
const parent = t.objectProperty(t.identifier("key"), node);
|
||||||
|
const grandparent = t.objectExpression([parent]);
|
||||||
|
|
||||||
|
expect(t.isBinding(node, parent, grandparent)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns true if node id a value of ObjectProperty of a pattern", function() {
|
||||||
|
const node = t.identifier("a");
|
||||||
|
const parent = t.objectProperty(t.identifier("key"), node);
|
||||||
|
const grandparent = t.objectPattern([parent]);
|
||||||
|
|
||||||
|
expect(t.isBinding(node, parent, grandparent)).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("isType", function() {
|
describe("isType", function() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user