Add support for the new decorators proposal (#7976)

This commit is contained in:
Nicolò Ribaudo 2018-09-07 15:58:42 +02:00 committed by GitHub
parent 29a2878973
commit 9aec4ad159
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 2097 additions and 29 deletions

View File

@ -1078,3 +1078,642 @@ helpers.classStaticPrivateFieldSpecSet = helper("7.0.1")`
return value; 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 = <A, B>(cl: Class<A>) => Class<B>;
// 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<C>(initialize: (instance: C) => void): {
F: Class<C>,
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 /*::<C>*/(
F /*: Class<C> */,
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 /*::<C>*/(
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 /*::<C>*/(
receiver /*: C | Class<C> */,
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 /*::<T>*/(
obj /*: T */,
name /*: $Keys<T> */,
) /*: ?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;
}
`;

View File

@ -16,6 +16,8 @@
], ],
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.0.0", "@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" "@babel/plugin-syntax-decorators": "^7.0.0"
}, },
"peerDependencies": { "peerDependencies": {

View File

@ -6,20 +6,21 @@ import legacyVisitor from "./transformer-legacy";
export default declare((api, options) => { export default declare((api, options) => {
api.assertVersion(7); api.assertVersion(7);
const { legacy = false, decoratorsBeforeExport } = options; const { legacy = false } = options;
if (typeof legacy !== "boolean") { if (typeof legacy !== "boolean") {
throw new Error("'legacy' must be a boolean."); throw new Error("'legacy' must be a boolean.");
} }
if (legacy !== true) { const { decoratorsBeforeExport } = options;
throw new Error( if (decoratorsBeforeExport === undefined) {
"The new decorators proposal is not supported yet." + if (!legacy) {
' You must pass the `"legacy": true` option to' + throw new Error(
" @babel/plugin-proposal-decorators", "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.",
);
if (decoratorsBeforeExport !== undefined) { }
} else {
if (legacy) { if (legacy) {
throw new Error( throw new Error(
"'decoratorsBeforeExport' can't be used with legacy decorators.", "'decoratorsBeforeExport' can't be used with legacy decorators.",

View File

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

View File

@ -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);

View File

@ -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);

View File

@ -0,0 +1,10 @@
@(_ => desc = _)
class Foo {
[getKey()]() {
return 1;
}
[getKey()]() {
return 2;
}
}

View File

@ -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;
}
}]
};
});

View File

@ -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);

View File

@ -0,0 +1,10 @@
@(_ => desc = _)
class Foo {
[getKeyI()]() {
return 1;
}
[getKeyJ()]() {
return 2;
}
}

View File

@ -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;
}
}]
};
});

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -0,0 +1,11 @@
function dec(el) { return el }
expect(() => {
class A {
@dec
get foo() {}
@dec
set foo(x) {}
}
}).toThrow(ReferenceError);

View File

@ -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);

View File

@ -0,0 +1,7 @@
{
"plugins": [
["proposal-decorators", { "decoratorsBeforeExport": false }],
"proposal-class-properties",
"external-helpers"
]
}

View File

@ -0,0 +1,13 @@
expect(() => {
class A {
@(el => el)
method() {
return 1;
}
@(el => el)
method() {
return 2;
}
}
}).toThrow(ReferenceError);

View File

@ -0,0 +1,12 @@
expect(() => {
class A {
method() {
return 1;
}
@(el => el)
method() {
return 2;
}
}
}).toThrow(ReferenceError);

View File

@ -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);

View File

@ -0,0 +1,12 @@
expect(() => {
class A {
@(el => el)
method() {
return 1;
}
method() {
return 2;
}
}
}).toThrow(ReferenceError);

View File

@ -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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);

View File

@ -0,0 +1,8 @@
var dec1, dec2;
@(_ => dec1 = _)
@(_ => dec2 = _)
class A {}
expect(dec1).toEqual(dec2);
expect(dec1).not.toBe(dec2);

View File

@ -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);

View File

@ -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);

View File

@ -0,0 +1,7 @@
{
"plugins": [
["proposal-decorators", { "decoratorsBeforeExport": false }],
"proposal-class-properties",
"external-helpers"
]
}

View File

@ -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);

View File

@ -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" }));

View File

@ -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);

View File

@ -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" }));

View File

@ -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);

View File

@ -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" }));

View File

@ -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);

View File

@ -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();

View File

@ -0,0 +1,7 @@
{
"plugins": [
["proposal-decorators", { "decoratorsBeforeExport": false }],
"proposal-class-properties",
"external-helpers"
]
}

View File

@ -0,0 +1,16 @@
class C {}
function decorator(el) {
return Object.assign(el, {
finisher() {
return C;
},
});
}
class A {
@decorator
foo() {}
}
expect(A).toBe(C);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -0,0 +1,7 @@
{
"plugins": [
["proposal-decorators", { "decoratorsBeforeExport": false }],
"proposal-class-properties",
"external-helpers"
]
}

View File

@ -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);

View File

@ -0,0 +1,4 @@
@dec(a, b, ...c)
class A {
@dec(a, b, ...c) method() {}
}

View File

@ -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() {}
}]
};
});

View File

@ -0,0 +1,5 @@
async function* f() {
@(yield dec1)
@(await dec2)
class A {}
}

View File

@ -0,0 +1,8 @@
{
"plugins": [
["proposal-decorators", { "decoratorsBeforeExport": false }],
"proposal-class-properties",
"external-helpers",
"syntax-async-generators"
]
}

View File

@ -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: []
};
});
}

View File

@ -0,0 +1,2 @@
@dec()
class A {}

View File

@ -0,0 +1,15 @@
let A = babelHelpers.decorate([dec()], function (_initialize) {
"use strict";
class A {
constructor() {
_initialize(this);
}
}
return {
F: A,
d: []
};
});

View File

@ -0,0 +1 @@
export default @dec() class {}

View File

@ -0,0 +1,13 @@
export default babelHelpers.decorate([dec()], function (_initialize) {
class _class {
constructor() {
_initialize(this);
}
}
return {
F: _class,
d: []
};
});

View File

@ -0,0 +1 @@
export default @dec() class Foo {}

View File

@ -0,0 +1,14 @@
let Foo = babelHelpers.decorate([dec()], function (_initialize) {
class Foo {
constructor() {
_initialize(this);
}
}
return {
F: Foo,
d: []
};
});
export { Foo as default };

View File

@ -0,0 +1 @@
(@dec() class {});

View File

@ -0,0 +1,15 @@
babelHelpers.decorate([dec()], function (_initialize) {
"use strict";
class _class {
constructor() {
_initialize(this);
}
}
return {
F: _class,
d: []
};
});

View File

@ -0,0 +1,3 @@
async function g() {
@dec class A extends (await B) {}
}

View File

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

View File

@ -0,0 +1,3 @@
function* g() {
@dec class A extends (yield B) {}
}

View File

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

View File

@ -0,0 +1,7 @@
class B {}
@(_ => _)
class A extends B {}
expect(new A).toBeInstanceOf(A);
expect(new A).toBeInstanceOf(B);

View File

@ -0,0 +1 @@
@dec class A extends B {}

View File

@ -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);

View File

@ -0,0 +1,4 @@
class B {
foo = 2;
bar() {}
}

View File

@ -0,0 +1,8 @@
class B {
constructor() {
babelHelpers.defineProperty(this, "foo", 2);
}
bar() {}
}

View File

@ -0,0 +1,7 @@
{
"plugins": [
["proposal-decorators", { "decoratorsBeforeExport": false }],
"proposal-class-properties",
"external-helpers"
]
}

View File

@ -0,0 +1,15 @@
(() => {
"use strict";
@dec
class Foo {
method() {}
}
});
(() => {
@dec
class Foo {
method() {}
}
});

View File

@ -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() {}
}]
};
});
};

View File

@ -8,20 +8,14 @@ export default declare((api, options) => {
throw new Error("'legacy' must be a 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-syntax-decorators",
);
}
const { decoratorsBeforeExport } = options; const { decoratorsBeforeExport } = options;
if (decoratorsBeforeExport === undefined) { if (decoratorsBeforeExport === undefined) {
if (!legacy) { if (!legacy) {
throw new Error( throw new Error(
"The '@babel/plugin-syntax-decorators' plugin requires a" + "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 { } else {

View File

@ -15,7 +15,7 @@ describe("'legacy' option", function() {
expect(makeParser("", { legacy: "legacy" })).toThrow(); expect(makeParser("", { legacy: "legacy" })).toThrow();
}); });
test.skip("'legacy': false", function() { test("'legacy': false", function() {
expect(makeParser("({ @dec fn() {} })", { legacy: false })).toThrow(); expect(makeParser("({ @dec fn() {} })", { legacy: false })).toThrow();
}); });
@ -23,17 +23,13 @@ describe("'legacy' option", function() {
expect(makeParser("({ @dec fn() {} })", { legacy: true })).not.toThrow(); expect(makeParser("({ @dec fn() {} })", { legacy: true })).not.toThrow();
}); });
test.skip("defaults to 'false'", function() { test("defaults to 'false'", function() {
expect(makeParser("({ @dec fn() {} })", {})).toThrow(); expect(makeParser("({ @dec fn() {} })", {})).toThrow();
}); });
test("it must be true", function() {
expect(makeParser("", { legacy: false })).toThrow();
});
}); });
describe("'decoratorsBeforeExport' option", function() { describe("'decoratorsBeforeExport' option", function() {
test.skip("must be boolean", function() { test("must be boolean", function() {
expect(makeParser("", { decoratorsBeforeExport: "before" })).toThrow(); expect(makeParser("", { decoratorsBeforeExport: "before" })).toThrow();
}); });
@ -63,7 +59,7 @@ describe("'decoratorsBeforeExport' option", function() {
(code === BEFORE ? "before" : "after") + (code === BEFORE ? "before" : "after") +
"export"; "export";
test.skip(name, function() { test(name, function() {
const expectTheParser = expect( const expectTheParser = expect(
makeParser(code, { decoratorsBeforeExport: before }), makeParser(code, { decoratorsBeforeExport: before }),
); );