Mark FOO in "var { x: FOO }˝ as a binding, not as a reference (#9492)

This commit is contained in:
Nicolò Ribaudo 2019-02-27 00:17:11 +01:00 committed by GitHub
parent 43eed1ac92
commit 5c8cc0d536
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 95 additions and 16 deletions

View File

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

View File

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

View File

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

View File

@ -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++) {

View File

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

View File

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