diff --git a/packages/babel-plugin-proposal-object-rest-spread/src/index.js b/packages/babel-plugin-proposal-object-rest-spread/src/index.js index c4738f0c67..1eb8e7b70f 100644 --- a/packages/babel-plugin-proposal-object-rest-spread/src/index.js +++ b/packages/babel-plugin-proposal-object-rest-spread/src/index.js @@ -2,6 +2,17 @@ import { declare } from "@babel/helper-plugin-utils"; import syntaxObjectRestSpread from "@babel/plugin-syntax-object-rest-spread"; 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) => { api.assertVersion(7); @@ -98,7 +109,7 @@ export default declare((api, opts) => { Object.keys(bindings).forEach(bindingName => { const bindingParentPath = bindings[bindingName].parentPath; if ( - path.scope.getBinding(bindingName).references > 1 || + path.scope.getBinding(bindingName).references > ZERO_REFS || !bindingParentPath.isObjectProperty() ) { return; diff --git a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/misc/import-const-throw/output.js b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/misc/import-const-throw/output.js index 7b05f0c824..01984ae47f 100644 --- a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/misc/import-const-throw/output.js +++ b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/misc/import-const-throw/output.js @@ -18,24 +18,32 @@ _baz.Baz = (44, function () { throw new Error('"' + "Baz" + '" is read-only.'); }()); ({ - Foo: _foo.default -} = {}); + Foo +} = ({}, function () { + throw new Error('"' + "Foo" + '" is read-only.'); +}())); ({ Bar } = ({}, function () { 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 } = ({}, function () { throw new Error('"' + "Bar" + '" is read-only.'); }())); ({ - prop: _baz.Baz -} = {}); + prop: Baz +} = ({}, function () { + throw new Error('"' + "Baz" + '" is read-only.'); +}())); diff --git a/packages/babel-traverse/src/path/lib/virtual-types.js b/packages/babel-traverse/src/path/lib/virtual-types.js index 35319f6ee8..97cec0e3af 100644 --- a/packages/babel-traverse/src/path/lib/virtual-types.js +++ b/packages/babel-traverse/src/path/lib/virtual-types.js @@ -4,7 +4,8 @@ import * as t from "@babel/types"; export const ReferencedIdentifier = { 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.isJSXIdentifier(node, opts)) { if (react.isCompatTag(node.name)) return false; @@ -15,7 +16,7 @@ export const ReferencedIdentifier = { } // 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 = { types: ["Identifier"], - checkPath({ node, parent }: NodePath): boolean { - return t.isIdentifier(node) && t.isBinding(node, parent); + checkPath(path: NodePath): boolean { + const { node, parent } = path; + const grandparent = path.parentPath.parent; + return t.isIdentifier(node) && t.isBinding(node, parent, grandparent); }, }; diff --git a/packages/babel-types/src/validators/isBinding.js b/packages/babel-types/src/validators/isBinding.js index b1719a3b38..234da4f6ec 100644 --- a/packages/babel-types/src/validators/isBinding.js +++ b/packages/babel-types/src/validators/isBinding.js @@ -3,7 +3,22 @@ import getBindingIdentifiers from "../retrievers/getBindingIdentifiers"; /** * 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]; if (keys) { for (let i = 0; i < keys.length; i++) { diff --git a/packages/babel-types/src/validators/isReferenced.js b/packages/babel-types/src/validators/isReferenced.js index b629e625ee..6a5a3ebc8e 100644 --- a/packages/babel-types/src/validators/isReferenced.js +++ b/packages/babel-types/src/validators/isReferenced.js @@ -2,7 +2,11 @@ /** * 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) { // yes: PARENT[NODE] // yes: NODE.child @@ -37,6 +41,7 @@ export default function isReferenced(node: Object, parent: Object): boolean { // yes: { [NODE]: "" } // no: { NODE: "" } // depends: { NODE } + // depends: { key: NODE } case "ObjectProperty": // no: class { NODE = value; } // yes: class { [NODE] = value; } @@ -51,7 +56,10 @@ export default function isReferenced(node: Object, parent: Object): boolean { if (parent.key === node) { return !!parent.computed; } - return parent.value === node; + if (parent.value === node) { + return !grandparent || grandparent.type !== "ObjectPattern"; + } + return true; // no: class NODE {} // yes: class Foo extends NODE {} diff --git a/packages/babel-types/test/validators.js b/packages/babel-types/test/validators.js index ce47402881..8e1f18e115 100644 --- a/packages/babel-types/test/validators.js +++ b/packages/babel-types/test/validators.js @@ -121,6 +121,40 @@ describe("validators", function() { 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() {