Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] Updates decorator implementation to stage 1 #17690

Merged
merged 2 commits into from
Mar 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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