Skip to content

Commit

Permalink
Update decorators to the latest spec
Browse files Browse the repository at this point in the history
- Update `initialize` -> `init`
- Update decorator application ordering:
  1. Static method decorators
  2. Proto method decorators
  3. Static field decorators
  4. Proto field decorators
- Throw errors when `addInitializer` or metadata methods are called
  outside of decoration
  • Loading branch information
pzuraq committed Mar 13, 2022
1 parent 0470501 commit cd8f7b7
Show file tree
Hide file tree
Showing 23 changed files with 296 additions and 162 deletions.
2 changes: 1 addition & 1 deletion packages/babel-helpers/src/helpers-generated.ts

Large diffs are not rendered by default.

121 changes: 91 additions & 30 deletions packages/babel-helpers/src/helpers/applyDecs.js
Expand Up @@ -18,9 +18,20 @@
CLASS = 10; // only used in assertValidReturnValue
*/

function createMetadataMethodsForProperty(metadataMap, kind, property) {
function createMetadataMethodsForProperty(
metadataMap,
kind,
property,
decorationState
) {
return {
getMetadata: function (key) {
if (decorationState.finished) {
throw new Error(
"attempted to call getMetadata after decoration was finished"
);
}

if (typeof key !== "symbol") {
throw new TypeError("Metadata keys must be symbols, received: " + key);
}
Expand All @@ -44,6 +55,12 @@ function createMetadataMethodsForProperty(metadataMap, kind, property) {
}
},
setMetadata: function (key, value) {
if (decorationState.finished) {
throw new Error(
"attempted to call setMetadata after decoration was finished"
);
}

if (typeof key !== "symbol") {
throw new TypeError("Metadata keys must be symbols, received: " + key);
}
Expand Down Expand Up @@ -120,8 +137,14 @@ function convertMetadataMapToFinal(obj, metadataMap) {
obj[Symbol.metadata || Symbol.for("Symbol.metadata")] = metadataMap;
}

function createAddInitializerMethod(initializers) {
function createAddInitializerMethod(initializers, decorationState) {
return function addInitializer(initializer) {
if (decorationState.finished) {
throw new Error(
"attempted to call addInitializer after decoration was finished"
);
}

assertCallable(initializer, "An initializer");
initializers.push(initializer);
};
Expand All @@ -134,7 +157,8 @@ function memberDecCtx(
initializers,
kind,
isStatic,
isPrivate
isPrivate,
decorationState
) {
var kindStr;

Expand Down Expand Up @@ -163,7 +187,10 @@ function memberDecCtx(
};

if (kind !== 0 /* FIELD */) {
ctx.addInitializer = createAddInitializerMethod(initializers);
ctx.addInitializer = createAddInitializerMethod(
initializers,
decorationState
);
}

var metadataKind, metadataName;
Expand Down Expand Up @@ -204,7 +231,12 @@ function memberDecCtx(

return Object.assign(
ctx,
createMetadataMethodsForProperty(metadataMap, metadataKind, metadataName)
createMetadataMethodsForProperty(
metadataMap,
metadataKind,
metadataName,
decorationState
)
);
}

Expand All @@ -220,7 +252,7 @@ function assertValidReturnValue(kind, value) {
if (kind === 1 /* ACCESSOR */) {
if (type !== "object" || value === null) {
throw new TypeError(
"accessor decorators must return an object with get, set, or initializer properties or void 0"
"accessor decorators must return an object with get, set, or init properties or void 0"
);
}
if (value.get !== undefined) {
Expand All @@ -229,8 +261,8 @@ function assertValidReturnValue(kind, value) {
if (value.set !== undefined) {
assertCallable(value.set, "accessor.set");
}
if (value.initialize !== undefined) {
assertCallable(value.initialize, "accessor.initialize");
if (value.init !== undefined) {
assertCallable(value.init, "accessor.init");
}
} else if (type !== "function") {
var hint;
Expand Down Expand Up @@ -296,28 +328,32 @@ function applyMemberDec(
value = desc.set;
}

var ctx = memberDecCtx(
name,
desc,
metadataMap,
initializers,
kind,
isStatic,
isPrivate
);

var newValue, get, set;
var newValue, get, set, decorationState, ctx;

if (typeof decs === "function") {
decorationState = { finished: false };
ctx = memberDecCtx(
name,
desc,
metadataMap,
initializers,
kind,
isStatic,
isPrivate,
decorationState
);

newValue = decs(value, ctx);

decorationState.finished = true;

if (newValue !== void 0) {
assertValidReturnValue(kind, newValue);

if (kind === 0 /* FIELD */) {
initializer = newValue;
} else if (kind === 1 /* ACCESSOR */) {
initializer = newValue.initialize;
initializer = newValue.init;

get = newValue.get || value.get;
set = newValue.set || value.set;
Expand All @@ -330,17 +366,30 @@ function applyMemberDec(
} else {
for (var i = decs.length - 1; i >= 0; i--) {
var dec = decs[i];
decorationState = { finished: false };
ctx = memberDecCtx(
name,
desc,
metadataMap,
initializers,
kind,
isStatic,
isPrivate,
decorationState
);

newValue = dec(value, ctx);

decorationState.finished = true;

if (newValue !== void 0) {
assertValidReturnValue(kind, newValue);
var newInit;

if (kind === 0 /* FIELD */) {
newInit = newValue;
} else if (kind === 1 /* ACCESSOR */) {
newInit = newValue.initialize;
newInit = newValue.init;

get = newValue.get || value.get;
set = newValue.set || value.set;
Expand Down Expand Up @@ -537,23 +586,35 @@ function applyClassDecs(ret, targetClass, metadataMap, classDecs) {
if (classDecs.length > 0) {
var initializers = [];
var newClass = targetClass;

var name = targetClass.name;
var ctx = Object.assign(
{
kind: "class",
name: name,
addInitializer: createAddInitializerMethod(initializers),
},
createMetadataMethodsForProperty(metadataMap, 0 /* CONSTRUCTOR */, name)
);

for (var i = classDecs.length - 1; i >= 0; i--) {
var decorationState = { finished: false };

var ctx = Object.assign(
{
kind: "class",
name: name,
addInitializer: createAddInitializerMethod(
initializers,
decorationState
),
},
createMetadataMethodsForProperty(
metadataMap,
0 /* CONSTRUCTOR */,
name,
decorationState
)
);

var nextNewClass = classDecs[i](newClass, ctx);
if (nextNewClass !== undefined) {
assertValidReturnValue(10 /* CLASS */, nextNewClass);
newClass = nextNewClass;
}

decorationState.finished = true;
}

ret.push(newClass);
Expand Down
Expand Up @@ -311,11 +311,28 @@ function isDecoratorInfo(
return "decorators" in info;
}

function filteredOrderedDecoratorInfo(
info: (DecoratorInfo | ComputedPropInfo)[],
): DecoratorInfo[] {
const filtered = info.filter(isDecoratorInfo);

return [
...filtered.filter(
el => el.isStatic && el.kind >= ACCESSOR && el.kind <= SETTER,
),
...filtered.filter(
el => !el.isStatic && el.kind >= ACCESSOR && el.kind <= SETTER,
),
...filtered.filter(el => el.isStatic && el.kind === FIELD),
...filtered.filter(el => !el.isStatic && el.kind === FIELD),
];
}

function generateDecorationExprs(
info: (DecoratorInfo | ComputedPropInfo)[],
): t.ArrayExpression {
return t.arrayExpression(
info.filter(isDecoratorInfo).map(el => {
filteredOrderedDecoratorInfo(info).map(el => {
const decs =
el.decorators.length > 1
? t.arrayExpression(el.decorators)
Expand All @@ -341,19 +358,19 @@ function generateDecorationExprs(
function extractElementLocalAssignments(
decorationInfo: (DecoratorInfo | ComputedPropInfo)[],
) {
const locals: t.Identifier[] = [];
const localIds: t.Identifier[] = [];

for (const el of decorationInfo) {
if ("locals" in el && el.locals) {
if (Array.isArray(el.locals)) {
locals.push(...el.locals);
} else {
locals.push(el.locals);
}
for (const el of filteredOrderedDecoratorInfo(decorationInfo)) {
const { locals } = el;

if (Array.isArray(locals)) {
localIds.push(...locals);
} else if (locals !== undefined) {
localIds.push(locals);
}
}

return locals;
return localIds;
}

function addCallAccessorsFor(
Expand Down
Expand Up @@ -12,7 +12,7 @@ function dec({ get, set }, context) {
set.call(this, v + 1);
},

initialize(v) {
init(v) {
return v ? v : 1;
}
}
Expand Down
Expand Up @@ -12,7 +12,7 @@ function dec({ get, set }, context) {
set.call(this, v + 1);
},

initialize(v) {
init(v) {
return v ? v : 1;
}
}
Expand Down
Expand Up @@ -12,7 +12,7 @@ function dec({ get, set }, context) {
set.call(this, v + 1);
},

initialize(v) {
init(v) {
return v ? v : 1;
}
}
Expand Down
Expand Up @@ -12,7 +12,7 @@ function dec({ get, set }, context) {
set.call(this, v + 1);
},

initialize(v) {
init(v) {
return v ? v : 1;
}
}
Expand Down
Expand Up @@ -17,7 +17,7 @@ class Foo {
expect(elements).toHaveLength(2);

expect(elements[0].context.name).toBe("a");
expect(elements[0].val).toBe(undefined);
expect(elements[0].val()).toBe(1);

expect(elements[1].context.name).toBe("a");
expect(elements[1].val()).toBe(1);
expect(elements[1].val).toBe(undefined);
Expand Up @@ -13,4 +13,4 @@ class Foo {

}

[_init_a, _initProto] = babelHelpers.applyDecs(Foo, [[dec, 0, "a"], [dec, 2, "a"]], []);
[_init_a, _initProto] = babelHelpers.applyDecs(Foo, [[dec, 2, "a"], [dec, 0, "a"]], []);
Expand Up @@ -4,7 +4,7 @@ const dec = () => {};

class Foo {
static {
[_init_a, _initProto] = babelHelpers.applyDecs(this, [[dec, 0, "a"], [dec, 2, "a"]], []);
[_init_a, _initProto] = babelHelpers.applyDecs(this, [[dec, 2, "a"], [dec, 0, "a"]], []);
}
a = (_initProto(this), _init_a(this, 123));

Expand Down
@@ -0,0 +1,14 @@
let addInitializer;

function decMethod(_, context) {
({ addInitializer } = context);
addInitializer(() => null);
}

class C {
@decMethod m() {}
}

expect(() => {
addInitializer(() => null);
}).toThrow('attempted to call addInitializer after decoration was finished')
@@ -0,0 +1,15 @@
let getMetadata;
let metadataSymbol = Symbol();

function decMethod(_, context) {
({ getMetadata } = context);
getMetadata(metadataSymbol);
}

class C {
@decMethod m() {}
}

expect(() => {
getMetadata(metadataSymbol);
}).toThrow('attempted to call getMetadata after decoration was finished')
@@ -0,0 +1,15 @@
let setMetadata;
let metadataSymbol = Symbol();

function decMethod(_, context) {
({ setMetadata } = context);
setMetadata(metadataSymbol, 'value');
}

class C {
@decMethod m() {}
}

expect(() => {
setMetadata(metadataSymbol, 'value');
}).toThrow('attempted to call setMetadata after decoration was finished')

0 comments on commit cd8f7b7

Please sign in to comment.