diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index 2ac4251c02..566aa199b0 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -700,3 +700,70 @@ helpers.toPropertyKey = defineHelper(` } } `); + +/** + * Add a helper that will throw a useful error if the transform fails to detect the class + * property assignment, so users know something failed. + */ +helpers.initializerWarningHelper = defineHelper(` + export default function _initializerWarningHelper(descriptor, context){ + throw new Error( + 'Decorating class property failed. Please ensure that ' + + 'proposal-class-properties is enabled and set to use loose mode. ' + + 'To use proposal-class-properties in spec mode with decorators, wait for ' + + 'the next major version of decorators in stage 2.' + ); + } +`); + +/** + * Add a helper to call as a replacement for class property definition. + */ +helpers.initializerDefineProperty = defineHelper(` + export default function _initializerDefineProperty(target, property, descriptor, context){ + if (!descriptor) return; + + Object.defineProperty(target, property, { + enumerable: descriptor.enumerable, + configurable: descriptor.configurable, + writable: descriptor.writable, + value: descriptor.initializer ? descriptor.initializer.call(context) : void 0, + }); + } +`); + +/** + * Add a helper to take an initial descriptor, apply some decorators to it, and optionally + * define the property. + */ +helpers.applyDecoratedDescriptor = defineHelper(` + export default function _applyDecoratedDescriptor(target, property, decorators, descriptor, context){ + var desc = {}; + Object['ke' + 'ys'](descriptor).forEach(function(key){ + desc[key] = descriptor[key]; + }); + desc.enumerable = !!desc.enumerable; + desc.configurable = !!desc.configurable; + if ('value' in desc || desc.initializer){ + desc.writable = true; + } + + desc = decorators.slice().reverse().reduce(function(desc, decorator){ + return decorator(target, property, desc) || desc; + }, desc); + + if (context && desc.initializer !== void 0){ + desc.value = desc.initializer ? desc.initializer.call(context) : void 0; + desc.initializer = undefined; + } + + if (desc.initializer === void 0){ + // This is a hack to avoid this being processed by 'transform-runtime'. + // See issue #9. + Object['define' + 'Property'](target, property, desc); + desc = null; + } + + return desc; + } +`); diff --git a/packages/babel-plugin-proposal-decorators/src/index.js b/packages/babel-plugin-proposal-decorators/src/index.js index 127e358863..0a6d1f6c60 100644 --- a/packages/babel-plugin-proposal-decorators/src/index.js +++ b/packages/babel-plugin-proposal-decorators/src/index.js @@ -26,116 +26,7 @@ const buildGetObjectInitializer = template(` }) `); -const buildInitializerWarningHelper = template(` - function NAME(descriptor, context){ - throw new Error( - 'Decorating class property failed. Please ensure that ' + - 'proposal-class-properties is enabled and set to use loose mode. ' + - 'To use proposal-class-properties in spec mode with decorators, wait for ' + - 'the next major version of decorators in stage 2.' - ); - } -`); - -const buildInitializerDefineProperty = template(` - function NAME(target, property, descriptor, context){ - if (!descriptor) return; - - Object.defineProperty(target, property, { - enumerable: descriptor.enumerable, - configurable: descriptor.configurable, - writable: descriptor.writable, - value: descriptor.initializer ? descriptor.initializer.call(context) : void 0, - }); - } -`); - -const buildApplyDecoratedDescriptor = template(` - function NAME(target, property, decorators, descriptor, context){ - var desc = {}; - Object['ke' + 'ys'](descriptor).forEach(function(key){ - desc[key] = descriptor[key]; - }); - desc.enumerable = !!desc.enumerable; - desc.configurable = !!desc.configurable; - if ('value' in desc || desc.initializer){ - desc.writable = true; - } - - desc = decorators.slice().reverse().reduce(function(desc, decorator){ - return decorator(target, property, desc) || desc; - }, desc); - - if (context && desc.initializer !== void 0){ - desc.value = desc.initializer ? desc.initializer.call(context) : void 0; - desc.initializer = undefined; - } - - if (desc.initializer === void 0){ - // This is a hack to avoid this being processed by 'transform-runtime'. - // See issue #9. - Object['define' + 'Property'](target, property, desc); - desc = null; - } - - return desc; - } -`); - export default function() { - /** - * Add a helper to take an initial descriptor, apply some decorators to it, and optionally - * define the property. - */ - function ensureApplyDecoratedDescriptorHelper(path, state) { - if (!state.applyDecoratedDescriptor) { - state.applyDecoratedDescriptor = path.scope.generateUidIdentifier( - "applyDecoratedDescriptor", - ); - const helper = buildApplyDecoratedDescriptor({ - NAME: state.applyDecoratedDescriptor, - }); - path.scope.getProgramParent().path.unshiftContainer("body", helper); - } - - return state.applyDecoratedDescriptor; - } - - /** - * Add a helper to call as a replacement for class property definition. - */ - function ensureInitializerDefineProp(path, state) { - if (!state.initializerDefineProp) { - state.initializerDefineProp = path.scope.generateUidIdentifier( - "initDefineProp", - ); - const helper = buildInitializerDefineProperty({ - NAME: state.initializerDefineProp, - }); - path.scope.getProgramParent().path.unshiftContainer("body", helper); - } - - return state.initializerDefineProp; - } - - /** - * Add a helper that will throw a useful error if the transform fails to detect the class - * property assignment, so users know something failed. - */ - function ensureInitializerWarning(path, state) { - if (!state.initializerWarningHelper) { - state.initializerWarningHelper = path.scope.generateUidIdentifier( - "initializerWarningHelper", - ); - const helper = buildInitializerWarningHelper({ - NAME: state.initializerWarningHelper, - }); - path.scope.getProgramParent().path.unshiftContainer("body", helper); - } - - return state.initializerWarningHelper; - } - /** * If the decorator expressions are non-identifiers, hoist them to before the class so we can be sure * that they are evaluated in order. @@ -260,35 +151,32 @@ export default function() { t.blockStatement([t.returnStatement(node.value)]), ) : t.nullLiteral(); - node.value = t.callExpression(ensureInitializerWarning(path, state), [ - descriptor, - t.thisExpression(), - ]); + node.value = t.callExpression( + state.addHelper("initializerWarningHelper"), + [descriptor, t.thisExpression()], + ); acc = acc.concat([ t.assignmentExpression( "=", descriptor, - t.callExpression( - ensureApplyDecoratedDescriptorHelper(path, state), - [ - target, - property, - t.arrayExpression(decorators.map(dec => dec.expression)), - t.objectExpression([ - t.objectProperty( - t.identifier("enumerable"), - t.booleanLiteral(true), - ), - t.objectProperty(t.identifier("initializer"), initializer), - ]), - ], - ), + t.callExpression(state.addHelper("applyDecoratedDescriptor"), [ + target, + property, + t.arrayExpression(decorators.map(dec => dec.expression)), + t.objectExpression([ + t.objectProperty( + t.identifier("enumerable"), + t.booleanLiteral(true), + ), + t.objectProperty(t.identifier("initializer"), initializer), + ]), + ]), ), ]); } else { acc = acc.concat( - t.callExpression(ensureApplyDecoratedDescriptorHelper(path, state), [ + t.callExpression(state.addHelper("applyDecoratedDescriptor"), [ target, property, t.arrayExpression(decorators.map(dec => dec.expression)), @@ -367,21 +255,13 @@ export default function() { }, AssignmentExpression(path, state) { - if (!state.initializerWarningHelper) return; - if (!path.get("left").isMemberExpression()) return; if (!path.get("left.property").isIdentifier()) return; if (!path.get("right").isCallExpression()) return; - if ( - !path - .get("right.callee") - .isIdentifier({ name: state.initializerWarningHelper.name }) - ) { - return; - } + if (!path.get("right.callee").isIdentifier()) return; path.replaceWith( - t.callExpression(ensureInitializerDefineProp(path, state), [ + t.callExpression(state.addHelper("initializerDefineProperty"), [ path.get("left.object").node, t.stringLiteral(path.get("left.property").node.name), path.get("right.arguments")[0].node,