diff --git a/.eslintrc.js b/.eslintrc.js index 78e38e6f2d7..acd91be167d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,6 +2,7 @@ const path = require('path'); module.exports = { root: true, + parser: 'babel-eslint', extends: [ 'eslint:recommended', 'prettier', diff --git a/broccoli/packages.js b/broccoli/packages.js index e250e1a30c5..3c1eb53edd6 100644 --- a/broccoli/packages.js +++ b/broccoli/packages.js @@ -16,6 +16,7 @@ const WriteFile = require('broccoli-file-creator'); const StringReplace = require('broccoli-string-replace'); const GlimmerTemplatePrecompiler = require('./glimmer-template-compiler'); const VERSION_PLACEHOLDER = /VERSION_STRING_PLACEHOLDER/g; +const transfromBabelPlugins = require('./transforms/transform-babel-plugins'); const debugTree = BroccoliDebug.buildDebugCallback('ember-source'); @@ -83,6 +84,13 @@ module.exports.getPackagesES = function getPackagesES() { exclude: ['**/*.ts'], }); + // tsc / typescript handles decorators and class properties on its own + // so for non ts, transpile the proposal features (decorators, etc) + let transpiledProposals = debugTree( + transfromBabelPlugins(debugTree(nonTypeScriptContents, `get-packages-es:babel-plugins:input`)), + `get-packages-es:babel-plugins:output` + ); + let typescriptContents = new Funnel(debuggedCompiledTemplatesAndTypeScript, { include: ['**/*.ts'], }); @@ -95,7 +103,7 @@ module.exports.getPackagesES = function getPackagesES() { let debuggedCompiledTypescript = debugTree(typescriptCompiled, `get-packages-es:ts:output`); - let mergedFinalOutput = new MergeTrees([nonTypeScriptContents, debuggedCompiledTypescript], { + let mergedFinalOutput = new MergeTrees([transpiledProposals, debuggedCompiledTypescript], { overwrite: true, }); diff --git a/broccoli/transforms/transform-babel-plugins.js b/broccoli/transforms/transform-babel-plugins.js new file mode 100644 index 00000000000..e0e1b63dc59 --- /dev/null +++ b/broccoli/transforms/transform-babel-plugins.js @@ -0,0 +1,13 @@ +const Babel = require('broccoli-babel-transpiler'); + +module.exports = function(tree) { + let options = { + sourceMaps: true, + plugins: [ + ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true, legacy: false }], + ['@babel/plugin-proposal-class-properties'], + ], + }; + + return new Babel(tree, options); +}; diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 00000000000..aab64f650f1 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2016", + "experimentalDecorators": true + }, + "exclude": [ + "node_modules", + "**/node_modules/*" + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 07e9913d3f8..b74f698663c 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,8 @@ }, "devDependencies": { "@babel/helper-module-imports": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.2.1", + "@babel/plugin-proposal-decorators": "^7.2.0", "@babel/plugin-transform-arrow-functions": "^7.2.0", "@babel/plugin-transform-block-scoping": "^7.2.0", "@babel/plugin-transform-classes": "^7.2.2", @@ -101,6 +103,7 @@ "@types/rsvp": "^4.0.2", "auto-dist-tag": "^1.0.0", "aws-sdk": "^2.395.0", + "babel-eslint": "^10.0.1", "babel-plugin-debug-macros": "^0.3.0", "babel-plugin-filter-imports": "^2.0.4", "babel-plugin-module-resolver": "^3.1.3", diff --git a/packages/@ember/-internals/meta/index.ts b/packages/@ember/-internals/meta/index.ts index 2083134cd48..11ebd60739c 100644 --- a/packages/@ember/-internals/meta/index.ts +++ b/packages/@ember/-internals/meta/index.ts @@ -1,8 +1,6 @@ export { counters, deleteMeta, - descriptorFor, - isDescriptor, Meta, meta, MetaCounters, diff --git a/packages/@ember/-internals/meta/lib/meta.ts b/packages/@ember/-internals/meta/lib/meta.ts index 8c046b4985b..872201ff444 100644 --- a/packages/@ember/-internals/meta/lib/meta.ts +++ b/packages/@ember/-internals/meta/lib/meta.ts @@ -978,48 +978,6 @@ if (DEBUG) { meta._counters = counters; } -/** - Returns the CP descriptor assocaited with `obj` and `keyName`, if any. - - @method descriptorFor - @param {Object} obj the object to check - @param {String} keyName the key to check - @return {Descriptor} - @private -*/ -export function descriptorFor(obj: object, keyName: string, _meta?: Meta | null) { - assert('Cannot call `descriptorFor` on null', obj !== null); - assert('Cannot call `descriptorFor` on undefined', obj !== undefined); - assert( - `Cannot call \`descriptorFor\` on ${typeof obj}`, - typeof obj === 'object' || typeof obj === 'function' - ); - - let meta = _meta === undefined ? peekMeta(obj) : _meta; - - if (meta !== null) { - return meta.peekDescriptors(keyName); - } -} - -/** - Check whether a value is a CP descriptor. - - @method isDescriptor - @param {any} possibleDesc the value to check - @return {boolean} - @private -*/ -export function isDescriptor(possibleDesc: any | undefined | null): boolean { - // TODO make this return `possibleDesc is Descriptor` - return ( - possibleDesc !== undefined && - possibleDesc !== null && - typeof possibleDesc === 'object' && - possibleDesc.isDescriptor === true - ); -} - export { counters }; function indexOfListener( diff --git a/packages/@ember/-internals/metal/index.ts b/packages/@ember/-internals/metal/index.ts index 0733389a964..531a17e7135 100644 --- a/packages/@ember/-internals/metal/index.ts +++ b/packages/@ember/-internals/metal/index.ts @@ -1,4 +1,4 @@ -export { default as computed, ComputedProperty, _globalsComputed } from './lib/computed'; +export { default as computed, _globalsComputed } from './lib/computed'; export { getCacheFor, getCachedValueFor, peekCacheFor } from './lib/computed_cache'; export { default as alias } from './lib/alias'; export { deprecateProperty } from './lib/deprecate_property'; @@ -28,7 +28,13 @@ export { overrideChains, PROPERTY_DID_CHANGE, } from './lib/property_events'; -export { defineProperty, Descriptor } from './lib/properties'; +export { defineProperty } from './lib/properties'; +export { + descriptorForProperty, + isComputedDecorator, + setComputedDecorator, + nativeDescDecorator, +} from './lib/decorator'; export { watchKey, unwatchKey } from './lib/watch_key'; export { ChainNode, finishChains, removeChainWatcher } from './lib/chains'; export { watchPath, unwatchPath } from './lib/watch_path'; @@ -40,10 +46,9 @@ export { default as expandProperties } from './lib/expand_properties'; export { addObserver, removeObserver } from './lib/observer'; export { Mixin, aliasMethod, mixin, observer, applyMixin } from './lib/mixin'; -export { default as InjectedProperty } from './lib/injected_property'; +export { default as inject, DEBUG_INJECTION_FUNCTIONS } from './lib/injected_property'; export { setHasViews, tagForProperty, tagFor, markObjectAsDirty } from './lib/tags'; export { default as runInTransaction, didRender, assertNotRendered } from './lib/transaction'; -export { default as descriptor } from './lib/descriptor'; export { tracked } from './lib/tracked'; export { diff --git a/packages/@ember/-internals/metal/lib/alias.ts b/packages/@ember/-internals/metal/lib/alias.ts index b8f3b6cdb1e..8b332ffbbee 100644 --- a/packages/@ember/-internals/metal/lib/alias.ts +++ b/packages/@ember/-internals/metal/lib/alias.ts @@ -2,25 +2,50 @@ import { Meta, meta as metaFor } from '@ember/-internals/meta'; import { inspect } from '@ember/-internals/utils'; import { assert } from '@ember/debug'; import EmberError from '@ember/error'; -import { ComputedProperty } from './computed'; import { getCachedValueFor, getCacheFor } from './computed_cache'; import { addDependentKeys, - DescriptorWithDependentKeys, + ComputedDescriptor, + Decorator, + descriptorForDecorator, + makeComputedDecorator, removeDependentKeys, -} from './dependent_keys'; -import { defineProperty, Descriptor } from './properties'; +} from './decorator'; +import { defineProperty } from './properties'; import { get } from './property_get'; import { set } from './property_set'; const CONSUMED = Object.freeze({}); -export default function alias(altKey: string): AliasedProperty { - return new AliasedProperty(altKey); +export type AliasDecorator = Decorator & PropertyDecorator & AliasDecoratorImpl; + +export default function alias(altKey: string): AliasDecorator { + return makeComputedDecorator(new AliasedProperty(altKey), AliasDecoratorImpl) as AliasDecorator; +} + +class AliasDecoratorImpl { + readOnly(this: Decorator) { + descriptorForDecorator(this).readOnly(); + return this; + } + + oneWay(this: Decorator) { + descriptorForDecorator(this).oneWay(); + return this; + } + + meta(this: Decorator, meta?: any): any { + let prop = descriptorForDecorator(this); + + if (arguments.length === 0) { + return prop._meta || {}; + } else { + prop._meta = meta; + } + } } -export class AliasedProperty extends Descriptor implements DescriptorWithDependentKeys { - readonly _dependentKeys: string[]; +export class AliasedProperty extends ComputedDescriptor { readonly altKey: string; constructor(altKey: string) { @@ -29,9 +54,10 @@ export class AliasedProperty extends Descriptor implements DescriptorWithDepende this._dependentKeys = [altKey]; } - setup(obj: object, keyName: string, meta: Meta): void { + setup(obj: object, keyName: string, propertyDesc: PropertyDescriptor, meta: Meta): void { assert(`Setting alias '${keyName}' on self`, this.altKey !== keyName); - super.setup(obj, keyName, meta); + super.setup(obj, keyName, propertyDesc, meta); + if (meta.peekWatching(keyName) > 0) { this.consume(obj, keyName, meta); } @@ -74,14 +100,12 @@ export class AliasedProperty extends Descriptor implements DescriptorWithDepende return set(obj, this.altKey, value); } - readOnly(): this { + readOnly(): void { this.set = AliasedProperty_readOnlySet; - return this; } - oneWay(): this { + oneWay(): void { this.set = AliasedProperty_oneWaySet; - return this; } } @@ -94,7 +118,3 @@ function AliasedProperty_oneWaySet(obj: object, keyName: string, value: any): an defineProperty(obj, keyName, null); return set(obj, keyName, value); } - -// Backwards compatibility with Ember Data. -(AliasedProperty.prototype as any)._meta = undefined; -(AliasedProperty.prototype as any).meta = ComputedProperty.prototype.meta; diff --git a/packages/@ember/-internals/metal/lib/chains.ts b/packages/@ember/-internals/metal/lib/chains.ts index 9a2e65383f9..3dbb5b74872 100644 --- a/packages/@ember/-internals/metal/lib/chains.ts +++ b/packages/@ember/-internals/metal/lib/chains.ts @@ -1,5 +1,6 @@ -import { descriptorFor, Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; +import { Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; import { getCachedValueFor } from './computed_cache'; +import { descriptorForProperty } from './decorator'; import { eachProxyFor } from './each_proxy'; import { get } from './property_get'; import { unwatchKey, watchKey } from './watch_key'; @@ -9,7 +10,7 @@ function isObject(obj: any): obj is object { } function isVolatile(obj: any, keyName: string, meta?: Meta | null): boolean { - let desc = descriptorFor(obj, keyName, meta); + let desc = descriptorForProperty(obj, keyName, meta); return !(desc !== undefined && desc._volatile === false); } diff --git a/packages/@ember/-internals/metal/lib/computed.ts b/packages/@ember/-internals/metal/lib/computed.ts index 9b9b50dc03a..cf6e7bd3578 100644 --- a/packages/@ember/-internals/metal/lib/computed.ts +++ b/packages/@ember/-internals/metal/lib/computed.ts @@ -1,4 +1,4 @@ -import { meta as metaFor, peekMeta } from '@ember/-internals/meta'; +import { Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; import { inspect, toString } from '@ember/-internals/utils'; import { EMBER_METAL_TRACKED_PROPERTIES } from '@ember/canary-features'; import { assert, deprecate, warn } from '@ember/debug'; @@ -12,18 +12,21 @@ import { } from './computed_cache'; import { addDependentKeys, - DescriptorWithDependentKeys, + ComputedDescriptor, + Decorator, + descriptorForDecorator, + makeComputedDecorator, removeDependentKeys, -} from './dependent_keys'; +} from './decorator'; import expandProperties from './expand_properties'; -import { defineProperty, Descriptor } from './properties'; +import { defineProperty } from './properties'; import { notifyPropertyChange } from './property_events'; import { set } from './property_set'; import { tagForProperty, update } from './tags'; import { getCurrentTracker, setCurrentTracker } from './tracked'; export type ComputedPropertyGetter = (keyName: string) => any; -export type ComputedPropertySetter = (keyName: string, value: any) => any; +export type ComputedPropertySetter = (keyName: string, value: any, cachedValue?: any) => any; export interface ComputedPropertyGetterAndSetter { get?: ComputedPropertyGetter; @@ -31,11 +34,6 @@ export interface ComputedPropertyGetterAndSetter { } export type ComputedPropertyConfig = ComputedPropertyGetter | ComputedPropertyGetterAndSetter; -export interface ComputedPropertyOptions { - dependentKeys?: string[]; - readOnly?: boolean; -} - /** @module @ember/object */ @@ -43,7 +41,6 @@ export interface ComputedPropertyOptions { const DEEP_EACH_REGEX = /\.@each\.[^.]+\./; function noop(): void {} - /** A computed property transforms an object literal with object's accessor function(s) into a property. @@ -153,77 +150,131 @@ function noop(): void {} @class ComputedProperty @public */ -class ComputedProperty extends Descriptor implements DescriptorWithDependentKeys { - private _meta: any | undefined; - private _volatile: boolean; - private _readOnly: boolean; - private _suspended: any; - _getter: ComputedPropertyGetter; - _setter?: ComputedPropertySetter; +export class ComputedProperty extends ComputedDescriptor { + private _volatile = false; + private _readOnly = false; + private _suspended: any = undefined; + private _hasConfig = false; + + _getter?: ComputedPropertyGetter = undefined; + _setter?: ComputedPropertySetter = undefined; _auto?: boolean; - _dependentKeys: string[] | undefined; - constructor(config: ComputedPropertyConfig, opts?: ComputedPropertyOptions) { + constructor(args: Array) { super(); - let hasGetterOnly = typeof config === 'function'; - if (hasGetterOnly) { - this._getter = config as ComputedPropertyGetter; - } else { - const objectConfig = config as ComputedPropertyGetterAndSetter; - assert( - 'computed expects a function or an object as last argument.', - typeof objectConfig === 'object' && !Array.isArray(objectConfig) - ); - assert( - 'Config object passed to computed can only contain `get` and `set` keys.', - Object.keys(objectConfig).every(key => key === 'get' || key === 'set') - ); - assert( - 'Computed properties must receive a getter or a setter, you passed none.', - Boolean(objectConfig.get) || Boolean(objectConfig.set) - ); - this._getter = objectConfig.get || noop; - this._setter = objectConfig.set; + + let maybeConfig = args[args.length - 1]; + + if ( + typeof maybeConfig === 'function' || + (maybeConfig !== null && typeof maybeConfig === 'object') + ) { + this._hasConfig = true; + let config = args.pop(); + + if (typeof config === 'function') { + this._getter = config as ComputedPropertyGetter; + } else { + const objectConfig = config as ComputedPropertyGetterAndSetter; + assert( + 'computed expects a function or an object as last argument.', + typeof objectConfig === 'object' && !Array.isArray(objectConfig) + ); + assert( + 'Config object passed to computed can only contain `get` and `set` keys.', + Object.keys(objectConfig).every(key => key === 'get' || key === 'set') + ); + assert( + 'Computed properties must receive a getter or a setter, you passed none.', + Boolean(objectConfig.get) || Boolean(objectConfig.set) + ); + this._getter = objectConfig.get || noop; + this._setter = objectConfig.set; + } } - this._suspended = undefined; - this._meta = undefined; - this._volatile = false; + if (args.length > 0) { + this._property(...(args as string[])); + } if (EMBER_METAL_TRACKED_PROPERTIES) { this._auto = false; } + } + + setup( + obj: object, + keyName: string, + propertyDesc: PropertyDescriptor & { initializer: any }, + meta: Meta + ) { + super.setup(obj, keyName, propertyDesc, meta); + + assert( + `@computed can only be used on accessors or fields, attempted to use it with ${keyName} but that was a method. Try converting it to a getter (e.g. \`get ${keyName}() {}\`)`, + !(propertyDesc && typeof propertyDesc.value === 'function') + ); + + assert( + `@computed can only be used on empty fields. ${keyName} has an initial value (e.g. \`${keyName} = someValue\`)`, + !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 && + (typeof propertyDesc.get === 'function' || typeof propertyDesc.set === 'function') + ) + ); - this._dependentKeys = opts && opts.dependentKeys; - this._readOnly = Boolean(opts) && hasGetterOnly && opts!.readOnly === true; + 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' + ); + + if (get !== undefined) { + this._getter = propertyDesc.get as ComputedPropertyGetter; + } + + if (set !== undefined) { + this._setter = function setterWrapper(_key, value) { + let ret = set!.call(this, value); + + if (get !== undefined) { + return typeof ret === 'undefined' ? get.call(this) : ret; + } + + return ret; + }; + } + } } /** Call on a computed property to set it into non-cached mode. When in this mode the computed property will not automatically cache the return value. - It also does not automatically fire any change events. You must manually notify any changes if you want to observe this property. - Dependency keys have no effect on volatile properties as they are for cache invalidation and notification when cached value is invalidated. - ```javascript import EmberObject, { computed } from '@ember/object'; - let outsideService = EmberObject.extend({ value: computed(function() { return OutsideService.getValue(); }).volatile() }).create(); ``` - @method volatile @return {ComputedProperty} this @chainable @public */ - volatile(): ComputedProperty { + volatile(): void { deprecate( 'Setting a computed property as volatile has been deprecated. Instead, consider using a native getter with native class syntax.', false, @@ -235,72 +286,59 @@ class ComputedProperty extends Descriptor implements DescriptorWithDependentKeys ); this._volatile = true; - return this; } /** Call on a computed property to set it into read-only mode. When in this mode the computed property will throw an error when set. - ```javascript import EmberObject, { computed } from '@ember/object'; - let Person = EmberObject.extend({ guid: computed(function() { return 'guid-guid-guid'; }).readOnly() }); - let person = Person.create(); - person.set('guid', 'new-guid'); // will throw an exception ``` - @method readOnly @return {ComputedProperty} this @chainable @public */ - readOnly(): ComputedProperty { + readOnly(): void { this._readOnly = true; assert( 'Computed properties that define a setter using the new syntax cannot be read-only', !(this._readOnly && this._setter && this._setter !== this._getter) ); - return this; } /** Sets the dependent keys on this computed property. Pass any number of arguments containing key paths that this computed property depends on. - ```javascript import EmberObject, { computed } from '@ember/object'; - let President = EmberObject.extend({ fullName: computed('firstName', 'lastName', function() { return this.get('firstName') + ' ' + this.get('lastName'); - // Tell Ember that this computed property depends on firstName // and lastName }) }); - let president = President.create({ firstName: 'Barack', lastName: 'Obama' }); - president.get('fullName'); // 'Barack Obama' ``` - @method property @param {String} path* zero or more property paths @return {ComputedProperty} this @chainable @public */ - property(...passedArgs: string[]): ComputedProperty { + property(...passedArgs: string[]): void { deprecate( 'Setting dependency keys using the `.property()` modifier has been deprecated. Pass the dependency keys directly to computed as arguments instead. If you are using `.property()` on a computed property macro, consider refactoring your macro to receive additional dependent keys in its initial declaration.', false, @@ -312,10 +350,9 @@ class ComputedProperty extends Descriptor implements DescriptorWithDependentKeys ); this._property(...passedArgs); - return this; } - _property(...passedArgs: string[]): ComputedProperty { + _property(...passedArgs: string[]): void { let args: string[] = []; function addArg(property: string): void { @@ -334,44 +371,6 @@ class ComputedProperty extends Descriptor implements DescriptorWithDependentKeys } this._dependentKeys = args; - return this; - } - - /** - In some cases, you may want to annotate computed properties with additional - metadata about how they function or what values they operate on. For example, - computed property functions may close over variables that are then no longer - available for introspection. - - You can pass a hash of these values to a computed property like this: - - ``` - import { computed } from '@ember/object'; - import Person from 'my-app/utils/person'; - - person: computed(function() { - let personId = this.get('personId'); - return Person.create({ id: personId }); - }).meta({ type: Person }) - ``` - - The hash that you pass to the `meta()` function will be saved on the - computed property descriptor under the `_meta` key. Ember runtime - exposes a public API for retrieving these values from classes, - via the `metaForProperty()` function. - - @method meta - @param {Object} meta - @chainable - @public - */ - meta(meta?: any): any { - if (arguments.length === 0) { - return this._meta || {}; - } else { - this._meta = meta; - return this; - } } // invalidate cache when CP key changes @@ -396,7 +395,7 @@ class ComputedProperty extends Descriptor implements DescriptorWithDependentKeys get(obj: object, keyName: string): any { if (this._volatile) { - return this._getter.call(obj, keyName); + return this._getter!.call(obj, keyName); } let cache = getCacheFor(obj); @@ -431,7 +430,7 @@ class ComputedProperty extends Descriptor implements DescriptorWithDependentKeys tracker = setCurrentTracker(); } - let ret = this._getter.call(obj, keyName); + let ret = this._getter!.call(obj, keyName); if (EMBER_METAL_TRACKED_PROPERTIES) { setCurrentTracker(parent!); @@ -557,6 +556,40 @@ if (EMBER_METAL_TRACKED_PROPERTIES) { }; } +export type ComputedDecorator = Decorator & PropertyDecorator & ComputedDecoratorImpl; + +class ComputedDecoratorImpl { + readOnly(this: Decorator) { + descriptorForDecorator(this).readOnly(); + return this; + } + + volatile(this: Decorator) { + descriptorForDecorator(this).volatile(); + return this; + } + + property(this: Decorator, ...keys: string[]) { + descriptorForDecorator(this).property(...keys); + return this; + } + + meta(this: Decorator, meta?: any): any { + let prop = descriptorForDecorator(this); + + if (arguments.length === 0) { + return prop._meta || {}; + } else { + prop._meta = meta; + return this; + } + } + + set enumerable(this: Decorator, value: boolean) { + descriptorForDecorator(this).enumerable = value; + } +} + /** This helper returns a new property descriptor that wraps the passed computed property function. You can use this helper to define properties @@ -567,6 +600,7 @@ if (EMBER_METAL_TRACKED_PROPERTIES) { ```js import EmberObject, { computed } from '@ember/object'; +import { DecoratorDescriptor } from '../../../../../tmp/funnel-input_base_path-gav85kkw.tmp/@ember/-internals/metal/lib/decorator_descriptor'; let Person = EmberObject.extend({ init() { @@ -642,21 +676,16 @@ if (EMBER_METAL_TRACKED_PROPERTIES) { @static @param {String} [dependentKeys*] Optional dependent keys that trigger this computed property. @param {Function} func The computed property function. - @return {ComputedProperty} property descriptor instance + @return {ComputedDecorator} property descriptor instance @public */ -export default function computed(...args: (string | ComputedPropertyConfig)[]): ComputedProperty { - let func = args.pop(); - - let cp = new ComputedProperty(func as ComputedPropertyConfig); - - if (args.length > 0) { - cp._property(...(args as string[])); - } - - return cp; +export function computed(...args: (string | ComputedPropertyConfig)[]): ComputedDecorator { + return makeComputedDecorator( + new ComputedProperty(args), + ComputedDecoratorImpl + ) as ComputedDecorator; } -// used for the Ember.computed global only + export const _globalsComputed = computed.bind(null); -export { ComputedProperty, computed }; +export default computed; diff --git a/packages/@ember/-internals/metal/lib/decorator.ts b/packages/@ember/-internals/metal/lib/decorator.ts new file mode 100644 index 00000000000..b86d192195c --- /dev/null +++ b/packages/@ember/-internals/metal/lib/decorator.ts @@ -0,0 +1,213 @@ +import { Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; +import { assert } from '@ember/debug'; +import { DEBUG } from '@glimmer/env'; +import { unwatch, watch } from './watching'; + +const DECORATOR_DESCRIPTOR_MAP: WeakMap = new WeakMap(); + +// 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 Decorator = (desc: ElementDescriptor) => ElementDescriptor; + +// .......................................................... +// DESCRIPTOR +// + +/** + Returns the CP descriptor assocaited with `obj` and `keyName`, if any. + + @method descriptorFor + @param {Object} obj the object to check + @param {String} keyName the key to check + @return {Descriptor} + @private +*/ +export function descriptorForProperty(obj: object, keyName: string, _meta?: Meta | null) { + assert('Cannot call `descriptorFor` on null', obj !== null); + assert('Cannot call `descriptorFor` on undefined', obj !== undefined); + assert( + `Cannot call \`descriptorFor\` on ${typeof obj}`, + typeof obj === 'object' || typeof obj === 'function' + ); + + let meta = _meta === undefined ? peekMeta(obj) : _meta; + + if (meta !== null) { + return meta.peekDescriptors(keyName); + } +} + +export function descriptorForDecorator(dec: Decorator) { + return DECORATOR_DESCRIPTOR_MAP.get(dec); +} + +export function isComputedDecorator(desc: Decorator | null | undefined) { + return desc === null || desc === undefined ? false : DECORATOR_DESCRIPTOR_MAP.has(desc); +} + +export function setComputedDecorator(desc: Decorator) { + DECORATOR_DESCRIPTOR_MAP.set(desc, true); +} + +// .......................................................... +// DEPENDENT KEYS +// + +export function addDependentKeys( + desc: ComputedDescriptor, + obj: object, + keyName: string, + meta: Meta +): void { + // the descriptor has a list of dependent keys, so + // add all of its dependent keys. + let depKeys = desc._dependentKeys; + if (depKeys === null || depKeys === undefined) { + return; + } + + for (let idx = 0; idx < depKeys.length; idx++) { + let depKey = depKeys[idx]; + // Increment the number of times depKey depends on keyName. + meta.writeDeps(depKey, keyName, meta.peekDeps(depKey, keyName) + 1); + // Watch the depKey + watch(obj, depKey, meta); + } +} + +export function removeDependentKeys( + desc: ComputedDescriptor, + obj: object, + keyName: string, + meta: Meta +): void { + // the descriptor has a list of dependent keys, so + // remove all of its dependent keys. + let depKeys = desc._dependentKeys; + if (depKeys === null || depKeys === undefined) { + return; + } + + for (let idx = 0; idx < depKeys.length; idx++) { + let depKey = depKeys[idx]; + // Decrement the number of times depKey depends on keyName. + meta.writeDeps(depKey, keyName, meta.peekDeps(depKey, keyName) - 1); + // Unwatch the depKey + unwatch(obj, depKey, meta); + } +} + +export function nativeDescDecorator(propertyDesc: PropertyDescriptor) { + let decorator = function(elementDesc: ElementDescriptor) { + elementDesc.descriptor = propertyDesc; + return elementDesc; + }; + + setComputedDecorator(decorator); + + return decorator; +} + +/** + Objects of this type can implement an interface to respond to requests to + get and set. The default implementation handles simple properties. + + @class Descriptor + @private +*/ +export abstract class ComputedDescriptor { + enumerable = true; + configurable = true; + _dependentKeys?: string[] = undefined; + _meta: any = undefined; + + setup(_obj: object, keyName: string, _propertyDesc: PropertyDescriptor, meta: Meta): void { + meta.writeDescriptors(keyName, this); + } + + teardown(_obj: object, keyName: string, meta: Meta): void { + meta.removeDescriptors(keyName); + } + + abstract get(obj: object, keyName: string): any | null | undefined; + abstract set(obj: object, keyName: string, value: any | null | undefined): any | null | undefined; + + willWatch?(obj: object, keyName: string, meta: Meta): void; + didUnwatch?(obj: object, keyName: string, meta: Meta): void; + + didChange?(obj: object, keyName: string): void; + + /** + In some cases, you may want to annotate computed properties with additional + metadata about how they function or what values they operate on. For example, + computed property functions may close over variables that are then no longer + available for introspection. + You can pass a hash of these values to a computed property like this: + ``` + import { computed } from '@ember/object'; + import Person from 'my-app/utils/person'; + person: computed(function() { + let personId = this.get('personId'); + return Person.create({ id: personId }); + }).meta({ type: Person }) + ``` + The hash that you pass to the `meta()` function will be saved on the + computed property descriptor under the `_meta` key. Ember runtime + exposes a public API for retrieving these values from classes, + via the `metaForProperty()` function. + @method meta + @param {Object} meta + @chainable + @public + */ +} + +function DESCRIPTOR_GETTER_FUNCTION(name: string, descriptor: ComputedDescriptor): () => any { + return function CPGETTER_FUNCTION(this: object): any { + return descriptor.get(this, name); + }; +} + +export function makeComputedDecorator( + desc: ComputedDescriptor, + DecoratorClass: { prototype: object } +) { + let decorator = function COMPUTED_DECORATOR(elementDesc: ElementDescriptor): ElementDescriptor { + let { key, descriptor: propertyDesc } = elementDesc; + + if (DEBUG) { + // Store the initializer for assertions + propertyDesc.initializer = elementDesc.initializer; + } + + elementDesc.kind = 'method'; + elementDesc.descriptor = { + enumerable: desc.enumerable, + configurable: desc.configurable, + get: DESCRIPTOR_GETTER_FUNCTION(elementDesc.key, desc), + }; + + elementDesc.finisher = (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; + }; + + DECORATOR_DESCRIPTOR_MAP.set(decorator, desc); + + Object.setPrototypeOf(decorator, DecoratorClass.prototype); + + return decorator; +} diff --git a/packages/@ember/-internals/metal/lib/dependent_keys.ts b/packages/@ember/-internals/metal/lib/dependent_keys.ts index 8e4ee34de85..e69de29bb2d 100644 --- a/packages/@ember/-internals/metal/lib/dependent_keys.ts +++ b/packages/@ember/-internals/metal/lib/dependent_keys.ts @@ -1,54 +0,0 @@ -import { Meta } from '@ember/-internals/meta'; -import { unwatch, watch } from './watching'; - -export interface DescriptorWithDependentKeys { - _dependentKeys?: string[]; -} - -// .......................................................... -// DEPENDENT KEYS -// - -export function addDependentKeys( - desc: DescriptorWithDependentKeys, - obj: object, - keyName: string, - meta: Meta -): void { - // the descriptor has a list of dependent keys, so - // add all of its dependent keys. - let depKeys = desc._dependentKeys; - if (depKeys === null || depKeys === undefined) { - return; - } - - for (let idx = 0; idx < depKeys.length; idx++) { - let depKey = depKeys[idx]; - // Increment the number of times depKey depends on keyName. - meta.writeDeps(depKey, keyName, meta.peekDeps(depKey, keyName) + 1); - // Watch the depKey - watch(obj, depKey, meta); - } -} - -export function removeDependentKeys( - desc: DescriptorWithDependentKeys, - obj: object, - keyName: string, - meta: Meta -): void { - // the descriptor has a list of dependent keys, so - // remove all of its dependent keys. - let depKeys = desc._dependentKeys; - if (depKeys === null || depKeys === undefined) { - return; - } - - for (let idx = 0; idx < depKeys.length; idx++) { - let depKey = depKeys[idx]; - // Decrement the number of times depKey depends on keyName. - meta.writeDeps(depKey, keyName, meta.peekDeps(depKey, keyName) - 1); - // Unwatch the depKey - unwatch(obj, depKey, meta); - } -} diff --git a/packages/@ember/-internals/metal/lib/descriptor.ts b/packages/@ember/-internals/metal/lib/descriptor.ts deleted file mode 100644 index 1df6707818e..00000000000 --- a/packages/@ember/-internals/metal/lib/descriptor.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Meta } from '@ember/-internals/meta'; -import { Descriptor } from './properties'; - -export default function descriptor(desc: PropertyDescriptor): NativeDescriptor { - return new NativeDescriptor(desc); -} - -/** - A wrapper for a native ES5 descriptor. In an ideal world, we wouldn't need - this at all, however, the way we currently flatten/merge our mixins require - a special value to denote a descriptor. - - @class NativeDescriptor - @private -*/ -class NativeDescriptor extends Descriptor { - desc: PropertyDescriptor; - - constructor(desc: PropertyDescriptor) { - super(); - this.desc = desc; - this.enumerable = desc.enumerable !== false; - this.configurable = desc.configurable !== false; - } - - setup(obj: object, key: string, meta: Meta): void { - Object.defineProperty(obj, key, this.desc); - meta.writeDescriptors(key, this); - } - - get(obj: object, key: string): any { - return obj[key]; - } - - set(obj: object, key: string, value: any): any { - return (obj[key] = value); - } -} diff --git a/packages/@ember/-internals/metal/lib/injected_property.ts b/packages/@ember/-internals/metal/lib/injected_property.ts index ee8cc64a90b..29a2af31686 100644 --- a/packages/@ember/-internals/metal/lib/injected_property.ts +++ b/packages/@ember/-internals/metal/lib/injected_property.ts @@ -1,10 +1,16 @@ -import { descriptorFor } from '@ember/-internals/meta'; import { getOwner } from '@ember/-internals/owner'; import { EMBER_MODULE_UNIFICATION } from '@ember/canary-features'; import { assert } from '@ember/debug'; -import { ComputedProperty } from './computed'; +import { DEBUG } from '@glimmer/env'; +import { computed } from './computed'; import { defineProperty } from './properties'; +export let DEBUG_INJECTION_FUNCTIONS: WeakMap; + +if (DEBUG) { + DEBUG_INJECTION_FUNCTIONS = new WeakMap(); +} + export interface InjectedPropertyOptions { source: string; } @@ -25,58 +31,48 @@ export interface InjectedPropertyOptions { to the property's name @private */ -export default class InjectedProperty extends ComputedProperty { - readonly type: string; - readonly name: string; - readonly source: string | undefined; - readonly namespace: string | undefined; +export default function inject(type: string, name?: string, options?: InjectedPropertyOptions) { + let source: string | undefined, namespace: string | undefined; - constructor(type: string, name: string, options?: InjectedPropertyOptions) { - super(injectedPropertyDesc); + if (EMBER_MODULE_UNIFICATION) { + source = options ? options.source : undefined; + namespace = undefined; - this.type = type; - this.name = name; + if (name !== undefined) { + let namespaceDelimiterOffset = name.indexOf('::'); - if (EMBER_MODULE_UNIFICATION) { - this.source = options ? options.source : undefined; - this.namespace = undefined; - - if (name) { - let namespaceDelimiterOffset = name.indexOf('::'); - if (namespaceDelimiterOffset === -1) { - this.name = name; - this.namespace = undefined; - } else { - this.name = name.slice(namespaceDelimiterOffset + 2); - this.namespace = name.slice(0, namespaceDelimiterOffset); - } + if (namespaceDelimiterOffset !== -1) { + name = name.slice(namespaceDelimiterOffset + 2); + namespace = name.slice(0, namespaceDelimiterOffset); } } } -} -const injectedPropertyDesc = { - get(this: any, keyName: string): any { - let desc = descriptorFor(this, keyName); + let getInjection = function getInjection(this: any, propertyName: string) { let owner = getOwner(this) || this.container; // fallback to `container` for backwards compat - assert( - `InjectedProperties should be defined with the inject computed property macros.`, - desc && desc.type - ); assert( `Attempting to lookup an injected property on an object without a container, ensure that the object was instantiated via a container.`, Boolean(owner) ); - let specifier = `${desc.type}:${desc.name || keyName}`; - return owner.lookup(specifier, { - source: desc.source, - namespace: desc.namespace, + return owner.lookup(`${type}:${name || propertyName}`, { source, namespace }); + }; + + if (DEBUG) { + DEBUG_INJECTION_FUNCTIONS.set(getInjection, { + namespace, + source, + type, + name, }); - }, + } - set(this: any, keyName: string, value: any) { - defineProperty(this, keyName, null, value); - }, -}; + return computed({ + get: getInjection, + + set(this: any, keyName: string, value: any) { + defineProperty(this, keyName, null, value); + }, + }); +} diff --git a/packages/@ember/-internals/metal/lib/mixin.ts b/packages/@ember/-internals/metal/lib/mixin.ts index ab374685281..34396cf478e 100644 --- a/packages/@ember/-internals/metal/lib/mixin.ts +++ b/packages/@ember/-internals/metal/lib/mixin.ts @@ -1,7 +1,7 @@ /** @module @ember/object */ -import { descriptorFor, Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; +import { Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; import { getListeners, getObservers, @@ -16,12 +16,23 @@ import { assert, deprecate } from '@ember/debug'; import { ALIAS_METHOD } from '@ember/deprecated-features'; import { assign } from '@ember/polyfills'; import { DEBUG } from '@glimmer/env'; -import { ComputedProperty, ComputedPropertyGetter, ComputedPropertySetter } from './computed'; +import { + ComputedDecorator, + ComputedProperty, + ComputedPropertyGetter, + ComputedPropertySetter, +} from './computed'; +import { + descriptorForDecorator, + descriptorForProperty, + isComputedDecorator, + makeComputedDecorator, +} from './decorator'; import { addListener, removeListener } from './events'; import expandProperties from './expand_properties'; import { classToString, setUnprocessedMixins } from './namespace_search'; import { addObserver, removeObserver } from './observer'; -import { defineProperty, Descriptor } from './properties'; +import { defineProperty } from './properties'; const a_concat = Array.prototype.concat; const { isArray } = Array; @@ -70,43 +81,65 @@ function concatenatedMixinProperties( function giveDescriptorSuper( meta: Meta, key: string, - property: ComputedProperty, + decorator: ComputedDecorator, values: { [key: string]: any }, descs: { [key: string]: any }, base: object -): ComputedProperty { +): ComputedDecorator { + let property = descriptorForDecorator(decorator); let superProperty; + if (property._getter === undefined) { + return decorator; + } + // Computed properties override methods, and do not call super to them if (values[key] === undefined) { // Find the original descriptor in a parent mixin - superProperty = descs[key]; + superProperty = descriptorForDecorator(descs[key]); } // If we didn't find the original descriptor in a parent mixin, find // it on the original object. if (!superProperty) { - superProperty = descriptorFor(base, key, meta); + superProperty = descriptorForProperty(base, key, meta); } if (superProperty === undefined || !(superProperty instanceof ComputedProperty)) { - return property; + return decorator; } - // Since multiple mixins may inherit from the same parent, we need - // to clone the computed property so that other mixins do not receive - // the wrapped version. - property = Object.create(property); - property._getter = wrap(property._getter, superProperty._getter) as ComputedPropertyGetter; + let get = wrap(property._getter!, superProperty._getter!) as ComputedPropertyGetter; + let set; + if (superProperty._setter) { if (property._setter) { - property._setter = wrap(property._setter, superProperty._setter) as ComputedPropertySetter; + set = wrap(property._setter, superProperty._setter) as ComputedPropertySetter; } else { - property._setter = superProperty._setter; + // If the super property has a setter, we default to using it no matter what. + // This is clearly very broken and weird, but it's what was here so we have + // to keep it until the next major at least. + // + // TODO: Add a deprecation here. + set = superProperty._setter; } + } else { + set = property._setter; } - return property; + // only create a new CP if we must + if (get !== property._getter || set !== property._setter) { + // Since multiple mixins may inherit from the same parent, we need + // to clone the computed property so that other mixins do not receive + // the wrapped version. + let newProperty = Object.create(property); + newProperty._getter = get; + newProperty._setter = set; + + return makeComputedDecorator(newProperty, ComputedProperty) as ComputedDecorator; + } + + return decorator; } function giveMethodSuper( @@ -126,7 +159,7 @@ function giveMethodSuper( // If we didn't find the original value in a parent mixin, find it in // the original object - if (superMethod === undefined && descriptorFor(obj, key) === undefined) { + if (superMethod === undefined && descriptorForProperty(obj, key) === undefined) { superMethod = obj[key]; } @@ -206,21 +239,16 @@ function applyMergedProperties( function addNormalizedProperty( base: any, key: string, - value: Descriptor | any, + value: any, meta: Meta, descs: { [key: string]: any }, values: { [key: string]: any }, concats?: string[], mergings?: string[] ): void { - if (value instanceof Descriptor) { - // Wrap descriptor function to implement - // _super() if needed - if ((value as ComputedProperty)._getter) { - value = giveDescriptorSuper(meta, key, value as ComputedProperty, values, descs, base); - } - - descs[key] = value; + if (isComputedDecorator(value)) { + // Wrap descriptor function to implement _super() if needed + descs[key] = giveDescriptorSuper(meta, key, value, values, descs, base); values[key] = undefined; } else { if ( @@ -323,7 +351,7 @@ if (ALIAS_METHOD) { if (desc !== undefined || value !== undefined) { // do nothing - } else if ((possibleDesc = descriptorFor(obj, altKey)) !== undefined) { + } else if ((possibleDesc = descriptorForProperty(obj, altKey)) !== undefined) { desc = possibleDesc; value = undefined; } else { @@ -409,7 +437,7 @@ export function applyMixin(obj: { [key: string]: any }, mixins: Mixin[]) { continue; } - if (descriptorFor(obj, key) !== undefined) { + if (descriptorForProperty(obj, key) !== undefined) { replaceObserversAndListeners(obj, key, null, value); } else { replaceObserversAndListeners(obj, key, obj[key], value); diff --git a/packages/@ember/-internals/metal/lib/properties.ts b/packages/@ember/-internals/metal/lib/properties.ts index 5d0096e3fcb..5c9108d397b 100644 --- a/packages/@ember/-internals/metal/lib/properties.ts +++ b/packages/@ember/-internals/metal/lib/properties.ts @@ -2,9 +2,15 @@ @module @ember/object */ -import { descriptorFor, Meta, meta as metaFor, peekMeta, UNDEFINED } from '@ember/-internals/meta'; +import { Meta, meta as metaFor, peekMeta, UNDEFINED } from '@ember/-internals/meta'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; +import { + Decorator, + descriptorForProperty, + ElementDescriptor, + isComputedDecorator, +} from './decorator'; import { overrideChains } from './property_events'; export type MandatorySetterFunction = ((this: object, value: any) => void) & { @@ -15,44 +21,6 @@ export type InheritingGetterFunction = ((this: object) => void) & { isInheritingGetter: true; }; -// .......................................................... -// DESCRIPTOR -// - -/** - Objects of this type can implement an interface to respond to requests to - get and set. The default implementation handles simple properties. - - @class Descriptor - @private -*/ -export abstract class Descriptor { - isDescriptor = true; - enumerable = true; - configurable = true; - - setup(obj: object, keyName: string, meta: Meta): void { - Object.defineProperty(obj, keyName, { - enumerable: this.enumerable, - configurable: this.configurable, - get: DESCRIPTOR_GETTER_FUNCTION(keyName, this), - }); - meta.writeDescriptors(keyName, this); - } - - teardown(_obj: object, keyName: string, meta: Meta): void { - meta.removeDescriptors(keyName); - } - - abstract get(obj: object, keyName: string): any | null | undefined; - abstract set(obj: object, keyName: string, value: any | null | undefined): any | null | undefined; - - willWatch?(obj: object, keyName: string, meta: Meta): void; - didUnwatch?(obj: object, keyName: string, meta: Meta): void; - - didChange?(obj: object, keyName: string): void; -} - interface ExtendedObject { didDefineProperty?: (obj: object, keyName: string, value: any) => void; } @@ -105,12 +73,6 @@ export function INHERITING_GETTER_FUNCTION(name: string): InheritingGetterFuncti }); } -function DESCRIPTOR_GETTER_FUNCTION(name: string, descriptor: Descriptor): () => any { - return function CPGETTER_FUNCTION(this: object): any { - return descriptor.get(this, name); - }; -} - /** NOTE: This is a low-level method used by other parts of the API. You almost never want to call this method directly. Instead you should use @@ -162,7 +124,7 @@ function DESCRIPTOR_GETTER_FUNCTION(name: string, descriptor: Descriptor): () => export function defineProperty( obj: object, keyName: string, - desc?: Descriptor | undefined | null, + desc?: Decorator | undefined | null, data?: any | undefined | null, meta?: Meta ): void { @@ -171,7 +133,7 @@ export function defineProperty( } let watching = meta.peekWatching(keyName) > 0; - let previousDesc = descriptorFor(obj, keyName, meta); + let previousDesc = descriptorForProperty(obj, keyName, meta); let wasDescriptor = previousDesc !== undefined; if (wasDescriptor) { @@ -191,9 +153,34 @@ export function defineProperty( } let value; - if (desc instanceof Descriptor) { + if (isComputedDecorator(desc)) { + let elementDesc: ElementDescriptor = desc!({ + key: keyName, + kind: 'field', + placement: 'own', + descriptor: { + value: undefined, + }, + }); + + 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); + } + } + + // pass the decorator function forward for backwards compat value = desc; - desc.setup(obj, keyName, meta); } else if (desc === undefined || desc === null) { value = data; diff --git a/packages/@ember/-internals/metal/lib/property_events.ts b/packages/@ember/-internals/metal/lib/property_events.ts index 7ab6f8173e7..da9ebf2013e 100644 --- a/packages/@ember/-internals/metal/lib/property_events.ts +++ b/packages/@ember/-internals/metal/lib/property_events.ts @@ -1,7 +1,8 @@ -import { descriptorFor, Meta, peekMeta } from '@ember/-internals/meta'; +import { Meta, peekMeta } from '@ember/-internals/meta'; import { symbol } from '@ember/-internals/utils'; import { DEBUG } from '@glimmer/env'; import changeEvent from './change_event'; +import { descriptorForProperty } from './decorator'; import { sendEvent } from './events'; import ObserverSet from './observer_set'; import { markObjectAsDirty } from './tags'; @@ -41,7 +42,7 @@ function notifyPropertyChange(obj: object, keyName: string, _meta?: Meta | null) return; } - let possibleDesc = descriptorFor(obj, keyName, meta); + let possibleDesc = descriptorForProperty(obj, keyName, meta); if (possibleDesc !== undefined && typeof possibleDesc.didChange === 'function') { possibleDesc.didChange(obj, keyName); @@ -112,7 +113,7 @@ function iterDeps( let possibleDesc; meta.forEachInDeps(depKey, (key: string) => { - possibleDesc = descriptorFor(obj, key, meta); + possibleDesc = descriptorForProperty(obj, key, meta); if (possibleDesc !== undefined && possibleDesc._suspended === obj) { return; diff --git a/packages/@ember/-internals/metal/lib/property_get.ts b/packages/@ember/-internals/metal/lib/property_get.ts index b22dcbd69c0..89290818fcc 100644 --- a/packages/@ember/-internals/metal/lib/property_get.ts +++ b/packages/@ember/-internals/metal/lib/property_get.ts @@ -1,11 +1,11 @@ /** @module @ember/object */ -import { descriptorFor } from '@ember/-internals/meta'; import { HAS_NATIVE_PROXY, symbol } from '@ember/-internals/utils'; import { EMBER_METAL_TRACKED_PROPERTIES } from '@ember/canary-features'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; +import { descriptorForProperty } from './decorator'; import { isPath } from './path_cache'; import { tagForProperty } from './tags'; import { getCurrentTracker } from './tracked'; @@ -108,7 +108,7 @@ export function get(obj: object, keyName: string): any { if (tracker) tracker.add(tagForProperty(obj, keyName)); } - let descriptor = descriptorFor(obj, keyName); + let descriptor = descriptorForProperty(obj, keyName); if (descriptor !== undefined) { return descriptor.get(obj, keyName); } diff --git a/packages/@ember/-internals/metal/lib/property_set.ts b/packages/@ember/-internals/metal/lib/property_set.ts index e9c874262b9..77f9c585d0c 100644 --- a/packages/@ember/-internals/metal/lib/property_set.ts +++ b/packages/@ember/-internals/metal/lib/property_set.ts @@ -1,8 +1,9 @@ -import { descriptorFor, Meta, peekMeta } from '@ember/-internals/meta'; +import { Meta, peekMeta } from '@ember/-internals/meta'; import { HAS_NATIVE_PROXY, lookupDescriptor, toString } from '@ember/-internals/utils'; import { assert } from '@ember/debug'; import EmberError from '@ember/error'; import { DEBUG } from '@glimmer/env'; +import { descriptorForProperty } from './decorator'; import { isPath } from './path_cache'; import { MandatorySetterFunction } from './properties'; import { notifyPropertyChange } from './property_events'; @@ -78,7 +79,7 @@ export function set(obj: object, keyName: string, value: any, tolerant?: boolean } let meta = peekMeta(obj); - let descriptor = descriptorFor(obj, keyName, meta); + let descriptor = descriptorForProperty(obj, keyName, meta); if (descriptor !== undefined) { descriptor.set(obj, keyName, value); diff --git a/packages/@ember/-internals/metal/lib/watch_key.ts b/packages/@ember/-internals/metal/lib/watch_key.ts index b2499a6fdbf..9191dda3ff2 100644 --- a/packages/@ember/-internals/metal/lib/watch_key.ts +++ b/packages/@ember/-internals/metal/lib/watch_key.ts @@ -1,13 +1,7 @@ -import { - descriptorFor, - isDescriptor, - Meta, - meta as metaFor, - peekMeta, - UNDEFINED, -} from '@ember/-internals/meta'; +import { Meta, meta as metaFor, peekMeta, UNDEFINED } from '@ember/-internals/meta'; import { lookupDescriptor } from '@ember/-internals/utils'; import { DEBUG } from '@glimmer/env'; +import { descriptorForProperty, isComputedDecorator } from './decorator'; import { DEFAULT_GETTER_FUNCTION, INHERITING_GETTER_FUNCTION, @@ -33,7 +27,7 @@ export function watchKey(obj: object, keyName: string, _meta?: Meta): void { if (count === 0) { // activate watching first time - let possibleDesc = descriptorFor(obj, keyName, meta); + let possibleDesc = descriptorForProperty(obj, keyName, meta); if (possibleDesc !== undefined && possibleDesc.willWatch !== undefined) { possibleDesc.willWatch(obj, keyName, meta); @@ -62,7 +56,7 @@ if (DEBUG) { let descriptor = lookupDescriptor(obj, keyName); let hasDescriptor = descriptor !== null; let possibleDesc = hasDescriptor && descriptor!.value; - if (isDescriptor(possibleDesc)) { + if (isComputedDecorator(possibleDesc)) { return; } let configurable = hasDescriptor ? descriptor!.configurable : true; @@ -102,7 +96,7 @@ export function unwatchKey(obj: object, keyName: string, _meta?: Meta): void { if (count === 1) { meta.writeWatching(keyName, 0); - let possibleDesc = descriptorFor(obj, keyName, meta); + let possibleDesc = descriptorForProperty(obj, keyName, meta); let isDescriptor = possibleDesc !== undefined; if (isDescriptor && possibleDesc.didUnwatch !== undefined) { diff --git a/packages/@ember/-internals/metal/tests/computed_decorator_test.js b/packages/@ember/-internals/metal/tests/computed_decorator_test.js new file mode 100644 index 00000000000..6cf944fa67c --- /dev/null +++ b/packages/@ember/-internals/metal/tests/computed_decorator_test.js @@ -0,0 +1,585 @@ +import { Object as EmberObject } from '@ember/-internals/runtime'; +import { + computed, + getCachedValueFor, + defineProperty, + get, + set, + setProperties, + isWatching, + addObserver, +} from '..'; +import { meta as metaFor } from '@ember/-internals/meta'; +import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; + +let obj, count; + +moduleFor( + 'computed - decorator - compatibility', + class extends AbstractTestCase { + ['@test computed can be used to compose new decorators'](assert) { + let firstName = 'Diana'; + + let firstNameAlias = computed('firstName', { + get() { + return this.firstName; + }, + }); + + class Class1 { + firstName = firstName; + + @firstNameAlias otherFirstName; + } + + class Class2 { + firstName = firstName; + + @firstNameAlias otherFirstName; + } + + let obj1 = new Class1(); + let obj2 = new Class2(); + + assert.equal(firstName, obj1.otherFirstName); + assert.equal(firstName, obj2.otherFirstName); + } + + ['@test decorator can still have a configuration object'](assert) { + class Foo { + bar = 'something'; + foo = 'else'; + + @computed('foo', { + get() { + return this.bar; + }, + }) + baz; + } + + let obj1 = new Foo(); + + assert.equal('something', obj1.baz); + } + + ['@test it works with functions'](assert) { + assert.expect(2); + + class Foo { + first = 'rob'; + last = 'jackson'; + + @computed('first', 'last', function() { + assert.equal(this.first, 'rob'); + assert.equal(this.last, 'jackson'); + }) + fullName; + } + + let obj = new Foo(); + get(obj, 'fullName'); + } + + ['@test it works with computed desc'](assert) { + assert.expect(4); + + let expectedName = 'rob jackson'; + let expectedFirst = 'rob'; + let expectedLast = 'jackson'; + + class Foo { + first = 'rob'; + last = 'jackson'; + + @computed('first', 'last', { + get() { + assert.equal(this.first, expectedFirst, 'getter: first name matches'); + assert.equal(this.last, expectedLast, 'getter: last name matches'); + return `${this.first} ${this.last}`; + }, + + set(key, name) { + assert.equal(name, expectedName, 'setter: name matches'); + + const [first, last] = name.split(' '); + setProperties(this, { first, last }); + + return name; + }, + }) + fullName; + } + + let obj = new Foo(); + get(obj, 'fullName'); + + expectedName = 'yehuda katz'; + expectedFirst = 'yehuda'; + expectedLast = 'katz'; + set(obj, 'fullName', 'yehuda katz'); + + assert.strictEqual( + get(obj, 'fullName'), + expectedName, + 'return value of getter is new value of property' + ); + } + + ['@test it works with classic classes with full desc'](assert) { + assert.expect(4); + + let expectedName = 'rob jackson'; + let expectedFirst = 'rob'; + let expectedLast = 'jackson'; + + const Foo = EmberObject.extend({ + first: 'rob', + last: 'jackson', + + fullName: computed('first', 'last', { + get() { + assert.equal(this.first, expectedFirst, 'getter: first name matches'); + assert.equal(this.last, expectedLast, 'getter: last name matches'); + return `${this.first} ${this.last}`; + }, + + set(key, name) { + assert.equal(name, expectedName, 'setter: name matches'); + + const [first, last] = name.split(' '); + setProperties(this, { first, last }); + + return name; + }, + }), + }); + + let obj = Foo.create(); + get(obj, 'fullName'); + + expectedName = 'yehuda katz'; + expectedFirst = 'yehuda'; + expectedLast = 'katz'; + set(obj, 'fullName', 'yehuda katz'); + + assert.strictEqual( + get(obj, 'fullName'), + expectedName, + 'return value of getter is new value of property' + ); + } + } +); + +moduleFor( + 'computed - decorator - usage tests', + class extends AbstractTestCase { + ['@test computed property asserts the presence of a getter'](assert) { + assert.throws(() => { + class TestObj { + @computed() + nonGetter() { + return true; + } + } + + new TestObj(); + }, /Try converting it to a getter/); + } + + ['@test computed property works with a getter'](assert) { + class TestObj { + @computed() + get someGetter() { + return true; + } + } + + let instance = new TestObj(); + assert.ok(instance.someGetter); + } + + ['@test computed property with dependent key and getter'](assert) { + class TestObj { + other = true; + + @computed('other') + get someGetter() { + return `${this.other}`; + } + } + + let instance = new TestObj(); + assert.equal(instance.someGetter, 'true'); + + set(instance, 'other', false); + assert.equal(instance.someGetter, 'false'); + } + } +); + +moduleFor( + 'computed - decorators', + class extends AbstractTestCase { + ['@test computed property can be accessed without `get`'](assert) { + let count = 0; + class Obj { + @computed() + get foo() { + count++; + return `computed foo`; + } + } + let obj = new Obj(); + + assert.equal(obj.foo, 'computed foo', 'should return value'); + assert.equal(count, 1, 'should have invoked computed property'); + } + + ['@test defining computed property should invoke property on get'](assert) { + let count = 0; + class Obj { + @computed() + get foo() { + count++; + return `computed foo`; + } + } + let obj = new Obj(); + + assert.equal(obj.foo, 'computed foo', 'should return value'); + assert.equal(count, 1, 'should have invoked computed property'); + } + + ['@test defining computed property should invoke property on set with native'](assert) { + let count = 0; + class Obj { + __foo = 'not set'; + + @computed() + get foo() { + return this.__foo; + } + set foo(value) { + count++; + this.__foo = `computed ${value}`; + return this.__foo; + } + } + let obj = new Obj(); + + assert.equal(set(obj, 'foo', 'bar'), 'bar', 'should return set value'); + assert.equal(count, 1, 'should have invoked computed property'); + assert.equal(obj.foo, 'computed bar', 'should return new value'); + } + + ['@test defining computed property should invoke property on set'](assert) { + let count = 0; + class Obj { + __foo = 'not set'; + + @computed() + get foo() { + return this.__foo; + } + set foo(value) { + count++; + this.__foo = `computed ${value}`; + return this.__foo; + } + } + let obj = new Obj(); + + assert.equal(set(obj, 'foo', 'bar'), 'bar', 'should return set value with set()'); + assert.equal(count, 1, 'should have invoked computed property'); + assert.equal(get(obj, 'foo'), 'computed bar', 'should return new value with get()'); + } + } +); + +moduleFor( + 'computed - decorators - cacheable', + class extends AbstractTestCase { + beforeEach() { + count = 0; + let func = function() { + count++; + return 'bar ' + count; + }; + + class Obj { + @computed() + get foo() { + return func(); + } + set foo(value) { + return func(value); + } + } + + obj = new Obj(); + } + + afterEach() { + obj = count = null; + } + ['@test cacheable should cache'](assert) { + assert.equal(get(obj, 'foo'), 'bar 1', 'first get'); + assert.equal(get(obj, 'foo'), 'bar 1', 'second get'); + assert.equal(count, 1, 'should only invoke once'); + } + + ['@test modifying a cacheable property should update cache'](assert) { + assert.equal(get(obj, 'foo'), 'bar 1', 'first get'); + assert.equal(get(obj, 'foo'), 'bar 1', 'second get'); + + assert.equal(set(obj, 'foo', 'baz'), 'baz', 'setting'); + assert.equal(get(obj, 'foo'), 'bar 2', 'third get'); + assert.equal(count, 2, 'should not invoke again'); + } + + ['@test inherited property should not pick up cache'](assert) { + let objB = Object.create(obj); + + assert.equal(get(obj, 'foo'), 'bar 1', 'obj first get'); + assert.equal(get(objB, 'foo'), 'bar 2', 'objB first get'); + + assert.equal(get(obj, 'foo'), 'bar 1', 'obj second get'); + assert.equal(get(objB, 'foo'), 'bar 2', 'objB second get'); + + set(obj, 'foo', 'baz'); // modify A + assert.equal(get(obj, 'foo'), 'bar 3', 'obj third get'); + assert.equal(get(objB, 'foo'), 'bar 2', 'objB third get'); + } + + ['@test getCachedValueFor should return the cached value'](assert) { + assert.equal(getCachedValueFor(obj, 'foo'), undefined, 'should not yet be a cached value'); + + get(obj, 'foo'); + + assert.equal(getCachedValueFor(obj, 'foo'), 'bar 1', 'should retrieve cached value'); + } + + ['@test getCachedValueFor should return falsy cached values'](assert) { + let obj = new class { + @computed() + get falsy() { + return false; + } + }(); + + assert.equal(getCachedValueFor(obj, 'falsy'), undefined, 'should not yet be a cached value'); + + get(obj, 'falsy'); + + assert.equal(getCachedValueFor(obj, 'falsy'), false, 'should retrieve cached value'); + } + } +); + +// .......................................................... +// DEPENDENT KEYS +// + +moduleFor( + 'computed - dependentkey', + class extends AbstractTestCase { + beforeEach() { + count = 0; + let getterAndSetter = function() { + count++; + get(this, 'bar'); + return 'bar ' + count; + }; + + obj = new class { + bar = 'baz'; + + @computed('bar') + get foo() { + return getterAndSetter(); + } + set foo(_value) { + return getterAndSetter(); + } + }(); + } + + afterEach() { + obj = count = null; + } + + ['@test should lazily watch dependent keys on set'](assert) { + assert.equal(isWatching(obj, 'bar'), false, 'precond not watching dependent key'); + set(obj, 'foo', 'bar'); + assert.equal(isWatching(obj, 'bar'), true, 'lazily watching dependent key'); + } + + ['@test should lazily watch dependent keys on get'](assert) { + assert.equal(isWatching(obj, 'bar'), false, 'precond not watching dependent key'); + get(obj, 'foo'); + assert.equal(isWatching(obj, 'bar'), true, 'lazily watching dependent key'); + } + + ['@test local dependent key should invalidate cache'](assert) { + assert.equal(isWatching(obj, 'bar'), false, 'precond not watching dependent key'); + assert.equal(get(obj, 'foo'), 'bar 1', 'get once'); + assert.equal(isWatching(obj, 'bar'), true, 'lazily setup watching dependent key'); + assert.equal(get(obj, 'foo'), 'bar 1', 'cached retrieve'); + + set(obj, 'bar', 'BIFF'); // should invalidate foo + + assert.equal(get(obj, 'foo'), 'bar 2', 'should recache'); + assert.equal(get(obj, 'foo'), 'bar 2', 'cached retrieve'); + } + + ['@test should invalidate multiple nested dependent keys'](assert) { + let count = 0; + defineProperty( + obj, + 'bar', + computed(function() { + count++; + get(this, 'baz'); + return 'baz ' + count; + }).property('baz') + ); + + assert.equal(isWatching(obj, 'bar'), false, 'precond not watching dependent key'); + assert.equal(isWatching(obj, 'baz'), false, 'precond not watching dependent key'); + assert.equal(get(obj, 'foo'), 'bar 1', 'get once'); + assert.equal(isWatching(obj, 'bar'), true, 'lazily setup watching dependent key'); + assert.equal(isWatching(obj, 'baz'), true, 'lazily setup watching dependent key'); + assert.equal(get(obj, 'foo'), 'bar 1', 'cached retrieve'); + + set(obj, 'baz', 'BIFF'); // should invalidate bar -> foo + assert.equal( + isWatching(obj, 'bar'), + false, + 'should not be watching dependent key after cache cleared' + ); + assert.equal( + isWatching(obj, 'baz'), + false, + 'should not be watching dependent key after cache cleared' + ); + + assert.equal(get(obj, 'foo'), 'bar 2', 'should recache'); + assert.equal(get(obj, 'foo'), 'bar 2', 'cached retrieve'); + assert.equal(isWatching(obj, 'bar'), true, 'lazily setup watching dependent key'); + assert.equal(isWatching(obj, 'baz'), true, 'lazily setup watching dependent key'); + } + + ['@test circular keys should not blow up'](assert) { + let func = function() { + count++; + return 'bar ' + count; + }; + defineProperty(obj, 'bar', computed({ get: func, set: func }).property('foo')); + + defineProperty( + obj, + 'foo', + computed(function() { + count++; + return 'foo ' + count; + }).property('bar') + ); + + assert.equal(get(obj, 'foo'), 'foo 1', 'get once'); + assert.equal(get(obj, 'foo'), 'foo 1', 'cached retrieve'); + + set(obj, 'bar', 'BIFF'); // should invalidate bar -> foo -> bar + + assert.equal(get(obj, 'foo'), 'foo 3', 'should recache'); + assert.equal(get(obj, 'foo'), 'foo 3', 'cached retrieve'); + } + + ['@test redefining a property should undo old dependent keys'](assert) { + assert.equal(isWatching(obj, 'bar'), false, 'precond not watching dependent key'); + assert.equal(get(obj, 'foo'), 'bar 1'); + assert.equal(isWatching(obj, 'bar'), true, 'lazily watching dependent key'); + + defineProperty( + obj, + 'foo', + computed(function() { + count++; + return 'baz ' + count; + }).property('baz') + ); + + assert.equal( + isWatching(obj, 'bar'), + false, + 'after redefining should not be watching dependent key' + ); + + assert.equal(get(obj, 'foo'), 'baz 2'); + + set(obj, 'bar', 'BIFF'); // should not kill cache + assert.equal(get(obj, 'foo'), 'baz 2'); + + set(obj, 'baz', 'BOP'); + assert.equal(get(obj, 'foo'), 'baz 3'); + } + + ['@test can watch multiple dependent keys specified declaratively via brace expansion']( + assert + ) { + defineProperty( + obj, + 'foo', + computed(function() { + count++; + return 'foo ' + count; + }).property('qux.{bar,baz}') + ); + + assert.equal(get(obj, 'foo'), 'foo 1', 'get once'); + assert.equal(get(obj, 'foo'), 'foo 1', 'cached retrieve'); + + set(obj, 'qux', {}); + set(obj, 'qux.bar', 'bar'); // invalidate foo + + assert.equal(get(obj, 'foo'), 'foo 2', 'foo invalidated from bar'); + + set(obj, 'qux.baz', 'baz'); // invalidate foo + + assert.equal(get(obj, 'foo'), 'foo 3', 'foo invalidated from baz'); + + set(obj, 'qux.quux', 'quux'); // do not invalidate foo + + assert.equal(get(obj, 'foo'), 'foo 3', 'foo not invalidated by quux'); + } + + ['@test throws assertion if brace expansion notation has spaces']() { + expectAssertion(function() { + defineProperty( + obj, + 'roo', + computed(function() { + count++; + return 'roo ' + count; + }).property('fee.{bar, baz,bop , }') + ); + }, /cannot contain spaces/); + } + + ['@test throws an assertion if an uncached `get` is called after object is destroyed'](assert) { + assert.equal(isWatching(obj, 'bar'), false, 'precond not watching dependent key'); + + let meta = metaFor(obj); + meta.destroy(); + + obj.toString = () => ''; + + expectAssertion(() => { + get(obj, 'foo'); + }, 'Cannot modify dependent keys for `foo` on `` after it has been destroyed.'); + + assert.equal(isWatching(obj, 'bar'), false, 'deps were not updated'); + } + } +); diff --git a/packages/@ember/-internals/metal/tests/computed_test.js b/packages/@ember/-internals/metal/tests/computed_test.js index d524a067d5b..7f9ed038c8c 100644 --- a/packages/@ember/-internals/metal/tests/computed_test.js +++ b/packages/@ember/-internals/metal/tests/computed_test.js @@ -1,10 +1,9 @@ import { Object as EmberObject } from '@ember/-internals/runtime'; import { - ComputedProperty, computed, getCachedValueFor, - Descriptor, defineProperty, + isComputedDecorator, get, set, isWatching, @@ -19,28 +18,35 @@ moduleFor( 'computed', class extends AbstractTestCase { ['@test computed property should be an instance of descriptor'](assert) { - assert.ok(computed(function() {}) instanceof Descriptor); + assert.ok(isComputedDecorator(computed(function() {}))); } ['@test computed properties assert the presence of a getter or setter function']() { expectAssertion(function() { - computed('nogetternorsetter', {}); + let obj = {}; + defineProperty(obj, 'someProp', computed('nogetternorsetter', {})); }, 'Computed properties must receive a getter or a setter, you passed none.'); } ['@test computed properties check for the presence of a function or configuration object']() { expectAssertion(function() { - computed('nolastargument'); - }, 'computed expects a function or an object as last argument.'); + let obj = {}; + defineProperty(obj, 'someProp', computed('nolastargument')); + }, 'Attempted to use @computed on someProp, 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'); } + // non valid properties are stripped away in the process of creating a computed property descriptor ['@test computed properties defined with an object only allow `get` and `set` keys']() { expectAssertion(function() { - computed({ - get() {}, - set() {}, - other() {}, + let obj = EmberObject.extend({ + someProp: computed({ + get() {}, + set() {}, + other() {}, + }), }); + + obj.create().someProp; }, 'Config object passed to computed can only contain `get` and `set` keys.'); } @@ -113,33 +119,39 @@ moduleFor( assert.equal(get(obj, 'foo'), 'computed bar', 'should return new value'); } - ['@test defining a computed property with a dependent key ending with @each is expanded to []']( - assert - ) { - let cp = computed('blazo.@each', function() {}); + // this should be a unit test elsewhere + // computed is more integration-like, and this test asserts on implementation details. + // ['@test defining a computed property with a dependent key ending with @each is expanded to []']( + // assert + // ) { + // let cp = computed('blazo.@each', function() {}); - assert.deepEqual(cp._dependentKeys, ['blazo.[]']); + // assert.deepEqual(cp._dependentKeys, ['blazo.[]']); - cp = computed('qux', 'zoopa.@each', function() {}); + // cp = computed('qux', 'zoopa.@each', function() {}); - assert.deepEqual(cp._dependentKeys, ['qux', 'zoopa.[]']); - } + // assert.deepEqual(cp._dependentKeys, ['qux', 'zoopa.[]']); + // } ['@test defining a computed property with a dependent key more than one level deep beyond @each is not supported']() { expectNoWarning(() => { - computed('todos', () => {}); + let obj = {}; + defineProperty(obj, 'someProp', computed('todos', () => {})); }); expectNoWarning(() => { - computed('todos.@each.owner', () => {}); + let obj = {}; + defineProperty(obj, 'someProp', computed('todos.@each.owner', () => {})); }); expectWarning(() => { - computed('todos.@each.owner.name', () => {}); + let obj = {}; + defineProperty(obj, 'someProp', computed('todos.@each.owner.name', () => {})); }, /You used the key "todos\.@each\.owner\.name" which is invalid\. /); expectWarning(() => { - computed('todos.@each.owner.@each.name', () => {}); + let obj = {}; + defineProperty(obj, 'someProp', computed('todos.@each.owner.@each.name', () => {})); }, /You used the key "todos\.@each\.owner\.@each\.name" which is invalid\. /); } } @@ -972,18 +984,23 @@ moduleFor( 'computed - readOnly', class extends AbstractTestCase { ['@test is chainable'](assert) { - let cp = computed(function() {}).readOnly(); + let cp = computed(function() {}); + let readOnlyCp = cp.readOnly(); - assert.ok(cp instanceof Descriptor); - assert.ok(cp instanceof ComputedProperty); + assert.equal(cp, readOnlyCp); } ['@test throws assertion if called over a CP with a setter defined with the new syntax']() { expectAssertion(() => { - computed({ - get() {}, - set() {}, - }).readOnly(); + let obj = {}; + defineProperty( + obj, + 'someProp', + computed({ + get() {}, + set() {}, + }).readOnly() + ); }, /Computed properties that define a setter using the new syntax cannot be read-only/); } diff --git a/packages/@ember/-internals/metal/tests/injected_property_test.js b/packages/@ember/-internals/metal/tests/injected_property_test.js index 6e21e546779..f51a5da4035 100644 --- a/packages/@ember/-internals/metal/tests/injected_property_test.js +++ b/packages/@ember/-internals/metal/tests/injected_property_test.js @@ -1,17 +1,17 @@ import { setOwner } from '@ember/-internals/owner'; -import { Descriptor, defineProperty, get, set, InjectedProperty } from '..'; +import { defineProperty, get, isComputedDecorator, set, inject } from '..'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; moduleFor( - 'InjectedProperty', + 'inject', class extends AbstractTestCase { ['@test injected properties should be descriptors'](assert) { - assert.ok(new InjectedProperty() instanceof Descriptor); + assert.ok(isComputedDecorator(inject())); } ['@test injected properties should be overridable'](assert) { let obj = {}; - defineProperty(obj, 'foo', new InjectedProperty()); + defineProperty(obj, 'foo', inject()); set(obj, 'foo', 'bar'); @@ -20,7 +20,7 @@ moduleFor( ['@test getting on an object without an owner or container should fail assertion']() { let obj = {}; - defineProperty(obj, 'foo', new InjectedProperty('type', 'name')); + defineProperty(obj, 'foo', inject('type', 'name')); expectAssertion(function() { get(obj, 'foo'); @@ -37,7 +37,7 @@ moduleFor( }, }; - defineProperty(obj, 'foo', new InjectedProperty('type', 'name')); + defineProperty(obj, 'foo', inject('type', 'name')); assert.equal(get(obj, 'foo'), 'type:name', 'should return the value of container.lookup'); } @@ -54,7 +54,7 @@ moduleFor( }, }); - defineProperty(obj, 'foo', new InjectedProperty('type', 'name')); + defineProperty(obj, 'foo', inject('type', 'name')); assert.equal(get(obj, 'foo'), 'type:name', 'should return the value of container.lookup'); } @@ -68,7 +68,7 @@ moduleFor( }, }); - defineProperty(obj, 'foo', new InjectedProperty('type')); + defineProperty(obj, 'foo', inject('type')); assert.equal(get(obj, 'foo'), 'type:foo', 'should lookup the type using the property name'); } diff --git a/packages/@ember/-internals/metal/tests/mixin/computed_test.js b/packages/@ember/-internals/metal/tests/mixin/computed_test.js index 071464c5d86..c64583e088b 100644 --- a/packages/@ember/-internals/metal/tests/mixin/computed_test.js +++ b/packages/@ember/-internals/metal/tests/mixin/computed_test.js @@ -12,6 +12,8 @@ moduleFor( let MixinA, MixinB, MixinC, MixinD; let obj; + window.testStarted = true; + MixinA = Mixin.create({ aProp: computed(function() { return 'A'; diff --git a/packages/@ember/-internals/metal/tests/descriptor_test.js b/packages/@ember/-internals/metal/tests/native_desc_decorator_test.js similarity index 89% rename from packages/@ember/-internals/metal/tests/descriptor_test.js rename to packages/@ember/-internals/metal/tests/native_desc_decorator_test.js index 6f80b92a71d..ea95c4727e4 100644 --- a/packages/@ember/-internals/metal/tests/descriptor_test.js +++ b/packages/@ember/-internals/metal/tests/native_desc_decorator_test.js @@ -1,5 +1,5 @@ import { Object as EmberObject } from '@ember/-internals/runtime'; -import { Mixin, defineProperty, descriptor } from '..'; +import { Mixin, defineProperty, nativeDescDecorator } from '..'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; let classes = [ @@ -150,12 +150,12 @@ let classes = [ classes.forEach(TestClass => { moduleFor( - TestClass.module('@ember/-internals/metal/descriptor'), + TestClass.module('@ember/-internals/metal/nativeDescDecorator'), class extends AbstractTestCase { ['@test defining a configurable property'](assert) { let factory = new TestClass(assert); - factory.install('foo', descriptor({ configurable: true, value: 'bar' }), assert); + factory.install('foo', nativeDescDecorator({ configurable: true, value: 'bar' }), assert); let obj = factory.finalize(); @@ -174,7 +174,7 @@ classes.forEach(TestClass => { ['@test defining a non-configurable property'](assert) { let factory = new TestClass(assert); - factory.install('foo', descriptor({ configurable: false, value: 'bar' }), assert); + factory.install('foo', nativeDescDecorator({ configurable: false, value: 'bar' }), assert); let obj = factory.finalize(); @@ -198,7 +198,7 @@ classes.forEach(TestClass => { ['@test defining an enumerable property'](assert) { let factory = new TestClass(assert); - factory.install('foo', descriptor({ enumerable: true, value: 'bar' }), assert); + factory.install('foo', nativeDescDecorator({ enumerable: true, value: 'bar' }), assert); let obj = factory.finalize(); @@ -211,7 +211,7 @@ classes.forEach(TestClass => { ['@test defining a non-enumerable property'](assert) { let factory = new TestClass(assert); - factory.install('foo', descriptor({ enumerable: false, value: 'bar' }), assert); + factory.install('foo', nativeDescDecorator({ enumerable: false, value: 'bar' }), assert); let obj = factory.finalize(); @@ -224,7 +224,7 @@ classes.forEach(TestClass => { ['@test defining a writable property'](assert) { let factory = new TestClass(assert); - factory.install('foo', descriptor({ writable: true, value: 'bar' }), assert); + factory.install('foo', nativeDescDecorator({ writable: true, value: 'bar' }), assert); let obj = factory.finalize(); @@ -243,7 +243,7 @@ classes.forEach(TestClass => { ['@test defining a non-writable property'](assert) { let factory = new TestClass(assert); - factory.install('foo', descriptor({ writable: false, value: 'bar' }), assert); + factory.install('foo', nativeDescDecorator({ writable: false, value: 'bar' }), assert); let obj = factory.finalize(); @@ -261,7 +261,7 @@ classes.forEach(TestClass => { let factory = new TestClass(assert); factory.install( 'foo', - descriptor({ + nativeDescDecorator({ get: function() { return this.__foo__; }, @@ -284,7 +284,7 @@ classes.forEach(TestClass => { let factory = new TestClass(assert); factory.install( 'foo', - descriptor({ + nativeDescDecorator({ set: function(value) { this.__foo__ = value; }, @@ -307,7 +307,7 @@ classes.forEach(TestClass => { let factory = new TestClass(assert); factory.install( 'foo', - descriptor({ + nativeDescDecorator({ get: function() { return this.__foo__; }, @@ -323,7 +323,7 @@ classes.forEach(TestClass => { factory.install( 'bar', - descriptor({ + nativeDescDecorator({ get: function() { return this.__bar__; }, @@ -339,7 +339,7 @@ classes.forEach(TestClass => { factory.install( 'fooBar', - descriptor({ + nativeDescDecorator({ get: function() { return this.foo + '-' + this.bar; }, diff --git a/packages/@ember/-internals/runtime/lib/system/core_object.js b/packages/@ember/-internals/runtime/lib/system/core_object.js index 788564e1c1e..87bc7487303 100644 --- a/packages/@ember/-internals/runtime/lib/system/core_object.js +++ b/packages/@ember/-internals/runtime/lib/system/core_object.js @@ -13,7 +13,7 @@ import { isInternalSymbol, } from '@ember/-internals/utils'; import { schedule } from '@ember/runloop'; -import { descriptorFor, meta, peekMeta, deleteMeta } from '@ember/-internals/meta'; +import { meta, peekMeta, deleteMeta } from '@ember/-internals/meta'; import { PROXY_CONTENT, finishChains, @@ -21,9 +21,10 @@ import { Mixin, applyMixin, defineProperty, - ComputedProperty, - InjectedProperty, + descriptorForProperty, classToString, + isComputedDecorator, + DEBUG_INJECTION_FUNCTIONS, } from '@ember/-internals/metal'; import ActionHandler from '../mixins/action_handler'; import { assert, deprecate } from '@ember/debug'; @@ -77,7 +78,7 @@ function initialize(obj, properties) { 'EmberObject.create no longer supports defining computed ' + 'properties. Define computed properties using extend() or reopen() ' + 'before calling create().', - !(value instanceof ComputedProperty) + !isComputedDecorator(value) ); assert( 'EmberObject.create no longer supports defining methods that call _super.', @@ -89,7 +90,7 @@ function initialize(obj, properties) { !(keyName === 'actions' && ActionHandler.detect(obj)) ); - let possibleDesc = descriptorFor(obj, keyName, m); + let possibleDesc = descriptorForProperty(obj, keyName, m); let isDescriptor = possibleDesc !== undefined; if (!isDescriptor) { @@ -936,7 +937,7 @@ class CoreObject { */ static metaForProperty(key) { let proto = this.proto(); // ensure prototype is initialized - let possibleDesc = descriptorFor(proto, key); + let possibleDesc = descriptorForProperty(proto, key); assert( `metaForProperty() could not find a computed property with key '${key}'.`, @@ -1066,11 +1067,11 @@ if (DEBUG) { let proto = this.proto(); for (let key in proto) { - let desc = descriptorFor(proto, key); - if (desc instanceof InjectedProperty) { + let desc = descriptorForProperty(proto, key); + if (desc && DEBUG_INJECTION_FUNCTIONS.has(desc._getter)) { assert( `Defining \`${key}\` as an injected controller property on a non-controller (\`${debugContainerKey}\`) is not allowed.`, - type === 'controller' || desc.type !== 'controller' + type === 'controller' || DEBUG_INJECTION_FUNCTIONS.get(desc._getter).type !== 'controller' ); } } @@ -1091,12 +1092,14 @@ if (DEBUG) { let desc; for (key in proto) { - desc = descriptorFor(proto, key); - if (desc instanceof InjectedProperty) { + desc = descriptorForProperty(proto, key); + if (desc && DEBUG_INJECTION_FUNCTIONS.has(desc._getter)) { + let { namespace, source, type, name } = DEBUG_INJECTION_FUNCTIONS.get(desc._getter); + injections[key] = { - namespace: desc.namespace, - source: desc.source, - specifier: `${desc.type}:${desc.name || key}`, + namespace, + source, + specifier: `${type}:${name || key}`, }; } } diff --git a/packages/@ember/-internals/runtime/tests/inject_test.js b/packages/@ember/-internals/runtime/tests/inject_test.js index 91747604105..79375e0fcc5 100644 --- a/packages/@ember/-internals/runtime/tests/inject_test.js +++ b/packages/@ember/-internals/runtime/tests/inject_test.js @@ -1,4 +1,4 @@ -import { InjectedProperty } from '@ember/-internals/metal'; +import { inject } from '@ember/-internals/metal'; import { DEBUG } from '@glimmer/env'; import EmberObject from '../lib/system/object'; import { buildOwner } from 'internal-test-helpers'; @@ -10,7 +10,7 @@ moduleFor( ['@test attempting to inject a nonexistent container key should error']() { let owner = buildOwner(); let AnObject = EmberObject.extend({ - foo: new InjectedProperty('bar', 'baz'), + foo: inject('bar', 'baz'), }); owner.register('foo:main', AnObject); @@ -23,8 +23,8 @@ moduleFor( ['@test factories should return a list of lazy injection full names'](assert) { if (DEBUG) { let AnObject = EmberObject.extend({ - foo: new InjectedProperty('foo', 'bar'), - bar: new InjectedProperty('quux'), + foo: inject('foo', 'bar'), + bar: inject('quux'), }); assert.deepEqual( diff --git a/packages/@ember/-internals/views/lib/mixins/child_views_support.js b/packages/@ember/-internals/views/lib/mixins/child_views_support.js index 1cd92bff43a..1af78716c2f 100644 --- a/packages/@ember/-internals/views/lib/mixins/child_views_support.js +++ b/packages/@ember/-internals/views/lib/mixins/child_views_support.js @@ -1,7 +1,7 @@ /** @module ember */ -import { Mixin, descriptor } from '@ember/-internals/metal'; +import { Mixin, nativeDescDecorator } from '@ember/-internals/metal'; import { getChildViews, addChildView } from '../system/utils'; export default Mixin.create({ @@ -13,7 +13,7 @@ export default Mixin.create({ @default [] @private */ - childViews: descriptor({ + childViews: nativeDescDecorator({ configurable: false, enumerable: false, get() { diff --git a/packages/@ember/-internals/views/lib/mixins/class_names_support.js b/packages/@ember/-internals/views/lib/mixins/class_names_support.js index e03fa190b80..0beaa12dd5b 100644 --- a/packages/@ember/-internals/views/lib/mixins/class_names_support.js +++ b/packages/@ember/-internals/views/lib/mixins/class_names_support.js @@ -1,8 +1,7 @@ /** @module ember */ -import { descriptorFor } from '@ember/-internals/meta'; -import { Mixin } from '@ember/-internals/metal'; +import { descriptorForProperty, Mixin } from '@ember/-internals/metal'; import { assert } from '@ember/debug'; const EMPTY_ARRAY = Object.freeze([]); @@ -20,12 +19,12 @@ export default Mixin.create({ assert( `Only arrays are allowed for 'classNameBindings'`, - descriptorFor(this, 'classNameBindings') === undefined && + descriptorForProperty(this, 'classNameBindings') === undefined && Array.isArray(this.classNameBindings) ); assert( `Only arrays of static class strings are allowed for 'classNames'. For dynamic classes, use 'classNameBindings'.`, - descriptorFor(this, 'classNames') === undefined && Array.isArray(this.classNames) + descriptorForProperty(this, 'classNames') === undefined && Array.isArray(this.classNames) ); }, diff --git a/packages/@ember/-internals/views/lib/mixins/view_support.js b/packages/@ember/-internals/views/lib/mixins/view_support.js index 4b12662c7f6..4aeed30daea 100644 --- a/packages/@ember/-internals/views/lib/mixins/view_support.js +++ b/packages/@ember/-internals/views/lib/mixins/view_support.js @@ -1,6 +1,5 @@ import { guidFor } from '@ember/-internals/utils'; -import { descriptorFor } from '@ember/-internals/meta'; -import { descriptor, Mixin } from '@ember/-internals/metal'; +import { descriptorForProperty, Mixin, nativeDescDecorator } from '@ember/-internals/metal'; import { assert } from '@ember/debug'; import { hasDOM } from '@ember/-internals/browser-environment'; import { matches } from '../system/utils'; @@ -143,11 +142,11 @@ let mixin = { /** Returns the current DOM element for the view. - @property element - @type DOMElement - @public - */ - element: descriptor({ + @property element + @type DOMElement + @public + */ + element: nativeDescDecorator({ configurable: false, enumerable: false, get() { @@ -399,13 +398,13 @@ let mixin = { // tslint:disable-next-line:max-line-length assert( `You cannot use a computed property for the component's \`elementId\` (${this}).`, - descriptorFor(this, 'elementId') === undefined + descriptorForProperty(this, 'elementId') === undefined ); // tslint:disable-next-line:max-line-length assert( `You cannot use a computed property for the component's \`tagName\` (${this}).`, - descriptorFor(this, 'tagName') === undefined + descriptorForProperty(this, 'tagName') === undefined ); if (!this.elementId && this.tagName !== '') { diff --git a/packages/@ember/controller/index.js b/packages/@ember/controller/index.js index f4e09d9f850..f31947f957b 100644 --- a/packages/@ember/controller/index.js +++ b/packages/@ember/controller/index.js @@ -1,6 +1,6 @@ import { Object as EmberObject } from '@ember/-internals/runtime'; import ControllerMixin from './lib/controller_mixin'; -import { InjectedProperty } from '@ember/-internals/metal'; +import { inject as metalInject } from '@ember/-internals/metal'; /** @module @ember/controller @@ -44,7 +44,7 @@ const Controller = EmberObject.extend(ControllerMixin); @public */ export function inject(name, options) { - return new InjectedProperty('controller', name, options); + return metalInject('controller', name, options); } export default Controller; diff --git a/packages/@ember/object/lib/computed/computed_macros.js b/packages/@ember/object/lib/computed/computed_macros.js index 7f3a11fe2b6..f904bfa07fa 100644 --- a/packages/@ember/object/lib/computed/computed_macros.js +++ b/packages/@ember/object/lib/computed/computed_macros.js @@ -2,7 +2,6 @@ import { get, set, computed, - ComputedProperty, isEmpty, isNone, alias, @@ -38,21 +37,18 @@ function generateComputedWithPredicate(name, predicate) { return (...properties) => { let dependentKeys = expandPropertiesToArray(name, properties); - let computedFunc = new ComputedProperty( - function() { - let lastIdx = dependentKeys.length - 1; + let computedFunc = computed(...dependentKeys, function() { + let lastIdx = dependentKeys.length - 1; - for (let i = 0; i < lastIdx; i++) { - let value = get(this, dependentKeys[i]); - if (!predicate(value)) { - return value; - } + for (let i = 0; i < lastIdx; i++) { + let value = get(this, dependentKeys[i]); + if (!predicate(value)) { + return value; } + } - return get(this, dependentKeys[lastIdx]); - }, - { dependentKeys } - ); + return get(this, dependentKeys[lastIdx]); + }); return computedFunc; }; diff --git a/packages/@ember/object/lib/computed/reduce_computed_macros.js b/packages/@ember/object/lib/computed/reduce_computed_macros.js index 4f5a864bd40..63aeef51ac7 100644 --- a/packages/@ember/object/lib/computed/reduce_computed_macros.js +++ b/packages/@ember/object/lib/computed/reduce_computed_macros.js @@ -3,13 +3,7 @@ */ import { DEBUG } from '@glimmer/env'; import { assert } from '@ember/debug'; -import { - get, - computed, - ComputedProperty, - addObserver, - removeObserver, -} from '@ember/-internals/metal'; +import { get, computed, addObserver, removeObserver } from '@ember/-internals/metal'; import { compare, isArray, A as emberA, uniqBy as uniqByArray } from '@ember/-internals/runtime'; function reduceMacro(dependentKey, callback, initialValue, name) { @@ -18,18 +12,13 @@ function reduceMacro(dependentKey, callback, initialValue, name) { !/[\[\]\{\}]/g.test(dependentKey) ); - let cp = new ComputedProperty( - function() { - let arr = get(this, dependentKey); - if (arr === null || typeof arr !== 'object') { - return initialValue; - } - return arr.reduce(callback, initialValue, this); - }, - { dependentKeys: [`${dependentKey}.[]`], readOnly: true } - ); - - return cp; + return computed(`${dependentKey}.[]`, function() { + let arr = get(this, dependentKey); + if (arr === null || typeof arr !== 'object') { + return initialValue; + } + return arr.reduce(callback, initialValue, this); + }).readOnly(); } function arrayMacro(dependentKey, additionalDependentKeys, callback) { @@ -59,14 +48,9 @@ function multiArrayMacro(_dependentKeys, callback, name) { ); let dependentKeys = _dependentKeys.map(key => `${key}.[]`); - let cp = new ComputedProperty( - function() { - return emberA(callback.call(this, _dependentKeys)); - }, - { dependentKeys, readOnly: true } - ); - - return cp; + return computed(...dependentKeys, function() { + return emberA(callback.call(this, _dependentKeys)); + }).readOnly(); } /** @@ -579,15 +563,10 @@ export function uniqBy(dependentKey, propertyKey) { !/[\[\]\{\}]/g.test(dependentKey) ); - let cp = new ComputedProperty( - function() { - let list = get(this, dependentKey); - return isArray(list) ? uniqByArray(list, propertyKey) : emberA(); - }, - { dependentKeys: [`${dependentKey}.[]`], readOnly: true } - ); - - return cp; + return computed(`${dependentKey}.[]`, function() { + let list = get(this, dependentKey); + return isArray(list) ? uniqByArray(list, propertyKey) : emberA(); + }).readOnly(); } /** @@ -737,27 +716,19 @@ export function setDiff(setAProperty, setBProperty) { !/[\[\]\{\}]/g.test(setAProperty) && !/[\[\]\{\}]/g.test(setBProperty) ); - let cp = new ComputedProperty( - function() { - let setA = this.get(setAProperty); - let setB = this.get(setBProperty); + return computed(`${setAProperty}.[]`, `${setBProperty}.[]`, function() { + let setA = this.get(setAProperty); + let setB = this.get(setBProperty); - if (!isArray(setA)) { - return emberA(); - } - if (!isArray(setB)) { - return emberA(setA); - } - - return setA.filter(x => setB.indexOf(x) === -1); - }, - { - dependentKeys: [`${setAProperty}.[]`, `${setBProperty}.[]`], - readOnly: true, + if (!isArray(setA)) { + return emberA(); + } + if (!isArray(setB)) { + return emberA(setA); } - ); - return cp; + return setA.filter(x => setB.indexOf(x) === -1); + }).readOnly(); } /** @@ -981,68 +952,59 @@ function customSort(itemsKey, additionalDependentKeys, comparator) { // This one needs to dynamically set up and tear down observers on the itemsKey // depending on the sortProperties function propertySort(itemsKey, sortPropertiesKey) { - let cp = new ComputedProperty( - function(key) { - let sortProperties = get(this, sortPropertiesKey); - - assert( - `The sort definition for '${key}' on ${this} must be a function or an array of strings`, - isArray(sortProperties) && sortProperties.every(s => typeof s === 'string') - ); - - // Add/remove property observers as required. - let activeObserversMap = cp._activeObserverMap || (cp._activeObserverMap = new WeakMap()); - let activeObservers = activeObserversMap.get(this); - - let sortPropertyDidChangeMap = - cp._sortPropertyDidChangeMap || (cp._sortPropertyDidChangeMap = new WeakMap()); - - if (!sortPropertyDidChangeMap.has(this)) { - sortPropertyDidChangeMap.set(this, function() { - this.notifyPropertyChange(key); - }); - } + let activeObserversMap = new WeakMap(); + let sortPropertyDidChangeMap = new WeakMap(); - let sortPropertyDidChange = sortPropertyDidChangeMap.get(this); + return computed(`${sortPropertiesKey}.[]`, function(key) { + let sortProperties = get(this, sortPropertiesKey); - if (activeObservers !== undefined) { - activeObservers.forEach(path => removeObserver(this, path, sortPropertyDidChange)); - } + assert( + `The sort definition for '${key}' on ${this} must be a function or an array of strings`, + isArray(sortProperties) && sortProperties.every(s => typeof s === 'string') + ); - let itemsKeyIsAtThis = itemsKey === '@this'; - let normalizedSortProperties = normalizeSortProperties(sortProperties); - if (normalizedSortProperties.length === 0) { - let path = itemsKeyIsAtThis ? `[]` : `${itemsKey}.[]`; - addObserver(this, path, sortPropertyDidChange); - activeObservers = [path]; - } else { - activeObservers = normalizedSortProperties.map(([prop]) => { - let path = itemsKeyIsAtThis ? `@each.${prop}` : `${itemsKey}.@each.${prop}`; - addObserver(this, path, sortPropertyDidChange); - return path; - }); - } + // Add/remove property observers as required. + let activeObservers = activeObserversMap.get(this); - activeObserversMap.set(this, activeObservers); + if (!sortPropertyDidChangeMap.has(this)) { + sortPropertyDidChangeMap.set(this, function() { + this.notifyPropertyChange(key); + }); + } - let items = itemsKeyIsAtThis ? this : get(this, itemsKey); - if (!isArray(items)) { - return emberA(); - } + let sortPropertyDidChange = sortPropertyDidChangeMap.get(this); - if (normalizedSortProperties.length === 0) { - return emberA(items.slice()); - } else { - return sortByNormalizedSortProperties(items, normalizedSortProperties); - } - }, - { dependentKeys: [`${sortPropertiesKey}.[]`], readOnly: true } - ); + if (activeObservers !== undefined) { + activeObservers.forEach(path => removeObserver(this, path, sortPropertyDidChange)); + } + + let itemsKeyIsAtThis = itemsKey === '@this'; + let normalizedSortProperties = normalizeSortProperties(sortProperties); + if (normalizedSortProperties.length === 0) { + let path = itemsKeyIsAtThis ? `[]` : `${itemsKey}.[]`; + addObserver(this, path, sortPropertyDidChange); + activeObservers = [path]; + } else { + activeObservers = normalizedSortProperties.map(([prop]) => { + let path = itemsKeyIsAtThis ? `@each.${prop}` : `${itemsKey}.@each.${prop}`; + addObserver(this, path, sortPropertyDidChange); + return path; + }); + } - cp._activeObserverMap = undefined; - cp._sortPropertyDidChangeMap = undefined; + activeObserversMap.set(this, activeObservers); - return cp; + let items = itemsKeyIsAtThis ? this : get(this, itemsKey); + if (!isArray(items)) { + return emberA(); + } + + if (normalizedSortProperties.length === 0) { + return emberA(items.slice()); + } else { + return sortByNormalizedSortProperties(items, normalizedSortProperties); + } + }).readOnly(); } function normalizeSortProperties(sortProperties) { diff --git a/packages/@ember/service/index.js b/packages/@ember/service/index.js index abe9a3ef0fa..90155e2008e 100644 --- a/packages/@ember/service/index.js +++ b/packages/@ember/service/index.js @@ -1,5 +1,5 @@ import { Object as EmberObject } from '@ember/-internals/runtime'; -import { InjectedProperty } from '@ember/-internals/metal'; +import { inject as metalInject } from '@ember/-internals/metal'; /** @module @ember/service @@ -39,7 +39,7 @@ import { InjectedProperty } from '@ember/-internals/metal'; @public */ export function inject(name, options) { - return new InjectedProperty('service', name, options); + return metalInject('service', name, options); } /** diff --git a/packages/ember/index.js b/packages/ember/index.js index 736423efee8..15f3d2a859d 100644 --- a/packages/ember/index.js +++ b/packages/ember/index.js @@ -271,7 +271,6 @@ Object.defineProperty(Ember.run, 'currentRunLoop', { const computed = metal._globalsComputed; Ember.computed = computed; computed.alias = metal.alias; -Ember.ComputedProperty = metal.ComputedProperty; Ember.cacheFor = metal.getCachedValueFor; Ember.meta = meta; Ember.get = metal.get; diff --git a/yarn.lock b/yarn.lock index 5e70d57796a..c97e0d72af6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -40,6 +40,17 @@ source-map "^0.5.0" trim-right "^1.0.1" +"@babel/generator@^7.2.2": + version "7.3.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.0.tgz#f663838cd7b542366de3aa608a657b8ccb2a99eb" + integrity sha512-dZTwMvTgWfhmibq4V9X+LMf6Bgl7zAodRn9PvcPdhlzFMbvUutx74dbEv7Atz3ToeEpevYEJtAwfxq/bDCzHWg== + dependencies: + "@babel/types" "^7.3.0" + jsesc "^2.5.1" + lodash "^4.17.10" + source-map "^0.5.0" + trim-right "^1.0.1" + "@babel/helper-annotate-as-pure@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" @@ -64,6 +75,17 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.0.0" +"@babel/helper-create-class-features-plugin@^7.2.1": + version "7.2.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.2.1.tgz#f6e8027291669ef64433220dc8327531233f1161" + integrity sha512-EsEP7XLFmcJHjcuFYBxYD1FkP0irC8C9fsrt2tX/jrAi/eTnFI6DOPgVFb+WREeg1GboF+Ib+nCHbGBodyAXSg== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-member-expression-to-functions" "^7.0.0" + "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.1.0" + "@babel/helper-define-map@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz#3b74caec329b3c80c116290887c0dd9ae468c20c" @@ -213,6 +235,11 @@ esutils "^2.0.2" js-tokens "^4.0.0" +"@babel/parser@^7.0.0", "@babel/parser@^7.2.3": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.1.tgz#8f4ffd45f779e6132780835ffa7a215fa0b2d181" + integrity sha512-ATz6yX/L8LEnC3dtLQnIx4ydcPxhLcoy9Vl6re00zb2w5lG6itY6Vhnr1KFRPq/FHNsgl/gh2mjNN20f9iJTTA== + "@babel/parser@^7.1.2", "@babel/parser@^7.1.6", "@babel/parser@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.2.0.tgz#02d01dbc330b6cbf36b76ac93c50752c69027065" @@ -227,6 +254,24 @@ "@babel/helper-remap-async-to-generator" "^7.1.0" "@babel/plugin-syntax-async-generators" "^7.2.0" +"@babel/plugin-proposal-class-properties@^7.2.1": + version "7.2.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.2.1.tgz#c734a53e0a1ec40fe5c22ee5069d26da3b187d05" + integrity sha512-/4FKFChkQ2Jgb8lBDsvFX496YTi7UWTetVgS8oJUpX1e/DlaoeEK57At27ug8Hu2zI2g8bzkJ+8k9qrHZRPGPA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.2.1" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-proposal-decorators@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.2.0.tgz#6b4278282a6f5dd08b5d89b94f21aa1671fea071" + integrity sha512-yrDmvCsOMvNPpjCC6HMseiac2rUuQdeNqUyPU+3QbW7gLg/APX0c/7l9i/aulSICJQOkP6/4EHxkcB4d4DqZhg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + "@babel/plugin-syntax-decorators" "^7.2.0" + "@babel/plugin-proposal-json-strings@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317" @@ -267,6 +312,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-syntax-decorators@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz#c50b1b957dcc69e4b1127b65e1c33eef61570c1b" + integrity sha512-38QdqVoXdHUQfTpZo3rQwqQdWtCn5tMv4uV6r2RMfTqNBuv4ZBhz79SfaQWKTVmxHjeFv/DnXVC/+agHCklYWA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-json-strings@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz#72bd13f6ffe1d25938129d2a186b11fd62951470" @@ -591,6 +643,21 @@ "@babel/parser" "^7.1.2" "@babel/types" "^7.1.2" +"@babel/traverse@^7.0.0": + version "7.2.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8" + integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.2.2" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + "@babel/parser" "^7.2.3" + "@babel/types" "^7.2.2" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.10" + "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.1.6": version "7.1.6" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.1.6.tgz#c8db9963ab4ce5b894222435482bd8ea854b7b5c" @@ -615,6 +682,15 @@ lodash "^4.17.10" to-fast-properties "^2.0.0" +"@babel/types@^7.2.2", "@babel/types@^7.3.0": + version "7.3.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.0.tgz#61dc0b336a93badc02bf5f69c4cd8e1353f2ffc0" + integrity sha512-QkFPw68QqWU1/RVPyBe8SO7lXbPfjtqAxRYQKpFpaB8yMq7X2qAqfwK5LKoQufEkSmO5NQ70O6Kc3Afk03RwXw== + dependencies: + esutils "^2.0.2" + lodash "^4.17.10" + to-fast-properties "^2.0.0" + "@glimmer/compiler@^0.37.1": version "0.37.1" resolved "https://registry.yarnpkg.com/@glimmer/compiler/-/compiler-0.37.1.tgz#a3d59e0b1e51341314d3f8202aa0d4b70dc55173" @@ -1156,6 +1232,18 @@ babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: esutils "^2.0.2" js-tokens "^3.0.2" +babel-eslint@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed" + integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.0.0" + "@babel/traverse" "^7.0.0" + "@babel/types" "^7.0.0" + eslint-scope "3.7.1" + eslint-visitor-keys "^1.0.0" + babel-messages@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" @@ -3246,6 +3334,14 @@ eslint-plugin-qunit@^4.0.0: resolved "https://registry.yarnpkg.com/eslint-plugin-qunit/-/eslint-plugin-qunit-4.0.0.tgz#5945ba3434bfe8879bea195192e906701051cf01" integrity sha512-+0i2xcYryUoLawi47Lp0iJKzkP931G5GXwIOq1KBKQc2pknV1VPjfE6b4mI2mR2RnL7WRoS30YjwC9SjQgJDXQ== +eslint-scope@3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug= + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + eslint-scope@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172"