diff --git a/packages/babel-helper-member-expression-to-functions/.npmignore b/packages/babel-helper-member-expression-to-functions/.npmignore new file mode 100644 index 0000000000..f980694583 --- /dev/null +++ b/packages/babel-helper-member-expression-to-functions/.npmignore @@ -0,0 +1,3 @@ +src +test +*.log diff --git a/packages/babel-helper-member-expression-to-functions/README.md b/packages/babel-helper-member-expression-to-functions/README.md new file mode 100644 index 0000000000..70221cf2b4 --- /dev/null +++ b/packages/babel-helper-member-expression-to-functions/README.md @@ -0,0 +1,66 @@ +# @babel/helper-member-expression-to-functions + +Helper function to replace certain member expressions with function calls + +## Usage + +> Designed for internal Babel use. + +Traverses the `path` using the supplied `visitor` and an augmented `state`. + +```js +const visitor = { + MemberExpression(memberPath, state) { + + if (someCondition(memberPath)) { + + // The handle method is supplied by memberExpressionToFunctions. + // It should be called whenever a MemberExpression should be + // converted into the proper function calls. + state.handle(memberPath); + + } + + }, +}; + +// The helper requires three special methods on state: `get`, `set`, and +// `call`. +// Everything else will be passed through as normal. +const state = { + get(memberPath) { + // Return some AST that will get the member + return t.callExpression( + this.file.addHelper('superGet'), + [t.thisExpression(), memberPath.node.property] + ); + }, + + get(memberPath, value) { + // Return some AST that will set the member + return t.callExpression( + this.file.addHelper('superSet'), + [t.thisExpression(), memberPath.node.property, value] + ); + }, + + call(memberPath, args) { + // Return some AST that will call the member with the proper context + // and args + return t.callExpression( + t.memberExpression(this.get(memberPath), t.identifier("apply")), + [t.thisExpression(), t.arrayExpression(args)] + ); + }, + + // The handle method is provided by memberExpressionToFunctions. + // handle(memberPath) { ... } + + // Other state stuff is left untouched. + someState: new Set(), +}; + +// Replace all the special MemberExpressions in rootPath, as determined +// by our visitor, using the state methods. +memberExpressionToFunctions(rootPath, visitor, state); +``` diff --git a/packages/babel-helper-member-expression-to-functions/package.json b/packages/babel-helper-member-expression-to-functions/package.json new file mode 100644 index 0000000000..3f6e45254f --- /dev/null +++ b/packages/babel-helper-member-expression-to-functions/package.json @@ -0,0 +1,12 @@ +{ + "name": "@babel/helper-member-expression-to-functions", + "version": "7.0.0-beta.44", + "description": "Helper function to replace certain member expressions with function calls", + "repository": "https://github.com/babel/babel/tree/master/packages/babel-helper-member-expression-to-functions", + "license": "MIT", + "main": "lib/index.js", + "author": "Justin Ridgewell ", + "dependencies": { + "@babel/types": "7.0.0-beta.44" + } +} diff --git a/packages/babel-helper-member-expression-to-functions/src/index.js b/packages/babel-helper-member-expression-to-functions/src/index.js new file mode 100644 index 0000000000..33e3685b20 --- /dev/null +++ b/packages/babel-helper-member-expression-to-functions/src/index.js @@ -0,0 +1,72 @@ +import * as t from "@babel/types"; + +const handle = { + handle(member) { + const { node, parent, parentPath } = member; + + // MEMBER++ -> _set(MEMBER, (_ref = (+_get(MEMBER))) + 1), _ref + // ++MEMBER -> _set(MEMBER, (+_get(MEMBER)) + 1) + if (parentPath.isUpdateExpression({ argument: node })) { + const { operator, prefix } = parent; + + const value = t.binaryExpression( + operator[0], + t.unaryExpression("+", this.get(member)), + t.numericLiteral(1), + ); + + if (prefix) { + parentPath.replaceWith(this.set(member, value)); + } else { + const { scope } = member; + const ref = scope.generateUidIdentifierBasedOnNode(node); + scope.push({ id: ref }); + + value.left = t.assignmentExpression("=", t.cloneNode(ref), value.left); + + parentPath.replaceWith( + t.sequenceExpression([this.set(member, value), t.cloneNode(ref)]), + ); + } + return; + } + + // MEMBER = VALUE -> _set(MEMBER, VALUE) + // MEMBER += VALUE -> _set(MEMBER, _get(MEMBER) + VALUE) + if (parentPath.isAssignmentExpression({ left: node })) { + const { operator, right } = parent; + let value = right; + + if (operator !== "=") { + value = t.binaryExpression( + operator.slice(0, -1), + this.get(member), + value, + ); + } + + parentPath.replaceWith(this.set(member, value)); + return; + } + + // MEMBER(ARGS) -> _call(MEMBER, ARGS) + if (parentPath.isCallExpression({ callee: node })) { + const { arguments: args } = parent; + + parentPath.replaceWith(this.call(member, args)); + return; + } + + // MEMBER -> _get(MEMBER) + member.replaceWith(this.get(member)); + }, +}; + +// We do not provide a default traversal visitor +// Instead, caller passes one, and must call `state.handle` on the members +// it wishes to be transformed. +// Additionally, the caller must pass in a state object with at least +// get, set, and call methods. +export default function memberExpressionToFunctions(path, visitor, state) { + path.traverse(visitor, Object.assign({}, state, handle)); +} diff --git a/packages/babel-helper-replace-supers/package.json b/packages/babel-helper-replace-supers/package.json index 7067655df0..384439cc85 100644 --- a/packages/babel-helper-replace-supers/package.json +++ b/packages/babel-helper-replace-supers/package.json @@ -6,8 +6,8 @@ "license": "MIT", "main": "lib/index.js", "dependencies": { + "@babel/helper-member-expression-to-functions": "7.0.0-beta.44", "@babel/helper-optimise-call-expression": "7.0.0-beta.44", - "@babel/template": "7.0.0-beta.44", "@babel/traverse": "7.0.0-beta.44", "@babel/types": "7.0.0-beta.44" } diff --git a/packages/babel-helper-replace-supers/src/index.js b/packages/babel-helper-replace-supers/src/index.js index d92c4a83f6..8501ec9990 100644 --- a/packages/babel-helper-replace-supers/src/index.js +++ b/packages/babel-helper-replace-supers/src/index.js @@ -1,5 +1,6 @@ import type { NodePath, Scope } from "@babel/traverse"; import traverse from "@babel/traverse"; +import memberExpressionToFunctions from "@babel/helper-member-expression-to-functions"; import optimiseCall from "@babel/helper-optimise-call-expression"; import * as t from "@babel/types"; @@ -74,11 +75,83 @@ const visitor = traverse.visitors.merge([ state.bareSupers.add(parentPath); return; } - state[state.isLoose ? "looseHandle" : "specHandle"](path); + state.handle(parentPath); }, }, ]); +const specHandlers = { + get(superMember) { + const { computed, property } = superMember.node; + let thisExpr = t.thisExpression(); + + // TODO Remove + if (this.inConstructor) { + thisExpr = t.callExpression( + this.file.addHelper("assertThisInitialized"), + [thisExpr], + ); + } + + return t.callExpression(this.file.addHelper("get"), [ + getPrototypeOfExpression(this.getObjectRef(), this.isStatic, this.file), + computed ? property : t.stringLiteral(property.name), + thisExpr, + ]); + }, + + set(superMember, value) { + const { computed, property } = superMember.node; + + return t.callExpression(this.file.addHelper("set"), [ + getPrototypeOfExpression(this.getObjectRef(), this.isStatic, this.file), + computed ? property : t.stringLiteral(property.name), + value, + t.thisExpression(), + t.booleanLiteral(superMember.isInStrictMode()), + ]); + }, + + call(superMember, args) { + return optimiseCall(this.get(superMember), t.thisExpression(), args); + }, +}; + +const looseHandlers = { + get(superMember) { + const { isStatic, superRef } = this; + const { property, computed } = superMember.node; + let object; + + if (isStatic) { + object = superRef + ? t.cloneNode(superRef) + : t.memberExpression( + t.identifier("Function"), + t.identifier("prototype"), + ); + } else { + object = superRef + ? t.memberExpression(t.cloneNode(superRef), t.identifier("prototype")) + : t.memberExpression(t.identifier("Object"), t.identifier("prototype")); + } + + return t.memberExpression(object, property, computed); + }, + + set(superMember, value) { + // TODO https://github.com/babel/babel/pull/7553#issuecomment-381434519 + return t.assignmentExpression("=", this.get(superMember), value); + }, + + call(superMember, args) { + return t.callExpression( + t.memberExpression(this.get(superMember), t.identifier("call")), + [t.thisExpression(), ...args], + ); + }, +}; + export default class ReplaceSupers { constructor(opts: Object) { const path = opts.methodPath; @@ -119,205 +192,24 @@ export default class ReplaceSupers { return t.cloneNode(this.opts.objectRef || this.opts.getObjectRef()); } - /** - * Sets a super class value of the named property. - * - * @example - * - * _set(Object.getPrototypeOf(CLASS.prototype), "METHOD", "VALUE", this, isStrict) - * - */ - - setSuperProperty( - property: Object, - value: Object, - isComputed: boolean, - isStrict: boolean, - ): Object { - return t.callExpression(this.file.addHelper("set"), [ - getPrototypeOfExpression(this.getObjectRef(), this.isStatic, this.file), - isComputed ? property : t.stringLiteral(property.name), - value, - t.thisExpression(), - t.booleanLiteral(isStrict), - ]); - } - - /** - * Gets a node representing the super class value of the named property. - * - * @example - * - * _get(Object.getPrototypeOf(CLASS.prototype), "METHOD", this) - * - */ - - getSuperProperty(property: Object, isComputed: boolean): Object { - let thisExpr = t.thisExpression(); - if (this.inConstructor) { - thisExpr = t.callExpression( - this.file.addHelper("assertThisInitialized"), - [thisExpr], - ); - } - - return t.callExpression(this.file.addHelper("get"), [ - getPrototypeOfExpression(this.getObjectRef(), this.isStatic, this.file), - isComputed ? property : t.stringLiteral(property.name), - thisExpr, - ]); - } - replace() { - this.methodPath.traverse(visitor, this); - } + const { get, set, call } = this.isLoose ? looseHandlers : specHandlers; - getLooseSuperProperty(path) { - const { isStatic, superRef } = this; + memberExpressionToFunctions(this.methodPath, visitor, { + get, + set, + call, - let object; - if (isStatic) { - object = superRef - ? t.cloneNode(superRef) - : t.memberExpression( - t.identifier("Function"), - t.identifier("prototype"), - ); - } else { - object = superRef - ? t.memberExpression(t.cloneNode(superRef), t.identifier("prototype")) - : t.memberExpression(t.identifier("Object"), t.identifier("prototype")); - } - path.get("object").replaceWith(object); - } + // Necessary state + file: this.file, + isStatic: this.isStatic, + getObjectRef: this.getObjectRef.bind(this), + superRef: this.superRef, - looseHandle(path: NodePath) { - const { node, parentPath } = path; - - // super.test - if (parentPath.isMemberExpression({ object: node })) { - this.getLooseSuperProperty(parentPath); - } - - // super.test() - // though, it's SUPER.prototype.test() after the above. - const grandParentPath = parentPath.parentPath; - const callee = parentPath.node; - if (grandParentPath.isCallExpression({ callee })) { - grandParentPath - .get("callee") - .replaceWith(t.memberExpression(callee, t.identifier("call"))); - grandParentPath.node.arguments.unshift(t.thisExpression()); - } - } - - specHandleAssignmentExpression(path) { - const { node } = path; - const { operator } = node; - const { computed, property } = node.left; - if (operator === "=") { - // super.name = "val" - // to - // _set(Object.getPrototypeOf(CLASS.prototype), "name", this); - const setter = this.setSuperProperty( - property, - node.right, - computed, - path.isInStrictMode(), - ); - return [setter]; - } - - // super.age += 2; - // to - // _set( - // Object.getPrototypeOf(CLASS.prototype), - // "name", - // _get(Object.getPrototypeOf(CLASS.prototype), "METHOD", this) + 2, - // this, - // true, - // ); - // TODO this needs cleanup. Should be a single proto lookup - const { scope } = path; - const ref = scope.generateUidIdentifierBasedOnNode(node); - scope.push({ id: ref }); - const setter = this.setSuperProperty( - property, - t.binaryExpression(operator.slice(0, -1), t.cloneNode(ref), node.right), - computed, - path.isInStrictMode(), - ); - return [ - t.assignmentExpression( - "=", - t.cloneNode(ref), - this.getSuperProperty(property, computed), - ), - setter, - ]; - } - - specHandle(path: NodePath) { - const { node, parentPath } = path; - const grandParentPath = parentPath.parentPath; - let parent = parentPath.node; - - if (grandParentPath.isUpdateExpression({ argument: parent })) { - const { operator, prefix } = grandParentPath.node; - const assignment = t.assignmentExpression( - operator[0] + "=", - parent, - t.numericLiteral(1), - ); - grandParentPath.replaceWith(assignment); - - // ++super.foo; - // to - // _ref = Number(super.foo), super.foo = _ref + 1 - // super.foo++; - // to - // _ref = Number(super.foo), super.foo = _ref + 1, _ref - const nodes = this.specHandleAssignmentExpression(grandParentPath); - const [first] = nodes; - first.right = t.callExpression(t.identifier("Number"), [first.right]); - - // Postfix returns the old value, not the new. - if (!prefix) { - nodes.push(t.cloneNode(first.left)); - } - grandParentPath.replaceWith(t.sequenceExpression(nodes)); - return; - } - - if (grandParentPath.isAssignmentExpression({ left: parent })) { - grandParentPath.replaceWithMultiple( - this.specHandleAssignmentExpression(grandParentPath), - ); - return; - } - - if (parentPath.isMemberExpression({ object: node })) { - // super.name; - // to - // _get(Object.getPrototypeOf(CLASS.prototype), "name", this); - const { node } = parentPath; - const { computed, property } = node; - - parent = this.getSuperProperty(property, computed); - parentPath.replaceWith(parent); - } - - if (grandParentPath.isCallExpression({ callee: parent })) { - // _get(Object.getPrototypeOf(CLASS.prototype), "test", this)(); - // to - // _get(Object.getPrototypeOf(CLASS.prototype), "test", this).call(this); - const call = this.optimiseCall(parent, grandParentPath.node.arguments); - grandParentPath.replaceWith(call); - return; - } - } - - optimiseCall(callee, args) { - return optimiseCall(callee, t.thisExpression(), args); + // TODO Remove this shit. + inConstructor: this.inConstructor, + returns: this.returns, + bareSupers: this.bareSupers, + }); } } diff --git a/packages/babel-plugin-transform-classes/test/fixtures/regression/5769/output.js b/packages/babel-plugin-transform-classes/test/fixtures/regression/5769/output.js index 91beae28f9..b940ccbffd 100644 --- a/packages/babel-plugin-transform-classes/test/fixtures/regression/5769/output.js +++ b/packages/babel-plugin-transform-classes/test/fixtures/regression/5769/output.js @@ -29,7 +29,7 @@ function (_Point) { babelHelpers.classCallCheck(this, ColorPoint); _this = babelHelpers.possibleConstructorReturn(this, babelHelpers.getPrototypeOf(ColorPoint).call(this)); _this.x = 2; - babelHelpers.set(babelHelpers.getPrototypeOf(ColorPoint.prototype), "x", 3, _this, true) + babelHelpers.set(babelHelpers.getPrototypeOf(ColorPoint.prototype), "x", 3, _this, true); expect(_this.x).toBe(3); // A expect(babelHelpers.get(babelHelpers.getPrototypeOf(ColorPoint.prototype), "x", babelHelpers.assertThisInitialized(_this))).toBeUndefined(); // B diff --git a/packages/babel-plugin-transform-exponentiation-operator/test/fixtures/regression/4349/output.js b/packages/babel-plugin-transform-exponentiation-operator/test/fixtures/regression/4349/output.js index da6620de31..2eac4a1d87 100644 --- a/packages/babel-plugin-transform-exponentiation-operator/test/fixtures/regression/4349/output.js +++ b/packages/babel-plugin-transform-exponentiation-operator/test/fixtures/regression/4349/output.js @@ -1,22 +1,20 @@ var _obj; -function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); } - function set(target, property, value, receiver) { if (typeof Reflect !== "undefined" && Reflect.set) { set = Reflect.set; } else { set = function set(target, property, value, receiver) { var base = _superPropBase(target, property); var desc; if (base) { desc = Object.getOwnPropertyDescriptor(base, property); if (desc.set) { desc.set.call(receiver, value); return true; } else if (!desc.writable) { return false; } } desc = Object.getOwnPropertyDescriptor(receiver, property); if (desc) { if (!desc.writable) { return false; } desc.value = value; Object.defineProperty(receiver, property, desc); } else { _defineProperty(receiver, property, value); } return true; }; } return set(target, property, value, receiver); } function _set(target, property, value, receiver, isStrict) { const s = set(target, property, value, receiver || target); if (!s && isStrict) { throw new Error('failed to set property'); } return value; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } +function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); } + function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } function _getPrototypeOf(o) { _getPrototypeOf = Object.getPrototypeOf || function _getPrototypeOf(o) { return o.__proto__; }; return _getPrototypeOf(o); } foo = _obj = { bar() { - var _super$baz; - - return _super$baz = _get(_getPrototypeOf(_obj), "baz", this), _set(_getPrototypeOf(_obj), "baz", Math.pow(_super$baz, 12), this, false); + return _set(_getPrototypeOf(_obj), "baz", Math.pow(_get(_getPrototypeOf(_obj), "baz", this), 12), this, false); } }; diff --git a/packages/babel-plugin-transform-object-super/test/fixtures/object-super/super-exponentiation/output.js b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/super-exponentiation/output.js index d43c549113..85b697172c 100644 --- a/packages/babel-plugin-transform-object-super/test/fixtures/object-super/super-exponentiation/output.js +++ b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/super-exponentiation/output.js @@ -1,21 +1,19 @@ var _obj; -function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); } - function set(target, property, value, receiver) { if (typeof Reflect !== "undefined" && Reflect.set) { set = Reflect.set; } else { set = function set(target, property, value, receiver) { var base = _superPropBase(target, property); var desc; if (base) { desc = Object.getOwnPropertyDescriptor(base, property); if (desc.set) { desc.set.call(receiver, value); return true; } else if (!desc.writable) { return false; } } desc = Object.getOwnPropertyDescriptor(receiver, property); if (desc) { if (!desc.writable) { return false; } desc.value = value; Object.defineProperty(receiver, property, desc); } else { _defineProperty(receiver, property, value); } return true; }; } return set(target, property, value, receiver); } function _set(target, property, value, receiver, isStrict) { const s = set(target, property, value, receiver || target); if (!s && isStrict) { throw new Error('failed to set property'); } return value; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } +function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); } + function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } function _getPrototypeOf(o) { _getPrototypeOf = Object.getPrototypeOf || function _getPrototypeOf(o) { return o.__proto__; }; return _getPrototypeOf(o); } foo = _obj = { bar: function () { - var _super$baz; - - return _super$baz = _get(_getPrototypeOf(_obj), "baz", this), _set(_getPrototypeOf(_obj), "baz", _super$baz ** 12, this, false); + return _set(_getPrototypeOf(_obj), "baz", _get(_getPrototypeOf(_obj), "baz", this) ** 12, this, false); } }; diff --git a/packages/babel-plugin-transform-object-super/test/fixtures/object-super/super-increment-postfix/output.js b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/super-increment-postfix/output.js index 2098e094ae..55103d5f27 100644 --- a/packages/babel-plugin-transform-object-super/test/fixtures/object-super/super-increment-postfix/output.js +++ b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/super-increment-postfix/output.js @@ -7,7 +7,7 @@ var obj = _obj = { bar: function () { var _super$test; - return _super$test = Number(babelHelpers.get(babelHelpers.getPrototypeOf(_obj), "test", this)), babelHelpers.set(babelHelpers.getPrototypeOf(_obj), "test", _super$test + 1, this, false), _super$test; + return babelHelpers.set(babelHelpers.getPrototypeOf(_obj), "test", (_super$test = +babelHelpers.get(babelHelpers.getPrototypeOf(_obj), "test", this)) + 1, this, false), _super$test; } }; Object.setPrototypeOf(obj, Base); diff --git a/packages/babel-plugin-transform-object-super/test/fixtures/object-super/super-increment-prefix/output.js b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/super-increment-prefix/output.js index 1b47694d54..c901504c89 100644 --- a/packages/babel-plugin-transform-object-super/test/fixtures/object-super/super-increment-prefix/output.js +++ b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/super-increment-prefix/output.js @@ -5,9 +5,7 @@ var Base = { }; var obj = _obj = { bar: function () { - var _super$test; - - return _super$test = Number(babelHelpers.get(babelHelpers.getPrototypeOf(_obj), "test", this)), babelHelpers.set(babelHelpers.getPrototypeOf(_obj), "test", _super$test + 1, this, false); + return babelHelpers.set(babelHelpers.getPrototypeOf(_obj), "test", +babelHelpers.get(babelHelpers.getPrototypeOf(_obj), "test", this) + 1, this, false); } }; Object.setPrototypeOf(obj, Base);