From 9aec4ad1594c9aa14dce66a1e4689f5bb201926b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Fri, 7 Sep 2018 15:58:42 +0200 Subject: [PATCH] Add support for the new decorators proposal (#7976) --- packages/babel-helpers/src/helpers.js | 639 ++++++++++++++++++ .../package.json | 2 + .../src/index.js | 21 +- .../src/transformer.js | 233 ++++++- .../duplicated-keys/coalesce-get-set/exec.js | 22 + .../computed-keys-same-ast/exec.js | 28 + .../computed-keys-same-ast/input.js | 10 + .../computed-keys-same-ast/output.js | 31 + .../computed-keys-same-value/exec.js | 30 + .../computed-keys-same-value/input.js | 10 + .../computed-keys-same-value/output.js | 31 + .../exec.js | 21 + .../exec.js | 23 + .../exec.js | 25 + .../duplicated-keys/extras-duplicated/exec.js | 32 + .../extras-same-as-return/exec.js | 31 + .../get-set-both-decorated/exec.js | 11 + .../duplicated-keys/moved-and-created/exec.js | 33 + .../fixtures/duplicated-keys/options.json | 7 + .../exec.js | 13 + .../exec.js | 12 + .../exec.js | 16 + .../exec.js | 12 + .../exec.js | 17 + .../created-own-field/exec.js | 30 + .../created-own-method/exec.js | 28 + .../created-prototype-field/exec.js | 29 + .../created-prototype-method/exec.js | 27 + .../created-static-field/exec.js | 30 + .../created-static-method/exec.js | 28 + .../element-descriptors/default/exec.js | 14 + .../not-reused-class/exec.js | 8 + .../not-reused-field/exec.js | 13 + .../not-reused-method/exec.js | 13 + .../fixtures/element-descriptors/options.json | 7 + .../original-class/exec.js | 19 + .../exec.js | 18 + .../original-own-field/exec.js | 21 + .../original-prototype-method/exec.js | 18 + .../original-static-field/exec.js | 21 + .../original-static-method/exec.js | 18 + .../finishers/class-as-parameter/exec.js | 16 + .../fixtures/finishers/no-in-extras/exec.js | 21 + .../test/fixtures/finishers/options.json | 7 + .../fixtures/finishers/return-class/exec.js | 16 + .../test/fixtures/ordering/decorators/exec.js | 31 + .../field-initializers-after-methods/exec.js | 27 + .../test/fixtures/ordering/finishers/exec.js | 34 + .../test/fixtures/ordering/options.json | 7 + .../exec.js | 23 + .../transformation/arguments/input.js | 4 + .../transformation/arguments/output.js | 22 + .../class-decorators-yield-await/input.js | 5 + .../class-decorators-yield-await/options.json | 8 + .../class-decorators-yield-await/output.js | 17 + .../transformation/declaration/input.js | 2 + .../transformation/declaration/output.js | 15 + .../export-default-anonymous/input.mjs | 1 + .../export-default-anonymous/output.mjs | 13 + .../export-default-named/input.mjs | 1 + .../export-default-named/output.mjs | 14 + .../transformation/expression/input.js | 1 + .../transformation/expression/output.js | 15 + .../transformation/extends-await/input.js | 3 + .../transformation/extends-await/output.js | 19 + .../transformation/extends-yield/input.js | 3 + .../transformation/extends-yield/output.js | 19 + .../fixtures/transformation/extends/exec.js | 7 + .../fixtures/transformation/extends/input.js | 1 + .../fixtures/transformation/extends/output.js | 17 + .../transformation/only-decorated/input.js | 4 + .../transformation/only-decorated/output.js | 8 + .../test/fixtures/transformation/options.json | 7 + .../transformation/strict-directive/input.js | 15 + .../transformation/strict-directive/output.js | 47 ++ .../src/index.js | 12 +- .../test/index.js | 12 +- 77 files changed, 2097 insertions(+), 29 deletions(-) create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/coalesce-get-set/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-ast/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-ast/input.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-ast/output.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-value/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-value/input.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-value/output.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/create-existing-element-from-class-decorator/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/create-existing-element-from-method-decorator/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/create-existing-element-with-extras/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/extras-duplicated/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/extras-same-as-return/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/get-set-both-decorated/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/moved-and-created/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/options.json create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-both-decorated/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-first-decorated/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-no-decorators/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-second-decorated/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-prototype-and-static/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-own-field/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-own-method/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-prototype-field/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-prototype-method/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-static-field/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-static-method/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/default/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/not-reused-class/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/not-reused-field/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/not-reused-method/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/options.json create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-class/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-own-field-without-initiailzer/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-own-field/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-prototype-method/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-static-field/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-static-method/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/finishers/class-as-parameter/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/finishers/no-in-extras/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/finishers/options.json create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/finishers/return-class/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/ordering/decorators/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/ordering/field-initializers-after-methods/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/ordering/finishers/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/ordering/options.json create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/ordering/static-field-initializers-after-methods/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/arguments/input.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/arguments/output.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/class-decorators-yield-await/input.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/class-decorators-yield-await/options.json create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/class-decorators-yield-await/output.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/declaration/input.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/declaration/output.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-anonymous/input.mjs create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-anonymous/output.mjs create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-named/input.mjs create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-named/output.mjs create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/expression/input.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/expression/output.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-await/input.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-await/output.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-yield/input.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-yield/output.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends/input.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends/output.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/only-decorated/input.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/only-decorated/output.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/options.json create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/strict-directive/input.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/transformation/strict-directive/output.js diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index ff5528435d..c8f83ac0ee 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -1078,3 +1078,642 @@ helpers.classStaticPrivateFieldSpecSet = helper("7.0.1")` return value; } `; + +helpers.decorate = helper("7.0.1")` + import toArray from "toArray"; + + // These comments are stripped by @babel/template + /*:: + type PropertyDescriptor = + | { + value: any, + writable: boolean, + configurable: boolean, + enumerable: boolean, + } + | { + get?: () => any, + set?: (v: any) => void, + configurable: boolean, + enumerable: boolean, + }; + + type FieldDescriptor ={ + writable: boolean, + configurable: boolean, + enumerable: boolean, + }; + + type Placement = "static" | "prototype" | "own"; + type Key = string | symbol; // PrivateName is not supported yet. + + type ElementDescriptor = + | { + kind: "method", + key: Key, + placement: Placement, + descriptor: PropertyDescriptor + } + | { + kind: "field", + key: Key, + placement: Placement, + descriptor: FieldDescriptor, + initializer?: () => any, + }; + + // This is exposed to the user code + type ElementObjectInput = ElementDescriptor & { + [@@toStringTag]?: "Descriptor" + }; + + // This is exposed to the user code + type ElementObjectOutput = ElementDescriptor & { + [@@toStringTag]?: "Descriptor" + extras?: ElementDescriptor[], + finisher?: ClassFinisher, + }; + + // This is exposed to the user code + type ClassObject = { + [@@toStringTag]?: "Descriptor", + kind: "class", + elements: ElementDescriptor[], + }; + + type ElementDecorator = (descriptor: ElementObjectInput) => ?ElementObjectOutput; + type ClassDecorator = (descriptor: ClassObject) => ?ClassObject; + type ClassFinisher = (cl: Class) => Class; + + // Only used by Babel in the transform output, not part of the spec. + type ElementDefinition = + | { + kind: "method", + value: any, + key: Key, + static?: boolean, + decorators?: ElementDecorator[], + } + | { + kind: "field", + value: () => any, + key: Key, + static?: boolean, + decorators?: ElementDecorator[], + }; + + declare function ClassFactory(initialize: (instance: C) => void): { + F: Class, + d: ElementDefinition[] + } + + */ + + /*:: + // Various combinations with/without extras and with one or many finishers + + type ElementFinisherExtras = { + element: ElementDescriptor, + finisher?: ClassFinisher, + extras?: ElementDescriptor[], + }; + + type ElementFinishersExtras = { + element: ElementDescriptor, + finishers: ClassFinisher[], + extras: ElementDescriptor[], + }; + + type ElementsFinisher = { + elements: ElementDescriptor[], + finisher?: ClassFinisher, + }; + + type ElementsFinishers = { + elements: ElementDescriptor[], + finishers: ClassFinisher[], + }; + + */ + + // ClassDefinitionEvaluation (Steps 26-*) + export default function _decorate( + decorators /*: ClassDecorator[] */, + factory /*: ClassFactory */, + superClass /*: ?Class<*> */, + ) /*: Class<*> */ { + var r = factory(function initialize(O) { + _initializeInstanceElements(O, decorated.elements); + }, superClass); + var decorated = _decorateClass( + _coalesceClassElements(r.d.map(_createElementDescriptor)), + decorators, + ); + + _initializeClassElements(r.F, decorated.elements); + + return _runClassFinishers(r.F, decorated.finishers); + } + + // ClassElementEvaluation + function _createElementDescriptor( + def /*: ElementDefinition */, + ) /*: ElementDescriptor */ { + var descriptor /*: PropertyDescriptor */; + if (def.kind === "method") { + descriptor = { + value: def.value, + writable: true, + configurable: true, + enumerable: false, + }; + } else if (def.kind === "get") { + descriptor = { get: def.value, configurable: true, enumerable: false }; + } else if (def.kind === "set") { + descriptor = { set: def.value, configurable: true, enumerable: false }; + } else if (def.kind === "field") { + descriptor = { configurable: true, writable: true, enumerable: false }; + } + + var element /*: ElementDescriptor */ = { + kind: def.kind === "field" ? "field" : "method", + key: def.key, + placement: def.static + ? "static" + : def.kind === "field" + ? "own" + : "prototype", + descriptor: descriptor, + }; + if (def.decorators) element.decorators = def.decorators; + if (def.kind === "field") element.initializer = def.value; + + return element; + } + + // CoalesceGetterSetter + function _coalesceGetterSetter( + element /*: ElementDescriptor */, + other /*: ElementDescriptor */, + ) { + if (element.descriptor.get !== undefined) { + other.descriptor.get = element.descriptor.get; + } else { + other.descriptor.set = element.descriptor.set; + } + } + + // CoalesceClassElements + function _coalesceClassElements( + elements /*: ElementDescriptor[] */, + ) /*: ElementDescriptor[] */ { + var newElements /*: ElementDescriptor[] */ = []; + + var isSameElement = function(other /*: ElementDescriptor */) /*: boolean */ { + return ( + other.kind === "method" && + other.key === element.key && + other.placement === element.placement + ); + }; + + for (var i = 0; i < elements.length; i++) { + var element /*: ElementDescriptor */ = elements[i]; + var other /*: ElementDescriptor */; + + if ( + element.kind === "method" && + (other = newElements.find(isSameElement)) + ) { + if ( + _isDataDescriptor(element.descriptor) || + _isDataDescriptor(other.descriptor) + ) { + if (_hasDecorators(element) || _hasDecorators(other)) { + throw new ReferenceError( + "Duplicated methods (" + element.key + ") can't be decorated.", + ); + } + other.descriptor = element.descriptor; + } else { + if (_hasDecorators(element)) { + if (_hasDecorators(other)) { + throw new ReferenceError( + "Decorators can't be placed on different accessors with for " + + "the same property (" + + element.key + + ").", + ); + } + other.decorators = element.decorators; + } + _coalesceGetterSetter(element, other); + } + } else { + newElements.push(element); + } + } + + return newElements; + } + + function _hasDecorators(element /*: ElementDescriptor */) /*: boolean */ { + return element.decorators && element.decorators.length; + } + + function _isDataDescriptor(desc /*: PropertyDescriptor */) /*: boolean */ { + return ( + desc !== undefined && + !(desc.value === undefined && desc.writable === undefined) + ); + } + + // InitializeClassElements + function _initializeClassElements /*::*/( + F /*: Class */, + elements /*: ElementDescriptor[] */, + ) { + var proto = F.prototype; + + ["method", "field"].forEach(function(kind) { + elements.forEach(function(element /*: ElementDescriptor */) { + var placement = element.placement; + if ( + element.kind === kind && + (placement === "static" || placement === "prototype") + ) { + var receiver = placement === "static" ? F : proto; + _defineClassElement(receiver, element); + } + }); + }); + } + + // InitializeInstanceElements + function _initializeInstanceElements /*::*/( + O /*: C */, + elements /*: ElementDescriptor[] */, + ) { + ["method", "field"].forEach(function(kind) { + elements.forEach(function(element /*: ElementDescriptor */) { + if (element.kind === kind && element.placement === "own") { + _defineClassElement(O, element); + } + }); + }); + } + + // DefineClassElement + function _defineClassElement /*::*/( + receiver /*: C | Class */, + element /*: ElementDescriptor */, + ) { + var descriptor /*: PropertyDescriptor */ = element.descriptor; + if (element.kind === "field") { + var initializer = element.initializer; + descriptor = { + enumerable: descriptor.enumerable, + writable: descriptor.writable, + configurable: descriptor.configurable, + value: initializer === void 0 ? void 0 : initializer.call(receiver), + }; + } + Object.defineProperty(receiver, element.key, descriptor); + } + + /*:: + + type Placements = { + static: Key[], + prototype: Key[], + own: Key[], + }; + + */ + + // DecorateClass + function _decorateClass( + elements /*: ElementDescriptor[] */, + decorators /*: ClassDecorator[] */, + ) /*: ElementsFinishers */ { + var newElements /*: ElementDescriptor[] */ = []; + var finishers /*: ClassFinisher[] */ = []; + var placements /*: Placements */ = { static: [], prototype: [], own: [] }; + + elements.forEach(function(element /*: ElementDescriptor */) { + _addElementPlacement(element, placements); + }); + + elements.forEach(function(element /*: ElementDescriptor */) { + if (!_hasDecorators(element)) return newElements.push(element); + + var elementFinishersExtras /*: ElementFinishersExtras */ = _decorateElement( + element, + placements, + ); + newElements.push(elementFinishersExtras.element); + newElements.push.apply(newElements, elementFinishersExtras.extras); + finishers.push.apply(finishers, elementFinishersExtras.finishers); + }); + + if (!decorators) { + return { elements: newElements, finishers: finishers }; + } + + var result /*: ElementsFinishers */ = _decorateConstructor( + newElements, + decorators, + ); + finishers.push.apply(finishers, result.finishers); + result.finishers = finishers; + + return result; + } + + // AddElementPlacement + function _addElementPlacement( + element /*: ElementDescriptor */, + placements /*: Placements */, + silent /*: boolean */, + ) { + var keys = placements[element.placement]; + if (!silent && keys.indexOf(element.key) !== -1) { + throw new TypeError("Duplicated element (" + element.key + ")"); + } + keys.push(element.key); + } + + // DecorateElement + function _decorateElement( + element /*: ElementDescriptor */, + placements /*: Placements */, + ) /*: ElementFinishersExtras */ { + var extras /*: ElementDescriptor[] */ = []; + var finishers /*: ClassFinisher[] */ = []; + + for ( + var decorators = element.decorators, i = decorators.length - 1; + i >= 0; + i-- + ) { + // (inlined) RemoveElementPlacement + var keys = placements[element.placement]; + keys.splice(keys.indexOf(element.key), 1); + + var elementObject /*: ElementObjectInput */ = _fromElementDescriptor( + element, + ); + var elementFinisherExtras /*: ElementFinisherExtras */ = _toElementFinisherExtras( + (0, decorators[i])(elementObject) /*: ElementObjectOutput */ || + elementObject, + ); + + element = elementFinisherExtras.element; + _addElementPlacement(element, placements); + + if (elementFinisherExtras.finisher) { + finishers.push(elementFinisherExtras.finisher); + } + + var newExtras /*: ElementDescriptor[] | void */ = + elementFinisherExtras.extras; + if (newExtras) { + for (var j = 0; j < newExtras.length; j++) { + _addElementPlacement(newExtras[j], placements); + } + extras.push.apply(extras, newExtras); + } + } + + return { element: element, finishers: finishers, extras: extras }; + } + + // DecorateConstructor + function _decorateConstructor( + elements /*: ElementDescriptor[] */, + decorators /*: ClassDecorator[] */, + ) /*: ElementsFinishers */ { + var finishers /*: ClassFinisher[] */ = []; + + for (var i = decorators.length - 1; i >= 0; i--) { + var obj /*: ClassObject */ = _fromClassDescriptor(elements); + var elementsAndFinisher /*: ElementsFinisher */ = _toClassDescriptor( + (0, decorators[i])(obj) /*: ClassObject */ || obj, + ); + + if (elementsAndFinisher.finisher !== undefined) { + finishers.push(elementsAndFinisher.finisher); + } + + if (elementsAndFinisher.elements !== undefined) { + elements = elementsAndFinisher.elements; + + for (var j = 0; j < elements.length - 1; j++) { + for (var k = j + 1; k < elements.length; k++) { + if ( + elements[j].key === elements[k].key && + elements[j].placement === elements[k].placement + ) { + throw new TypeError("Duplicated element (" + elements[j].key + ")"); + } + } + } + } + } + + return { elements: elements, finishers: finishers }; + } + + // FromElementDescriptor + function _fromElementDescriptor( + element /*: ElementDescriptor */, + ) /*: ElementObject */ { + var obj /*: ElementObject */ = { + kind: element.kind, + key: element.key, + placement: element.placement, + descriptor: element.descriptor, + }; + + var desc = { + value: "Descriptor", + configurable: true, + }; + Object.defineProperty(obj, Symbol.toStringTag, desc); + + if (element.kind === "field") obj.initializer = element.initializer; + + return obj; + } + + // ToElementDescriptors + function _toElementDescriptors( + elementObjects /*: ElementObject[] */, + ) /*: ElementDescriptor[] */ { + if (elementObjects === undefined) return; + return toArray(elementObjects).map(function(elementObject) { + var element = _toElementDescriptor(elementObject); + _disallowProperty(elementObject, "finisher", "An element descriptor"); + _disallowProperty(elementObject, "extras", "An element descriptor"); + return element; + }); + } + + // ToElementDescriptor + function _toElementDescriptor( + elementObject /*: ElementObject */, + ) /*: ElementDescriptor */ { + var kind = String(elementObject.kind); + if (kind !== "method" && kind !== "field") { + throw new TypeError( + 'An element descriptor\\'s .kind property must be either "method" or' + + ' "field", but a decorator created an element descriptor with' + + ' .kind "' + + kind + + '"', + ); + } + + var key = elementObject.key; + if (typeof key !== "string" && typeof key !== "symbol") key = String(key); + + var placement = String(elementObject.placement); + if ( + placement !== "static" && + placement !== "prototype" && + placement !== "own" + ) { + throw new TypeError( + 'An element descriptor\\'s .placement property must be one of "static",' + + ' "prototype" or "own", but a decorator created an element descriptor' + + ' with .placement "' + + placement + + '"', + ); + } + + var descriptor /*: PropertyDescriptor */ = elementObject.descriptor; + + _disallowProperty(elementObject, "elements", "An element descriptor"); + + var element /*: ElementDescriptor */ = { + kind: kind, + key: key, + placement: placement, + descriptor: Object.assign({}, descriptor), + }; + + if (kind !== "field") { + _disallowProperty(elementObject, "initializer", "A method descriptor"); + } else { + _disallowProperty( + descriptor, + "get", + "The property descriptor of a field descriptor", + ); + _disallowProperty( + descriptor, + "set", + "The property descriptor of a field descriptor", + ); + _disallowProperty( + descriptor, + "value", + "The property descriptor of a field descriptor", + ); + + element.initializer = elementObject.initializer; + } + + return element; + } + + function _toElementFinisherExtras( + elementObject /*: ElementObject */, + ) /*: ElementFinisherExtras */ { + var element /*: ElementDescriptor */ = _toElementDescriptor(elementObject); + var finisher /*: ClassFinisher */ = _optionalCallableProperty( + elementObject, + "finisher", + ); + var extras /*: ElementDescriptors[] */ = _toElementDescriptors( + elementObject.extras, + ); + + return { element: element, finisher: finisher, extras: extras }; + } + + // FromClassDescriptor + function _fromClassDescriptor( + elements /*: ElementDescriptor[] */, + ) /*: ClassObject */ { + var obj = { + kind: "class", + elements: elements.map(_fromElementDescriptor), + }; + + var desc = { value: "Descriptor", configurable: true }; + Object.defineProperty(obj, Symbol.toStringTag, desc); + + return obj; + } + + // ToClassDescriptor + function _toClassDescriptor(obj /*: ClassObject */) /*: ElementsFinisher */ { + var kind = String(obj.kind); + if (kind !== "class") { + throw new TypeError( + 'A class descriptor\\'s .kind property must be "class", but a decorator' + + ' created a class descriptor with .kind "' + + kind + + '"', + ); + } + + _disallowProperty(obj, "key", "A class descriptor"); + _disallowProperty(obj, "placement", "A class descriptor"); + _disallowProperty(obj, "descriptor", "A class descriptor"); + _disallowProperty(obj, "initializer", "A class descriptor"); + _disallowProperty(obj, "extras", "A class descriptor"); + + var finisher = _optionalCallableProperty(obj, "finisher"); + var elements = _toElementDescriptors(obj.elements); + + return { elements: elements, finisher: finisher }; + } + + function _disallowProperty(obj, name, objectType) { + if (obj[name] !== undefined) { + throw new TypeError(objectType + " can't have a ." + name + " property."); + } + } + + function _optionalCallableProperty /*::*/( + obj /*: T */, + name /*: $Keys */, + ) /*: ?Function */ { + var value = obj[name]; + if (value !== undefined && typeof value !== "function") { + throw new TypeError("Expected '" + name + "' to be a function"); + } + return value; + } + + // RunClassFinishers + function _runClassFinishers( + constructor /*: Class<*> */, + finishers /*: ClassFinisher[] */, + ) /*: Class<*> */ { + for (var i = 0; i < finishers.length; i++) { + var newConstructor /*: ?Class<*> */ = (0, finishers[i])(constructor); + if (newConstructor !== undefined) { + // NOTE: This should check if IsConstructor(newConstructor) is false. + if (typeof newConstructor !== "function") { + throw new TypeError("Finishers must return a constructor."); + } + constructor = newConstructor; + } + } + return constructor; + } + `; diff --git a/packages/babel-plugin-proposal-decorators/package.json b/packages/babel-plugin-proposal-decorators/package.json index 5fa98f139a..905f94c67d 100644 --- a/packages/babel-plugin-proposal-decorators/package.json +++ b/packages/babel-plugin-proposal-decorators/package.json @@ -16,6 +16,8 @@ ], "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.0.0", + "@babel/helper-split-export-declaration": "^7.0.0", "@babel/plugin-syntax-decorators": "^7.0.0" }, "peerDependencies": { diff --git a/packages/babel-plugin-proposal-decorators/src/index.js b/packages/babel-plugin-proposal-decorators/src/index.js index 3c242ec4a0..5467f115eb 100644 --- a/packages/babel-plugin-proposal-decorators/src/index.js +++ b/packages/babel-plugin-proposal-decorators/src/index.js @@ -6,20 +6,21 @@ import legacyVisitor from "./transformer-legacy"; export default declare((api, options) => { api.assertVersion(7); - const { legacy = false, decoratorsBeforeExport } = options; + const { legacy = false } = options; if (typeof legacy !== "boolean") { throw new Error("'legacy' must be a boolean."); } - if (legacy !== true) { - throw new Error( - "The new decorators proposal is not supported yet." + - ' You must pass the `"legacy": true` option to' + - " @babel/plugin-proposal-decorators", - ); - } - - if (decoratorsBeforeExport !== undefined) { + const { decoratorsBeforeExport } = options; + if (decoratorsBeforeExport === undefined) { + if (!legacy) { + throw new Error( + "The decorators plugin requires a 'decoratorsBeforeExport' option," + + " whose value must be a boolean. If you want to use the legacy" + + " decorators semantics, you can set the 'legacy: true' option.", + ); + } + } else { if (legacy) { throw new Error( "'decoratorsBeforeExport' can't be used with legacy decorators.", diff --git a/packages/babel-plugin-proposal-decorators/src/transformer.js b/packages/babel-plugin-proposal-decorators/src/transformer.js index 8dab9cadab..25196f0353 100644 --- a/packages/babel-plugin-proposal-decorators/src/transformer.js +++ b/packages/babel-plugin-proposal-decorators/src/transformer.js @@ -1,3 +1,232 @@ -// Not implemented yet +import { types as t, template } from "@babel/core"; +import splitExportDeclaration from "@babel/helper-split-export-declaration"; +import ReplaceSupers from "@babel/helper-replace-supers"; -export default {}; +function prop(key, value) { + if (!value) return null; + return t.objectProperty(t.identifier(key), value); +} + +function value(body, params = []) { + return t.objectMethod("method", t.identifier("value"), params, body); +} + +function hasDecorators({ node }) { + if (node.decorators && node.decorators.length > 0) return true; + + const body = node.body.body; + for (let i = 0; i < body.length; i++) { + const method = body[i]; + if (method.decorators && method.decorators.length > 0) { + return true; + } + } + + return false; +} + +function takeDecorators({ node }) { + let result; + if (node.decorators && node.decorators.length > 0) { + result = t.arrayExpression( + node.decorators.map(decorator => decorator.expression), + ); + } + node.decorators = undefined; + return result; +} + +function getKey(node) { + if (node.computed) { + return node.key; + } else if (t.isIdentifier(node.key)) { + return t.stringLiteral(node.key.name); + } else { + return t.stringLiteral(String(node.key.value)); + } +} + +function getSingleElementDefinition(path, superRef, classRef, file) { + const { node, scope } = path; + const isMethod = path.isClassMethod(); + + if (path.isPrivate()) { + throw path.buildCodeFrameError( + `Private ${ + isMethod ? "methods" : "fields" + } in decorated classes are not supported yet.`, + ); + } + + new ReplaceSupers( + { + methodPath: path, + methodNode: node, + objectRef: classRef, + isStatic: node.static, + superRef, + scope, + file, + }, + true, + ).replace(); + + const properties = [ + prop("kind", t.stringLiteral(isMethod ? node.kind : "field")), + prop("decorators", takeDecorators(path)), + prop("static", node.static && t.booleanLiteral(true)), + prop("key", getKey(node)), + isMethod + ? value(node.body, node.params) + : node.value + ? value(template.ast`{ return ${node.value} }`) + : prop("value", scope.buildUndefinedNode()), + ].filter(Boolean); + + return t.objectExpression(properties); +} + +function getElementsDefinitions(path, fId, file) { + const elements = []; + for (const p of path.get("body.body")) { + if (!p.isClassMethod({ kind: "constructor" })) { + elements.push( + getSingleElementDefinition(p, path.node.superClass, fId, file), + ); + p.remove(); + } + } + + return t.arrayExpression(elements); +} + +function getConstructorPath(path) { + return path + .get("body.body") + .find(path => path.isClassMethod({ kind: "constructor" })); +} + +const bareSupersVisitor = { + CallExpression(path, { initializeInstanceElements }) { + if (path.get("callee").isSuper()) { + path.insertAfter(t.cloneNode(initializeInstanceElements)); + } + }, + Function(path) { + if (!path.isArrowFunctionExpression()) path.skip(); + }, +}; + +function insertInitializeInstanceElements(path, initializeInstanceId) { + const isBase = !path.node.superClass; + const initializeInstanceElements = t.callExpression(initializeInstanceId, [ + t.thisExpression(), + ]); + + const constructorPath = getConstructorPath(path); + if (constructorPath) { + if (isBase) { + constructorPath + .get("body") + .unshiftContainer("body", [ + t.expressionStatement(initializeInstanceElements), + ]); + } else { + constructorPath.traverse(bareSupersVisitor, { + initializeInstanceElements, + }); + } + } else { + const constructor = isBase + ? t.classMethod( + "constructor", + t.identifier("constructor"), + [], + t.blockStatement([t.expressionStatement(initializeInstanceElements)]), + ) + : t.classMethod( + "constructor", + t.identifier("constructor"), + [t.restElement(t.identifier("args"))], + t.blockStatement([ + t.expressionStatement( + t.callExpression(t.Super(), [ + t.spreadElement(t.identifier("args")), + ]), + ), + t.expressionStatement(initializeInstanceElements), + ]), + ); + path.node.body.body.push(constructor); + } +} + +function transformClass(path, file) { + const isDeclaration = path.node.id && path.isDeclaration(); + const isStrict = path.isInStrictMode(); + const { superClass } = path.node; + + path.node.type = "ClassDeclaration"; + if (!path.node.id) path.node.id = path.scope.generateUidIdentifier("class"); + + const initializeId = path.scope.generateUidIdentifier("initialize"); + const superId = + superClass && + path.scope.generateUidIdentifierBasedOnNode(path.node.superClass, "super"); + + if (superClass) path.node.superClass = superId; + + const classDecorators = takeDecorators(path); + const definitions = getElementsDefinitions(path, path.node.id, file); + + insertInitializeInstanceElements(path, initializeId); + + const expr = template.expression.ast` + ${addDecorateHelper(file)}( + ${classDecorators || t.nullLiteral()}, + function (${initializeId}, ${superClass ? superId : null}) { + ${path.node} + return { F: ${t.cloneNode(path.node.id)}, d: ${definitions} }; + }, + ${superClass} + ) + `; + if (!isStrict) { + expr.arguments[1].body.directives.push( + t.directive(t.directiveLiteral("use strict")), + ); + } + + return isDeclaration ? template.ast`let ${path.node.id} = ${expr}` : expr; +} + +function addDecorateHelper(file) { + try { + return file.addHelper("decorate"); + } catch (err) { + if (err.code === "BABEL_HELPER_UNKNOWN") { + err.message += + "\n '@babel/plugin-transform-decorators' in non-legacy mode" + + " requires '@babel/core' version ^7.0.1 and you appear to be using" + + " an older version."; + } + throw err; + } +} + +export default { + ExportDefaultDeclaration(path) { + let decl = path.get("declaration"); + if (!decl.isClassDeclaration() || !hasDecorators(decl)) return; + + if (decl.node.id) decl = splitExportDeclaration(path); + + decl.replaceWith(transformClass(decl, this.file)); + }, + + Class(path) { + if (hasDecorators(path)) { + path.replaceWith(transformClass(path, this.file)); + } + }, +}; diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/coalesce-get-set/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/coalesce-get-set/exec.js new file mode 100644 index 0000000000..ff096295c5 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/coalesce-get-set/exec.js @@ -0,0 +1,22 @@ +var el, el1; + +@(_ => el = _) +class A { + @(_ => el1 = _) + get foo() { return 1; } + + set foo(x) { return 2; } +} + +expect(el.elements).toHaveLength(1); + +expect(el1).toEqual(expect.objectContaining({ + descriptor: expect.objectContaining({ + get: expect.any(Function), + set: expect.any(Function) + }) +})); + +var desc = Object.getOwnPropertyDescriptor(A.prototype, "foo"); +expect(desc.get()).toBe(1); +expect(desc.set()).toBe(2); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-ast/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-ast/exec.js new file mode 100644 index 0000000000..3a314e78ca --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-ast/exec.js @@ -0,0 +1,28 @@ +var i = 0; + +function getKey() { + return (i++).toString(); +} + +var desc; + +@(_ => desc = _) +class Foo { + [getKey()]() { + return 1; + } + + [getKey()]() { + return 2; + } +} + +expect(desc.elements).toHaveLength(2); + +expect(desc.elements[0].key).toBe("0"); +expect(desc.elements[0].descriptor.value()).toBe(1); + +expect(desc.elements[1].key).toBe("1"); +expect(desc.elements[1].descriptor.value()).toBe(2); + +expect(i).toBe(2); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-ast/input.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-ast/input.js new file mode 100644 index 0000000000..af96205034 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-ast/input.js @@ -0,0 +1,10 @@ +@(_ => desc = _) +class Foo { + [getKey()]() { + return 1; + } + + [getKey()]() { + return 2; + } +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-ast/output.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-ast/output.js new file mode 100644 index 0000000000..a08d2e1db8 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-ast/output.js @@ -0,0 +1,31 @@ +let Foo = babelHelpers.decorate([_ => desc = _], function (_initialize) { + "use strict"; + + class Foo { + constructor() { + _initialize(this); + } + + } + + return { + F: Foo, + d: [{ + kind: "method", + key: getKey(), + + value() { + return 1; + } + + }, { + kind: "method", + key: getKey(), + + value() { + return 2; + } + + }] + }; +}); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-value/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-value/exec.js new file mode 100644 index 0000000000..48a03a2eb8 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-value/exec.js @@ -0,0 +1,30 @@ +var i = 0; +var j = 0; + +function getKeyI() { + return (i++).toString(); +} +function getKeyJ() { + return (j++).toString(); +} + +var desc; + +@(_ => desc = _) +class Foo { + [getKeyI()]() { + return 1; + } + + [getKeyJ()]() { + return 2; + } +} + +expect(desc.elements).toHaveLength(1); + +expect(desc.elements[0].key).toBe("0"); +expect(desc.elements[0].descriptor.value()).toBe(2); + +expect(i).toBe(1); +expect(j).toBe(1); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-value/input.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-value/input.js new file mode 100644 index 0000000000..8ce01d7f50 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-value/input.js @@ -0,0 +1,10 @@ +@(_ => desc = _) +class Foo { + [getKeyI()]() { + return 1; + } + + [getKeyJ()]() { + return 2; + } +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-value/output.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-value/output.js new file mode 100644 index 0000000000..bffa9e17a1 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-value/output.js @@ -0,0 +1,31 @@ +let Foo = babelHelpers.decorate([_ => desc = _], function (_initialize) { + "use strict"; + + class Foo { + constructor() { + _initialize(this); + } + + } + + return { + F: Foo, + d: [{ + kind: "method", + key: getKeyI(), + + value() { + return 1; + } + + }, { + kind: "method", + key: getKeyJ(), + + value() { + return 2; + } + + }] + }; +}); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/create-existing-element-from-class-decorator/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/create-existing-element-from-class-decorator/exec.js new file mode 100644 index 0000000000..878af82f5a --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/create-existing-element-from-class-decorator/exec.js @@ -0,0 +1,21 @@ +function pushElement(e) { + return function (c) { c.elements.push(e); return c }; +} + +expect(() => { + @pushElement({ + kind: "method", + key: "foo", + descriptor: { + enumerable: true, + configurable: true, + writable: true, + value: function() {}, + } + }) + class A { + foo() {} + } +}).toThrow(TypeError); + + diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/create-existing-element-from-method-decorator/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/create-existing-element-from-method-decorator/exec.js new file mode 100644 index 0000000000..49ccffb251 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/create-existing-element-from-method-decorator/exec.js @@ -0,0 +1,23 @@ +function decorate() { + return { + kind: "method", + key: "foo", + descriptor: { + enumerable: true, + configurable: true, + writable: true, + value: function() {}, + } + }; +} + +expect(() => { + class A { + @decorate + bar() {} + + foo() {} + } +}).toThrow(TypeError); + + diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/create-existing-element-with-extras/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/create-existing-element-with-extras/exec.js new file mode 100644 index 0000000000..683f9876e4 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/create-existing-element-with-extras/exec.js @@ -0,0 +1,25 @@ +function decorate(el) { + el.extras = [{ + kind: "method", + key: "foo", + descriptor: { + enumerable: true, + configurable: true, + writable: true, + value: function() {}, + } + }]; + + return el; +} + +expect(() => { + class A { + @decorate + bar() {} + + foo() {} + } +}).toThrow(TypeError); + + diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/extras-duplicated/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/extras-duplicated/exec.js new file mode 100644 index 0000000000..84ba83f7d2 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/extras-duplicated/exec.js @@ -0,0 +1,32 @@ +function decorate(el) { + el.extras = [{ + kind: "method", + key: "foo", + descriptor: { + enumerable: true, + configurable: true, + writable: true, + value: function() {}, + } + }, { + kind: "method", + key: "foo", + descriptor: { + enumerable: true, + configurable: true, + writable: true, + value: function() {}, + } + }]; + + return el; +} + +expect(() => { + class A { + @decorate + method() {} + } +}).toThrow(TypeError); + + diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/extras-same-as-return/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/extras-same-as-return/exec.js new file mode 100644 index 0000000000..9254ae4e7e --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/extras-same-as-return/exec.js @@ -0,0 +1,31 @@ +function decorate(el) { + return { + kind: "method", + key: "foo", + descriptor: { + enumerable: true, + configurable: true, + writable: true, + value: function() {}, + }, + extras: [{ + kind: "method", + key: "foo", + descriptor: { + enumerable: true, + configurable: true, + writable: true, + value: function() {}, + } + }] + }; +} + +expect(() => { + class A { + @decorate + method() {} + } +}).toThrow(TypeError); + + diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/get-set-both-decorated/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/get-set-both-decorated/exec.js new file mode 100644 index 0000000000..6fbc663f5a --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/get-set-both-decorated/exec.js @@ -0,0 +1,11 @@ +function dec(el) { return el } + +expect(() => { + class A { + @dec + get foo() {} + + @dec + set foo(x) {} + } +}).toThrow(ReferenceError); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/moved-and-created/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/moved-and-created/exec.js new file mode 100644 index 0000000000..af42307cbc --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/moved-and-created/exec.js @@ -0,0 +1,33 @@ +var value1, value2 = {}; + +function makeStatic(el) { + el.placement = "static"; + return el; +} + +function defineBar(el) { + el.extras = [{ + key: "bar", + kind: "method", + placement: "prototype", + descriptor: { + value: value2, + }, + }]; + return el; +} + +function storeValue(el) { + value1 = el.descriptor.value; + return el; +} + +class Foo { + @defineBar + @makeStatic + @storeValue + bar() {} +} + +expect(Foo.bar).toBe(value1); +expect(Foo.prototype.bar).toBe(value2); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/options.json b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/options.json new file mode 100644 index 0000000000..4f9b5b01d1 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + ["proposal-decorators", { "decoratorsBeforeExport": false }], + "proposal-class-properties", + "external-helpers" + ] +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-both-decorated/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-both-decorated/exec.js new file mode 100644 index 0000000000..c9593b343a --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-both-decorated/exec.js @@ -0,0 +1,13 @@ +expect(() => { + class A { + @(el => el) + method() { + return 1; + } + + @(el => el) + method() { + return 2; + } + } +}).toThrow(ReferenceError); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-first-decorated/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-first-decorated/exec.js new file mode 100644 index 0000000000..9ae41d96fa --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-first-decorated/exec.js @@ -0,0 +1,12 @@ +expect(() => { + class A { + method() { + return 1; + } + + @(el => el) + method() { + return 2; + } + } +}).toThrow(ReferenceError); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-no-decorators/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-no-decorators/exec.js new file mode 100644 index 0000000000..4cf8763819 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-no-decorators/exec.js @@ -0,0 +1,16 @@ +var el; + +@(_ => el = _) +class A { + method() { + return 1; + } + + method() { + return 2; + } +} + +expect(el.elements).toHaveLength(1); + +expect(A.prototype.method()).toBe(2); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-second-decorated/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-second-decorated/exec.js new file mode 100644 index 0000000000..bf38611a23 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-overwritten-second-decorated/exec.js @@ -0,0 +1,12 @@ +expect(() => { + class A { + @(el => el) + method() { + return 1; + } + + method() { + return 2; + } + } +}).toThrow(ReferenceError); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-prototype-and-static/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-prototype-and-static/exec.js new file mode 100644 index 0000000000..bf72a77ae3 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/original-method-prototype-and-static/exec.js @@ -0,0 +1,17 @@ +var el; + +@(_ => el = _) +class A { + method() { + return 1; + } + + static method() { + return 2; + } +} + +expect(el.elements).toHaveLength(2); + +expect(A.prototype.method()).toBe(1); +expect(A.method()).toBe(2); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-own-field/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-own-field/exec.js new file mode 100644 index 0000000000..d91d2d9973 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-own-field/exec.js @@ -0,0 +1,30 @@ +function pushElement(e) { + return function (c) { c.elements.push(e); return c }; +} + +var value = {}; + +@pushElement({ + kind: "field", + placement: "own", + key: "foo", + descriptor: { + enumerable: true, + configurable: true, + writable: true, + }, + initializer() { + return value; + } +}) +class A {} + +expect(A).not.toHaveProperty("foo"); +expect(A.prototype).not.toHaveProperty("foo"); + +expect(Object.getOwnPropertyDescriptor(new A(), "foo")).toEqual({ + enumerable: true, + configurable: true, + writable: true, + value: value, +}); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-own-method/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-own-method/exec.js new file mode 100644 index 0000000000..8c562448cc --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-own-method/exec.js @@ -0,0 +1,28 @@ +function pushElement(e) { + return function (c) { c.elements.push(e); return c }; +} + +function method() {} + +@pushElement({ + kind: "method", + placement: "own", + key: "foo", + descriptor: { + enumerable: true, + configurable: true, + writable: true, + value: method, + } +}) +class A {} + +expect(A).not.toHaveProperty("foo"); +expect(A.prototype).not.toHaveProperty("foo"); + +expect(Object.getOwnPropertyDescriptor(new A(), "foo")).toEqual({ + enumerable: true, + configurable: true, + writable: true, + value: method, +}); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-prototype-field/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-prototype-field/exec.js new file mode 100644 index 0000000000..fdb65df542 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-prototype-field/exec.js @@ -0,0 +1,29 @@ +function pushElement(e) { + return function (c) { c.elements.push(e); return c }; +} + +var value = {}; + +@pushElement({ + kind: "field", + placement: "prototype", + key: "foo", + descriptor: { + enumerable: true, + configurable: true, + writable: true, + }, + initializer() { + return value; + } +}) +class A {} + +expect(A).not.toHaveProperty("foo"); + +expect(Object.getOwnPropertyDescriptor(A.prototype, "foo")).toEqual({ + enumerable: true, + configurable: true, + writable: true, + value: value, +}); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-prototype-method/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-prototype-method/exec.js new file mode 100644 index 0000000000..3346f4d3f2 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-prototype-method/exec.js @@ -0,0 +1,27 @@ +function pushElement(e) { + return function (c) { c.elements.push(e); return c }; +} + +function method() {} + +@pushElement({ + kind: "method", + placement: "prototype", + key: "foo", + descriptor: { + enumerable: true, + configurable: true, + writable: true, + value: method, + } +}) +class A {} + +expect(A).not.toHaveProperty("foo"); + +expect(Object.getOwnPropertyDescriptor(A.prototype, "foo")).toEqual({ + enumerable: true, + configurable: true, + writable: true, + value: method, +}); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-static-field/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-static-field/exec.js new file mode 100644 index 0000000000..efebd91796 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-static-field/exec.js @@ -0,0 +1,30 @@ +function pushElement(e) { + return function (c) { c.elements.push(e); return c }; +} + +var value = {}; + +@pushElement({ + kind: "field", + placement: "static", + key: "foo", + descriptor: { + enumerable: true, + configurable: true, + writable: true, + }, + initializer() { + return value; + } +}) +class A {} + +expect(A.prototype).not.toHaveProperty("foo"); +expect(new A()).not.toHaveProperty("foo"); + +expect(Object.getOwnPropertyDescriptor(A, "foo")).toEqual({ + enumerable: true, + configurable: true, + writable: true, + value: value, +}); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-static-method/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-static-method/exec.js new file mode 100644 index 0000000000..2919a1c824 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/created-static-method/exec.js @@ -0,0 +1,28 @@ +function pushElement(e) { + return function (c) { c.elements.push(e); return c }; +} + +function method() {} + +@pushElement({ + kind: "method", + placement: "static", + key: "foo", + descriptor: { + enumerable: true, + configurable: true, + writable: true, + value: method, + } +}) +class A {} + +expect(A.prototype).not.toHaveProperty("foo"); +expect(new A()).not.toHaveProperty("foo"); + +expect(Object.getOwnPropertyDescriptor(A, "foo")).toEqual({ + enumerable: true, + configurable: true, + writable: true, + value: method, +}); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/default/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/default/exec.js new file mode 100644 index 0000000000..8c5c80a518 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/default/exec.js @@ -0,0 +1,14 @@ +function decorate(el) { + el.descriptor.value = 2; +} + +var Foo; + +expect(() => { + Foo = @(() => void 0) class Foo { + @decorate + bar() {} + } +}).not.toThrow(); + +expect(Foo.prototype.bar).toBe(2); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/not-reused-class/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/not-reused-class/exec.js new file mode 100644 index 0000000000..72803f2356 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/not-reused-class/exec.js @@ -0,0 +1,8 @@ +var dec1, dec2; + +@(_ => dec1 = _) +@(_ => dec2 = _) +class A {} + +expect(dec1).toEqual(dec2); +expect(dec1).not.toBe(dec2); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/not-reused-field/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/not-reused-field/exec.js new file mode 100644 index 0000000000..827d1864ea --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/not-reused-field/exec.js @@ -0,0 +1,13 @@ +var dec1, dec2; + +class A { + @(_ => dec1 = _) + @(_ => dec2 = _) + field = {} +} + +expect(dec1).toEqual(dec2); +expect(dec1).not.toBe(dec2); +expect(dec1.descriptor).toEqual(dec2.descriptor); +expect(dec1.descriptor).not.toBe(dec2.descriptor); +expect(dec1.initializer).toBe(dec2.initializer); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/not-reused-method/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/not-reused-method/exec.js new file mode 100644 index 0000000000..c4cf0708f1 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/not-reused-method/exec.js @@ -0,0 +1,13 @@ +var dec1, dec2; + +class A { + @(_ => dec1 = _) + @(_ => dec2 = _) + fn() {} +} + +expect(dec1).toEqual(dec2); +expect(dec1).not.toBe(dec2); +expect(dec1.descriptor).toEqual(dec2.descriptor); +expect(dec1.descriptor).not.toBe(dec2.descriptor); +expect(dec1.descriptor.value).toBe(dec2.descriptor.value); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/options.json b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/options.json new file mode 100644 index 0000000000..4f9b5b01d1 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + ["proposal-decorators", { "decoratorsBeforeExport": false }], + "proposal-class-properties", + "external-helpers" + ] +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-class/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-class/exec.js new file mode 100644 index 0000000000..d2770fd577 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-class/exec.js @@ -0,0 +1,19 @@ +var el = null; + +@(_ => el = _) +class A {} + +expect(el).toEqual(Object.defineProperty({ + kind: "class", + elements: [] +}, Symbol.toStringTag, { value: "Descriptor" })); + +@(_ => el = _) +class B { + foo = 2; + static bar() {} + get baz() {} + set baz(x) {} +} + +expect(el.elements).toHaveLength(3); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-own-field-without-initiailzer/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-own-field-without-initiailzer/exec.js new file mode 100644 index 0000000000..ea1b5dd984 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-own-field-without-initiailzer/exec.js @@ -0,0 +1,18 @@ +var el = null; + +class A { + @(_ => el = _) + foo; +} + +expect(el).toEqual(Object.defineProperty({ + kind: "field", + key: "foo", + placement: "own", + descriptor: { + enumerable: false, + configurable: true, + writable: true, + }, + initializer: undefined, +}, Symbol.toStringTag, { value: "Descriptor" })); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-own-field/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-own-field/exec.js new file mode 100644 index 0000000000..7c823735e5 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-own-field/exec.js @@ -0,0 +1,21 @@ +var el = null; +var val = {}; + +class A { + @(_ => el = _) + foo = val; +} + +expect(el).toEqual(Object.defineProperty({ + kind: "field", + key: "foo", + placement: "own", + descriptor: { + enumerable: false, + configurable: true, + writable: true, + }, + initializer: expect.any(Function), +}, Symbol.toStringTag, { value: "Descriptor" })); + +expect(el.initializer()).toBe(val); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-prototype-method/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-prototype-method/exec.js new file mode 100644 index 0000000000..9e58df2b15 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-prototype-method/exec.js @@ -0,0 +1,18 @@ +var el = null; + +class A { + @(_ => el = _) + foo() {} +} + +expect(el).toEqual(Object.defineProperty({ + kind: "method", + key: "foo", + placement: "prototype", + descriptor: { + enumerable: false, + configurable: true, + writable: true, + value: A.prototype.foo, + }, +}, Symbol.toStringTag, { value: "Descriptor" })); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-static-field/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-static-field/exec.js new file mode 100644 index 0000000000..4855b30f1b --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-static-field/exec.js @@ -0,0 +1,21 @@ +var el = null; +var val = { foo: 2 }; + +class A { + @(_ => el = _) + static foo = val; +} + +expect(el).toEqual(Object.defineProperty({ + kind: "field", + key: "foo", + placement: "static", + descriptor: { + enumerable: false, + configurable: true, + writable: true, + }, + initializer: expect.any(Function), +}, Symbol.toStringTag, { value: "Descriptor" })); + +expect(el.initializer()).toBe(val); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-static-method/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-static-method/exec.js new file mode 100644 index 0000000000..d55a51f082 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/element-descriptors/original-static-method/exec.js @@ -0,0 +1,18 @@ +var el = null; + +class A { + @(_ => el = _) + static foo() {} +} + +expect(el).toEqual(Object.defineProperty({ + kind: "method", + key: "foo", + placement: "static", + descriptor: { + enumerable: false, + configurable: true, + writable: true, + value: A.foo, + }, +}, Symbol.toStringTag, { value: "Descriptor" })); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/finishers/class-as-parameter/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/finishers/class-as-parameter/exec.js new file mode 100644 index 0000000000..46b15e6845 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/finishers/class-as-parameter/exec.js @@ -0,0 +1,16 @@ +var C; + +function decorator(el) { + return Object.assign(el, { + finisher(Class) { + C = Class; + }, + }); +} + +class A { + @decorator + foo() {} +} + +expect(C).toBe(A); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/finishers/no-in-extras/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/finishers/no-in-extras/exec.js new file mode 100644 index 0000000000..cab527c82d --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/finishers/no-in-extras/exec.js @@ -0,0 +1,21 @@ +class C {} + +function decorator(el) { + return Object.assign(el, { + extras: [ + Object.assign({}, el, { + key: "bar", + finisher() { + return C; + } + }) + ] + }); +} + +expect(() => { + class A { + @decorator + foo() {} + } +}).toThrow(); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/finishers/options.json b/packages/babel-plugin-proposal-decorators/test/fixtures/finishers/options.json new file mode 100644 index 0000000000..4f9b5b01d1 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/finishers/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + ["proposal-decorators", { "decoratorsBeforeExport": false }], + "proposal-class-properties", + "external-helpers" + ] +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/finishers/return-class/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/finishers/return-class/exec.js new file mode 100644 index 0000000000..34623728f7 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/finishers/return-class/exec.js @@ -0,0 +1,16 @@ +class C {} + +function decorator(el) { + return Object.assign(el, { + finisher() { + return C; + }, + }); +} + +class A { + @decorator + foo() {} +} + +expect(A).toBe(C); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/ordering/decorators/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/ordering/decorators/exec.js new file mode 100644 index 0000000000..8e7cc41e16 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/ordering/decorators/exec.js @@ -0,0 +1,31 @@ +var log = []; + +function push(x) { log.push(x); return x; } + +function logDecoratorRun(a, b) { + push(a); + return function (el) { push(b); return el; }; +} + +@logDecoratorRun(0, 23) +@logDecoratorRun(1, 22) +class A { + @logDecoratorRun(2, 15) + @logDecoratorRun(3, 14) + [push(4)] = "4"; + + @logDecoratorRun(5, 17) + @logDecoratorRun(6, 16) + static [push(7)]() {} + + @logDecoratorRun(8, 19) + @logDecoratorRun(9, 18) + static [push(10)] = "10"; + + @logDecoratorRun(11, 21) + @logDecoratorRun(12, 20) + [push(13)]() {} +} + +var numsFrom0to23 = Array.from({ length: 24 }, (_, i) => i); +expect(log).toEqual(numsFrom0to23); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/ordering/field-initializers-after-methods/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/ordering/field-initializers-after-methods/exec.js new file mode 100644 index 0000000000..46e71f7281 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/ordering/field-initializers-after-methods/exec.js @@ -0,0 +1,27 @@ +var counter = 0; + +@(x => x) +class A { + foo = (() => { + counter++; + expect(typeof this.method).toBe("function"); + expect(this.foo).toBeUndefined(); + expect(this.bar).toBeUndefined(); + return "foo"; + })(); + + method() {} + + bar = (() => { + counter++; + expect(typeof this.method).toBe("function"); + expect(this.foo).toBe("foo"); + expect(this.bar).toBeUndefined(); + })(); +} + +expect(counter).toBe(0); + +new A(); + +expect(counter).toBe(2); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/ordering/finishers/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/ordering/finishers/exec.js new file mode 100644 index 0000000000..0e8094b637 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/ordering/finishers/exec.js @@ -0,0 +1,34 @@ +var log = []; + +function push(x) { log.push(x); return x; } + +function logFinisher(x) { + return function (el) { + return Object.assign(el, { + finisher() { push(x); } + }); + }; +} + +@logFinisher(9) +@logFinisher(8) +class A { + @logFinisher(1) + @logFinisher(0) + foo; + + @logFinisher(3) + @logFinisher(2) + static bar() {} + + @logFinisher(5) + @logFinisher(4) + static baz; + + @logFinisher(7) + @logFinisher(6) + asd() {} +} + +var numsFrom0to9 = Array.from({ length: 10 }, (_, i) => i); +expect(log).toEqual(numsFrom0to9); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/ordering/options.json b/packages/babel-plugin-proposal-decorators/test/fixtures/ordering/options.json new file mode 100644 index 0000000000..4f9b5b01d1 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/ordering/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + ["proposal-decorators", { "decoratorsBeforeExport": false }], + "proposal-class-properties", + "external-helpers" + ] +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/ordering/static-field-initializers-after-methods/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/ordering/static-field-initializers-after-methods/exec.js new file mode 100644 index 0000000000..0703f091cc --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/ordering/static-field-initializers-after-methods/exec.js @@ -0,0 +1,23 @@ +var counter = 0; + +@(x => x) +class A { + static foo = (() => { + counter++; + expect(typeof this.method).toBe("function"); + expect(this.foo).toBeUndefined(); + expect(this.bar).toBeUndefined(); + return "foo"; + })(); + + static method() {} + + static bar = (() => { + counter++; + expect(typeof this.method).toBe("function"); + expect(this.foo).toBe("foo"); + expect(this.bar).toBeUndefined(); + })(); +} + +expect(counter).toBe(2); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/arguments/input.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/arguments/input.js new file mode 100644 index 0000000000..2ba5ec0e6f --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/arguments/input.js @@ -0,0 +1,4 @@ +@dec(a, b, ...c) +class A { + @dec(a, b, ...c) method() {} +} \ No newline at end of file diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/arguments/output.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/arguments/output.js new file mode 100644 index 0000000000..d8a47bf3a1 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/arguments/output.js @@ -0,0 +1,22 @@ +let A = babelHelpers.decorate([dec(a, b, ...c)], function (_initialize) { + "use strict"; + + class A { + constructor() { + _initialize(this); + } + + } + + return { + F: A, + d: [{ + kind: "method", + decorators: [dec(a, b, ...c)], + key: "method", + + value() {} + + }] + }; +}); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/class-decorators-yield-await/input.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/class-decorators-yield-await/input.js new file mode 100644 index 0000000000..60d93582dc --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/class-decorators-yield-await/input.js @@ -0,0 +1,5 @@ +async function* f() { + @(yield dec1) + @(await dec2) + class A {} +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/class-decorators-yield-await/options.json b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/class-decorators-yield-await/options.json new file mode 100644 index 0000000000..41a1ee9b40 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/class-decorators-yield-await/options.json @@ -0,0 +1,8 @@ +{ + "plugins": [ + ["proposal-decorators", { "decoratorsBeforeExport": false }], + "proposal-class-properties", + "external-helpers", + "syntax-async-generators" + ] +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/class-decorators-yield-await/output.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/class-decorators-yield-await/output.js new file mode 100644 index 0000000000..9404e8efe1 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/class-decorators-yield-await/output.js @@ -0,0 +1,17 @@ +async function* f() { + let A = babelHelpers.decorate([yield dec1, await dec2], function (_initialize) { + "use strict"; + + class A { + constructor() { + _initialize(this); + } + + } + + return { + F: A, + d: [] + }; + }); +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/declaration/input.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/declaration/input.js new file mode 100644 index 0000000000..8a7b27d874 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/declaration/input.js @@ -0,0 +1,2 @@ +@dec() +class A {} \ No newline at end of file diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/declaration/output.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/declaration/output.js new file mode 100644 index 0000000000..6a8ce61553 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/declaration/output.js @@ -0,0 +1,15 @@ +let A = babelHelpers.decorate([dec()], function (_initialize) { + "use strict"; + + class A { + constructor() { + _initialize(this); + } + + } + + return { + F: A, + d: [] + }; +}); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-anonymous/input.mjs b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-anonymous/input.mjs new file mode 100644 index 0000000000..532836c73d --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-anonymous/input.mjs @@ -0,0 +1 @@ +export default @dec() class {} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-anonymous/output.mjs b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-anonymous/output.mjs new file mode 100644 index 0000000000..e196ff96ff --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-anonymous/output.mjs @@ -0,0 +1,13 @@ +export default babelHelpers.decorate([dec()], function (_initialize) { + class _class { + constructor() { + _initialize(this); + } + + } + + return { + F: _class, + d: [] + }; +}); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-named/input.mjs b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-named/input.mjs new file mode 100644 index 0000000000..1eda2dacb8 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-named/input.mjs @@ -0,0 +1 @@ +export default @dec() class Foo {} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-named/output.mjs b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-named/output.mjs new file mode 100644 index 0000000000..6cf30240e0 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/export-default-named/output.mjs @@ -0,0 +1,14 @@ +let Foo = babelHelpers.decorate([dec()], function (_initialize) { + class Foo { + constructor() { + _initialize(this); + } + + } + + return { + F: Foo, + d: [] + }; +}); +export { Foo as default }; diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/expression/input.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/expression/input.js new file mode 100644 index 0000000000..34278333fc --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/expression/input.js @@ -0,0 +1 @@ +(@dec() class {}); \ No newline at end of file diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/expression/output.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/expression/output.js new file mode 100644 index 0000000000..7f99d76bd0 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/expression/output.js @@ -0,0 +1,15 @@ +babelHelpers.decorate([dec()], function (_initialize) { + "use strict"; + + class _class { + constructor() { + _initialize(this); + } + + } + + return { + F: _class, + d: [] + }; +}); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-await/input.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-await/input.js new file mode 100644 index 0000000000..f8fd41ae16 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-await/input.js @@ -0,0 +1,3 @@ +async function g() { + @dec class A extends (await B) {} +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-await/output.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-await/output.js new file mode 100644 index 0000000000..707d643923 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-await/output.js @@ -0,0 +1,19 @@ +async function g() { + let A = babelHelpers.decorate([dec], function (_initialize, _super) { + "use strict"; + + class A extends _super { + constructor(...args) { + super(...args); + + _initialize(this); + } + + } + + return { + F: A, + d: [] + }; + }, (await B)); +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-yield/input.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-yield/input.js new file mode 100644 index 0000000000..0a5c39de2f --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-yield/input.js @@ -0,0 +1,3 @@ +function* g() { + @dec class A extends (yield B) {} +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-yield/output.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-yield/output.js new file mode 100644 index 0000000000..2ccc1e94ac --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends-yield/output.js @@ -0,0 +1,19 @@ +function* g() { + let A = babelHelpers.decorate([dec], function (_initialize, _super) { + "use strict"; + + class A extends _super { + constructor(...args) { + super(...args); + + _initialize(this); + } + + } + + return { + F: A, + d: [] + }; + }, (yield B)); +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends/exec.js new file mode 100644 index 0000000000..e9e275b8f1 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends/exec.js @@ -0,0 +1,7 @@ +class B {} + +@(_ => _) +class A extends B {} + +expect(new A).toBeInstanceOf(A); +expect(new A).toBeInstanceOf(B); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends/input.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends/input.js new file mode 100644 index 0000000000..e7cdf6b53f --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends/input.js @@ -0,0 +1 @@ +@dec class A extends B {} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends/output.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends/output.js new file mode 100644 index 0000000000..eeef5ffce4 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/extends/output.js @@ -0,0 +1,17 @@ +let A = babelHelpers.decorate([dec], function (_initialize, _B) { + "use strict"; + + class A extends _B { + constructor(...args) { + super(...args); + + _initialize(this); + } + + } + + return { + F: A, + d: [] + }; +}, B); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/only-decorated/input.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/only-decorated/input.js new file mode 100644 index 0000000000..bcf77fe882 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/only-decorated/input.js @@ -0,0 +1,4 @@ +class B { + foo = 2; + bar() {} +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/only-decorated/output.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/only-decorated/output.js new file mode 100644 index 0000000000..2d487fa2b9 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/only-decorated/output.js @@ -0,0 +1,8 @@ +class B { + constructor() { + babelHelpers.defineProperty(this, "foo", 2); + } + + bar() {} + +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/options.json b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/options.json new file mode 100644 index 0000000000..4f9b5b01d1 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + ["proposal-decorators", { "decoratorsBeforeExport": false }], + "proposal-class-properties", + "external-helpers" + ] +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/strict-directive/input.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/strict-directive/input.js new file mode 100644 index 0000000000..85c55957e8 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/strict-directive/input.js @@ -0,0 +1,15 @@ +(() => { + "use strict"; + + @dec + class Foo { + method() {} + } +}); + +(() => { + @dec + class Foo { + method() {} + } +}); diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/strict-directive/output.js b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/strict-directive/output.js new file mode 100644 index 0000000000..65f4fbb073 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/transformation/strict-directive/output.js @@ -0,0 +1,47 @@ +() => { + "use strict"; + + let Foo = babelHelpers.decorate([dec], function (_initialize) { + class Foo { + constructor() { + _initialize(this); + } + + } + + return { + F: Foo, + d: [{ + kind: "method", + key: "method", + + value() {} + + }] + }; + }); +}; + +() => { + let Foo = babelHelpers.decorate([dec], function (_initialize2) { + "use strict"; + + class Foo { + constructor() { + _initialize2(this); + } + + } + + return { + F: Foo, + d: [{ + kind: "method", + key: "method", + + value() {} + + }] + }; + }); +}; diff --git a/packages/babel-plugin-syntax-decorators/src/index.js b/packages/babel-plugin-syntax-decorators/src/index.js index 1a73a63c88..5185a067f9 100644 --- a/packages/babel-plugin-syntax-decorators/src/index.js +++ b/packages/babel-plugin-syntax-decorators/src/index.js @@ -8,20 +8,14 @@ export default declare((api, options) => { throw new Error("'legacy' must be a boolean."); } - if (legacy !== true) { - throw new Error( - "The new decorators proposal is not supported yet." + - ' You must pass the `"legacy": true` option to' + - " @babel/plugin-syntax-decorators", - ); - } - const { decoratorsBeforeExport } = options; if (decoratorsBeforeExport === undefined) { if (!legacy) { throw new Error( "The '@babel/plugin-syntax-decorators' plugin requires a" + - " 'decoratorsBeforeExport' option, whose value must be a boolean.", + " 'decoratorsBeforeExport' option, whose value must be a boolean." + + " If you want to use the legacy decorators semantics, you can set" + + " the 'legacy: true' option.", ); } } else { diff --git a/packages/babel-plugin-syntax-decorators/test/index.js b/packages/babel-plugin-syntax-decorators/test/index.js index 38106f6288..d84797d7bb 100644 --- a/packages/babel-plugin-syntax-decorators/test/index.js +++ b/packages/babel-plugin-syntax-decorators/test/index.js @@ -15,7 +15,7 @@ describe("'legacy' option", function() { expect(makeParser("", { legacy: "legacy" })).toThrow(); }); - test.skip("'legacy': false", function() { + test("'legacy': false", function() { expect(makeParser("({ @dec fn() {} })", { legacy: false })).toThrow(); }); @@ -23,17 +23,13 @@ describe("'legacy' option", function() { expect(makeParser("({ @dec fn() {} })", { legacy: true })).not.toThrow(); }); - test.skip("defaults to 'false'", function() { + test("defaults to 'false'", function() { expect(makeParser("({ @dec fn() {} })", {})).toThrow(); }); - - test("it must be true", function() { - expect(makeParser("", { legacy: false })).toThrow(); - }); }); describe("'decoratorsBeforeExport' option", function() { - test.skip("must be boolean", function() { + test("must be boolean", function() { expect(makeParser("", { decoratorsBeforeExport: "before" })).toThrow(); }); @@ -63,7 +59,7 @@ describe("'decoratorsBeforeExport' option", function() { (code === BEFORE ? "before" : "after") + "export"; - test.skip(name, function() { + test(name, function() { const expectTheParser = expect( makeParser(code, { decoratorsBeforeExport: before }), );