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