Skip to content

Commit

Permalink
Merge pull request #17690 from emberjs/change-decorators-to-stage-1
Browse files Browse the repository at this point in the history
[FEAT] Updates decorator implementation to stage 1
  • Loading branch information
rwjblue authored Mar 2, 2019
2 parents d92c838 + 213efad commit 2ed6adf
Show file tree
Hide file tree
Showing 11 changed files with 179 additions and 234 deletions.
4 changes: 2 additions & 2 deletions broccoli/transforms/transform-babel-plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ module.exports = function(tree) {
let options = {
sourceMaps: true,
plugins: [
['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true, legacy: false }],
['@babel/plugin-proposal-class-properties'],
['@babel/plugin-proposal-decorators', { legacy: true }],
['@babel/plugin-proposal-class-properties', { loose: true }],
],
};

Expand Down
38 changes: 19 additions & 19 deletions packages/@ember/-internals/metal/lib/computed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
addDependentKeys,
ComputedDescriptor,
Decorator,
ElementDescriptor,
DecoratorPropertyDescriptor,
isElementDescriptor,
makeComputedDecorator,
removeDependentKeys,
Expand Down Expand Up @@ -212,12 +212,7 @@ export class ComputedProperty extends ComputedDescriptor {
}
}

setup(
obj: object,
keyName: string,
propertyDesc: PropertyDescriptor & { initializer: any },
meta: Meta
) {
setup(obj: object, keyName: string, propertyDesc: DecoratorPropertyDescriptor, meta: Meta) {
super.setup(obj, keyName, propertyDesc, meta);

assert(
Expand All @@ -227,27 +222,29 @@ export class ComputedProperty extends ComputedDescriptor {

assert(
`@computed can only be used on empty fields. ${keyName} has an initial value (e.g. \`${keyName} = someValue\`)`,
!propertyDesc.initializer
!propertyDesc || !propertyDesc.initializer
);

assert(
`Attempted to apply a computed property that already has a getter/setter to a ${keyName}, but it is a method or an accessor. If you passed @computed a function or getter/setter (e.g. \`@computed({ get() { ... } })\`), then it must be applied to a field`,
!(
this._hasConfig &&
propertyDesc &&
(typeof propertyDesc.get === 'function' || typeof propertyDesc.set === 'function')
)
);

if (this._hasConfig === false) {
let { get, set } = propertyDesc;

assert(
`Attempted to use @computed on ${keyName}, but it did not have a getter or a setter. You must either pass a get a function or getter/setter to @computed directly (e.g. \`@computed({ get() { ... } })\`) or apply @computed directly to a getter/setter`,
typeof get === 'function' || typeof set === 'function'
propertyDesc &&
(typeof propertyDesc.get === 'function' || typeof propertyDesc.set === 'function')
);

let { get, set } = propertyDesc!;

if (get !== undefined) {
this._getter = propertyDesc.get as ComputedPropertyGetter;
this._getter = get as ComputedPropertyGetter;
}

if (set !== undefined) {
Expand Down Expand Up @@ -713,14 +710,17 @@ class ComputedDecoratorImpl extends Function {
@return {ComputedDecorator} property decorator instance
@public
*/
export function computed(elementDesc: ElementDescriptor): ElementDescriptor;
export function computed(target: object, key: string, desc: PropertyDescriptor): PropertyDescriptor;
export function computed(...args: (string | ComputedPropertyConfig)[]): ComputedDecorator;
export function computed(
...args: (string | ComputedPropertyConfig | ElementDescriptor)[]
): ComputedDecorator | ElementDescriptor {
let firstArg = args[0];

if (isElementDescriptor(firstArg)) {
...args: (object | string | ComputedPropertyConfig | DecoratorPropertyDescriptor)[]
): ComputedDecorator | DecoratorPropertyDescriptor {
assert(
`@computed can only be used directly as a native decorator. If you're using tracked in classic classes, add parenthesis to call it like a function: computed()`,
!(isElementDescriptor(args.slice(0, 3)) && args.length === 5 && args[4] === true)
);

if (isElementDescriptor(args)) {
assert(
'Native decorators are not enabled without the EMBER_NATIVE_DECORATOR_SUPPORT flag. If you are using computed in a classic class, add parenthesis to it: computed()',
Boolean(EMBER_NATIVE_DECORATOR_SUPPORT)
Expand All @@ -731,7 +731,7 @@ export function computed(
ComputedDecoratorImpl
) as ComputedDecorator;

return decorator(firstArg);
return decorator(args[0], args[1], args[2]);
}

return makeComputedDecorator(
Expand Down
86 changes: 44 additions & 42 deletions packages/@ember/-internals/metal/lib/decorator.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,38 @@
import { Meta, meta as metaFor } from '@ember/-internals/meta';
import { EMBER_NATIVE_DECORATOR_SUPPORT } from '@ember/canary-features';
import { assert } from '@ember/debug';
import { DEBUG } from '@glimmer/env';
import { setComputedDecorator } from './descriptor_map';
import { unwatch, watch } from './watching';

// https://tc39.github.io/proposal-decorators/#sec-elementdescriptor-specification-type
export interface ElementDescriptor {
descriptor: PropertyDescriptor & { initializer?: any };
key: string;
kind: 'method' | 'field' | 'initializer';
placement: 'own' | 'prototype' | 'static';
initializer?: () => any;
finisher?: (obj: object, meta?: Meta) => any;
}
export type DecoratorPropertyDescriptor = PropertyDescriptor & { initializer?: any } | undefined;

export type Decorator = (
desc: ElementDescriptor,
target: object,
key: string,
desc?: DecoratorPropertyDescriptor,
maybeMeta?: Meta,
isClassicDecorator?: boolean
) => ElementDescriptor;
) => DecoratorPropertyDescriptor;

export function isElementDescriptor(
args: any[]
): args is [object, string, DecoratorPropertyDescriptor] {
let [maybeTarget, maybeKey, maybeDesc] = args;

export function isElementDescriptor(maybeDesc: any): maybeDesc is ElementDescriptor {
return (
maybeDesc !== undefined &&
typeof maybeDesc.toString === 'function' &&
maybeDesc.toString() === '[object Descriptor]'
// Ensure we have the right number of args
args.length === 3 &&
// Make sure the target is an object
(typeof maybeTarget === 'object' && maybeTarget !== null) &&
// Make sure the key is a string
typeof maybeKey === 'string' &&
// Make sure the descriptor is the right shape
((typeof maybeDesc === 'object' &&
maybeDesc !== null &&
'enumerable' in maybeDesc &&
'configurable' in maybeDesc) ||
// TS compatibility
maybeDesc === undefined)
);
}

Expand Down Expand Up @@ -77,9 +85,8 @@ export function removeDependentKeys(
}

export function nativeDescDecorator(propertyDesc: PropertyDescriptor) {
let decorator = function(elementDesc: ElementDescriptor) {
elementDesc.descriptor = propertyDesc;
return elementDesc;
let decorator = function() {
return propertyDesc;
};

setComputedDecorator(decorator);
Expand All @@ -100,7 +107,12 @@ export abstract class ComputedDescriptor {
_dependentKeys?: string[] = undefined;
_meta: any = undefined;

setup(_obj: object, keyName: string, _propertyDesc: PropertyDescriptor, meta: Meta): void {
setup(
_obj: object,
keyName: string,
_propertyDesc: DecoratorPropertyDescriptor,
meta: Meta
): void {
meta.writeDescriptors(keyName, this);
}

Expand All @@ -126,18 +138,14 @@ function DESCRIPTOR_GETTER_FUNCTION(name: string, descriptor: ComputedDescriptor
export function makeComputedDecorator(
desc: ComputedDescriptor,
DecoratorClass: { prototype: object }
) {
): Decorator {
let decorator = function COMPUTED_DECORATOR(
elementDesc: ElementDescriptor,
target: object,
key: string,
propertyDesc?: DecoratorPropertyDescriptor,
maybeMeta?: Meta,
isClassicDecorator?: boolean
): ElementDescriptor {
let { key, descriptor: propertyDesc } = elementDesc;

if (DEBUG) {
// Store the initializer for assertions
propertyDesc.initializer = elementDesc.initializer;
}

): DecoratorPropertyDescriptor {
assert(
'Native decorators are not enabled without the EMBER_NATIVE_DECORATOR_SUPPORT flag',
EMBER_NATIVE_DECORATOR_SUPPORT || isClassicDecorator
Expand All @@ -146,25 +154,19 @@ export function makeComputedDecorator(
assert(
`Only one computed property decorator can be applied to a class field or accessor, but '${key}' was decorated twice. You may have added the decorator to both a getter and setter, which is unecessary.`,
isClassicDecorator ||
!propertyDesc ||
!propertyDesc.get ||
propertyDesc.get.toString().indexOf('CPGETTER_FUNCTION') === -1
);

elementDesc.kind = 'method';
elementDesc.descriptor = {
let meta = arguments.length === 3 ? metaFor(target) : maybeMeta;
desc.setup(target, key, propertyDesc, meta!);

return {
enumerable: desc.enumerable,
configurable: desc.configurable,
get: DESCRIPTOR_GETTER_FUNCTION(elementDesc.key, desc),
get: DESCRIPTOR_GETTER_FUNCTION(key, desc),
};

elementDesc.finisher = function(klass: any, _meta?: Meta) {
let obj = klass.prototype !== undefined ? klass.prototype : klass;
let meta = arguments.length === 1 ? metaFor(obj) : _meta;

desc.setup(obj, key, propertyDesc, meta!);
};

return elementDesc;
};

setComputedDecorator(decorator, desc);
Expand Down
25 changes: 19 additions & 6 deletions packages/@ember/-internals/metal/lib/injected_property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { EMBER_MODULE_UNIFICATION, EMBER_NATIVE_DECORATOR_SUPPORT } from '@ember
import { assert } from '@ember/debug';
import { DEBUG } from '@glimmer/env';
import { computed } from './computed';
import { ElementDescriptor, isElementDescriptor } from './decorator';
import { Decorator, DecoratorPropertyDescriptor, isElementDescriptor } from './decorator';
import { defineProperty } from './properties';

export let DEBUG_INJECTION_FUNCTIONS: WeakMap<Function, any>;
Expand Down Expand Up @@ -34,13 +34,26 @@ export interface InjectedPropertyOptions {
*/
export default function inject(
type: string,
nameOrDesc: string | ElementDescriptor,
name: string,
options?: InjectedPropertyOptions
) {
): Decorator;
export default function inject(
type: string,
target: object,
key: string,
desc: DecoratorPropertyDescriptor
): DecoratorPropertyDescriptor;
export default function inject(
type: string,
...args: any[]
): Decorator | DecoratorPropertyDescriptor {
assert('a string type must be provided to inject', typeof type === 'string');

let calledAsDecorator = isElementDescriptor(args);
let source: string | undefined, namespace: string | undefined;
let name = typeof nameOrDesc === 'string' ? nameOrDesc : undefined;

let name = calledAsDecorator ? undefined : args[0];
let options = calledAsDecorator ? undefined : args[1];

if (EMBER_MODULE_UNIFICATION) {
source = options ? options.source : undefined;
Expand Down Expand Up @@ -84,13 +97,13 @@ export default function inject(
},
});

if (isElementDescriptor(nameOrDesc)) {
if (calledAsDecorator) {
assert(
'Native decorators are not enabled without the EMBER_NATIVE_DECORATOR_SUPPORT flag. If you are using inject in a classic class, add parenthesis to it: inject()',
Boolean(EMBER_NATIVE_DECORATOR_SUPPORT)
);

return decorator(nameOrDesc);
return decorator(args[0], args[1], args[2]);
} else {
return decorator;
}
Expand Down
34 changes: 5 additions & 29 deletions packages/@ember/-internals/metal/lib/properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { Meta, meta as metaFor, peekMeta, UNDEFINED } from '@ember/-internals/meta';
import { assert } from '@ember/debug';
import { DEBUG } from '@glimmer/env';
import { Decorator, ElementDescriptor } from './decorator';
import { Decorator } from './decorator';
import { descriptorForProperty, isComputedDecorator } from './descriptor_map';
import { overrideChains } from './property_events';

Expand Down Expand Up @@ -150,39 +150,15 @@ export function defineProperty(

let value;
if (isComputedDecorator(desc)) {
let elementDesc = {
key: keyName,
kind: 'field',
placement: 'own',
descriptor: {
value: undefined,
},
toString() {
return '[object Descriptor]';
},
} as ElementDescriptor;
let propertyDesc;

if (DEBUG) {
elementDesc = desc!(elementDesc, true);
propertyDesc = desc!(obj, keyName, undefined, meta, true);
} else {
elementDesc = desc!(elementDesc);
propertyDesc = desc!(obj, keyName, undefined, meta);
}

Object.defineProperty(obj, keyName, elementDesc.descriptor);

if (elementDesc.finisher !== undefined) {
if (obj.constructor !== undefined && obj.constructor.prototype === obj) {
// Nonstandard, we push the meta along here
elementDesc.finisher(obj.constructor, meta);
} else {
// The most correct thing to do here is only pass the constructor of the
// object to the finisher, but we have to support being able to
// `defineProperty` directly on instances as well. This is _not_ spec
// compliant, but it's limited to core decorators that work with the
// classic object model.
elementDesc.finisher(obj, meta);
}
}
Object.defineProperty(obj, keyName, propertyDesc as PropertyDescriptor);

// pass the decorator function forward for backwards compat
value = desc;
Expand Down
Loading

0 comments on commit 2ed6adf

Please sign in to comment.