diff --git a/.eslintrc.js b/.eslintrc.js index acd91be167d..78e38e6f2d7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,7 +2,6 @@ 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 3c1eb53edd6..e250e1a30c5 100644 --- a/broccoli/packages.js +++ b/broccoli/packages.js @@ -16,7 +16,6 @@ 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'); @@ -84,13 +83,6 @@ 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'], }); @@ -103,7 +95,7 @@ module.exports.getPackagesES = function getPackagesES() { let debuggedCompiledTypescript = debugTree(typescriptCompiled, `get-packages-es:ts:output`); - let mergedFinalOutput = new MergeTrees([transpiledProposals, debuggedCompiledTypescript], { + let mergedFinalOutput = new MergeTrees([nonTypeScriptContents, debuggedCompiledTypescript], { overwrite: true, }); diff --git a/broccoli/transforms/transform-babel-plugins.js b/broccoli/transforms/transform-babel-plugins.js deleted file mode 100644 index e0e1b63dc59..00000000000 --- a/broccoli/transforms/transform-babel-plugins.js +++ /dev/null @@ -1,13 +0,0 @@ -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/package.json b/package.json index 035730ff4e0..21ece75de8c 100644 --- a/package.json +++ b/package.json @@ -77,8 +77,6 @@ }, "devDependencies": { "@babel/helper-module-imports": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.3.3", - "@babel/plugin-proposal-decorators": "^7.3.0", "@babel/plugin-transform-arrow-functions": "^7.2.0", "@babel/plugin-transform-block-scoping": "^7.2.0", "@babel/plugin-transform-classes": "^7.3.3", @@ -103,7 +101,6 @@ "@types/rsvp": "^4.0.2", "auto-dist-tag": "^1.0.0", "aws-sdk": "^2.404.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.2.0", diff --git a/packages/@ember/-internals/meta/index.ts b/packages/@ember/-internals/meta/index.ts index 11ebd60739c..2083134cd48 100644 --- a/packages/@ember/-internals/meta/index.ts +++ b/packages/@ember/-internals/meta/index.ts @@ -1,6 +1,8 @@ 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 bba6281a4d7..c26278813b7 100644 --- a/packages/@ember/-internals/meta/lib/meta.ts +++ b/packages/@ember/-internals/meta/lib/meta.ts @@ -965,6 +965,48 @@ 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 145300fe99c..0733389a964 100644 --- a/packages/@ember/-internals/metal/index.ts +++ b/packages/@ember/-internals/metal/index.ts @@ -1,4 +1,4 @@ -export { default as computed, _globalsComputed, ComputedProperty } from './lib/computed'; +export { default as computed, ComputedProperty, _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,13 +28,7 @@ export { overrideChains, PROPERTY_DID_CHANGE, } from './lib/property_events'; -export { defineProperty } from './lib/properties'; -export { nativeDescDecorator } from './lib/decorator'; -export { - descriptorForProperty, - isComputedDecorator, - setComputedDecorator, -} from './lib/descriptor_map'; +export { defineProperty, Descriptor } from './lib/properties'; export { watchKey, unwatchKey } from './lib/watch_key'; export { ChainNode, finishChains, removeChainWatcher } from './lib/chains'; export { watchPath, unwatchPath } from './lib/watch_path'; @@ -46,10 +40,11 @@ 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 inject, DEBUG_INJECTION_FUNCTIONS } from './lib/injected_property'; +export { default as InjectedProperty } from './lib/injected_property'; export { setHasViews, tagForProperty, tagFor, markObjectAsDirty } from './lib/tags'; export { default as runInTransaction, didRender, assertNotRendered } from './lib/transaction'; -export { tracked, getCurrentTracker, setCurrentTracker } from './lib/tracked'; +export { default as descriptor } from './lib/descriptor'; +export { tracked } from './lib/tracked'; export { NAMESPACES, diff --git a/packages/@ember/-internals/metal/lib/alias.ts b/packages/@ember/-internals/metal/lib/alias.ts index a985f8c5429..b8f3b6cdb1e 100644 --- a/packages/@ember/-internals/metal/lib/alias.ts +++ b/packages/@ember/-internals/metal/lib/alias.ts @@ -2,51 +2,25 @@ 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, - ComputedDescriptor, - Decorator, - makeComputedDecorator, + DescriptorWithDependentKeys, removeDependentKeys, -} from './decorator'; -import { descriptorForDecorator } from './descriptor_map'; -import { defineProperty } from './properties'; +} from './dependent_keys'; +import { defineProperty, Descriptor } from './properties'; import { get } from './property_get'; import { set } from './property_set'; const CONSUMED = Object.freeze({}); -export type AliasDecorator = Decorator & PropertyDecorator & AliasDecoratorImpl; - -export default function alias(altKey: string): AliasDecorator { - return makeComputedDecorator(new AliasedProperty(altKey), AliasDecoratorImpl) as AliasDecorator; -} - -// TODO: This class can be svelted once `meta` has been deprecated -class AliasDecoratorImpl extends Function { - readOnly(this: Decorator) { - (descriptorForDecorator(this) as AliasedProperty).readOnly(); - return this; - } - - oneWay(this: Decorator) { - (descriptorForDecorator(this) as AliasedProperty).oneWay(); - return this; - } - - meta(this: Decorator, meta?: any): any { - let prop = descriptorForDecorator(this) as AliasedProperty; - - if (arguments.length === 0) { - return prop._meta || {}; - } else { - prop._meta = meta; - } - } +export default function alias(altKey: string): AliasedProperty { + return new AliasedProperty(altKey); } -export class AliasedProperty extends ComputedDescriptor { +export class AliasedProperty extends Descriptor implements DescriptorWithDependentKeys { + readonly _dependentKeys: string[]; readonly altKey: string; constructor(altKey: string) { @@ -55,10 +29,9 @@ export class AliasedProperty extends ComputedDescriptor { this._dependentKeys = [altKey]; } - setup(obj: object, keyName: string, propertyDesc: PropertyDescriptor, meta: Meta): void { + setup(obj: object, keyName: string, meta: Meta): void { assert(`Setting alias '${keyName}' on self`, this.altKey !== keyName); - super.setup(obj, keyName, propertyDesc, meta); - + super.setup(obj, keyName, meta); if (meta.peekWatching(keyName) > 0) { this.consume(obj, keyName, meta); } @@ -101,12 +74,14 @@ export class AliasedProperty extends ComputedDescriptor { return set(obj, this.altKey, value); } - readOnly(): void { + readOnly(): this { this.set = AliasedProperty_readOnlySet; + return this; } - oneWay(): void { + oneWay(): this { this.set = AliasedProperty_oneWaySet; + return this; } } @@ -119,3 +94,7 @@ 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 feb1cc5ae92..9a2e65383f9 100644 --- a/packages/@ember/-internals/metal/lib/chains.ts +++ b/packages/@ember/-internals/metal/lib/chains.ts @@ -1,6 +1,5 @@ -import { Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; +import { descriptorFor, Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; import { getCachedValueFor } from './computed_cache'; -import { descriptorForProperty } from './descriptor_map'; import { eachProxyFor } from './each_proxy'; import { get } from './property_get'; import { unwatchKey, watchKey } from './watch_key'; @@ -10,7 +9,7 @@ function isObject(obj: any): obj is object { } function isVolatile(obj: any, keyName: string, meta?: Meta | null): boolean { - let desc = descriptorForProperty(obj, keyName, meta); + let desc = descriptorFor(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 ea6698a29a0..9b9b50dc03a 100644 --- a/packages/@ember/-internals/metal/lib/computed.ts +++ b/packages/@ember/-internals/metal/lib/computed.ts @@ -1,9 +1,6 @@ -import { Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; +import { meta as metaFor, peekMeta } from '@ember/-internals/meta'; import { inspect, toString } from '@ember/-internals/utils'; -import { - EMBER_METAL_TRACKED_PROPERTIES, - EMBER_NATIVE_DECORATOR_SUPPORT, -} from '@ember/canary-features'; +import { EMBER_METAL_TRACKED_PROPERTIES } from '@ember/canary-features'; import { assert, deprecate, warn } from '@ember/debug'; import EmberError from '@ember/error'; import { @@ -15,23 +12,18 @@ import { } from './computed_cache'; import { addDependentKeys, - ComputedDescriptor, - Decorator, - ElementDescriptor, - isElementDescriptor, - makeComputedDecorator, + DescriptorWithDependentKeys, removeDependentKeys, -} from './decorator'; -import { descriptorForDecorator, isComputedDecorator } from './descriptor_map'; +} from './dependent_keys'; import expandProperties from './expand_properties'; -import { defineProperty } from './properties'; +import { defineProperty, Descriptor } 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, cachedValue?: any) => any; +export type ComputedPropertySetter = (keyName: string, value: any) => any; export interface ComputedPropertyGetterAndSetter { get?: ComputedPropertyGetter; @@ -39,6 +31,11 @@ export interface ComputedPropertyGetterAndSetter { } export type ComputedPropertyConfig = ComputedPropertyGetter | ComputedPropertyGetterAndSetter; +export interface ComputedPropertyOptions { + dependentKeys?: string[]; + readOnly?: boolean; +} + /** @module @ember/object */ @@ -46,6 +43,7 @@ export type ComputedPropertyConfig = ComputedPropertyGetter | ComputedPropertyGe const DEEP_EACH_REGEX = /\.@each\.[^.]+\./; function noop(): void {} + /** A computed property transforms an object literal with object's accessor function(s) into a property. @@ -155,136 +153,77 @@ function noop(): void {} @class ComputedProperty @public */ -export class ComputedProperty extends ComputedDescriptor { - private _volatile = false; - private _readOnly = false; - private _suspended: any = undefined; - private _hasConfig = false; - - _getter?: ComputedPropertyGetter = undefined; - _setter?: ComputedPropertySetter = undefined; +class ComputedProperty extends Descriptor implements DescriptorWithDependentKeys { + private _meta: any | undefined; + private _volatile: boolean; + private _readOnly: boolean; + private _suspended: any; + _getter: ComputedPropertyGetter; + _setter?: ComputedPropertySetter; _auto?: boolean; + _dependentKeys: string[] | undefined; - constructor(args: Array) { + constructor(config: ComputedPropertyConfig, opts?: ComputedPropertyOptions) { super(); - - 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') { - assert( - `You attempted to pass a computed property instance to computed(). Computed property instances are decorator functions, and cannot be passed to computed() because they cannot be turned into decorators twice`, - !isComputedDecorator(config) - ); - - 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 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; } - if (args.length > 0) { - this._property(...(args as string[])); - } + this._suspended = undefined; + this._meta = undefined; + this._volatile = false; 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') - ) - ); - - 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; - }; - } - } + this._dependentKeys = opts && opts.dependentKeys; + this._readOnly = Boolean(opts) && hasGetterOnly && opts!.readOnly === true; } /** 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(): void { + volatile(): ComputedProperty { deprecate( 'Setting a computed property as volatile has been deprecated. Instead, consider using a native getter with native class syntax.', false, @@ -296,59 +235,72 @@ export class ComputedProperty extends ComputedDescriptor { ); 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(): void { + readOnly(): ComputedProperty { 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[]): void { + property(...passedArgs: string[]): ComputedProperty { 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, @@ -360,9 +312,10 @@ export class ComputedProperty extends ComputedDescriptor { ); this._property(...passedArgs); + return this; } - _property(...passedArgs: string[]): void { + _property(...passedArgs: string[]): ComputedProperty { let args: string[] = []; function addArg(property: string): void { @@ -381,6 +334,7 @@ export class ComputedProperty extends ComputedDescriptor { } this._dependentKeys = args; + return this; } /** @@ -388,24 +342,37 @@ export class ComputedProperty extends ComputedDescriptor { 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 didChange(obj: object, keyName: string): void { @@ -429,7 +396,7 @@ export class ComputedProperty extends ComputedDescriptor { 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); @@ -464,7 +431,7 @@ export class ComputedProperty extends ComputedDescriptor { tracker = setCurrentTracker(); } - let ret = this._getter!.call(obj, keyName); + let ret = this._getter.call(obj, keyName); if (EMBER_METAL_TRACKED_PROPERTIES) { setCurrentTracker(parent!); @@ -590,41 +557,6 @@ if (EMBER_METAL_TRACKED_PROPERTIES) { }; } -export type ComputedDecorator = Decorator & PropertyDecorator & ComputedDecoratorImpl; - -// TODO: This class can be svelted once `meta` has been deprecated -class ComputedDecoratorImpl extends Function { - readOnly(this: Decorator) { - (descriptorForDecorator(this) as ComputedProperty).readOnly(); - return this; - } - - volatile(this: Decorator) { - (descriptorForDecorator(this) as ComputedProperty).volatile(); - return this; - } - - property(this: Decorator, ...keys: string[]) { - (descriptorForDecorator(this) as ComputedProperty).property(...keys); - return this; - } - - meta(this: Decorator, meta?: any): any { - let prop = descriptorForDecorator(this) as ComputedProperty; - - if (arguments.length === 0) { - return prop._meta || {}; - } else { - prop._meta = meta; - return this; - } - } - - set enumerable(this: Decorator, value: boolean) { - (descriptorForDecorator(this) as ComputedProperty).enumerable = value; - } -} - /** This helper returns a new property descriptor that wraps the passed computed property function. You can use this helper to define properties @@ -710,36 +642,21 @@ class ComputedDecoratorImpl extends Function { @static @param {String} [dependentKeys*] Optional dependent keys that trigger this computed property. @param {Function} func The computed property function. - @return {ComputedDecorator} property decorator instance + @return {ComputedProperty} property descriptor instance @public */ -export function computed(elementDesc: ElementDescriptor): ElementDescriptor; -export function computed(...args: (string | ComputedPropertyConfig)[]): ComputedDecorator; -export function computed( - ...args: (string | ComputedPropertyConfig | ElementDescriptor)[] -): ComputedDecorator | ElementDescriptor { - let firstArg = args[0]; - - if (isElementDescriptor(firstArg)) { - assert( - 'Native decorators are not enabled without the EMBER_NATIVE_DECORATOR_SUPPORT flag. If you are using computed in a classic class, add parenthesis to it: computed()', - Boolean(EMBER_NATIVE_DECORATOR_SUPPORT) - ); +export default function computed(...args: (string | ComputedPropertyConfig)[]): ComputedProperty { + let func = args.pop(); - let decorator = makeComputedDecorator( - new ComputedProperty([]), - ComputedDecoratorImpl - ) as ComputedDecorator; + let cp = new ComputedProperty(func as ComputedPropertyConfig); - return decorator(firstArg); + if (args.length > 0) { + cp._property(...(args as string[])); } - return makeComputedDecorator( - new ComputedProperty(args as (string | ComputedPropertyConfig)[]), - ComputedDecoratorImpl - ) as ComputedDecorator; + return cp; } - +// used for the Ember.computed global only export const _globalsComputed = computed.bind(null); -export default computed; +export { ComputedProperty, computed }; diff --git a/packages/@ember/-internals/metal/lib/decorator.ts b/packages/@ember/-internals/metal/lib/decorator.ts deleted file mode 100644 index 9cfc5acfcce..00000000000 --- a/packages/@ember/-internals/metal/lib/decorator.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { Meta, meta as metaFor } from '@ember/-internals/meta'; -import { EMBER_NATIVE_DECORATOR_SUPPORT } from '@ember/canary-features'; -import { assert } from '@ember/debug'; -import { DEBUG } from '@glimmer/env'; -import { setComputedDecorator } from './descriptor_map'; -import { unwatch, watch } from './watching'; - -// https://tc39.github.io/proposal-decorators/#sec-elementdescriptor-specification-type -export interface ElementDescriptor { - descriptor: PropertyDescriptor & { initializer?: any }; - key: string; - kind: 'method' | 'field' | 'initializer'; - placement: 'own' | 'prototype' | 'static'; - initializer?: () => any; - finisher?: (obj: object, meta?: Meta) => any; -} - -export type Decorator = ( - desc: ElementDescriptor, - isClassicDecorator?: boolean -) => ElementDescriptor; - -export function isElementDescriptor(maybeDesc: any): maybeDesc is ElementDescriptor { - return ( - maybeDesc !== undefined && - typeof maybeDesc.toString === 'function' && - maybeDesc.toString() === '[object Descriptor]' - ); -} - -// .......................................................... -// 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; -} - -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, - isClassicDecorator?: boolean - ): ElementDescriptor { - let { key, descriptor: propertyDesc } = elementDesc; - - if (DEBUG) { - // Store the initializer for assertions - propertyDesc.initializer = elementDesc.initializer; - } - - assert( - 'Native decorators are not enabled without the EMBER_NATIVE_DECORATOR_SUPPORT flag', - EMBER_NATIVE_DECORATOR_SUPPORT || isClassicDecorator - ); - - assert( - `Only one computed property decorator can be applied to a class field or accessor, but '${key}' was decorated twice. You may have added the decorator to both a getter and setter, which is unecessary.`, - isClassicDecorator || - !propertyDesc.get || - propertyDesc.get.toString().indexOf('CPGETTER_FUNCTION') === -1 - ); - - elementDesc.kind = 'method'; - elementDesc.descriptor = { - enumerable: desc.enumerable, - configurable: desc.configurable, - get: DESCRIPTOR_GETTER_FUNCTION(elementDesc.key, desc), - }; - - elementDesc.finisher = function(klass: any, _meta?: Meta) { - let obj = klass.prototype !== undefined ? klass.prototype : klass; - let meta = arguments.length === 1 ? metaFor(obj) : _meta; - - desc.setup(obj, key, propertyDesc, meta!); - }; - - return elementDesc; - }; - - setComputedDecorator(decorator, desc); - - 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 e69de29bb2d..8e4ee34de85 100644 --- a/packages/@ember/-internals/metal/lib/dependent_keys.ts +++ b/packages/@ember/-internals/metal/lib/dependent_keys.ts @@ -0,0 +1,54 @@ +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 new file mode 100644 index 00000000000..1df6707818e --- /dev/null +++ b/packages/@ember/-internals/metal/lib/descriptor.ts @@ -0,0 +1,38 @@ +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/descriptor_map.ts b/packages/@ember/-internals/metal/lib/descriptor_map.ts deleted file mode 100644 index b228e0646a6..00000000000 --- a/packages/@ember/-internals/metal/lib/descriptor_map.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Meta, peekMeta } from '@ember/-internals/meta'; -import { assert } from '@ember/debug'; - -const DECORATOR_DESCRIPTOR_MAP: WeakMap< - import('./decorator').Decorator, - import('./decorator').ComputedDescriptor | boolean -> = new WeakMap(); - -/** - 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: import('./decorator').Decorator) { - return DECORATOR_DESCRIPTOR_MAP.get(dec); -} - -/** - Check whether a value is a decorator - - @method isComputedDecorator - @param {any} possibleDesc the value to check - @return {boolean} - @private -*/ -export function isComputedDecorator(dec: any) { - return dec !== null && dec !== undefined && DECORATOR_DESCRIPTOR_MAP.has(dec); -} - -/** - Set a value as a decorator - - @method setComputedDecorator - @param {function} decorator the value to mark as a decorator - @private -*/ -export function setComputedDecorator(dec: import('./decorator').Decorator, value: any = true) { - DECORATOR_DESCRIPTOR_MAP.set(dec, value); -} diff --git a/packages/@ember/-internals/metal/lib/injected_property.ts b/packages/@ember/-internals/metal/lib/injected_property.ts index ed869729239..ee8cc64a90b 100644 --- a/packages/@ember/-internals/metal/lib/injected_property.ts +++ b/packages/@ember/-internals/metal/lib/injected_property.ts @@ -1,17 +1,10 @@ +import { descriptorFor } from '@ember/-internals/meta'; import { getOwner } from '@ember/-internals/owner'; -import { EMBER_MODULE_UNIFICATION, EMBER_NATIVE_DECORATOR_SUPPORT } from '@ember/canary-features'; +import { EMBER_MODULE_UNIFICATION } from '@ember/canary-features'; import { assert } from '@ember/debug'; -import { DEBUG } from '@glimmer/env'; -import { computed } from './computed'; -import { ElementDescriptor, isElementDescriptor } from './decorator'; +import { ComputedProperty } from './computed'; import { defineProperty } from './properties'; -export let DEBUG_INJECTION_FUNCTIONS: WeakMap; - -if (DEBUG) { - DEBUG_INJECTION_FUNCTIONS = new WeakMap(); -} - export interface InjectedPropertyOptions { source: string; } @@ -28,70 +21,62 @@ export interface InjectedPropertyOptions { @namespace Ember @constructor @param {String} type The container type the property will lookup - @param {String} nameOrDesc (optional) The name the property will lookup, defaults + @param {String} name (optional) The name the property will lookup, defaults to the property's name @private */ -export default function inject( - type: string, - nameOrDesc: string | ElementDescriptor, - options?: InjectedPropertyOptions -) { - assert('a string type must be provided to inject', typeof type === 'string'); - - let source: string | undefined, namespace: string | undefined; - let name = typeof nameOrDesc === 'string' ? nameOrDesc : undefined; - - if (EMBER_MODULE_UNIFICATION) { - source = options ? options.source : undefined; - namespace = undefined; - - if (name !== undefined) { - let namespaceDelimiterOffset = name.indexOf('::'); - - if (namespaceDelimiterOffset !== -1) { - namespace = name.slice(0, namespaceDelimiterOffset); - name = name.slice(namespaceDelimiterOffset + 2); +export default class InjectedProperty extends ComputedProperty { + readonly type: string; + readonly name: string; + readonly source: string | undefined; + readonly namespace: string | undefined; + + constructor(type: string, name: string, options?: InjectedPropertyOptions) { + super(injectedPropertyDesc); + + this.type = type; + this.name = name; + + 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); + } } } } +} - let getInjection = function(this: any, propertyName: string) { +const injectedPropertyDesc = { + get(this: any, keyName: string): any { + let desc = descriptorFor(this, keyName); 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) ); - return owner.lookup(`${type}:${name || propertyName}`, { source, namespace }); - }; - - if (DEBUG) { - DEBUG_INJECTION_FUNCTIONS.set(getInjection, { - namespace, - source, - type, - name, + let specifier = `${desc.type}:${desc.name || keyName}`; + return owner.lookup(specifier, { + source: desc.source, + namespace: desc.namespace, }); - } - - let decorator = computed({ - get: getInjection, + }, - set(this: any, keyName: string, value: any) { - defineProperty(this, keyName, null, value); - }, - }); - - if (isElementDescriptor(nameOrDesc)) { - assert( - 'Native decorators are not enabled without the EMBER_NATIVE_DECORATOR_SUPPORT flag. If you are using inject in a classic class, add parenthesis to it: inject()', - Boolean(EMBER_NATIVE_DECORATOR_SUPPORT) - ); - - return decorator(nameOrDesc); - } else { - return decorator; - } -} + 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 8d1326c0e1c..ab374685281 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 { Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; +import { descriptorFor, Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; import { getListeners, getObservers, @@ -16,23 +16,12 @@ import { assert, deprecate } from '@ember/debug'; import { ALIAS_METHOD } from '@ember/deprecated-features'; import { assign } from '@ember/polyfills'; import { DEBUG } from '@glimmer/env'; -import { - ComputedDecorator, - ComputedProperty, - ComputedPropertyGetter, - ComputedPropertySetter, -} from './computed'; -import { makeComputedDecorator } from './decorator'; -import { - descriptorForDecorator, - descriptorForProperty, - isComputedDecorator, -} from './descriptor_map'; +import { ComputedProperty, ComputedPropertyGetter, ComputedPropertySetter } from './computed'; import { addListener, removeListener } from './events'; import expandProperties from './expand_properties'; import { classToString, setUnprocessedMixins } from './namespace_search'; import { addObserver, removeObserver } from './observer'; -import { defineProperty } from './properties'; +import { defineProperty, Descriptor } from './properties'; const a_concat = Array.prototype.concat; const { isArray } = Array; @@ -78,68 +67,46 @@ function concatenatedMixinProperties( return concats; } -function giveDecoratorSuper( +function giveDescriptorSuper( meta: Meta, key: string, - decorator: ComputedDecorator, + property: ComputedProperty, values: { [key: string]: any }, descs: { [key: string]: any }, base: object -): ComputedDecorator { - let property = descriptorForDecorator(decorator)!; +): ComputedProperty { let superProperty; - if (!(property instanceof ComputedProperty) || 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 = descriptorForDecorator(descs[key]); + superProperty = descs[key]; } // If we didn't find the original descriptor in a parent mixin, find // it on the original object. if (!superProperty) { - superProperty = descriptorForProperty(base, key, meta); + superProperty = descriptorFor(base, key, meta); } if (superProperty === undefined || !(superProperty instanceof ComputedProperty)) { - return decorator; + return property; } - let get = wrap(property._getter!, superProperty._getter!) as ComputedPropertyGetter; - let set; - + // 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; if (superProperty._setter) { if (property._setter) { - set = wrap(property._setter, superProperty._setter) as ComputedPropertySetter; + property._setter = wrap(property._setter, superProperty._setter) as ComputedPropertySetter; } else { - // 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; + property._setter = superProperty._setter; } - } else { - set = property._setter; } - // 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; + return property; } function giveMethodSuper( @@ -159,7 +126,7 @@ function giveMethodSuper( // If we didn't find the original value in a parent mixin, find it in // the original object - if (superMethod === undefined && descriptorForProperty(obj, key) === undefined) { + if (superMethod === undefined && descriptorFor(obj, key) === undefined) { superMethod = obj[key]; } @@ -239,16 +206,21 @@ function applyMergedProperties( function addNormalizedProperty( base: any, key: string, - value: any, + value: Descriptor | any, meta: Meta, descs: { [key: string]: any }, values: { [key: string]: any }, concats?: string[], mergings?: string[] ): void { - if (isComputedDecorator(value)) { - // Wrap descriptor function to implement _super() if needed - descs[key] = giveDecoratorSuper(meta, key, value, values, descs, base); + 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; values[key] = undefined; } else { if ( @@ -351,7 +323,7 @@ if (ALIAS_METHOD) { if (desc !== undefined || value !== undefined) { // do nothing - } else if ((possibleDesc = descriptorForProperty(obj, altKey)) !== undefined) { + } else if ((possibleDesc = descriptorFor(obj, altKey)) !== undefined) { desc = possibleDesc; value = undefined; } else { @@ -437,7 +409,7 @@ export function applyMixin(obj: { [key: string]: any }, mixins: Mixin[]) { continue; } - if (descriptorForProperty(obj, key) !== undefined) { + if (descriptorFor(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 298d004cc1f..5d0096e3fcb 100644 --- a/packages/@ember/-internals/metal/lib/properties.ts +++ b/packages/@ember/-internals/metal/lib/properties.ts @@ -2,11 +2,9 @@ @module @ember/object */ -import { Meta, meta as metaFor, peekMeta, UNDEFINED } from '@ember/-internals/meta'; +import { descriptorFor, Meta, meta as metaFor, peekMeta, UNDEFINED } from '@ember/-internals/meta'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; -import { Decorator, ElementDescriptor } from './decorator'; -import { descriptorForProperty, isComputedDecorator } from './descriptor_map'; import { overrideChains } from './property_events'; export type MandatorySetterFunction = ((this: object, value: any) => void) & { @@ -17,6 +15,44 @@ 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; } @@ -69,6 +105,12 @@ 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 @@ -120,7 +162,7 @@ export function INHERITING_GETTER_FUNCTION(name: string): InheritingGetterFuncti export function defineProperty( obj: object, keyName: string, - desc?: Decorator | undefined | null, + desc?: Descriptor | undefined | null, data?: any | undefined | null, meta?: Meta ): void { @@ -129,7 +171,7 @@ export function defineProperty( } let watching = meta.peekWatching(keyName) > 0; - let previousDesc = descriptorForProperty(obj, keyName, meta); + let previousDesc = descriptorFor(obj, keyName, meta); let wasDescriptor = previousDesc !== undefined; if (wasDescriptor) { @@ -149,43 +191,9 @@ export function defineProperty( } let value; - if (isComputedDecorator(desc)) { - let elementDesc = { - key: keyName, - kind: 'field', - placement: 'own', - descriptor: { - value: undefined, - }, - toString() { - return '[object Descriptor]'; - }, - } as ElementDescriptor; - - if (DEBUG) { - elementDesc = desc!(elementDesc, true); - } else { - elementDesc = desc!(elementDesc); - } - - 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 + if (desc instanceof Descriptor) { 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 985e7c54b8b..7ab6f8173e7 100644 --- a/packages/@ember/-internals/metal/lib/property_events.ts +++ b/packages/@ember/-internals/metal/lib/property_events.ts @@ -1,8 +1,7 @@ -import { Meta, peekMeta } from '@ember/-internals/meta'; +import { descriptorFor, 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 './descriptor_map'; import { sendEvent } from './events'; import ObserverSet from './observer_set'; import { markObjectAsDirty } from './tags'; @@ -42,7 +41,7 @@ function notifyPropertyChange(obj: object, keyName: string, _meta?: Meta | null) return; } - let possibleDesc = descriptorForProperty(obj, keyName, meta); + let possibleDesc = descriptorFor(obj, keyName, meta); if (possibleDesc !== undefined && typeof possibleDesc.didChange === 'function') { possibleDesc.didChange(obj, keyName); @@ -113,7 +112,7 @@ function iterDeps( let possibleDesc; meta.forEachInDeps(depKey, (key: string) => { - possibleDesc = descriptorForProperty(obj, key, meta); + possibleDesc = descriptorFor(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 a86567d06d2..b22dcbd69c0 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 './descriptor_map'; 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 = descriptorForProperty(obj, keyName); + let descriptor = descriptorFor(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 2e3d6a420aa..e9c874262b9 100644 --- a/packages/@ember/-internals/metal/lib/property_set.ts +++ b/packages/@ember/-internals/metal/lib/property_set.ts @@ -1,9 +1,8 @@ -import { Meta, peekMeta } from '@ember/-internals/meta'; +import { descriptorFor, 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 './descriptor_map'; import { isPath } from './path_cache'; import { MandatorySetterFunction } from './properties'; import { notifyPropertyChange } from './property_events'; @@ -79,7 +78,7 @@ export function set(obj: object, keyName: string, value: any, tolerant?: boolean } let meta = peekMeta(obj); - let descriptor = descriptorForProperty(obj, keyName, meta); + let descriptor = descriptorFor(obj, keyName, meta); if (descriptor !== undefined) { descriptor.set(obj, keyName, value); diff --git a/packages/@ember/-internals/metal/lib/tracked.ts b/packages/@ember/-internals/metal/lib/tracked.ts index d8ef7e0cf4e..3d21d164eb1 100644 --- a/packages/@ember/-internals/metal/lib/tracked.ts +++ b/packages/@ember/-internals/metal/lib/tracked.ts @@ -1,18 +1,14 @@ -import { EMBER_NATIVE_DECORATOR_SUPPORT } from '@ember/canary-features'; -import { assert } from '@ember/debug'; -import { DEBUG } from '@glimmer/env'; import { combine, CONSTANT_TAG, Tag } from '@glimmer/reference'; -import { Decorator, ElementDescriptor } from './decorator'; -import { setComputedDecorator } from './descriptor_map'; -import { dirty, tagFor, tagForProperty } from './tags'; +import { dirty, tagFor, tagForProperty, update } from './tags'; type Option = T | null; +type unusable = null | undefined | void | {}; /** An object that that tracks @tracked properties that were consumed. @private -*/ + */ class Tracker { private tags = new Set(); private last: Option = null; @@ -99,158 +95,134 @@ class Tracker { ``` @param dependencies Optional dependents to be tracked. -*/ -export function tracked(propertyDesc: { value: any }): Decorator; -export function tracked(elementDesc: ElementDescriptor): ElementDescriptor; + */ +export function tracked(...dependencies: string[]): MethodDecorator; +export function tracked(target: unknown, key: PropertyKey): any; export function tracked( - elementDesc: ElementDescriptor | any, - isClassicDecorator?: boolean -): ElementDescriptor | Decorator { - if ( - elementDesc === undefined || - elementDesc === null || - elementDesc.toString() !== '[object Descriptor]' - ) { - assert( - `tracked() may only receive an options object containing 'value' or 'initializer', received ${elementDesc}`, - elementDesc === undefined || (elementDesc !== null && typeof elementDesc === 'object') - ); - - if (DEBUG && elementDesc) { - let keys = Object.keys(elementDesc); + target: unknown, + key: PropertyKey, + descriptor: PropertyDescriptor +): PropertyDescriptor; +export function tracked(...dependencies: any[]): any { + let [, key, descriptor] = dependencies; + + if (descriptor === undefined || 'initializer' in descriptor) { + return descriptorForDataProperty(key, descriptor); + } else { + return descriptorForAccessor(key, descriptor); + } +} - assert( - `The options object passed to tracked() may only contain a 'value' or 'initializer' property, not both. Received: [${keys}]`, - keys.length <= 1 && - (keys[0] === undefined || keys[0] === 'value' || keys[0] === 'undefined') - ); +/** + @private - assert( - `The initializer passed to tracked must be a function. Received ${elementDesc.initializer}`, - !('initializer' in elementDesc) || typeof elementDesc.initializer === 'function' - ); - } + Whenever a tracked computed property is entered, the current tracker is + saved off and a new tracker is replaced. - let initializer = elementDesc ? elementDesc.initializer : undefined; - let value = elementDesc ? elementDesc.value : undefined; + Any tracked properties consumed are added to the current tracker. - let decorator = function(elementDesc: ElementDescriptor, isClassicDecorator?: boolean) { - assert( - `You attempted to set a default value for ${ - elementDesc.key - } with the @tracked({ value: 'default' }) syntax. You can only use this syntax with classic classes. For native classes, you can use class initializers: @tracked field = 'default';`, - isClassicDecorator - ); + When a tracked computed property is exited, the tracker's tags are + combined and added to the parent tracker. - elementDesc.initializer = initializer || (() => value); + The consequence is that each tracked computed property has a tag + that corresponds to the tracked properties consumed inside of + itself, including child tracked computed properties. + */ +let CURRENT_TRACKER: Option = null; - return descriptorForField(elementDesc); - }; +export function getCurrentTracker(): Option { + return CURRENT_TRACKER; +} - setComputedDecorator(decorator); +export function setCurrentTracker(tracker: Tracker = new Tracker()): Tracker { + return (CURRENT_TRACKER = tracker); +} - return decorator; - } +function descriptorForAccessor( + key: string | symbol, + descriptor: PropertyDescriptor +): PropertyDescriptor { + let get = descriptor.get as Function; + let set = descriptor.set as Function; - assert( - 'Native decorators are not enabled without the EMBER_NATIVE_DECORATOR_SUPPORT flag', - Boolean(EMBER_NATIVE_DECORATOR_SUPPORT) - ); + function getter(this: any): any { + // Swap the parent tracker for a new tracker + let old = CURRENT_TRACKER; + let tracker = (CURRENT_TRACKER = new Tracker()); - assert( - `@tracked can only be used directly as a native decorator. If you're using tracked in classic classes, add parenthesis to call it like a function: tracked()`, - !isClassicDecorator - ); + // Call the getter + let ret = get.call(this); - return descriptorForField(elementDesc); -} + // Swap back the parent tracker + CURRENT_TRACKER = old; -if (DEBUG) { - // Normally this isn't a classic decorator, but we want to throw a helpful - // error in development so we need it to treat it like one - setComputedDecorator(tracked); -} + // Combine the tags in the new tracker and add them to the parent tracker + let tag = tracker.combine(); + if (CURRENT_TRACKER) CURRENT_TRACKER.add(tag); -const TRACKED_FIELDS_VALUES: WeakMap = new WeakMap(); + // Update the UpdatableTag for this property with the tag for all of the + // consumed dependencies. + update(tagForProperty(this, key), tag); -function getTrackedFieldValues(obj: any) { - let values = TRACKED_FIELDS_VALUES.get(obj); + return ret; + } - if (values === undefined) { - values = {}; - TRACKED_FIELDS_VALUES.set(obj, values); + function setter(this: unusable): void { + dirty(tagForProperty(this, key)); + set.apply(this, arguments); } - return values; + return { + enumerable: true, + configurable: false, + get: get && getter, + set: set && setter, + }; } -function descriptorForField(elementDesc: ElementDescriptor): ElementDescriptor { - let { key, kind, initializer } = elementDesc; +export type Key = string; - assert( - `You attempted to use @tracked on ${key}, but that element is not a class field. @tracked is only usable on class fields. Native getters and setters will autotrack add any tracked fields they encounter, so there is no need mark getters and setters with @tracked.`, - kind === 'field' - ); +/** + @private - return { - key, - kind: 'method', - placement: 'prototype', - descriptor: { - enumerable: true, - configurable: true, + A getter/setter for change tracking for a particular key. The accessor + acts just like a normal property, but it triggers the `propertyDidChange` + hook when written to. - get(): any { - if (CURRENT_TRACKER) CURRENT_TRACKER.add(tagForProperty(this, key)); + Values are saved on the object using a "shadow key," or a symbol based on the + tracked property name. Sets write the value to the shadow key, and gets read + from it. + */ - let values = getTrackedFieldValues(this); +function descriptorForDataProperty( + key: string, + descriptor: PropertyDescriptor +): PropertyDescriptor { + let shadowKey = Symbol(key); - if (!(key in values)) { - values[key] = initializer !== undefined ? initializer.call(this) : undefined; - } + return { + enumerable: true, + configurable: true, - return values[key]; - }, + get(): any { + if (CURRENT_TRACKER) CURRENT_TRACKER.add(tagForProperty(this, key)); - set(newValue: any): void { - tagFor(this).inner!['dirty'](); - dirty(tagForProperty(this, key)); + if (!(shadowKey in this)) { + this[shadowKey] = descriptor.value; + } - getTrackedFieldValues(this)[key] = newValue; + return this[shadowKey]; + }, - propertyDidChange(); - }, + set(newValue: any): void { + tagFor(this).inner!['dirty'](); + dirty(tagForProperty(this, key)); + this[shadowKey] = newValue; + propertyDidChange(); }, }; } -/** - @private - - Whenever a tracked computed property is entered, the current tracker is - saved off and a new tracker is replaced. - - Any tracked properties consumed are added to the current tracker. - - When a tracked computed property is exited, the tracker's tags are - combined and added to the parent tracker. - - The consequence is that each tracked computed property has a tag - that corresponds to the tracked properties consumed inside of - itself, including child tracked computed properties. -*/ -let CURRENT_TRACKER: Option = null; - -export function getCurrentTracker(): Option { - return CURRENT_TRACKER; -} - -export function setCurrentTracker(tracker: Tracker = new Tracker()): Tracker { - return (CURRENT_TRACKER = tracker); -} - -export type Key = string; - export interface Interceptors { [key: string]: boolean; } diff --git a/packages/@ember/-internals/metal/lib/watch_key.ts b/packages/@ember/-internals/metal/lib/watch_key.ts index 062681e9c99..b2499a6fdbf 100644 --- a/packages/@ember/-internals/metal/lib/watch_key.ts +++ b/packages/@ember/-internals/metal/lib/watch_key.ts @@ -1,7 +1,13 @@ -import { Meta, meta as metaFor, peekMeta, UNDEFINED } from '@ember/-internals/meta'; +import { + descriptorFor, + isDescriptor, + 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 './descriptor_map'; import { DEFAULT_GETTER_FUNCTION, INHERITING_GETTER_FUNCTION, @@ -27,7 +33,7 @@ export function watchKey(obj: object, keyName: string, _meta?: Meta): void { if (count === 0) { // activate watching first time - let possibleDesc = descriptorForProperty(obj, keyName, meta); + let possibleDesc = descriptorFor(obj, keyName, meta); if (possibleDesc !== undefined && possibleDesc.willWatch !== undefined) { possibleDesc.willWatch(obj, keyName, meta); @@ -56,7 +62,7 @@ if (DEBUG) { let descriptor = lookupDescriptor(obj, keyName); let hasDescriptor = descriptor !== null; let possibleDesc = hasDescriptor && descriptor!.value; - if (isComputedDecorator(possibleDesc)) { + if (isDescriptor(possibleDesc)) { return; } let configurable = hasDescriptor ? descriptor!.configurable : true; @@ -96,7 +102,7 @@ export function unwatchKey(obj: object, keyName: string, _meta?: Meta): void { if (count === 1) { meta.writeWatching(keyName, 0); - let possibleDesc = descriptorForProperty(obj, keyName, meta); + let possibleDesc = descriptorFor(obj, keyName, meta); let isDescriptor = possibleDesc !== undefined; if (isDescriptor && possibleDesc.didUnwatch !== undefined) { diff --git a/packages/@ember/-internals/metal/tests/accessors/set_test.js b/packages/@ember/-internals/metal/tests/accessors/set_test.js index b02020f1893..d159de71c6c 100644 --- a/packages/@ember/-internals/metal/tests/accessors/set_test.js +++ b/packages/@ember/-internals/metal/tests/accessors/set_test.js @@ -124,28 +124,5 @@ moduleFor( trySet(obj, 'favoriteFood', 'hot dogs'); assert.equal(obj.favoriteFood, undefined, 'does not set and does not error'); } - - ['@test should work with native setters'](assert) { - let count = 0; - - class Foo { - __foo = ''; - - get foo() { - return this.__foo; - } - - set foo(value) { - count++; - this.__foo = `computed ${value}`; - } - } - - let obj = new Foo(); - - assert.equal(set(obj, 'foo', 'bar'), 'bar', 'should return set value'); - assert.equal(count, 1, 'should have native setter'); - assert.equal(get(obj, 'foo'), 'computed bar', 'should return new value'); - } } ); diff --git a/packages/@ember/-internals/metal/tests/computed_decorator_test.js b/packages/@ember/-internals/metal/tests/computed_decorator_test.js deleted file mode 100644 index d23787657c7..00000000000 --- a/packages/@ember/-internals/metal/tests/computed_decorator_test.js +++ /dev/null @@ -1,330 +0,0 @@ -import { Object as EmberObject } from '@ember/-internals/runtime'; -import { computed, get, set, setProperties } from '..'; -import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; -import { EMBER_NATIVE_DECORATOR_SUPPORT } from '@ember/canary-features'; - -if (EMBER_NATIVE_DECORATOR_SUPPORT) { - 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; - } - - let Class2 = EmberObject.extend({ - firstName, - otherFirstName: firstNameAlias, - }); - - let obj1 = new Class1(); - let obj2 = Class2.create(); - - 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'); - - let [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 throws if it receives a desc and decorates a getter/setter']() { - expectAssertion(() => { - class Foo { - bar; - - @computed('bar', { - get() { - return this.bar; - }, - }) - set foo(value) { - set(this, 'bar', value); - } - } - - new Foo(); - }, /Attempted to apply a computed property that already has a getter\/setter to a foo, but it is a method or an accessor./); - } - - ['@test it throws if a CP is passed to it']() { - expectAssertion(() => { - class Foo { - bar; - - @computed( - 'bar', - computed({ - get() { - return this._foo; - }, - }) - ) - foo; - } - - new Foo(); - }, 'You attempted to pass a computed property instance to computed(). Computed property instances are decorator functions, and cannot be passed to computed() because they cannot be turned into decorators twice'); - } - } - ); - - moduleFor( - 'computed - decorator - usage tests', - class extends AbstractTestCase { - ['@test computed property asserts the presence of a getter']() { - expectAssertion(() => { - 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'); - } - - ['@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 setter is invoked with correct parameters'](assert) { - let count = 0; - class Obj { - __foo = 'not set'; - - @computed() - get foo() { - return this.__foo; - } - set foo(value) { - count++; - this.__foo = `computed ${value}`; - } - } - 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()'); - } - - ['@test when not returning from setter, getter is called'](assert) { - let count = 0; - class Obj { - __foo = 'not set'; - - @computed() - get foo() { - count++; - return this.__foo; - } - set foo(value) { - this.__foo = `computed ${value}`; - } - } - let obj = new Obj(); - - assert.equal(set(obj, 'foo', 'bar'), 'bar', 'should return set value with set()'); - assert.equal(count, 1, 'should have invoked getter'); - } - - ['@test when returning from setter, getter is not called'](assert) { - let count = 0; - class Obj { - __foo = 'not set'; - - @computed() - get foo() { - count++; - return this.__foo; - } - set foo(value) { - 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, 0, 'should not have invoked getter'); - } - - ['@test throws if a value is decorated twice']() { - expectAssertion(() => { - class Obj { - @computed - @computed - get foo() { - return this._foo; - } - } - new Obj(); - }, "Only one computed property decorator can be applied to a class field or accessor, but 'foo' was decorated twice. You may have added the decorator to both a getter and setter, which is unecessary."); - } - } - ); -} else { - moduleFor( - 'computed - decorator - disabled', - class extends AbstractTestCase { - ['@test using a native decorator throws if the feature flag is disabled']() { - expectAssertion(() => { - class Foo { - @computed('foo', { - get() { - return this.bar; - }, - }) - baz; - } - - new Foo(); - }, 'Native decorators are not enabled without the EMBER_NATIVE_DECORATOR_SUPPORT flag'); - } - } - ); -} diff --git a/packages/@ember/-internals/metal/tests/computed_test.js b/packages/@ember/-internals/metal/tests/computed_test.js index 7f9ed038c8c..d524a067d5b 100644 --- a/packages/@ember/-internals/metal/tests/computed_test.js +++ b/packages/@ember/-internals/metal/tests/computed_test.js @@ -1,9 +1,10 @@ import { Object as EmberObject } from '@ember/-internals/runtime'; import { + ComputedProperty, computed, getCachedValueFor, + Descriptor, defineProperty, - isComputedDecorator, get, set, isWatching, @@ -18,35 +19,28 @@ moduleFor( 'computed', class extends AbstractTestCase { ['@test computed property should be an instance of descriptor'](assert) { - assert.ok(isComputedDecorator(computed(function() {}))); + assert.ok(computed(function() {}) instanceof Descriptor); } ['@test computed properties assert the presence of a getter or setter function']() { expectAssertion(function() { - let obj = {}; - defineProperty(obj, 'someProp', computed('nogetternorsetter', {})); + 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() { - 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'); + computed('nolastargument'); + }, 'computed expects a function or an object as last argument.'); } - // 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() { - let obj = EmberObject.extend({ - someProp: computed({ - get() {}, - set() {}, - other() {}, - }), + computed({ + get() {}, + set() {}, + other() {}, }); - - obj.create().someProp; }, 'Config object passed to computed can only contain `get` and `set` keys.'); } @@ -119,39 +113,33 @@ moduleFor( assert.equal(get(obj, 'foo'), 'computed bar', 'should return new value'); } - // 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() {}); + ['@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(() => { - let obj = {}; - defineProperty(obj, 'someProp', computed('todos', () => {})); + computed('todos', () => {}); }); expectNoWarning(() => { - let obj = {}; - defineProperty(obj, 'someProp', computed('todos.@each.owner', () => {})); + computed('todos.@each.owner', () => {}); }); expectWarning(() => { - let obj = {}; - defineProperty(obj, 'someProp', computed('todos.@each.owner.name', () => {})); + computed('todos.@each.owner.name', () => {}); }, /You used the key "todos\.@each\.owner\.name" which is invalid\. /); expectWarning(() => { - let obj = {}; - defineProperty(obj, 'someProp', computed('todos.@each.owner.@each.name', () => {})); + computed('todos.@each.owner.@each.name', () => {}); }, /You used the key "todos\.@each\.owner\.@each\.name" which is invalid\. /); } } @@ -984,23 +972,18 @@ moduleFor( 'computed - readOnly', class extends AbstractTestCase { ['@test is chainable'](assert) { - let cp = computed(function() {}); - let readOnlyCp = cp.readOnly(); + let cp = computed(function() {}).readOnly(); - assert.equal(cp, readOnlyCp); + assert.ok(cp instanceof Descriptor); + assert.ok(cp instanceof ComputedProperty); } ['@test throws assertion if called over a CP with a setter defined with the new syntax']() { expectAssertion(() => { - let obj = {}; - defineProperty( - obj, - 'someProp', - computed({ - get() {}, - set() {}, - }).readOnly() - ); + 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/native_desc_decorator_test.js b/packages/@ember/-internals/metal/tests/descriptor_test.js similarity index 89% rename from packages/@ember/-internals/metal/tests/native_desc_decorator_test.js rename to packages/@ember/-internals/metal/tests/descriptor_test.js index ea95c4727e4..6f80b92a71d 100644 --- a/packages/@ember/-internals/metal/tests/native_desc_decorator_test.js +++ b/packages/@ember/-internals/metal/tests/descriptor_test.js @@ -1,5 +1,5 @@ import { Object as EmberObject } from '@ember/-internals/runtime'; -import { Mixin, defineProperty, nativeDescDecorator } from '..'; +import { Mixin, defineProperty, descriptor } 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/nativeDescDecorator'), + TestClass.module('@ember/-internals/metal/descriptor'), class extends AbstractTestCase { ['@test defining a configurable property'](assert) { let factory = new TestClass(assert); - factory.install('foo', nativeDescDecorator({ configurable: true, value: 'bar' }), assert); + factory.install('foo', descriptor({ 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', nativeDescDecorator({ configurable: false, value: 'bar' }), assert); + factory.install('foo', descriptor({ 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', nativeDescDecorator({ enumerable: true, value: 'bar' }), assert); + factory.install('foo', descriptor({ 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', nativeDescDecorator({ enumerable: false, value: 'bar' }), assert); + factory.install('foo', descriptor({ 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', nativeDescDecorator({ writable: true, value: 'bar' }), assert); + factory.install('foo', descriptor({ 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', nativeDescDecorator({ writable: false, value: 'bar' }), assert); + factory.install('foo', descriptor({ writable: false, value: 'bar' }), assert); let obj = factory.finalize(); @@ -261,7 +261,7 @@ classes.forEach(TestClass => { let factory = new TestClass(assert); factory.install( 'foo', - nativeDescDecorator({ + descriptor({ get: function() { return this.__foo__; }, @@ -284,7 +284,7 @@ classes.forEach(TestClass => { let factory = new TestClass(assert); factory.install( 'foo', - nativeDescDecorator({ + descriptor({ set: function(value) { this.__foo__ = value; }, @@ -307,7 +307,7 @@ classes.forEach(TestClass => { let factory = new TestClass(assert); factory.install( 'foo', - nativeDescDecorator({ + descriptor({ get: function() { return this.__foo__; }, @@ -323,7 +323,7 @@ classes.forEach(TestClass => { factory.install( 'bar', - nativeDescDecorator({ + descriptor({ get: function() { return this.__bar__; }, @@ -339,7 +339,7 @@ classes.forEach(TestClass => { factory.install( 'fooBar', - nativeDescDecorator({ + descriptor({ get: function() { return this.foo + '-' + this.bar; }, diff --git a/packages/@ember/-internals/metal/tests/injected_property_test.js b/packages/@ember/-internals/metal/tests/injected_property_test.js index 06434e74920..6e21e546779 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 { defineProperty, get, isComputedDecorator, set, inject } from '..'; +import { Descriptor, defineProperty, get, set, InjectedProperty } from '..'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; moduleFor( - 'inject', + 'InjectedProperty', class extends AbstractTestCase { ['@test injected properties should be descriptors'](assert) { - assert.ok(isComputedDecorator(inject('type'))); + assert.ok(new InjectedProperty() instanceof Descriptor); } ['@test injected properties should be overridable'](assert) { let obj = {}; - defineProperty(obj, 'foo', inject('type')); + defineProperty(obj, 'foo', new InjectedProperty()); 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', inject('type', 'name')); + defineProperty(obj, 'foo', new InjectedProperty('type', 'name')); expectAssertion(function() { get(obj, 'foo'); @@ -37,7 +37,7 @@ moduleFor( }, }; - defineProperty(obj, 'foo', inject('type', 'name')); + defineProperty(obj, 'foo', new InjectedProperty('type', 'name')); assert.equal(get(obj, 'foo'), 'type:name', 'should return the value of container.lookup'); } @@ -54,7 +54,7 @@ moduleFor( }, }); - defineProperty(obj, 'foo', inject('type', 'name')); + defineProperty(obj, 'foo', new InjectedProperty('type', 'name')); assert.equal(get(obj, 'foo'), 'type:name', 'should return the value of container.lookup'); } @@ -68,7 +68,7 @@ moduleFor( }, }); - defineProperty(obj, 'foo', inject('type')); + defineProperty(obj, 'foo', new InjectedProperty('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 c64583e088b..071464c5d86 100644 --- a/packages/@ember/-internals/metal/tests/mixin/computed_test.js +++ b/packages/@ember/-internals/metal/tests/mixin/computed_test.js @@ -12,8 +12,6 @@ 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/tracked/classic_classes_test.js b/packages/@ember/-internals/metal/tests/tracked/classic_classes_test.js deleted file mode 100644 index e4c0484b232..00000000000 --- a/packages/@ember/-internals/metal/tests/tracked/classic_classes_test.js +++ /dev/null @@ -1,177 +0,0 @@ -import { AbstractTestCase, moduleFor } from 'internal-test-helpers'; -import { defineProperty, tracked, nativeDescDecorator } from '../..'; - -import { track } from './support'; - -import { - EMBER_METAL_TRACKED_PROPERTIES, - EMBER_NATIVE_DECORATOR_SUPPORT, -} from '@ember/canary-features'; - -if (EMBER_METAL_TRACKED_PROPERTIES) { - moduleFor( - '@tracked decorator - classic classes', - class extends AbstractTestCase { - [`@test validators for tracked getters with dependencies should invalidate when the dependencies invalidate`]( - assert - ) { - let obj = {}; - - defineProperty(obj, 'first', tracked()); - defineProperty(obj, 'last', tracked()); - - defineProperty( - obj, - 'full', - nativeDescDecorator({ - get() { - return `${this.first} ${this.last}`; - }, - - set(value) { - let [first, last] = value.split(' '); - - this.first = first; - this.last = last; - }, - }) - ); - - obj.first = 'Tom'; - obj.last = 'Dale'; - - let tag = track(() => obj.full); - let snapshot = tag.value(); - - assert.equal(obj.full, 'Tom Dale', 'The full name starts correct'); - assert.equal(tag.validate(snapshot), true); - - snapshot = tag.value(); - assert.equal(tag.validate(snapshot), true); - - obj.full = 'Melanie Sumner'; - - assert.equal(tag.validate(snapshot), false); - - assert.equal(obj.full, 'Melanie Sumner'); - assert.equal(obj.first, 'Melanie'); - assert.equal(obj.last, 'Sumner'); - snapshot = tag.value(); - - assert.equal(tag.validate(snapshot), true); - } - - [`@test can pass a default value to the tracked decorator`](assert) { - class Tracked { - get full() { - return `${this.first} ${this.last}`; - } - } - - defineProperty(Tracked.prototype, 'first', tracked({ value: 'Tom' })); - defineProperty(Tracked.prototype, 'last', tracked({ value: 'Dale' })); - - let obj = new Tracked(); - - assert.equal(obj.full, 'Tom Dale', 'Default values are correctly assign'); - } - - [`@test errors if used directly on a classic class`]() { - expectAssertion(() => { - class Tracked { - get full() { - return `${this.first} ${this.last}`; - } - } - - defineProperty(Tracked.prototype, 'first', tracked); - }, "@tracked can only be used directly as a native decorator. If you're using tracked in classic classes, add parenthesis to call it like a function: tracked()"); - } - - [`@test errors on any keys besides 'value', 'get', or 'set' being passed`]() { - expectAssertion(() => { - class Tracked { - get full() { - return `${this.first} ${this.last}`; - } - } - - defineProperty( - Tracked.prototype, - 'first', - tracked({ - foo() {}, - }) - ); - }, "The options object passed to tracked() may only contain a 'value' or 'initializer' property, not both. Received: [foo]"); - } - - [`@test errors if 'value' and 'get'/'set' are passed together`]() { - expectAssertion(() => { - class Tracked { - get full() { - return `${this.first} ${this.last}`; - } - } - - defineProperty( - Tracked.prototype, - 'first', - tracked({ - value: 123, - initializer: () => 123, - }) - ); - }, "The options object passed to tracked() may only contain a 'value' or 'initializer' property, not both. Received: [value,initializer]"); - } - - [`@test errors on anything besides an options object being passed`]() { - expectAssertion(() => { - class Tracked { - get full() { - return `${this.first} ${this.last}`; - } - } - - defineProperty(Tracked.prototype, 'first', tracked(null)); - }, "tracked() may only receive an options object containing 'value' or 'initializer', received null"); - } - } - ); - - if (EMBER_NATIVE_DECORATOR_SUPPORT) { - moduleFor( - '@tracked decorator - native decorator behavior', - class extends AbstractTestCase { - [`@test errors if options are passed to native decorator`]() { - expectAssertion(() => { - class Tracked { - @tracked() first; - - get full() { - return `${this.first} ${this.last}`; - } - } - - new Tracked(); - }, "You attempted to set a default value for first with the @tracked({ value: 'default' }) syntax. You can only use this syntax with classic classes. For native classes, you can use class initializers: @tracked field = 'default';"); - } - } - ); - } else { - moduleFor( - '@tracked decorator - native decorator behavior', - class extends AbstractTestCase { - [`@test errors if used as a native decorator`]() { - expectAssertion(() => { - class Tracked { - @tracked first; - } - - new Tracked(); - }, 'Native decorators are not enabled without the EMBER_NATIVE_DECORATOR_SUPPORT flag'); - } - } - ); - } -} diff --git a/packages/@ember/-internals/metal/tests/tracked/computed_test.ts b/packages/@ember/-internals/metal/tests/tracked/computed_test.ts new file mode 100644 index 00000000000..88172798da0 --- /dev/null +++ b/packages/@ember/-internals/metal/tests/tracked/computed_test.ts @@ -0,0 +1,69 @@ +import { get, set, tracked } from '../..'; + +import { EMBER_METAL_TRACKED_PROPERTIES } from '@ember/canary-features'; +import { AbstractTestCase, moduleFor } from 'internal-test-helpers'; + +if (EMBER_METAL_TRACKED_PROPERTIES) { + moduleFor( + '@tracked getters', + class extends AbstractTestCase { + ['@test works without get'](assert: Assert) { + let count = 0; + + class Count { + @tracked + get foo() { + count++; + return `computed foo`; + } + } + + let obj = new Count(); + + 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: Assert) { + let count = 0; + + class Count { + @tracked + get foo() { + count++; + return `computed foo`; + } + } + + let obj = new Count(); + + assert.equal(get(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'](assert: Assert) { + let count = 0; + + class Foo { + __foo = ''; + + @tracked + get foo() { + return this.__foo; + } + + set foo(value) { + count++; + this.__foo = `computed ${value}`; + } + } + + let obj = new Foo(); + + assert.equal(set(obj, 'foo', 'bar'), 'bar', 'should return set value'); + assert.equal(count, 1, 'should have invoked computed property'); + assert.equal(get(obj, 'foo'), 'computed bar', 'should return new value'); + } + } + ); +} diff --git a/packages/@ember/-internals/metal/tests/tracked/get_test.js b/packages/@ember/-internals/metal/tests/tracked/get_test.ts similarity index 71% rename from packages/@ember/-internals/metal/tests/tracked/get_test.js rename to packages/@ember/-internals/metal/tests/tracked/get_test.ts index 5f06f9ad7cd..250f6dc9c75 100644 --- a/packages/@ember/-internals/metal/tests/tracked/get_test.js +++ b/packages/@ember/-internals/metal/tests/tracked/get_test.ts @@ -1,12 +1,9 @@ -import { - EMBER_METAL_TRACKED_PROPERTIES, - EMBER_NATIVE_DECORATOR_SUPPORT, -} from '@ember/canary-features'; +import { EMBER_METAL_TRACKED_PROPERTIES } from '@ember/canary-features'; import { AbstractTestCase, moduleFor } from 'internal-test-helpers'; import { get, getWithDefault, tracked } from '../..'; -if (EMBER_METAL_TRACKED_PROPERTIES && EMBER_NATIVE_DECORATOR_SUPPORT) { - let createObj = function() { +if (EMBER_METAL_TRACKED_PROPERTIES) { + const createObj = function() { class Obj { @tracked string = 'string'; @tracked number = 23; @@ -67,15 +64,37 @@ if (EMBER_METAL_TRACKED_PROPERTIES && EMBER_NATIVE_DECORATOR_SUPPORT) { this.assert.equal(get(obj, 'path.key.value'), 'value'); } + '@test should not access a property more than once'() { + let count = 20; + + class Count { + @tracked + get id() { + return ++count; + } + } + + let obj = new Count(); + + get(obj, 'id'); + + this.assert.equal(count, 21); + } + } + ); + + moduleFor( + '@tracked decorator: getWithDefault', + class extends AbstractTestCase { ['@test should get arbitrary properties on an object']() { let obj = createObj(); for (let key in obj) { - this.assert.equal(getWithDefault(obj, key, 'fail'), obj[key], key); + this.assert.equal(getWithDefault(obj, key as any, 'fail'), obj[key], key); } class Obj { - @tracked undef = undefined; + @tracked undef: string | undefined = undefined; } let obj2 = new Obj(); @@ -86,7 +105,7 @@ if (EMBER_METAL_TRACKED_PROPERTIES && EMBER_NATIVE_DECORATOR_SUPPORT) { 'explicit undefined retrieves the default' ); this.assert.equal( - getWithDefault(obj2, 'not-present', 'default'), + getWithDefault(obj2, 'not-present' as any, 'default'), 'default', 'non-present key retrieves the default' ); diff --git a/packages/@ember/-internals/metal/tests/tracked/set_test.js b/packages/@ember/-internals/metal/tests/tracked/set_test.ts similarity index 79% rename from packages/@ember/-internals/metal/tests/tracked/set_test.js rename to packages/@ember/-internals/metal/tests/tracked/set_test.ts index 9ee38d7d51b..4458c005595 100644 --- a/packages/@ember/-internals/metal/tests/tracked/set_test.js +++ b/packages/@ember/-internals/metal/tests/tracked/set_test.ts @@ -1,13 +1,10 @@ import { AbstractTestCase, moduleFor } from 'internal-test-helpers'; import { get, set, tracked } from '../..'; -import { - EMBER_METAL_TRACKED_PROPERTIES, - EMBER_NATIVE_DECORATOR_SUPPORT, -} from '@ember/canary-features'; +import { EMBER_METAL_TRACKED_PROPERTIES } from '@ember/canary-features'; -if (EMBER_METAL_TRACKED_PROPERTIES && EMBER_NATIVE_DECORATOR_SUPPORT) { - let createObj = () => { +if (EMBER_METAL_TRACKED_PROPERTIES) { + const createObj = () => { class Obj { @tracked string = 'string'; @tracked number = 23; @@ -23,7 +20,7 @@ if (EMBER_METAL_TRACKED_PROPERTIES && EMBER_NATIVE_DECORATOR_SUPPORT) { moduleFor( '@tracked set', class extends AbstractTestCase { - ['@test should set arbitrary properties on an object'](assert) { + ['@test should set arbitrary properties on an object'](assert: Assert) { let obj = createObj(); class Obj { @@ -38,7 +35,7 @@ if (EMBER_METAL_TRACKED_PROPERTIES && EMBER_NATIVE_DECORATOR_SUPPORT) { } } - ['@test should set a number key on an object'](assert) { + ['@test should set a number key on an object'](assert: Assert) { class Obj { @tracked 1 = 'original'; } diff --git a/packages/@ember/-internals/metal/tests/tracked/support.js b/packages/@ember/-internals/metal/tests/tracked/support.js index e58a979df9f..6d4bc761a78 100644 --- a/packages/@ember/-internals/metal/tests/tracked/support.js +++ b/packages/@ember/-internals/metal/tests/tracked/support.js @@ -1,17 +1,37 @@ -import { getCurrentTracker, setCurrentTracker } from '../..'; +import { tracked } from '../..'; -/** - Creates an autotrack stack so we can test field changes as they flow through - getters/setters, and through the system overall +export function createTracked(values, proto = {}) { + function Class() { + for (let prop in values) { + this[prop] = values[prop]; + } + } - @private -*/ -export function track(fn) { - let parent = getCurrentTracker(); - let tracker = setCurrentTracker(); + for (let prop in values) { + Object.defineProperty( + proto, + prop, + tracked(proto, prop, { + enumerable: true, + configurable: true, + writable: true, + value: values[prop], + }) + ); + } - fn(); + Class.prototype = proto; - setCurrentTracker(parent); - return tracker.combine(); + return new Class(); +} + +export function createWithDescriptors(values) { + function Class() {} + + for (let prop in values) { + let descriptor = Object.getOwnPropertyDescriptor(values, prop); + Object.defineProperty(Class.prototype, prop, tracked(Class.prototype, prop, descriptor)); + } + + return new Class(); } diff --git a/packages/@ember/-internals/metal/tests/tracked/validation_test.js b/packages/@ember/-internals/metal/tests/tracked/validation_test.ts similarity index 58% rename from packages/@ember/-internals/metal/tests/tracked/validation_test.js rename to packages/@ember/-internals/metal/tests/tracked/validation_test.ts index 3fc071dcd22..b8380f93b75 100644 --- a/packages/@ember/-internals/metal/tests/tracked/validation_test.js +++ b/packages/@ember/-internals/metal/tests/tracked/validation_test.ts @@ -1,54 +1,24 @@ import { computed, defineProperty, get, set, tagForProperty, tracked } from '../..'; -import { - EMBER_METAL_TRACKED_PROPERTIES, - EMBER_NATIVE_DECORATOR_SUPPORT, -} from '@ember/canary-features'; +import { EMBER_METAL_TRACKED_PROPERTIES } from '@ember/canary-features'; import { AbstractTestCase, moduleFor } from 'internal-test-helpers'; -import { track } from './support'; -if (EMBER_METAL_TRACKED_PROPERTIES && EMBER_NATIVE_DECORATOR_SUPPORT) { +if (EMBER_METAL_TRACKED_PROPERTIES) { moduleFor( '@tracked get validation', class extends AbstractTestCase { - [`@test autotracking should work with tracked fields`](assert) { - class Tracked { - @tracked first = undefined; - constructor(first) { - this.first = first; - } - } - - let obj = new Tracked('Tom', 'Dale'); - - let tag = track(() => obj.first); - let snapshot = tag.value(); - - assert.equal(obj.first, 'Tom', 'The full name starts correct'); - assert.equal(tag.validate(snapshot), true); - - snapshot = tag.value(); - assert.equal(tag.validate(snapshot), true); - - obj.first = 'Thomas'; - - assert.equal(tag.validate(snapshot), false); - - assert.equal(obj.first, 'Thomas'); - snapshot = tag.value(); - - assert.equal(tag.validate(snapshot), true); - } - - [`@test autotracking should work with native getters`](assert) { + [`@test validators for tracked getters with dependencies should invalidate when the dependencies invalidate`]( + assert: Assert + ) { class Tracked { - @tracked first = undefined; - @tracked last = undefined; - constructor(first, last) { + @tracked first?: string = undefined; + @tracked last?: string = undefined; + constructor(first: string, last: string) { this.first = first; this.last = last; } + @tracked get full() { return `${this.first} ${this.last}`; } @@ -56,10 +26,11 @@ if (EMBER_METAL_TRACKED_PROPERTIES && EMBER_NATIVE_DECORATOR_SUPPORT) { let obj = new Tracked('Tom', 'Dale'); - let tag = track(() => obj.full); + let tag = tagForProperty(obj, 'full'); let snapshot = tag.value(); - assert.equal(obj.full, 'Tom Dale', 'The full name starts correct'); + let full = obj.full; + assert.equal(full, 'Tom Dale', 'The full name starts correct'); assert.equal(tag.validate(snapshot), true); snapshot = tag.value(); @@ -75,60 +46,22 @@ if (EMBER_METAL_TRACKED_PROPERTIES && EMBER_NATIVE_DECORATOR_SUPPORT) { assert.equal(tag.validate(snapshot), true); } - [`@test autotracking should work with native setters`](assert) { - class Tracked { - @tracked first = undefined; - @tracked last = undefined; - constructor(first, last) { - this.first = first; - this.last = last; - } - - get full() { - return `${this.first} ${this.last}`; - } - - set full(value) { - let [first, last] = value.split(' '); - - this.first = first; - this.last = last; - } - } - - let obj = new Tracked('Tom', 'Dale'); - - let tag = track(() => obj.full); - let snapshot = tag.value(); - - assert.equal(obj.full, 'Tom Dale', 'The full name starts correct'); - assert.equal(tag.validate(snapshot), true); - - snapshot = tag.value(); - assert.equal(tag.validate(snapshot), true); - - obj.full = 'Melanie Sumner'; - - assert.equal(tag.validate(snapshot), false); - - assert.equal(obj.full, 'Melanie Sumner'); - assert.equal(obj.first, 'Melanie'); - assert.equal(obj.last, 'Sumner'); - snapshot = tag.value(); - - assert.equal(tag.validate(snapshot), true); - } - [`@test interaction with Ember object model (tracked property depending on Ember property)`]( - assert + assert: Assert ) { + interface NameInterface { + first: string; + last: string; + } class Tracked { - constructor(name) { + @tracked name: NameInterface; + constructor(name: NameInterface) { this.name = name; } + @tracked get full() { - return `${get(this, 'name.first')} ${get(this, 'name.last')}`; + return `${get(this.name, 'first')} ${get(this.name, 'last')}`; } } @@ -136,10 +69,11 @@ if (EMBER_METAL_TRACKED_PROPERTIES && EMBER_NATIVE_DECORATOR_SUPPORT) { let obj = new Tracked(tom); - let tag = track(() => obj.full); + let tag = tagForProperty(obj, 'full'); let snapshot = tag.value(); - assert.equal(obj.full, 'Tom Dale'); + let full = obj.full; + assert.equal(full, 'Tom Dale'); assert.equal(tag.validate(snapshot), true); snapshot = tag.value(); @@ -152,22 +86,14 @@ if (EMBER_METAL_TRACKED_PROPERTIES && EMBER_NATIVE_DECORATOR_SUPPORT) { snapshot = tag.value(); assert.equal(tag.validate(snapshot), true); - - set(obj, 'name', { first: 'Ricardo', last: 'Mendes' }); - - assert.equal(tag.validate(snapshot), false, 'invalid after setting with Ember set'); - - assert.equal(obj.full, 'Ricardo Mendes'); - snapshot = tag.value(); - - assert.equal(tag.validate(snapshot), true); } [`@test interaction with Ember object model (Ember computed property depending on tracked property)`]( - assert + assert: Assert ) { class EmberObject { - constructor(name) { + name: Name; + constructor(name: Name) { this.name = name; } } @@ -175,17 +101,16 @@ if (EMBER_METAL_TRACKED_PROPERTIES && EMBER_NATIVE_DECORATOR_SUPPORT) { defineProperty( EmberObject.prototype, 'full', - computed('name', function() { + computed('name', function(this: EmberObject) { let name = get(this, 'name'); return `${name.first} ${name.last}`; }) ); class Name { - @tracked first; - @tracked last; - - constructor(first, last) { + @tracked first: string; + @tracked last: string; + constructor(first: string, last: string) { this.first = first; this.last = last; } @@ -218,12 +143,12 @@ if (EMBER_METAL_TRACKED_PROPERTIES && EMBER_NATIVE_DECORATOR_SUPPORT) { } ['@test interaction with the Ember object model (paths going through tracked properties)']( - assert + assert: Assert ) { - let self; + let self: EmberObject; class EmberObject { - contact; - constructor(contact) { + contact: Contact; + constructor(contact: Contact) { this.contact = contact; self = this; } @@ -239,16 +164,16 @@ if (EMBER_METAL_TRACKED_PROPERTIES && EMBER_NATIVE_DECORATOR_SUPPORT) { ); class Contact { - @tracked name = undefined; - constructor(name) { + @tracked name?: EmberName = undefined; + constructor(name: EmberName) { this.name = name; } } class EmberName { - first; - last; - constructor(first, last) { + first: string; + last: string; + constructor(first: string, last: string) { this.first = first; this.last = last; } diff --git a/packages/@ember/-internals/runtime/lib/system/core_object.js b/packages/@ember/-internals/runtime/lib/system/core_object.js index 87bc7487303..788564e1c1e 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 { meta, peekMeta, deleteMeta } from '@ember/-internals/meta'; +import { descriptorFor, meta, peekMeta, deleteMeta } from '@ember/-internals/meta'; import { PROXY_CONTENT, finishChains, @@ -21,10 +21,9 @@ import { Mixin, applyMixin, defineProperty, - descriptorForProperty, + ComputedProperty, + InjectedProperty, classToString, - isComputedDecorator, - DEBUG_INJECTION_FUNCTIONS, } from '@ember/-internals/metal'; import ActionHandler from '../mixins/action_handler'; import { assert, deprecate } from '@ember/debug'; @@ -78,7 +77,7 @@ function initialize(obj, properties) { 'EmberObject.create no longer supports defining computed ' + 'properties. Define computed properties using extend() or reopen() ' + 'before calling create().', - !isComputedDecorator(value) + !(value instanceof ComputedProperty) ); assert( 'EmberObject.create no longer supports defining methods that call _super.', @@ -90,7 +89,7 @@ function initialize(obj, properties) { !(keyName === 'actions' && ActionHandler.detect(obj)) ); - let possibleDesc = descriptorForProperty(obj, keyName, m); + let possibleDesc = descriptorFor(obj, keyName, m); let isDescriptor = possibleDesc !== undefined; if (!isDescriptor) { @@ -937,7 +936,7 @@ class CoreObject { */ static metaForProperty(key) { let proto = this.proto(); // ensure prototype is initialized - let possibleDesc = descriptorForProperty(proto, key); + let possibleDesc = descriptorFor(proto, key); assert( `metaForProperty() could not find a computed property with key '${key}'.`, @@ -1067,11 +1066,11 @@ if (DEBUG) { let proto = this.proto(); for (let key in proto) { - let desc = descriptorForProperty(proto, key); - if (desc && DEBUG_INJECTION_FUNCTIONS.has(desc._getter)) { + let desc = descriptorFor(proto, key); + if (desc instanceof InjectedProperty) { assert( `Defining \`${key}\` as an injected controller property on a non-controller (\`${debugContainerKey}\`) is not allowed.`, - type === 'controller' || DEBUG_INJECTION_FUNCTIONS.get(desc._getter).type !== 'controller' + type === 'controller' || desc.type !== 'controller' ); } } @@ -1092,14 +1091,12 @@ if (DEBUG) { let desc; for (key in proto) { - desc = descriptorForProperty(proto, key); - if (desc && DEBUG_INJECTION_FUNCTIONS.has(desc._getter)) { - let { namespace, source, type, name } = DEBUG_INJECTION_FUNCTIONS.get(desc._getter); - + desc = descriptorFor(proto, key); + if (desc instanceof InjectedProperty) { injections[key] = { - namespace, - source, - specifier: `${type}:${name || key}`, + namespace: desc.namespace, + source: desc.source, + specifier: `${desc.type}:${desc.name || key}`, }; } } diff --git a/packages/@ember/-internals/runtime/tests/inject_test.js b/packages/@ember/-internals/runtime/tests/inject_test.js index addad3aaf8c..91747604105 100644 --- a/packages/@ember/-internals/runtime/tests/inject_test.js +++ b/packages/@ember/-internals/runtime/tests/inject_test.js @@ -1,5 +1,4 @@ -import { inject } from '@ember/-internals/metal'; -import { EMBER_NATIVE_DECORATOR_SUPPORT } from '@ember/canary-features'; +import { InjectedProperty } from '@ember/-internals/metal'; import { DEBUG } from '@glimmer/env'; import EmberObject from '../lib/system/object'; import { buildOwner } from 'internal-test-helpers'; @@ -11,7 +10,7 @@ moduleFor( ['@test attempting to inject a nonexistent container key should error']() { let owner = buildOwner(); let AnObject = EmberObject.extend({ - foo: inject('bar', 'baz'), + foo: new InjectedProperty('bar', 'baz'), }); owner.register('foo:main', AnObject); @@ -24,8 +23,8 @@ moduleFor( ['@test factories should return a list of lazy injection full names'](assert) { if (DEBUG) { let AnObject = EmberObject.extend({ - foo: inject('foo', 'bar'), - bar: inject('quux'), + foo: new InjectedProperty('foo', 'bar'), + bar: new InjectedProperty('quux'), }); assert.deepEqual( @@ -50,48 +49,3 @@ moduleFor( } } ); - -if (EMBER_NATIVE_DECORATOR_SUPPORT) { - moduleFor( - 'inject - decorator', - class extends AbstractTestCase { - ['@test works with native decorators'](assert) { - let owner = buildOwner(); - - class Service extends EmberObject {} - - class Foo extends EmberObject { - @inject('service', 'main') main; - } - - owner.register('service:main', Service); - owner.register('foo:main', Foo); - - let foo = owner.lookup('foo:main'); - - assert.ok(foo.main instanceof Service, 'service injected correctly'); - } - - ['@test uses the decorated property key if not provided'](assert) { - let owner = buildOwner(); - - function service() { - return inject('service', ...arguments); - } - - class Service extends EmberObject {} - - class Foo extends EmberObject { - @service main; - } - - owner.register('service:main', Service); - owner.register('foo:main', Foo); - - let foo = owner.lookup('foo:main'); - - assert.ok(foo.main instanceof Service, 'service injected correctly'); - } - } - ); -} 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 1af78716c2f..1cd92bff43a 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, nativeDescDecorator } from '@ember/-internals/metal'; +import { Mixin, descriptor } 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: nativeDescDecorator({ + childViews: descriptor({ 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 0beaa12dd5b..e03fa190b80 100644 --- a/packages/@ember/-internals/views/lib/mixins/class_names_support.js +++ b/packages/@ember/-internals/views/lib/mixins/class_names_support.js @@ -1,7 +1,8 @@ /** @module ember */ -import { descriptorForProperty, Mixin } from '@ember/-internals/metal'; +import { descriptorFor } from '@ember/-internals/meta'; +import { Mixin } from '@ember/-internals/metal'; import { assert } from '@ember/debug'; const EMPTY_ARRAY = Object.freeze([]); @@ -19,12 +20,12 @@ export default Mixin.create({ assert( `Only arrays are allowed for 'classNameBindings'`, - descriptorForProperty(this, 'classNameBindings') === undefined && + descriptorFor(this, 'classNameBindings') === undefined && Array.isArray(this.classNameBindings) ); assert( `Only arrays of static class strings are allowed for 'classNames'. For dynamic classes, use 'classNameBindings'.`, - descriptorForProperty(this, 'classNames') === undefined && Array.isArray(this.classNames) + descriptorFor(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 4aeed30daea..4b12662c7f6 100644 --- a/packages/@ember/-internals/views/lib/mixins/view_support.js +++ b/packages/@ember/-internals/views/lib/mixins/view_support.js @@ -1,5 +1,6 @@ import { guidFor } from '@ember/-internals/utils'; -import { descriptorForProperty, Mixin, nativeDescDecorator } from '@ember/-internals/metal'; +import { descriptorFor } from '@ember/-internals/meta'; +import { descriptor, Mixin } from '@ember/-internals/metal'; import { assert } from '@ember/debug'; import { hasDOM } from '@ember/-internals/browser-environment'; import { matches } from '../system/utils'; @@ -142,11 +143,11 @@ let mixin = { /** Returns the current DOM element for the view. - @property element - @type DOMElement - @public - */ - element: nativeDescDecorator({ + @property element + @type DOMElement + @public + */ + element: descriptor({ configurable: false, enumerable: false, get() { @@ -398,13 +399,13 @@ let mixin = { // tslint:disable-next-line:max-line-length assert( `You cannot use a computed property for the component's \`elementId\` (${this}).`, - descriptorForProperty(this, 'elementId') === undefined + descriptorFor(this, 'elementId') === undefined ); // tslint:disable-next-line:max-line-length assert( `You cannot use a computed property for the component's \`tagName\` (${this}).`, - descriptorForProperty(this, 'tagName') === undefined + descriptorFor(this, 'tagName') === undefined ); if (!this.elementId && this.tagName !== '') { diff --git a/packages/@ember/canary-features/index.ts b/packages/@ember/canary-features/index.ts index 64d33d7c205..a2603221be2 100644 --- a/packages/@ember/canary-features/index.ts +++ b/packages/@ember/canary-features/index.ts @@ -25,7 +25,6 @@ export const DEFAULT_FEATURES = { EMBER_GLIMMER_ANGLE_BRACKET_INVOCATION: true, EMBER_GLIMMER_ARRAY_HELPER: true, EMBER_ROUTING_BUILD_ROUTEINFO_METADATA: false, - EMBER_NATIVE_DECORATOR_SUPPORT: false, }; /** @@ -93,4 +92,3 @@ export const EMBER_GLIMMER_ARRAY_HELPER = featureValue(FEATURES.EMBER_GLIMMER_AR export const EMBER_ROUTING_BUILD_ROUTEINFO_METADATA = featureValue( FEATURES.EMBER_ROUTING_BUILD_ROUTEINFO_METADATA ); -export const EMBER_NATIVE_DECORATOR_SUPPORT = featureValue(FEATURES.EMBER_NATIVE_DECORATOR_SUPPORT); diff --git a/packages/@ember/controller/index.js b/packages/@ember/controller/index.js index a5e3f602888..f4e09d9f850 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 { inject as metalInject } from '@ember/-internals/metal'; import ControllerMixin from './lib/controller_mixin'; +import { InjectedProperty } from '@ember/-internals/metal'; /** @module @ember/controller @@ -40,11 +40,11 @@ const Controller = EmberObject.extend(ControllerMixin); @since 1.10.0 @param {String} name (optional) name of the controller to inject, defaults to the property's name - @return {ComputedDecorator} injection decorator instance + @return {Ember.InjectedProperty} injection descriptor instance @public */ -export function inject(nameOrDesc, options) { - return metalInject('controller', nameOrDesc, options); +export function inject(name, options) { + return new InjectedProperty('controller', name, options); } export default Controller; diff --git a/packages/@ember/object/index.js b/packages/@ember/object/index.js deleted file mode 100644 index 7f747f59cf5..00000000000 --- a/packages/@ember/object/index.js +++ /dev/null @@ -1,95 +0,0 @@ -import { EMBER_NATIVE_DECORATOR_SUPPORT } from '@ember/canary-features'; -import { assert } from '@ember/debug'; -import { assign } from '@ember/polyfills'; - -/** - Decorator that turns the target function into an Action - Adds an `actions` object to the target object and creates a passthrough - function that calls the original. This means the function still exists - on the original object, and can be used directly. - - ```js - export default class ActionDemoComponent extends Component { - @action - foo() { - // do something - } - } - ``` - ```hbs - - - ``` - - It also binds the function directly to the instance, so it can be used in any - context: - - ```hbs - - - ``` - @method computed - @for @ember/object - @static - @param {ElementDescriptor} elementDesc the descriptor of the element to decorate - @return {ElementDescriptor} the decorated descriptor - @private -*/ -export let action; - -if (EMBER_NATIVE_DECORATOR_SUPPORT) { - let BINDINGS_MAP = new WeakMap(); - - action = function action(elementDesc) { - assert( - 'The @action decorator must be applied to methods', - elementDesc && - elementDesc.kind === 'method' && - elementDesc.descriptor && - typeof elementDesc.descriptor.value === 'function' - ); - - let actionFn = elementDesc.descriptor.value; - - elementDesc.descriptor = { - get() { - let bindings = BINDINGS_MAP.get(this); - - if (bindings === undefined) { - bindings = new Map(); - BINDINGS_MAP.set(this, bindings); - } - - let fn = bindings.get(actionFn); - - if (fn === undefined) { - fn = actionFn.bind(this); - bindings.set(actionFn, fn); - } - - return fn; - }, - }; - - elementDesc.finisher = target => { - let { key } = elementDesc; - let { prototype } = target; - - if (typeof target.proto === 'function') { - target.proto(); - } - - if (!prototype.hasOwnProperty('actions')) { - let parentActions = prototype.actions; - // we need to assign because of the way mixins copy actions down when inheriting - prototype.actions = parentActions ? assign({}, parentActions) : {}; - } - - prototype.actions[key] = actionFn; - - return target; - }; - - return elementDesc; - }; -} diff --git a/packages/@ember/object/lib/computed/computed_macros.js b/packages/@ember/object/lib/computed/computed_macros.js index f904bfa07fa..7f3a11fe2b6 100644 --- a/packages/@ember/object/lib/computed/computed_macros.js +++ b/packages/@ember/object/lib/computed/computed_macros.js @@ -2,6 +2,7 @@ import { get, set, computed, + ComputedProperty, isEmpty, isNone, alias, @@ -37,18 +38,21 @@ function generateComputedWithPredicate(name, predicate) { return (...properties) => { let dependentKeys = expandPropertiesToArray(name, properties); - let computedFunc = computed(...dependentKeys, function() { - let lastIdx = dependentKeys.length - 1; + let computedFunc = new ComputedProperty( + 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]); - }); + return get(this, dependentKeys[lastIdx]); + }, + { dependentKeys } + ); 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 63aeef51ac7..4f5a864bd40 100644 --- a/packages/@ember/object/lib/computed/reduce_computed_macros.js +++ b/packages/@ember/object/lib/computed/reduce_computed_macros.js @@ -3,7 +3,13 @@ */ import { DEBUG } from '@glimmer/env'; import { assert } from '@ember/debug'; -import { get, computed, addObserver, removeObserver } from '@ember/-internals/metal'; +import { + get, + computed, + ComputedProperty, + 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) { @@ -12,13 +18,18 @@ function reduceMacro(dependentKey, callback, initialValue, name) { !/[\[\]\{\}]/g.test(dependentKey) ); - return computed(`${dependentKey}.[]`, function() { - let arr = get(this, dependentKey); - if (arr === null || typeof arr !== 'object') { - return initialValue; - } - return arr.reduce(callback, initialValue, this); - }).readOnly(); + 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; } function arrayMacro(dependentKey, additionalDependentKeys, callback) { @@ -48,9 +59,14 @@ function multiArrayMacro(_dependentKeys, callback, name) { ); let dependentKeys = _dependentKeys.map(key => `${key}.[]`); - return computed(...dependentKeys, function() { - return emberA(callback.call(this, _dependentKeys)); - }).readOnly(); + let cp = new ComputedProperty( + function() { + return emberA(callback.call(this, _dependentKeys)); + }, + { dependentKeys, readOnly: true } + ); + + return cp; } /** @@ -563,10 +579,15 @@ export function uniqBy(dependentKey, propertyKey) { !/[\[\]\{\}]/g.test(dependentKey) ); - return computed(`${dependentKey}.[]`, function() { - let list = get(this, dependentKey); - return isArray(list) ? uniqByArray(list, propertyKey) : emberA(); - }).readOnly(); + let cp = new ComputedProperty( + function() { + let list = get(this, dependentKey); + return isArray(list) ? uniqByArray(list, propertyKey) : emberA(); + }, + { dependentKeys: [`${dependentKey}.[]`], readOnly: true } + ); + + return cp; } /** @@ -716,19 +737,27 @@ export function setDiff(setAProperty, setBProperty) { !/[\[\]\{\}]/g.test(setAProperty) && !/[\[\]\{\}]/g.test(setBProperty) ); - return computed(`${setAProperty}.[]`, `${setBProperty}.[]`, function() { - let setA = this.get(setAProperty); - let setB = this.get(setBProperty); + let cp = new ComputedProperty( + function() { + let setA = this.get(setAProperty); + let setB = this.get(setBProperty); - if (!isArray(setA)) { - return emberA(); - } - if (!isArray(setB)) { - return emberA(setA); + if (!isArray(setA)) { + return emberA(); + } + if (!isArray(setB)) { + return emberA(setA); + } + + return setA.filter(x => setB.indexOf(x) === -1); + }, + { + dependentKeys: [`${setAProperty}.[]`, `${setBProperty}.[]`], + readOnly: true, } + ); - return setA.filter(x => setB.indexOf(x) === -1); - }).readOnly(); + return cp; } /** @@ -952,59 +981,68 @@ 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 activeObserversMap = new WeakMap(); - let sortPropertyDidChangeMap = new WeakMap(); - - return computed(`${sortPropertiesKey}.[]`, 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') - ); + 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); + }); + } - // Add/remove property observers as required. - let activeObservers = activeObserversMap.get(this); + let sortPropertyDidChange = sortPropertyDidChangeMap.get(this); - if (!sortPropertyDidChangeMap.has(this)) { - sortPropertyDidChangeMap.set(this, function() { - this.notifyPropertyChange(key); - }); - } + if (activeObservers !== undefined) { + activeObservers.forEach(path => removeObserver(this, path, sortPropertyDidChange)); + } - let sortPropertyDidChange = sortPropertyDidChangeMap.get(this); + 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; + }); + } - if (activeObservers !== undefined) { - activeObservers.forEach(path => removeObserver(this, path, sortPropertyDidChange)); - } + activeObserversMap.set(this, activeObservers); - 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; - }); - } + let items = itemsKeyIsAtThis ? this : get(this, itemsKey); + if (!isArray(items)) { + return emberA(); + } - activeObserversMap.set(this, activeObservers); + if (normalizedSortProperties.length === 0) { + return emberA(items.slice()); + } else { + return sortByNormalizedSortProperties(items, normalizedSortProperties); + } + }, + { dependentKeys: [`${sortPropertiesKey}.[]`], readOnly: true } + ); - let items = itemsKeyIsAtThis ? this : get(this, itemsKey); - if (!isArray(items)) { - return emberA(); - } + cp._activeObserverMap = undefined; + cp._sortPropertyDidChangeMap = undefined; - if (normalizedSortProperties.length === 0) { - return emberA(items.slice()); - } else { - return sortByNormalizedSortProperties(items, normalizedSortProperties); - } - }).readOnly(); + return cp; } function normalizeSortProperties(sortProperties) { diff --git a/packages/@ember/object/tests/action_test.js b/packages/@ember/object/tests/action_test.js deleted file mode 100644 index 11c13f55de4..00000000000 --- a/packages/@ember/object/tests/action_test.js +++ /dev/null @@ -1,234 +0,0 @@ -import { EMBER_NATIVE_DECORATOR_SUPPORT } from '@ember/canary-features'; -import { Component } from '@ember/-internals/glimmer'; -import { Object as EmberObject } from '@ember/-internals/runtime'; -import { moduleFor, RenderingTestCase, strip } from 'internal-test-helpers'; - -import { action } from '../index'; - -if (EMBER_NATIVE_DECORATOR_SUPPORT) { - moduleFor( - '@action decorator', - class extends RenderingTestCase { - '@test action decorator works with ES6 class'(assert) { - class FooComponent extends Component { - @action - foo() { - assert.ok(true, 'called!'); - } - } - - this.registerComponent('foo-bar', { - ComponentClass: FooComponent, - template: "", - }); - - this.render('{{foo-bar}}'); - - this.$('button').click(); - } - - '@test action decorator does not add actions to superclass'(assert) { - class Foo extends EmberObject { - @action - foo() { - // Do nothing - } - } - - class Bar extends Foo { - @action - bar() { - assert.ok(false, 'called'); - } - } - - let foo = Foo.create(); - let bar = Bar.create(); - - assert.equal(typeof foo.actions.foo, 'function', 'foo has foo action'); - assert.equal(typeof foo.actions.bar, 'undefined', 'foo does not have bar action'); - - assert.equal(typeof bar.actions.foo, 'function', 'bar has foo action'); - assert.equal(typeof bar.actions.bar, 'function', 'bar has bar action'); - } - - '@test actions are properly merged through traditional and ES6 prototype hierarchy'(assert) { - assert.expect(4); - - let FooComponent = Component.extend({ - actions: { - foo() { - assert.ok(true, 'foo called!'); - }, - }, - }); - - class BarComponent extends FooComponent { - @action - bar() { - assert.ok(true, 'bar called!'); - } - } - - let BazComponent = BarComponent.extend({ - actions: { - baz() { - assert.ok(true, 'baz called!'); - }, - }, - }); - - class QuxComponent extends BazComponent { - @action - qux() { - assert.ok(true, 'qux called!'); - } - } - - this.registerComponent('qux-component', { - ComponentClass: QuxComponent, - template: strip` - - - - - `, - }); - - this.render('{{qux-component}}'); - - this.$('button').click(); - } - - '@test action decorator super works with native class methods'(assert) { - class FooComponent extends Component { - foo() { - assert.ok(true, 'called!'); - } - } - - class BarComponent extends FooComponent { - @action - foo() { - super.foo(); - } - } - - this.registerComponent('bar-bar', { - ComponentClass: BarComponent, - template: "", - }); - - this.render('{{bar-bar}}'); - - this.$('button').click(); - } - - '@test action decorator super works with traditional class methods'(assert) { - let FooComponent = Component.extend({ - foo() { - assert.ok(true, 'called!'); - }, - }); - - class BarComponent extends FooComponent { - @action - foo() { - super.foo(); - } - } - - this.registerComponent('bar-bar', { - ComponentClass: BarComponent, - template: "", - }); - - this.render('{{bar-bar}}'); - - this.$('button').click(); - } - - '@test action decorator works with parent native class actions'(assert) { - class FooComponent extends Component { - @action - foo() { - assert.ok(true, 'called!'); - } - } - - class BarComponent extends FooComponent { - @action - foo() { - super.foo(); - } - } - - this.registerComponent('bar-bar', { - ComponentClass: BarComponent, - template: "", - }); - - this.render('{{bar-bar}}'); - - this.$('button').click(); - } - - '@test action decorator binds functions'(assert) { - class FooComponent extends Component { - bar = 'some value'; - - @action - foo() { - assert.equal(this.bar, 'some value', 'context bound correctly'); - } - } - - this.registerComponent('foo-bar', { - ComponentClass: FooComponent, - template: '', - }); - - this.render('{{foo-bar}}'); - - this.$('button').click(); - } - - '@test action decorator super works correctly when bound'(assert) { - class FooComponent extends Component { - bar = 'some value'; - - @action - foo() { - assert.equal(this.bar, 'some value', 'context bound correctly'); - } - } - - class BarComponent extends FooComponent { - @action - foo() { - super.foo(); - } - } - - this.registerComponent('bar-bar', { - ComponentClass: BarComponent, - template: '', - }); - - this.render('{{bar-bar}}'); - - this.$('button').click(); - } - - '@test action decorator throws an error if applied to non-methods'() { - expectAssertion(() => { - class TestObject extends EmberObject { - @action foo = 'bar'; - } - - new TestObject(); - }, /The @action decorator must be applied to methods/); - } - } - ); -} diff --git a/packages/@ember/service/index.js b/packages/@ember/service/index.js index dabf7fffcef..abe9a3ef0fa 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 { inject as metalInject } from '@ember/-internals/metal'; +import { InjectedProperty } from '@ember/-internals/metal'; /** @module @ember/service @@ -35,11 +35,11 @@ import { inject as metalInject } from '@ember/-internals/metal'; @for @ember/service @param {String} name (optional) name of the service to inject, defaults to the property's name - @return {ComputedDecorator} injection decorator instance + @return {Ember.InjectedProperty} injection descriptor instance @public */ -export function inject(nameOrDesc, options) { - return metalInject('service', nameOrDesc, options); +export function inject(name, options) { + return new InjectedProperty('service', name, options); } /** diff --git a/packages/ember/index.js b/packages/ember/index.js index b186fe246b9..736423efee8 100644 --- a/packages/ember/index.js +++ b/packages/ember/index.js @@ -28,8 +28,6 @@ import { } from '@ember/string'; import Service, { inject as injectService } from '@ember/service'; -import { action } from '@ember/object'; - import { and, bool, @@ -272,12 +270,9 @@ Object.defineProperty(Ember.run, 'currentRunLoop', { // in globals builds const computed = metal._globalsComputed; Ember.computed = computed; -Ember._descriptor = metal.nativeDescDecorator; -Ember._tracked = metal.tracked; computed.alias = metal.alias; -Ember.cacheFor = metal.getCachedValueFor; Ember.ComputedProperty = metal.ComputedProperty; -Ember._setComputedDecorator = metal.setComputedDecorator; +Ember.cacheFor = metal.getCachedValueFor; Ember.meta = meta; Ember.get = metal.get; Ember.getWithDefault = metal.getWithDefault; @@ -437,8 +432,6 @@ Ember._ProxyMixin = _ProxyMixin; Ember.RSVP = RSVP; Ember.Namespace = Namespace; -Ember._action = action; - computed.empty = empty; computed.notEmpty = notEmpty; computed.none = none; diff --git a/packages/ember/tests/reexports_test.js b/packages/ember/tests/reexports_test.js index 882914b7167..8e3043cdd0a 100644 --- a/packages/ember/tests/reexports_test.js +++ b/packages/ember/tests/reexports_test.js @@ -1,5 +1,5 @@ import Ember from '../index'; -import { FEATURES, EMBER_NATIVE_DECORATOR_SUPPORT } from '@ember/canary-features'; +import { FEATURES } from '@ember/canary-features'; import { confirmExport } from 'internal-test-helpers'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; import { jQueryDisabled, jQuery } from '@ember/-internals/views'; @@ -109,11 +109,8 @@ let allExports = [ // @ember/-internals/metal ['computed', '@ember/-internals/metal', '_globalsComputed'], - ['_descriptor', '@ember/-internals/metal', 'nativeDescDecorator'], - ['_tracked', '@ember/-internals/metal', 'tracked'], ['computed.alias', '@ember/-internals/metal', 'alias'], ['ComputedProperty', '@ember/-internals/metal'], - ['_setComputedDecorator', '@ember/-internals/metal', 'setComputedDecorator'], ['cacheFor', '@ember/-internals/metal', 'getCachedValueFor'], ['merge', '@ember/polyfills'], ['instrument', '@ember/instrumentation'], @@ -271,7 +268,6 @@ let allExports = [ '@ember/-internals/metal', { get: 'isNamespaceSearchDisabled', set: 'setNamespaceSearchDisabled' }, ], - EMBER_NATIVE_DECORATOR_SUPPORT ? ['_action', '@ember/object', 'action'] : null, ['computed.empty', '@ember/object/computed', 'empty'], ['computed.notEmpty', '@ember/object/computed', 'notEmpty'], ['computed.none', '@ember/object/computed', 'none'], diff --git a/packages/external-helpers/lib/external-helpers.js b/packages/external-helpers/lib/external-helpers.js index 55150bfbe12..004952f2c50 100644 --- a/packages/external-helpers/lib/external-helpers.js +++ b/packages/external-helpers/lib/external-helpers.js @@ -2,32 +2,6 @@ import { DEBUG } from '@glimmer/env'; const setPrototypeOf = Object.setPrototypeOf; -var nativeWrapperCache = new Map(); - -// Super minimal version of Babel's wrapNativeSuper. We only use this for -// extending Function, for ComputedDecoratorImpl and AliasDecoratorImpl. We know -// we will never directly create an instance of these classes so no need to -// include `construct` code or other helpers. -export function wrapNativeSuper(Class) { - if (nativeWrapperCache.has(Class)) { - return nativeWrapperCache.get(Class); - } - - function Wrapper() {} - Wrapper.prototype = Object.create(Class.prototype, { - constructor: { - value: Wrapper, - enumerable: false, - writable: true, - configurable: true, - }, - }); - - nativeWrapperCache.set(Class, Wrapper); - - return setPrototypeOf(Wrapper, Class); -} - export function classCallCheck(instance, Constructor) { if (DEBUG) { if (!(instance instanceof Constructor)) { diff --git a/tests/docs/expected.js b/tests/docs/expected.js index 3a9e419fca3..9274d51512d 100644 --- a/tests/docs/expected.js +++ b/tests/docs/expected.js @@ -292,7 +292,7 @@ module.exports = { 'isArray', 'isBlank', 'isBrowser', - 'isComputedDecorator', + 'isDescriptor', 'isDestroyed', 'isDestroying', 'isEmpty', @@ -492,7 +492,6 @@ module.exports = { 'serializeQueryParam', 'serializeQueryParamKey', 'set', - 'setComputedDecorator', 'setDiff', 'setEach', 'setEngineParent', diff --git a/yarn.lock b/yarn.lock index 426b91b5e92..ca06d639ac5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -75,17 +75,6 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.0.0" -"@babel/helper-create-class-features-plugin@^7.3.0": - version "7.3.2" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.3.2.tgz#ba1685603eb1c9f2f51c9106d5180135c163fe73" - integrity sha512-tdW8+V8ceh2US4GsYdNVNoohq5uVwOf9k6krjwW4E1lINcHgttnWcNqgdoessn12dAy8QkbezlbQh2nXISNY+A== - 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.2.3" - "@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" @@ -182,7 +171,7 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.0.0" -"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.2.3": +"@babel/helper-replace-supers@^7.1.0": version "7.2.3" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.2.3.tgz#19970020cf22677d62b3a689561dbd9644d8c5e5" integrity sha512-GyieIznGUfPXPWu0yLS6U55Mz67AZD9cUk0BfirOWlPrXlBcan9Gz+vHGz+cPfuoweZSnPzPIm67VtQM0OWZbA== @@ -235,16 +224,16 @@ 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" integrity sha512-M74+GvK4hn1eejD9lZ7967qAwvqTZayQa3g10ag4s9uewgR7TKjeaT0YMyoq+gVfKYABiWZ4MQD701/t5e1Jhg== +"@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/plugin-proposal-async-generator-functions@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e" @@ -254,23 +243,6 @@ "@babel/helper-remap-async-to-generator" "^7.1.0" "@babel/plugin-syntax-async-generators" "^7.2.0" -"@babel/plugin-proposal-class-properties@^7.3.3": - version "7.3.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.3.tgz#e69ee114a834a671293ace001708cc1682ed63f9" - integrity sha512-XO9eeU1/UwGPM8L+TjnQCykuVcXqaO5J1bkRPIygqZ/A2L1xVMJ9aZXrY31c0U4H2/LHKL4lbFQLsxktSrc/Ng== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.3.0" - "@babel/helper-plugin-utils" "^7.0.0" - -"@babel/plugin-proposal-decorators@^7.3.0": - version "7.3.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.3.0.tgz#637ba075fa780b1f75d08186e8fb4357d03a72a7" - integrity sha512-3W/oCUmsO43FmZIqermmq6TKaRSYhmh/vybPfVFwQWdSb8xwki38uAIvknCRzuyHRuYfCYmJzL9or1v0AffPjg== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.3.0" - "@babel/helper-plugin-utils" "^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" @@ -311,13 +283,6 @@ 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" @@ -642,32 +607,32 @@ "@babel/parser" "^7.1.2" "@babel/types" "^7.1.2" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.2.3": - version "7.2.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8" - integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw== +"@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" + integrity sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.2.2" + "@babel/generator" "^7.1.6" "@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" + "@babel/parser" "^7.1.6" + "@babel/types" "^7.1.6" 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" - integrity sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ== +"@babel/traverse@^7.2.3": + 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.1.6" + "@babel/generator" "^7.2.2" "@babel/helper-function-name" "^7.1.0" "@babel/helper-split-export-declaration" "^7.0.0" - "@babel/parser" "^7.1.6" - "@babel/types" "^7.1.6" + "@babel/parser" "^7.2.3" + "@babel/types" "^7.2.2" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.10" @@ -1231,18 +1196,6 @@ 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" @@ -3339,14 +3292,6 @@ 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"