diff --git a/.eslintrc.js b/.eslintrc.js index 78e38e6f2d7..acd91be167d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,6 +2,7 @@ const path = require('path'); module.exports = { root: true, + parser: 'babel-eslint', extends: [ 'eslint:recommended', 'prettier', diff --git a/broccoli/packages.js b/broccoli/packages.js index e250e1a30c5..3c1eb53edd6 100644 --- a/broccoli/packages.js +++ b/broccoli/packages.js @@ -16,6 +16,7 @@ const WriteFile = require('broccoli-file-creator'); const StringReplace = require('broccoli-string-replace'); const GlimmerTemplatePrecompiler = require('./glimmer-template-compiler'); const VERSION_PLACEHOLDER = /VERSION_STRING_PLACEHOLDER/g; +const transfromBabelPlugins = require('./transforms/transform-babel-plugins'); const debugTree = BroccoliDebug.buildDebugCallback('ember-source'); @@ -83,6 +84,13 @@ module.exports.getPackagesES = function getPackagesES() { exclude: ['**/*.ts'], }); + // tsc / typescript handles decorators and class properties on its own + // so for non ts, transpile the proposal features (decorators, etc) + let transpiledProposals = debugTree( + transfromBabelPlugins(debugTree(nonTypeScriptContents, `get-packages-es:babel-plugins:input`)), + `get-packages-es:babel-plugins:output` + ); + let typescriptContents = new Funnel(debuggedCompiledTemplatesAndTypeScript, { include: ['**/*.ts'], }); @@ -95,7 +103,7 @@ module.exports.getPackagesES = function getPackagesES() { let debuggedCompiledTypescript = debugTree(typescriptCompiled, `get-packages-es:ts:output`); - let mergedFinalOutput = new MergeTrees([nonTypeScriptContents, debuggedCompiledTypescript], { + let mergedFinalOutput = new MergeTrees([transpiledProposals, debuggedCompiledTypescript], { overwrite: true, }); diff --git a/broccoli/test-polyfills.js b/broccoli/test-polyfills.js new file mode 100644 index 00000000000..0c78c08ea33 --- /dev/null +++ b/broccoli/test-polyfills.js @@ -0,0 +1,20 @@ +const Rollup = require('broccoli-rollup'); +const writeFile = require('broccoli-file-creator'); +const resolve = require('rollup-plugin-node-resolve'); +const commonjs = require('rollup-plugin-commonjs'); + +module.exports = function polyfills() { + let polyfillEntry = writeFile('polyfill-entry.js', 'require("core-js/modules/es6.symbol");'); + + return new Rollup(polyfillEntry, { + rollup: { + input: 'polyfill-entry.js', + output: { + file: 'polyfill.js', + name: 'polyfill', + format: 'iife', + }, + plugins: [resolve(), commonjs()], + }, + }); +}; diff --git a/broccoli/transforms/transform-babel-plugins.js b/broccoli/transforms/transform-babel-plugins.js new file mode 100644 index 00000000000..e0e1b63dc59 --- /dev/null +++ b/broccoli/transforms/transform-babel-plugins.js @@ -0,0 +1,13 @@ +const Babel = require('broccoli-babel-transpiler'); + +module.exports = function(tree) { + let options = { + sourceMaps: true, + plugins: [ + ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true, legacy: false }], + ['@babel/plugin-proposal-class-properties'], + ], + }; + + return new Babel(tree, options); +}; diff --git a/ember-cli-build.js b/ember-cli-build.js index bd4165cbedd..b87394020c0 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -9,6 +9,7 @@ const bootstrapModule = require('./broccoli/bootstrap-modules'); const concatBundle = require('./broccoli/concat-bundle'); const concat = require('broccoli-concat'); const testIndexHTML = require('./broccoli/test-index-html'); +const testPolyfills = require('./broccoli/test-polyfills'); const toES5 = require('./broccoli/to-es5'); const toNamedAMD = require('./broccoli/to-named-amd'); const stripForProd = require('./broccoli/strip-for-prod'); @@ -141,6 +142,7 @@ module.exports = function() { nodeTests(), // test harness + testPolyfills(), testIndexHTML(), jquery(), qunit(), diff --git a/package.json b/package.json index 03dd5c5088e..f9feb5a44a4 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,8 @@ }, "devDependencies": { "@babel/helper-module-imports": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.2.1", + "@babel/plugin-proposal-decorators": "^7.2.0", "@babel/plugin-transform-arrow-functions": "^7.2.0", "@babel/plugin-transform-block-scoping": "^7.2.0", "@babel/plugin-transform-classes": "^7.2.2", @@ -101,6 +103,7 @@ "@types/rsvp": "^4.0.2", "auto-dist-tag": "^1.0.0", "aws-sdk": "^2.399.0", + "babel-eslint": "^10.0.1", "babel-plugin-debug-macros": "^0.3.0", "babel-plugin-filter-imports": "^2.0.4", "babel-plugin-module-resolver": "^3.1.3", @@ -118,6 +121,7 @@ "broccoli-typescript-compiler": "^4.0.1", "broccoli-uglify-sourcemap": "^2.2.0", "common-tags": "^1.8.0", + "core-js": "^2.6.3", "dag-map": "^2.0.2", "ember-cli": "^3.7.1", "ember-cli-blueprint-test-helpers": "^0.19.2", @@ -147,6 +151,8 @@ "prettier": "1.16.4", "puppeteer": "^1.12.2", "qunit": "^2.9.1", + "rollup-plugin-commonjs": "^9.2.0", + "rollup-plugin-node-resolve": "^4.0.0", "route-recognizer": "^0.3.4", "router_js": "^6.2.3", "rsvp": "^4.8.4", diff --git a/packages/@ember/-internals/glimmer/tests/integration/content-test.js b/packages/@ember/-internals/glimmer/tests/integration/content-test.js index d7fbf0c9554..0401203d63e 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/content-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/content-test.js @@ -6,6 +6,7 @@ import { set, computed } from '@ember/-internals/metal'; import { getDebugFunction, setDebugFunction } from '@ember/debug'; import { readOnly } from '@ember/object/computed'; import { Object as EmberObject, ObjectProxy } from '@ember/-internals/runtime'; +import { HAS_NATIVE_SYMBOL } from '@ember/-internals/utils'; import { constructStyleDeprecationMessage } from '@ember/-internals/views'; import { Component, SafeString, htmlSafe } from '../utils/helpers'; @@ -682,7 +683,7 @@ let GlimmerContentTestCases = new ContentTestGenerator([ [Object.create(null), EMPTY, 'an object with no toString'], ]); -if (typeof Symbol !== 'undefined') { +if (HAS_NATIVE_SYMBOL) { GlimmerContentTestCases.cases.push([Symbol('debug'), 'Symbol(debug)', 'a symbol']); } diff --git a/packages/@ember/-internals/meta/index.ts b/packages/@ember/-internals/meta/index.ts index 2083134cd48..11ebd60739c 100644 --- a/packages/@ember/-internals/meta/index.ts +++ b/packages/@ember/-internals/meta/index.ts @@ -1,8 +1,6 @@ export { counters, deleteMeta, - descriptorFor, - isDescriptor, Meta, meta, MetaCounters, diff --git a/packages/@ember/-internals/meta/lib/meta.ts b/packages/@ember/-internals/meta/lib/meta.ts index c26278813b7..bba6281a4d7 100644 --- a/packages/@ember/-internals/meta/lib/meta.ts +++ b/packages/@ember/-internals/meta/lib/meta.ts @@ -965,48 +965,6 @@ if (DEBUG) { meta._counters = counters; } -/** - Returns the CP descriptor assocaited with `obj` and `keyName`, if any. - - @method descriptorFor - @param {Object} obj the object to check - @param {String} keyName the key to check - @return {Descriptor} - @private -*/ -export function descriptorFor(obj: object, keyName: string, _meta?: Meta | null) { - assert('Cannot call `descriptorFor` on null', obj !== null); - assert('Cannot call `descriptorFor` on undefined', obj !== undefined); - assert( - `Cannot call \`descriptorFor\` on ${typeof obj}`, - typeof obj === 'object' || typeof obj === 'function' - ); - - let meta = _meta === undefined ? peekMeta(obj) : _meta; - - if (meta !== null) { - return meta.peekDescriptors(keyName); - } -} - -/** - Check whether a value is a CP descriptor. - - @method isDescriptor - @param {any} possibleDesc the value to check - @return {boolean} - @private -*/ -export function isDescriptor(possibleDesc: any | undefined | null): boolean { - // TODO make this return `possibleDesc is Descriptor` - return ( - possibleDesc !== undefined && - possibleDesc !== null && - typeof possibleDesc === 'object' && - possibleDesc.isDescriptor === true - ); -} - export { counters }; function indexOfListener( diff --git a/packages/@ember/-internals/metal/index.ts b/packages/@ember/-internals/metal/index.ts index 0733389a964..1b9e350a3f9 100644 --- a/packages/@ember/-internals/metal/index.ts +++ b/packages/@ember/-internals/metal/index.ts @@ -1,4 +1,4 @@ -export { default as computed, ComputedProperty, _globalsComputed } from './lib/computed'; +export { default as computed, _globalsComputed, ComputedProperty } from './lib/computed'; export { getCacheFor, getCachedValueFor, peekCacheFor } from './lib/computed_cache'; export { default as alias } from './lib/alias'; export { deprecateProperty } from './lib/deprecate_property'; @@ -28,7 +28,13 @@ export { overrideChains, PROPERTY_DID_CHANGE, } from './lib/property_events'; -export { defineProperty, Descriptor } from './lib/properties'; +export { defineProperty } from './lib/properties'; +export { + descriptorForProperty, + isComputedDecorator, + setComputedDecorator, + nativeDescDecorator, +} from './lib/decorator'; export { watchKey, unwatchKey } from './lib/watch_key'; export { ChainNode, finishChains, removeChainWatcher } from './lib/chains'; export { watchPath, unwatchPath } from './lib/watch_path'; @@ -40,10 +46,9 @@ export { default as expandProperties } from './lib/expand_properties'; export { addObserver, removeObserver } from './lib/observer'; export { Mixin, aliasMethod, mixin, observer, applyMixin } from './lib/mixin'; -export { default as InjectedProperty } from './lib/injected_property'; +export { default as inject, DEBUG_INJECTION_FUNCTIONS } from './lib/injected_property'; export { setHasViews, tagForProperty, tagFor, markObjectAsDirty } from './lib/tags'; export { default as runInTransaction, didRender, assertNotRendered } from './lib/transaction'; -export { default as descriptor } from './lib/descriptor'; export { tracked } from './lib/tracked'; export { diff --git a/packages/@ember/-internals/metal/lib/alias.ts b/packages/@ember/-internals/metal/lib/alias.ts index b8f3b6cdb1e..0cbea3a9efa 100644 --- a/packages/@ember/-internals/metal/lib/alias.ts +++ b/packages/@ember/-internals/metal/lib/alias.ts @@ -2,25 +2,51 @@ import { Meta, meta as metaFor } from '@ember/-internals/meta'; import { inspect } from '@ember/-internals/utils'; import { assert } from '@ember/debug'; import EmberError from '@ember/error'; -import { ComputedProperty } from './computed'; import { getCachedValueFor, getCacheFor } from './computed_cache'; import { addDependentKeys, - DescriptorWithDependentKeys, + ComputedDescriptor, + Decorator, + descriptorForDecorator, + makeComputedDecorator, removeDependentKeys, -} from './dependent_keys'; -import { defineProperty, Descriptor } from './properties'; +} from './decorator'; +import { defineProperty } from './properties'; import { get } from './property_get'; import { set } from './property_set'; const CONSUMED = Object.freeze({}); -export default function alias(altKey: string): AliasedProperty { - return new AliasedProperty(altKey); +export type AliasDecorator = Decorator & PropertyDecorator & AliasDecoratorImpl; + +export default function alias(altKey: string): AliasDecorator { + return makeComputedDecorator(new AliasedProperty(altKey), AliasDecoratorImpl) as AliasDecorator; +} + +// 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 class AliasedProperty extends Descriptor implements DescriptorWithDependentKeys { - readonly _dependentKeys: string[]; +export class AliasedProperty extends ComputedDescriptor { readonly altKey: string; constructor(altKey: string) { @@ -29,9 +55,10 @@ export class AliasedProperty extends Descriptor implements DescriptorWithDepende this._dependentKeys = [altKey]; } - setup(obj: object, keyName: string, meta: Meta): void { + setup(obj: object, keyName: string, propertyDesc: PropertyDescriptor, meta: Meta): void { assert(`Setting alias '${keyName}' on self`, this.altKey !== keyName); - super.setup(obj, keyName, meta); + super.setup(obj, keyName, propertyDesc, meta); + if (meta.peekWatching(keyName) > 0) { this.consume(obj, keyName, meta); } @@ -74,14 +101,12 @@ export class AliasedProperty extends Descriptor implements DescriptorWithDepende return set(obj, this.altKey, value); } - readOnly(): this { + readOnly(): void { this.set = AliasedProperty_readOnlySet; - return this; } - oneWay(): this { + oneWay(): void { this.set = AliasedProperty_oneWaySet; - return this; } } @@ -94,7 +119,3 @@ function AliasedProperty_oneWaySet(obj: object, keyName: string, value: any): an defineProperty(obj, keyName, null); return set(obj, keyName, value); } - -// Backwards compatibility with Ember Data. -(AliasedProperty.prototype as any)._meta = undefined; -(AliasedProperty.prototype as any).meta = ComputedProperty.prototype.meta; diff --git a/packages/@ember/-internals/metal/lib/chains.ts b/packages/@ember/-internals/metal/lib/chains.ts index 9a2e65383f9..3dbb5b74872 100644 --- a/packages/@ember/-internals/metal/lib/chains.ts +++ b/packages/@ember/-internals/metal/lib/chains.ts @@ -1,5 +1,6 @@ -import { descriptorFor, Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; +import { Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; import { getCachedValueFor } from './computed_cache'; +import { descriptorForProperty } from './decorator'; import { eachProxyFor } from './each_proxy'; import { get } from './property_get'; import { unwatchKey, watchKey } from './watch_key'; @@ -9,7 +10,7 @@ function isObject(obj: any): obj is object { } function isVolatile(obj: any, keyName: string, meta?: Meta | null): boolean { - let desc = descriptorFor(obj, keyName, meta); + let desc = descriptorForProperty(obj, keyName, meta); return !(desc !== undefined && desc._volatile === false); } diff --git a/packages/@ember/-internals/metal/lib/computed.ts b/packages/@ember/-internals/metal/lib/computed.ts index 9b9b50dc03a..433ef371471 100644 --- a/packages/@ember/-internals/metal/lib/computed.ts +++ b/packages/@ember/-internals/metal/lib/computed.ts @@ -1,4 +1,4 @@ -import { meta as metaFor, peekMeta } from '@ember/-internals/meta'; +import { Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; import { inspect, toString } from '@ember/-internals/utils'; import { EMBER_METAL_TRACKED_PROPERTIES } from '@ember/canary-features'; import { assert, deprecate, warn } from '@ember/debug'; @@ -12,18 +12,21 @@ import { } from './computed_cache'; import { addDependentKeys, - DescriptorWithDependentKeys, + ComputedDescriptor, + Decorator, + descriptorForDecorator, + makeComputedDecorator, removeDependentKeys, -} from './dependent_keys'; +} from './decorator'; import expandProperties from './expand_properties'; -import { defineProperty, Descriptor } from './properties'; +import { defineProperty } from './properties'; import { notifyPropertyChange } from './property_events'; import { set } from './property_set'; import { tagForProperty, update } from './tags'; import { getCurrentTracker, setCurrentTracker } from './tracked'; export type ComputedPropertyGetter = (keyName: string) => any; -export type ComputedPropertySetter = (keyName: string, value: any) => any; +export type ComputedPropertySetter = (keyName: string, value: any, cachedValue?: any) => any; export interface ComputedPropertyGetterAndSetter { get?: ComputedPropertyGetter; @@ -31,11 +34,6 @@ export interface ComputedPropertyGetterAndSetter { } export type ComputedPropertyConfig = ComputedPropertyGetter | ComputedPropertyGetterAndSetter; -export interface ComputedPropertyOptions { - dependentKeys?: string[]; - readOnly?: boolean; -} - /** @module @ember/object */ @@ -43,7 +41,6 @@ export interface ComputedPropertyOptions { const DEEP_EACH_REGEX = /\.@each\.[^.]+\./; function noop(): void {} - /** A computed property transforms an object literal with object's accessor function(s) into a property. @@ -153,77 +150,131 @@ function noop(): void {} @class ComputedProperty @public */ -class ComputedProperty extends Descriptor implements DescriptorWithDependentKeys { - private _meta: any | undefined; - private _volatile: boolean; - private _readOnly: boolean; - private _suspended: any; - _getter: ComputedPropertyGetter; - _setter?: ComputedPropertySetter; +export class ComputedProperty extends ComputedDescriptor { + private _volatile = false; + private _readOnly = false; + private _suspended: any = undefined; + private _hasConfig = false; + + _getter?: ComputedPropertyGetter = undefined; + _setter?: ComputedPropertySetter = undefined; _auto?: boolean; - _dependentKeys: string[] | undefined; - constructor(config: ComputedPropertyConfig, opts?: ComputedPropertyOptions) { + constructor(args: Array) { super(); - let hasGetterOnly = typeof config === 'function'; - if (hasGetterOnly) { - this._getter = config as ComputedPropertyGetter; - } else { - const objectConfig = config as ComputedPropertyGetterAndSetter; - assert( - 'computed expects a function or an object as last argument.', - typeof objectConfig === 'object' && !Array.isArray(objectConfig) - ); - assert( - 'Config object passed to computed can only contain `get` and `set` keys.', - Object.keys(objectConfig).every(key => key === 'get' || key === 'set') - ); - assert( - 'Computed properties must receive a getter or a setter, you passed none.', - Boolean(objectConfig.get) || Boolean(objectConfig.set) - ); - this._getter = objectConfig.get || noop; - this._setter = objectConfig.set; + + let maybeConfig = args[args.length - 1]; + + if ( + typeof maybeConfig === 'function' || + (maybeConfig !== null && typeof maybeConfig === 'object') + ) { + this._hasConfig = true; + let config = args.pop(); + + if (typeof config === 'function') { + this._getter = config as ComputedPropertyGetter; + } else { + const objectConfig = config as ComputedPropertyGetterAndSetter; + assert( + 'computed expects a function or an object as last argument.', + typeof objectConfig === 'object' && !Array.isArray(objectConfig) + ); + assert( + 'Config object passed to computed can only contain `get` and `set` keys.', + Object.keys(objectConfig).every(key => key === 'get' || key === 'set') + ); + assert( + 'Computed properties must receive a getter or a setter, you passed none.', + Boolean(objectConfig.get) || Boolean(objectConfig.set) + ); + this._getter = objectConfig.get || noop; + this._setter = objectConfig.set; + } } - this._suspended = undefined; - this._meta = undefined; - this._volatile = false; + if (args.length > 0) { + this._property(...(args as string[])); + } if (EMBER_METAL_TRACKED_PROPERTIES) { this._auto = false; } + } + + setup( + obj: object, + keyName: string, + propertyDesc: PropertyDescriptor & { initializer: any }, + meta: Meta + ) { + super.setup(obj, keyName, propertyDesc, meta); + + assert( + `@computed can only be used on accessors or fields, attempted to use it with ${keyName} but that was a method. Try converting it to a getter (e.g. \`get ${keyName}() {}\`)`, + !(propertyDesc && typeof propertyDesc.value === 'function') + ); - this._dependentKeys = opts && opts.dependentKeys; - this._readOnly = Boolean(opts) && hasGetterOnly && opts!.readOnly === true; + 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; + }; + } + } } /** Call on a computed property to set it into non-cached mode. When in this mode the computed property will not automatically cache the return value. - It also does not automatically fire any change events. You must manually notify any changes if you want to observe this property. - Dependency keys have no effect on volatile properties as they are for cache invalidation and notification when cached value is invalidated. - ```javascript import EmberObject, { computed } from '@ember/object'; - let outsideService = EmberObject.extend({ value: computed(function() { return OutsideService.getValue(); }).volatile() }).create(); ``` - @method volatile @return {ComputedProperty} this @chainable @public */ - volatile(): ComputedProperty { + volatile(): void { deprecate( 'Setting a computed property as volatile has been deprecated. Instead, consider using a native getter with native class syntax.', false, @@ -235,72 +286,59 @@ class ComputedProperty extends Descriptor implements DescriptorWithDependentKeys ); this._volatile = true; - return this; } /** Call on a computed property to set it into read-only mode. When in this mode the computed property will throw an error when set. - ```javascript import EmberObject, { computed } from '@ember/object'; - let Person = EmberObject.extend({ guid: computed(function() { return 'guid-guid-guid'; }).readOnly() }); - let person = Person.create(); - person.set('guid', 'new-guid'); // will throw an exception ``` - @method readOnly @return {ComputedProperty} this @chainable @public */ - readOnly(): ComputedProperty { + readOnly(): void { this._readOnly = true; assert( 'Computed properties that define a setter using the new syntax cannot be read-only', !(this._readOnly && this._setter && this._setter !== this._getter) ); - return this; } /** Sets the dependent keys on this computed property. Pass any number of arguments containing key paths that this computed property depends on. - ```javascript import EmberObject, { computed } from '@ember/object'; - let President = EmberObject.extend({ fullName: computed('firstName', 'lastName', function() { return this.get('firstName') + ' ' + this.get('lastName'); - // Tell Ember that this computed property depends on firstName // and lastName }) }); - let president = President.create({ firstName: 'Barack', lastName: 'Obama' }); - president.get('fullName'); // 'Barack Obama' ``` - @method property @param {String} path* zero or more property paths @return {ComputedProperty} this @chainable @public */ - property(...passedArgs: string[]): ComputedProperty { + property(...passedArgs: string[]): void { deprecate( 'Setting dependency keys using the `.property()` modifier has been deprecated. Pass the dependency keys directly to computed as arguments instead. If you are using `.property()` on a computed property macro, consider refactoring your macro to receive additional dependent keys in its initial declaration.', false, @@ -312,10 +350,9 @@ class ComputedProperty extends Descriptor implements DescriptorWithDependentKeys ); this._property(...passedArgs); - return this; } - _property(...passedArgs: string[]): ComputedProperty { + _property(...passedArgs: string[]): void { let args: string[] = []; function addArg(property: string): void { @@ -334,7 +371,6 @@ class ComputedProperty extends Descriptor implements DescriptorWithDependentKeys } this._dependentKeys = args; - return this; } /** @@ -342,37 +378,24 @@ class ComputedProperty extends Descriptor implements DescriptorWithDependentKeys 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 { @@ -396,7 +419,7 @@ class ComputedProperty extends Descriptor implements DescriptorWithDependentKeys get(obj: object, keyName: string): any { if (this._volatile) { - return this._getter.call(obj, keyName); + return this._getter!.call(obj, keyName); } let cache = getCacheFor(obj); @@ -431,7 +454,7 @@ class ComputedProperty extends Descriptor implements DescriptorWithDependentKeys tracker = setCurrentTracker(); } - let ret = this._getter.call(obj, keyName); + let ret = this._getter!.call(obj, keyName); if (EMBER_METAL_TRACKED_PROPERTIES) { setCurrentTracker(parent!); @@ -557,6 +580,41 @@ 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 @@ -642,21 +700,16 @@ if (EMBER_METAL_TRACKED_PROPERTIES) { @static @param {String} [dependentKeys*] Optional dependent keys that trigger this computed property. @param {Function} func The computed property function. - @return {ComputedProperty} property descriptor instance + @return {ComputedDecorator} property descriptor instance @public */ -export default function computed(...args: (string | ComputedPropertyConfig)[]): ComputedProperty { - let func = args.pop(); - - let cp = new ComputedProperty(func as ComputedPropertyConfig); - - if (args.length > 0) { - cp._property(...(args as string[])); - } - - return cp; +export function computed(...args: (string | ComputedPropertyConfig)[]): ComputedDecorator { + return makeComputedDecorator( + new ComputedProperty(args), + ComputedDecoratorImpl + ) as ComputedDecorator; } -// used for the Ember.computed global only + export const _globalsComputed = computed.bind(null); -export { ComputedProperty, computed }; +export default computed; diff --git a/packages/@ember/-internals/metal/lib/decorator.ts b/packages/@ember/-internals/metal/lib/decorator.ts new file mode 100644 index 00000000000..0a3608c4249 --- /dev/null +++ b/packages/@ember/-internals/metal/lib/decorator.ts @@ -0,0 +1,216 @@ +import { Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; +import { EMBER_NATIVE_DECORATOR_SUPPORT } from '@ember/canary-features'; +import { assert } from '@ember/debug'; +import { DEBUG } from '@glimmer/env'; +import { unwatch, watch } from './watching'; + +const DECORATOR_DESCRIPTOR_MAP: WeakMap = new WeakMap(); + +// https://tc39.github.io/proposal-decorators/#sec-elementdescriptor-specification-type +export interface ElementDescriptor { + descriptor: PropertyDescriptor & { initializer?: any }; + key: string; + kind: 'method' | 'field' | 'initializer'; + placement: 'own' | 'prototype' | 'static'; + initializer?: () => any; + finisher?: (obj: object, meta?: Meta) => any; +} + +export type Decorator = ( + desc: ElementDescriptor, + isClassicDecorator?: boolean +) => ElementDescriptor; + +// .......................................................... +// DESCRIPTOR +// + +/** + Returns the CP descriptor assocaited with `obj` and `keyName`, if any. + + @method descriptorFor + @param {Object} obj the object to check + @param {String} keyName the key to check + @return {Descriptor} + @private +*/ +export function descriptorForProperty(obj: object, keyName: string, _meta?: Meta | null) { + assert('Cannot call `descriptorFor` on null', obj !== null); + assert('Cannot call `descriptorFor` on undefined', obj !== undefined); + assert( + `Cannot call \`descriptorFor\` on ${typeof obj}`, + typeof obj === 'object' || typeof obj === 'function' + ); + + let meta = _meta === undefined ? peekMeta(obj) : _meta; + + if (meta !== null) { + return meta.peekDescriptors(keyName); + } +} + +export function descriptorForDecorator(dec: Decorator) { + return DECORATOR_DESCRIPTOR_MAP.get(dec); +} + +/** + Check whether a value is a decorator + + @method isComputedDecorator + @param {any} possibleDesc the value to check + @return {boolean} + @private +*/ +export function isComputedDecorator(dec: Decorator | null | undefined) { + 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: Decorator) { + DECORATOR_DESCRIPTOR_MAP.set(dec, true); +} + +// .......................................................... +// DEPENDENT KEYS +// + +export function addDependentKeys( + desc: ComputedDescriptor, + obj: object, + keyName: string, + meta: Meta +): void { + // the descriptor has a list of dependent keys, so + // add all of its dependent keys. + let depKeys = desc._dependentKeys; + if (depKeys === null || depKeys === undefined) { + return; + } + + for (let idx = 0; idx < depKeys.length; idx++) { + let depKey = depKeys[idx]; + // Increment the number of times depKey depends on keyName. + meta.writeDeps(depKey, keyName, meta.peekDeps(depKey, keyName) + 1); + // Watch the depKey + watch(obj, depKey, meta); + } +} + +export function removeDependentKeys( + desc: ComputedDescriptor, + obj: object, + keyName: string, + meta: Meta +): void { + // the descriptor has a list of dependent keys, so + // remove all of its dependent keys. + let depKeys = desc._dependentKeys; + if (depKeys === null || depKeys === undefined) { + return; + } + + for (let idx = 0; idx < depKeys.length; idx++) { + let depKey = depKeys[idx]; + // Decrement the number of times depKey depends on keyName. + meta.writeDeps(depKey, keyName, meta.peekDeps(depKey, keyName) - 1); + // Unwatch the depKey + unwatch(obj, depKey, meta); + } +} + +export function nativeDescDecorator(propertyDesc: PropertyDescriptor) { + let decorator = function(elementDesc: ElementDescriptor) { + elementDesc.descriptor = propertyDesc; + return elementDesc; + }; + + setComputedDecorator(decorator); + + return decorator; +} + +/** + Objects of this type can implement an interface to respond to requests to + get and set. The default implementation handles simple properties. + + @class Descriptor + @private +*/ +export abstract class ComputedDescriptor { + enumerable = true; + configurable = true; + _dependentKeys?: string[] = undefined; + _meta: any = undefined; + + setup(_obj: object, keyName: string, _propertyDesc: PropertyDescriptor, meta: Meta): void { + meta.writeDescriptors(keyName, this); + } + + teardown(_obj: object, keyName: string, meta: Meta): void { + meta.removeDescriptors(keyName); + } + + abstract get(obj: object, keyName: string): any | null | undefined; + abstract set(obj: object, keyName: string, value: any | null | undefined): any | null | undefined; + + willWatch?(obj: object, keyName: string, meta: Meta): void; + didUnwatch?(obj: object, keyName: string, meta: Meta): void; + + didChange?(obj: object, keyName: string): void; +} + +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 : isClassicDecorator + ); + + 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; + }; + + DECORATOR_DESCRIPTOR_MAP.set(decorator, desc); + + Object.setPrototypeOf(decorator, DecoratorClass.prototype); + + return decorator; +} diff --git a/packages/@ember/-internals/metal/lib/dependent_keys.ts b/packages/@ember/-internals/metal/lib/dependent_keys.ts index 8e4ee34de85..e69de29bb2d 100644 --- a/packages/@ember/-internals/metal/lib/dependent_keys.ts +++ b/packages/@ember/-internals/metal/lib/dependent_keys.ts @@ -1,54 +0,0 @@ -import { Meta } from '@ember/-internals/meta'; -import { unwatch, watch } from './watching'; - -export interface DescriptorWithDependentKeys { - _dependentKeys?: string[]; -} - -// .......................................................... -// DEPENDENT KEYS -// - -export function addDependentKeys( - desc: DescriptorWithDependentKeys, - obj: object, - keyName: string, - meta: Meta -): void { - // the descriptor has a list of dependent keys, so - // add all of its dependent keys. - let depKeys = desc._dependentKeys; - if (depKeys === null || depKeys === undefined) { - return; - } - - for (let idx = 0; idx < depKeys.length; idx++) { - let depKey = depKeys[idx]; - // Increment the number of times depKey depends on keyName. - meta.writeDeps(depKey, keyName, meta.peekDeps(depKey, keyName) + 1); - // Watch the depKey - watch(obj, depKey, meta); - } -} - -export function removeDependentKeys( - desc: DescriptorWithDependentKeys, - obj: object, - keyName: string, - meta: Meta -): void { - // the descriptor has a list of dependent keys, so - // remove all of its dependent keys. - let depKeys = desc._dependentKeys; - if (depKeys === null || depKeys === undefined) { - return; - } - - for (let idx = 0; idx < depKeys.length; idx++) { - let depKey = depKeys[idx]; - // Decrement the number of times depKey depends on keyName. - meta.writeDeps(depKey, keyName, meta.peekDeps(depKey, keyName) - 1); - // Unwatch the depKey - unwatch(obj, depKey, meta); - } -} diff --git a/packages/@ember/-internals/metal/lib/descriptor.ts b/packages/@ember/-internals/metal/lib/descriptor.ts deleted file mode 100644 index 1df6707818e..00000000000 --- a/packages/@ember/-internals/metal/lib/descriptor.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Meta } from '@ember/-internals/meta'; -import { Descriptor } from './properties'; - -export default function descriptor(desc: PropertyDescriptor): NativeDescriptor { - return new NativeDescriptor(desc); -} - -/** - A wrapper for a native ES5 descriptor. In an ideal world, we wouldn't need - this at all, however, the way we currently flatten/merge our mixins require - a special value to denote a descriptor. - - @class NativeDescriptor - @private -*/ -class NativeDescriptor extends Descriptor { - desc: PropertyDescriptor; - - constructor(desc: PropertyDescriptor) { - super(); - this.desc = desc; - this.enumerable = desc.enumerable !== false; - this.configurable = desc.configurable !== false; - } - - setup(obj: object, key: string, meta: Meta): void { - Object.defineProperty(obj, key, this.desc); - meta.writeDescriptors(key, this); - } - - get(obj: object, key: string): any { - return obj[key]; - } - - set(obj: object, key: string, value: any): any { - return (obj[key] = value); - } -} diff --git a/packages/@ember/-internals/metal/lib/injected_property.ts b/packages/@ember/-internals/metal/lib/injected_property.ts index ee8cc64a90b..1a0f590c943 100644 --- a/packages/@ember/-internals/metal/lib/injected_property.ts +++ b/packages/@ember/-internals/metal/lib/injected_property.ts @@ -1,10 +1,16 @@ -import { descriptorFor } from '@ember/-internals/meta'; import { getOwner } from '@ember/-internals/owner'; import { EMBER_MODULE_UNIFICATION } from '@ember/canary-features'; import { assert } from '@ember/debug'; -import { ComputedProperty } from './computed'; +import { DEBUG } from '@glimmer/env'; +import { computed } from './computed'; import { defineProperty } from './properties'; +export let DEBUG_INJECTION_FUNCTIONS: WeakMap; + +if (DEBUG) { + DEBUG_INJECTION_FUNCTIONS = new WeakMap(); +} + export interface InjectedPropertyOptions { source: string; } @@ -25,58 +31,48 @@ export interface InjectedPropertyOptions { to the property's name @private */ -export default class InjectedProperty extends ComputedProperty { - readonly type: string; - readonly name: string; - readonly source: string | undefined; - readonly namespace: string | undefined; +export default function inject(type: string, name?: string, options?: InjectedPropertyOptions) { + let source: string | undefined, namespace: string | undefined; - constructor(type: string, name: string, options?: InjectedPropertyOptions) { - super(injectedPropertyDesc); + if (EMBER_MODULE_UNIFICATION) { + source = options ? options.source : undefined; + namespace = undefined; - this.type = type; - this.name = name; + if (name !== undefined) { + let namespaceDelimiterOffset = name.indexOf('::'); - if (EMBER_MODULE_UNIFICATION) { - this.source = options ? options.source : undefined; - this.namespace = undefined; - - if (name) { - let namespaceDelimiterOffset = name.indexOf('::'); - if (namespaceDelimiterOffset === -1) { - this.name = name; - this.namespace = undefined; - } else { - this.name = name.slice(namespaceDelimiterOffset + 2); - this.namespace = name.slice(0, namespaceDelimiterOffset); - } + if (namespaceDelimiterOffset !== -1) { + namespace = name.slice(0, namespaceDelimiterOffset); + name = name.slice(namespaceDelimiterOffset + 2); } } } -} -const injectedPropertyDesc = { - get(this: any, keyName: string): any { - let desc = descriptorFor(this, keyName); + let getInjection = function getInjection(this: any, propertyName: string) { let owner = getOwner(this) || this.container; // fallback to `container` for backwards compat - assert( - `InjectedProperties should be defined with the inject computed property macros.`, - desc && desc.type - ); assert( `Attempting to lookup an injected property on an object without a container, ensure that the object was instantiated via a container.`, Boolean(owner) ); - let specifier = `${desc.type}:${desc.name || keyName}`; - return owner.lookup(specifier, { - source: desc.source, - namespace: desc.namespace, + return owner.lookup(`${type}:${name || propertyName}`, { source, namespace }); + }; + + if (DEBUG) { + DEBUG_INJECTION_FUNCTIONS.set(getInjection, { + namespace, + source, + type, + name, }); - }, + } - set(this: any, keyName: string, value: any) { - defineProperty(this, keyName, null, value); - }, -}; + return computed({ + get: getInjection, + + set(this: any, keyName: string, value: any) { + defineProperty(this, keyName, null, value); + }, + }); +} diff --git a/packages/@ember/-internals/metal/lib/mixin.ts b/packages/@ember/-internals/metal/lib/mixin.ts index ab374685281..df7837b8512 100644 --- a/packages/@ember/-internals/metal/lib/mixin.ts +++ b/packages/@ember/-internals/metal/lib/mixin.ts @@ -1,7 +1,7 @@ /** @module @ember/object */ -import { descriptorFor, Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; +import { Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; import { getListeners, getObservers, @@ -16,12 +16,23 @@ import { assert, deprecate } from '@ember/debug'; import { ALIAS_METHOD } from '@ember/deprecated-features'; import { assign } from '@ember/polyfills'; import { DEBUG } from '@glimmer/env'; -import { ComputedProperty, ComputedPropertyGetter, ComputedPropertySetter } from './computed'; +import { + ComputedDecorator, + ComputedProperty, + ComputedPropertyGetter, + ComputedPropertySetter, +} from './computed'; +import { + descriptorForDecorator, + descriptorForProperty, + isComputedDecorator, + makeComputedDecorator, +} from './decorator'; import { addListener, removeListener } from './events'; import expandProperties from './expand_properties'; import { classToString, setUnprocessedMixins } from './namespace_search'; import { addObserver, removeObserver } from './observer'; -import { defineProperty, Descriptor } from './properties'; +import { defineProperty } from './properties'; const a_concat = Array.prototype.concat; const { isArray } = Array; @@ -67,46 +78,68 @@ function concatenatedMixinProperties( return concats; } -function giveDescriptorSuper( +function giveDecoratorSuper( meta: Meta, key: string, - property: ComputedProperty, + decorator: ComputedDecorator, values: { [key: string]: any }, descs: { [key: string]: any }, base: object -): ComputedProperty { +): ComputedDecorator { + let property = descriptorForDecorator(decorator)!; let superProperty; + if (!(property 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 = descs[key]; + superProperty = descriptorForDecorator(descs[key]); } // If we didn't find the original descriptor in a parent mixin, find // it on the original object. if (!superProperty) { - superProperty = descriptorFor(base, key, meta); + superProperty = descriptorForProperty(base, key, meta); } if (superProperty === undefined || !(superProperty instanceof ComputedProperty)) { - return property; + return decorator; } - // Since multiple mixins may inherit from the same parent, we need - // to clone the computed property so that other mixins do not receive - // the wrapped version. - property = Object.create(property); - property._getter = wrap(property._getter, superProperty._getter) as ComputedPropertyGetter; + let get = wrap(property._getter!, superProperty._getter!) as ComputedPropertyGetter; + let set; + if (superProperty._setter) { if (property._setter) { - property._setter = wrap(property._setter, superProperty._setter) as ComputedPropertySetter; + set = wrap(property._setter, superProperty._setter) as ComputedPropertySetter; } else { - property._setter = superProperty._setter; + // If the super property has a setter, we default to using it no matter what. + // This is clearly very broken and weird, but it's what was here so we have + // to keep it until the next major at least. + // + // TODO: Add a deprecation here. + set = superProperty._setter; } + } else { + set = property._setter; } - return property; + // only create a new CP if we must + if (get !== property._getter || set !== property._setter) { + // Since multiple mixins may inherit from the same parent, we need + // to clone the computed property so that other mixins do not receive + // the wrapped version. + let newProperty = Object.create(property); + newProperty._getter = get; + newProperty._setter = set; + + return makeComputedDecorator(newProperty, ComputedProperty) as ComputedDecorator; + } + + return decorator; } function giveMethodSuper( @@ -126,7 +159,7 @@ function giveMethodSuper( // If we didn't find the original value in a parent mixin, find it in // the original object - if (superMethod === undefined && descriptorFor(obj, key) === undefined) { + if (superMethod === undefined && descriptorForProperty(obj, key) === undefined) { superMethod = obj[key]; } @@ -206,21 +239,16 @@ function applyMergedProperties( function addNormalizedProperty( base: any, key: string, - value: Descriptor | any, + value: any, meta: Meta, descs: { [key: string]: any }, values: { [key: string]: any }, concats?: string[], mergings?: string[] ): void { - if (value instanceof Descriptor) { - // Wrap descriptor function to implement - // _super() if needed - if ((value as ComputedProperty)._getter) { - value = giveDescriptorSuper(meta, key, value as ComputedProperty, values, descs, base); - } - - descs[key] = value; + if (isComputedDecorator(value)) { + // Wrap descriptor function to implement _super() if needed + descs[key] = giveDecoratorSuper(meta, key, value, values, descs, base); values[key] = undefined; } else { if ( @@ -323,7 +351,7 @@ if (ALIAS_METHOD) { if (desc !== undefined || value !== undefined) { // do nothing - } else if ((possibleDesc = descriptorFor(obj, altKey)) !== undefined) { + } else if ((possibleDesc = descriptorForProperty(obj, altKey)) !== undefined) { desc = possibleDesc; value = undefined; } else { @@ -409,7 +437,7 @@ export function applyMixin(obj: { [key: string]: any }, mixins: Mixin[]) { continue; } - if (descriptorFor(obj, key) !== undefined) { + if (descriptorForProperty(obj, key) !== undefined) { replaceObserversAndListeners(obj, key, null, value); } else { replaceObserversAndListeners(obj, key, obj[key], value); diff --git a/packages/@ember/-internals/metal/lib/properties.ts b/packages/@ember/-internals/metal/lib/properties.ts index 5d0096e3fcb..c20febff765 100644 --- a/packages/@ember/-internals/metal/lib/properties.ts +++ b/packages/@ember/-internals/metal/lib/properties.ts @@ -2,9 +2,16 @@ @module @ember/object */ -import { descriptorFor, Meta, meta as metaFor, peekMeta, UNDEFINED } from '@ember/-internals/meta'; +import { Meta, meta as metaFor, peekMeta, UNDEFINED } from '@ember/-internals/meta'; +import { EMBER_NATIVE_DECORATOR_SUPPORT } from '@ember/canary-features'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; +import { + Decorator, + descriptorForProperty, + ElementDescriptor, + isComputedDecorator, +} from './decorator'; import { overrideChains } from './property_events'; export type MandatorySetterFunction = ((this: object, value: any) => void) & { @@ -15,44 +22,6 @@ export type InheritingGetterFunction = ((this: object) => void) & { isInheritingGetter: true; }; -// .......................................................... -// DESCRIPTOR -// - -/** - Objects of this type can implement an interface to respond to requests to - get and set. The default implementation handles simple properties. - - @class Descriptor - @private -*/ -export abstract class Descriptor { - isDescriptor = true; - enumerable = true; - configurable = true; - - setup(obj: object, keyName: string, meta: Meta): void { - Object.defineProperty(obj, keyName, { - enumerable: this.enumerable, - configurable: this.configurable, - get: DESCRIPTOR_GETTER_FUNCTION(keyName, this), - }); - meta.writeDescriptors(keyName, this); - } - - teardown(_obj: object, keyName: string, meta: Meta): void { - meta.removeDescriptors(keyName); - } - - abstract get(obj: object, keyName: string): any | null | undefined; - abstract set(obj: object, keyName: string, value: any | null | undefined): any | null | undefined; - - willWatch?(obj: object, keyName: string, meta: Meta): void; - didUnwatch?(obj: object, keyName: string, meta: Meta): void; - - didChange?(obj: object, keyName: string): void; -} - interface ExtendedObject { didDefineProperty?: (obj: object, keyName: string, value: any) => void; } @@ -105,12 +74,6 @@ export function INHERITING_GETTER_FUNCTION(name: string): InheritingGetterFuncti }); } -function DESCRIPTOR_GETTER_FUNCTION(name: string, descriptor: Descriptor): () => any { - return function CPGETTER_FUNCTION(this: object): any { - return descriptor.get(this, name); - }; -} - /** NOTE: This is a low-level method used by other parts of the API. You almost never want to call this method directly. Instead you should use @@ -162,7 +125,7 @@ function DESCRIPTOR_GETTER_FUNCTION(name: string, descriptor: Descriptor): () => export function defineProperty( obj: object, keyName: string, - desc?: Descriptor | undefined | null, + desc?: Decorator | undefined | null, data?: any | undefined | null, meta?: Meta ): void { @@ -171,7 +134,7 @@ export function defineProperty( } let watching = meta.peekWatching(keyName) > 0; - let previousDesc = descriptorFor(obj, keyName, meta); + let previousDesc = descriptorForProperty(obj, keyName, meta); let wasDescriptor = previousDesc !== undefined; if (wasDescriptor) { @@ -191,9 +154,40 @@ export function defineProperty( } let value; - if (desc instanceof Descriptor) { + if (isComputedDecorator(desc)) { + let elementDesc: ElementDescriptor = { + key: keyName, + kind: 'field', + placement: 'own', + descriptor: { + value: undefined, + }, + }; + + if (DEBUG && !EMBER_NATIVE_DECORATOR_SUPPORT) { + 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 value = desc; - desc.setup(obj, keyName, meta); } else if (desc === undefined || desc === null) { value = data; diff --git a/packages/@ember/-internals/metal/lib/property_events.ts b/packages/@ember/-internals/metal/lib/property_events.ts index 7ab6f8173e7..da9ebf2013e 100644 --- a/packages/@ember/-internals/metal/lib/property_events.ts +++ b/packages/@ember/-internals/metal/lib/property_events.ts @@ -1,7 +1,8 @@ -import { descriptorFor, Meta, peekMeta } from '@ember/-internals/meta'; +import { Meta, peekMeta } from '@ember/-internals/meta'; import { symbol } from '@ember/-internals/utils'; import { DEBUG } from '@glimmer/env'; import changeEvent from './change_event'; +import { descriptorForProperty } from './decorator'; import { sendEvent } from './events'; import ObserverSet from './observer_set'; import { markObjectAsDirty } from './tags'; @@ -41,7 +42,7 @@ function notifyPropertyChange(obj: object, keyName: string, _meta?: Meta | null) return; } - let possibleDesc = descriptorFor(obj, keyName, meta); + let possibleDesc = descriptorForProperty(obj, keyName, meta); if (possibleDesc !== undefined && typeof possibleDesc.didChange === 'function') { possibleDesc.didChange(obj, keyName); @@ -112,7 +113,7 @@ function iterDeps( let possibleDesc; meta.forEachInDeps(depKey, (key: string) => { - possibleDesc = descriptorFor(obj, key, meta); + possibleDesc = descriptorForProperty(obj, key, meta); if (possibleDesc !== undefined && possibleDesc._suspended === obj) { return; diff --git a/packages/@ember/-internals/metal/lib/property_get.ts b/packages/@ember/-internals/metal/lib/property_get.ts index b22dcbd69c0..89290818fcc 100644 --- a/packages/@ember/-internals/metal/lib/property_get.ts +++ b/packages/@ember/-internals/metal/lib/property_get.ts @@ -1,11 +1,11 @@ /** @module @ember/object */ -import { descriptorFor } from '@ember/-internals/meta'; import { HAS_NATIVE_PROXY, symbol } from '@ember/-internals/utils'; import { EMBER_METAL_TRACKED_PROPERTIES } from '@ember/canary-features'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; +import { descriptorForProperty } from './decorator'; import { isPath } from './path_cache'; import { tagForProperty } from './tags'; import { getCurrentTracker } from './tracked'; @@ -108,7 +108,7 @@ export function get(obj: object, keyName: string): any { if (tracker) tracker.add(tagForProperty(obj, keyName)); } - let descriptor = descriptorFor(obj, keyName); + let descriptor = descriptorForProperty(obj, keyName); if (descriptor !== undefined) { return descriptor.get(obj, keyName); } diff --git a/packages/@ember/-internals/metal/lib/property_set.ts b/packages/@ember/-internals/metal/lib/property_set.ts index e9c874262b9..77f9c585d0c 100644 --- a/packages/@ember/-internals/metal/lib/property_set.ts +++ b/packages/@ember/-internals/metal/lib/property_set.ts @@ -1,8 +1,9 @@ -import { descriptorFor, Meta, peekMeta } from '@ember/-internals/meta'; +import { Meta, peekMeta } from '@ember/-internals/meta'; import { HAS_NATIVE_PROXY, lookupDescriptor, toString } from '@ember/-internals/utils'; import { assert } from '@ember/debug'; import EmberError from '@ember/error'; import { DEBUG } from '@glimmer/env'; +import { descriptorForProperty } from './decorator'; import { isPath } from './path_cache'; import { MandatorySetterFunction } from './properties'; import { notifyPropertyChange } from './property_events'; @@ -78,7 +79,7 @@ export function set(obj: object, keyName: string, value: any, tolerant?: boolean } let meta = peekMeta(obj); - let descriptor = descriptorFor(obj, keyName, meta); + let descriptor = descriptorForProperty(obj, keyName, meta); if (descriptor !== undefined) { descriptor.set(obj, keyName, value); diff --git a/packages/@ember/-internals/metal/lib/watch_key.ts b/packages/@ember/-internals/metal/lib/watch_key.ts index b2499a6fdbf..9191dda3ff2 100644 --- a/packages/@ember/-internals/metal/lib/watch_key.ts +++ b/packages/@ember/-internals/metal/lib/watch_key.ts @@ -1,13 +1,7 @@ -import { - descriptorFor, - isDescriptor, - Meta, - meta as metaFor, - peekMeta, - UNDEFINED, -} from '@ember/-internals/meta'; +import { Meta, meta as metaFor, peekMeta, UNDEFINED } from '@ember/-internals/meta'; import { lookupDescriptor } from '@ember/-internals/utils'; import { DEBUG } from '@glimmer/env'; +import { descriptorForProperty, isComputedDecorator } from './decorator'; import { DEFAULT_GETTER_FUNCTION, INHERITING_GETTER_FUNCTION, @@ -33,7 +27,7 @@ export function watchKey(obj: object, keyName: string, _meta?: Meta): void { if (count === 0) { // activate watching first time - let possibleDesc = descriptorFor(obj, keyName, meta); + let possibleDesc = descriptorForProperty(obj, keyName, meta); if (possibleDesc !== undefined && possibleDesc.willWatch !== undefined) { possibleDesc.willWatch(obj, keyName, meta); @@ -62,7 +56,7 @@ if (DEBUG) { let descriptor = lookupDescriptor(obj, keyName); let hasDescriptor = descriptor !== null; let possibleDesc = hasDescriptor && descriptor!.value; - if (isDescriptor(possibleDesc)) { + if (isComputedDecorator(possibleDesc)) { return; } let configurable = hasDescriptor ? descriptor!.configurable : true; @@ -102,7 +96,7 @@ export function unwatchKey(obj: object, keyName: string, _meta?: Meta): void { if (count === 1) { meta.writeWatching(keyName, 0); - let possibleDesc = descriptorFor(obj, keyName, meta); + let possibleDesc = descriptorForProperty(obj, keyName, meta); let isDescriptor = possibleDesc !== undefined; if (isDescriptor && possibleDesc.didUnwatch !== undefined) { diff --git a/packages/@ember/-internals/metal/tests/computed_decorator_test.js b/packages/@ember/-internals/metal/tests/computed_decorator_test.js new file mode 100644 index 00000000000..04702a76816 --- /dev/null +++ b/packages/@ember/-internals/metal/tests/computed_decorator_test.js @@ -0,0 +1,297 @@ +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./); + } + } + ); + + 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'); + } + } + ); +} 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 d524a067d5b..7f9ed038c8c 100644 --- a/packages/@ember/-internals/metal/tests/computed_test.js +++ b/packages/@ember/-internals/metal/tests/computed_test.js @@ -1,10 +1,9 @@ import { Object as EmberObject } from '@ember/-internals/runtime'; import { - ComputedProperty, computed, getCachedValueFor, - Descriptor, defineProperty, + isComputedDecorator, get, set, isWatching, @@ -19,28 +18,35 @@ moduleFor( 'computed', class extends AbstractTestCase { ['@test computed property should be an instance of descriptor'](assert) { - assert.ok(computed(function() {}) instanceof Descriptor); + assert.ok(isComputedDecorator(computed(function() {}))); } ['@test computed properties assert the presence of a getter or setter function']() { expectAssertion(function() { - computed('nogetternorsetter', {}); + let obj = {}; + defineProperty(obj, 'someProp', computed('nogetternorsetter', {})); }, 'Computed properties must receive a getter or a setter, you passed none.'); } ['@test computed properties check for the presence of a function or configuration object']() { expectAssertion(function() { - computed('nolastargument'); - }, 'computed expects a function or an object as last argument.'); + let obj = {}; + defineProperty(obj, 'someProp', computed('nolastargument')); + }, 'Attempted to use @computed on someProp, but it did not have a getter or a setter. You must either pass a get a function or getter/setter to @computed directly (e.g. `@computed({ get() { ... } })`) or apply @computed directly to a getter/setter'); } + // non valid properties are stripped away in the process of creating a computed property descriptor ['@test computed properties defined with an object only allow `get` and `set` keys']() { expectAssertion(function() { - computed({ - get() {}, - set() {}, - other() {}, + let obj = EmberObject.extend({ + someProp: computed({ + get() {}, + set() {}, + other() {}, + }), }); + + obj.create().someProp; }, 'Config object passed to computed can only contain `get` and `set` keys.'); } @@ -113,33 +119,39 @@ moduleFor( assert.equal(get(obj, 'foo'), 'computed bar', 'should return new value'); } - ['@test defining a computed property with a dependent key ending with @each is expanded to []']( - assert - ) { - let cp = computed('blazo.@each', function() {}); + // this should be a unit test elsewhere + // computed is more integration-like, and this test asserts on implementation details. + // ['@test defining a computed property with a dependent key ending with @each is expanded to []']( + // assert + // ) { + // let cp = computed('blazo.@each', function() {}); - assert.deepEqual(cp._dependentKeys, ['blazo.[]']); + // assert.deepEqual(cp._dependentKeys, ['blazo.[]']); - cp = computed('qux', 'zoopa.@each', function() {}); + // cp = computed('qux', 'zoopa.@each', function() {}); - assert.deepEqual(cp._dependentKeys, ['qux', 'zoopa.[]']); - } + // assert.deepEqual(cp._dependentKeys, ['qux', 'zoopa.[]']); + // } ['@test defining a computed property with a dependent key more than one level deep beyond @each is not supported']() { expectNoWarning(() => { - computed('todos', () => {}); + let obj = {}; + defineProperty(obj, 'someProp', computed('todos', () => {})); }); expectNoWarning(() => { - computed('todos.@each.owner', () => {}); + let obj = {}; + defineProperty(obj, 'someProp', computed('todos.@each.owner', () => {})); }); expectWarning(() => { - computed('todos.@each.owner.name', () => {}); + let obj = {}; + defineProperty(obj, 'someProp', computed('todos.@each.owner.name', () => {})); }, /You used the key "todos\.@each\.owner\.name" which is invalid\. /); expectWarning(() => { - computed('todos.@each.owner.@each.name', () => {}); + let obj = {}; + defineProperty(obj, 'someProp', computed('todos.@each.owner.@each.name', () => {})); }, /You used the key "todos\.@each\.owner\.@each\.name" which is invalid\. /); } } @@ -972,18 +984,23 @@ moduleFor( 'computed - readOnly', class extends AbstractTestCase { ['@test is chainable'](assert) { - let cp = computed(function() {}).readOnly(); + let cp = computed(function() {}); + let readOnlyCp = cp.readOnly(); - assert.ok(cp instanceof Descriptor); - assert.ok(cp instanceof ComputedProperty); + assert.equal(cp, readOnlyCp); } ['@test throws assertion if called over a CP with a setter defined with the new syntax']() { expectAssertion(() => { - computed({ - get() {}, - set() {}, - }).readOnly(); + let obj = {}; + defineProperty( + obj, + 'someProp', + computed({ + get() {}, + set() {}, + }).readOnly() + ); }, /Computed properties that define a setter using the new syntax cannot be read-only/); } diff --git a/packages/@ember/-internals/metal/tests/injected_property_test.js b/packages/@ember/-internals/metal/tests/injected_property_test.js index 6e21e546779..f51a5da4035 100644 --- a/packages/@ember/-internals/metal/tests/injected_property_test.js +++ b/packages/@ember/-internals/metal/tests/injected_property_test.js @@ -1,17 +1,17 @@ import { setOwner } from '@ember/-internals/owner'; -import { Descriptor, defineProperty, get, set, InjectedProperty } from '..'; +import { defineProperty, get, isComputedDecorator, set, inject } from '..'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; moduleFor( - 'InjectedProperty', + 'inject', class extends AbstractTestCase { ['@test injected properties should be descriptors'](assert) { - assert.ok(new InjectedProperty() instanceof Descriptor); + assert.ok(isComputedDecorator(inject())); } ['@test injected properties should be overridable'](assert) { let obj = {}; - defineProperty(obj, 'foo', new InjectedProperty()); + defineProperty(obj, 'foo', inject()); set(obj, 'foo', 'bar'); @@ -20,7 +20,7 @@ moduleFor( ['@test getting on an object without an owner or container should fail assertion']() { let obj = {}; - defineProperty(obj, 'foo', new InjectedProperty('type', 'name')); + defineProperty(obj, 'foo', inject('type', 'name')); expectAssertion(function() { get(obj, 'foo'); @@ -37,7 +37,7 @@ moduleFor( }, }; - defineProperty(obj, 'foo', new InjectedProperty('type', 'name')); + defineProperty(obj, 'foo', inject('type', 'name')); assert.equal(get(obj, 'foo'), 'type:name', 'should return the value of container.lookup'); } @@ -54,7 +54,7 @@ moduleFor( }, }); - defineProperty(obj, 'foo', new InjectedProperty('type', 'name')); + defineProperty(obj, 'foo', inject('type', 'name')); assert.equal(get(obj, 'foo'), 'type:name', 'should return the value of container.lookup'); } @@ -68,7 +68,7 @@ moduleFor( }, }); - defineProperty(obj, 'foo', new InjectedProperty('type')); + defineProperty(obj, 'foo', inject('type')); assert.equal(get(obj, 'foo'), 'type:foo', 'should lookup the type using the property name'); } diff --git a/packages/@ember/-internals/metal/tests/mixin/computed_test.js b/packages/@ember/-internals/metal/tests/mixin/computed_test.js index 071464c5d86..c64583e088b 100644 --- a/packages/@ember/-internals/metal/tests/mixin/computed_test.js +++ b/packages/@ember/-internals/metal/tests/mixin/computed_test.js @@ -12,6 +12,8 @@ moduleFor( let MixinA, MixinB, MixinC, MixinD; let obj; + window.testStarted = true; + MixinA = Mixin.create({ aProp: computed(function() { return 'A'; diff --git a/packages/@ember/-internals/metal/tests/descriptor_test.js b/packages/@ember/-internals/metal/tests/native_desc_decorator_test.js similarity index 89% rename from packages/@ember/-internals/metal/tests/descriptor_test.js rename to packages/@ember/-internals/metal/tests/native_desc_decorator_test.js index 6f80b92a71d..ea95c4727e4 100644 --- a/packages/@ember/-internals/metal/tests/descriptor_test.js +++ b/packages/@ember/-internals/metal/tests/native_desc_decorator_test.js @@ -1,5 +1,5 @@ import { Object as EmberObject } from '@ember/-internals/runtime'; -import { Mixin, defineProperty, descriptor } from '..'; +import { Mixin, defineProperty, nativeDescDecorator } from '..'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; let classes = [ @@ -150,12 +150,12 @@ let classes = [ classes.forEach(TestClass => { moduleFor( - TestClass.module('@ember/-internals/metal/descriptor'), + TestClass.module('@ember/-internals/metal/nativeDescDecorator'), class extends AbstractTestCase { ['@test defining a configurable property'](assert) { let factory = new TestClass(assert); - factory.install('foo', descriptor({ configurable: true, value: 'bar' }), assert); + factory.install('foo', nativeDescDecorator({ configurable: true, value: 'bar' }), assert); let obj = factory.finalize(); @@ -174,7 +174,7 @@ classes.forEach(TestClass => { ['@test defining a non-configurable property'](assert) { let factory = new TestClass(assert); - factory.install('foo', descriptor({ configurable: false, value: 'bar' }), assert); + factory.install('foo', nativeDescDecorator({ configurable: false, value: 'bar' }), assert); let obj = factory.finalize(); @@ -198,7 +198,7 @@ classes.forEach(TestClass => { ['@test defining an enumerable property'](assert) { let factory = new TestClass(assert); - factory.install('foo', descriptor({ enumerable: true, value: 'bar' }), assert); + factory.install('foo', nativeDescDecorator({ enumerable: true, value: 'bar' }), assert); let obj = factory.finalize(); @@ -211,7 +211,7 @@ classes.forEach(TestClass => { ['@test defining a non-enumerable property'](assert) { let factory = new TestClass(assert); - factory.install('foo', descriptor({ enumerable: false, value: 'bar' }), assert); + factory.install('foo', nativeDescDecorator({ enumerable: false, value: 'bar' }), assert); let obj = factory.finalize(); @@ -224,7 +224,7 @@ classes.forEach(TestClass => { ['@test defining a writable property'](assert) { let factory = new TestClass(assert); - factory.install('foo', descriptor({ writable: true, value: 'bar' }), assert); + factory.install('foo', nativeDescDecorator({ writable: true, value: 'bar' }), assert); let obj = factory.finalize(); @@ -243,7 +243,7 @@ classes.forEach(TestClass => { ['@test defining a non-writable property'](assert) { let factory = new TestClass(assert); - factory.install('foo', descriptor({ writable: false, value: 'bar' }), assert); + factory.install('foo', nativeDescDecorator({ writable: false, value: 'bar' }), assert); let obj = factory.finalize(); @@ -261,7 +261,7 @@ classes.forEach(TestClass => { let factory = new TestClass(assert); factory.install( 'foo', - descriptor({ + nativeDescDecorator({ get: function() { return this.__foo__; }, @@ -284,7 +284,7 @@ classes.forEach(TestClass => { let factory = new TestClass(assert); factory.install( 'foo', - descriptor({ + nativeDescDecorator({ set: function(value) { this.__foo__ = value; }, @@ -307,7 +307,7 @@ classes.forEach(TestClass => { let factory = new TestClass(assert); factory.install( 'foo', - descriptor({ + nativeDescDecorator({ get: function() { return this.__foo__; }, @@ -323,7 +323,7 @@ classes.forEach(TestClass => { factory.install( 'bar', - descriptor({ + nativeDescDecorator({ get: function() { return this.__bar__; }, @@ -339,7 +339,7 @@ classes.forEach(TestClass => { factory.install( 'fooBar', - descriptor({ + nativeDescDecorator({ get: function() { return this.foo + '-' + this.bar; }, diff --git a/packages/@ember/-internals/runtime/lib/system/core_object.js b/packages/@ember/-internals/runtime/lib/system/core_object.js index 788564e1c1e..87bc7487303 100644 --- a/packages/@ember/-internals/runtime/lib/system/core_object.js +++ b/packages/@ember/-internals/runtime/lib/system/core_object.js @@ -13,7 +13,7 @@ import { isInternalSymbol, } from '@ember/-internals/utils'; import { schedule } from '@ember/runloop'; -import { descriptorFor, meta, peekMeta, deleteMeta } from '@ember/-internals/meta'; +import { meta, peekMeta, deleteMeta } from '@ember/-internals/meta'; import { PROXY_CONTENT, finishChains, @@ -21,9 +21,10 @@ import { Mixin, applyMixin, defineProperty, - ComputedProperty, - InjectedProperty, + descriptorForProperty, classToString, + isComputedDecorator, + DEBUG_INJECTION_FUNCTIONS, } from '@ember/-internals/metal'; import ActionHandler from '../mixins/action_handler'; import { assert, deprecate } from '@ember/debug'; @@ -77,7 +78,7 @@ function initialize(obj, properties) { 'EmberObject.create no longer supports defining computed ' + 'properties. Define computed properties using extend() or reopen() ' + 'before calling create().', - !(value instanceof ComputedProperty) + !isComputedDecorator(value) ); assert( 'EmberObject.create no longer supports defining methods that call _super.', @@ -89,7 +90,7 @@ function initialize(obj, properties) { !(keyName === 'actions' && ActionHandler.detect(obj)) ); - let possibleDesc = descriptorFor(obj, keyName, m); + let possibleDesc = descriptorForProperty(obj, keyName, m); let isDescriptor = possibleDesc !== undefined; if (!isDescriptor) { @@ -936,7 +937,7 @@ class CoreObject { */ static metaForProperty(key) { let proto = this.proto(); // ensure prototype is initialized - let possibleDesc = descriptorFor(proto, key); + let possibleDesc = descriptorForProperty(proto, key); assert( `metaForProperty() could not find a computed property with key '${key}'.`, @@ -1066,11 +1067,11 @@ if (DEBUG) { let proto = this.proto(); for (let key in proto) { - let desc = descriptorFor(proto, key); - if (desc instanceof InjectedProperty) { + let desc = descriptorForProperty(proto, key); + if (desc && DEBUG_INJECTION_FUNCTIONS.has(desc._getter)) { assert( `Defining \`${key}\` as an injected controller property on a non-controller (\`${debugContainerKey}\`) is not allowed.`, - type === 'controller' || desc.type !== 'controller' + type === 'controller' || DEBUG_INJECTION_FUNCTIONS.get(desc._getter).type !== 'controller' ); } } @@ -1091,12 +1092,14 @@ if (DEBUG) { let desc; for (key in proto) { - desc = descriptorFor(proto, key); - if (desc instanceof InjectedProperty) { + desc = descriptorForProperty(proto, key); + if (desc && DEBUG_INJECTION_FUNCTIONS.has(desc._getter)) { + let { namespace, source, type, name } = DEBUG_INJECTION_FUNCTIONS.get(desc._getter); + injections[key] = { - namespace: desc.namespace, - source: desc.source, - specifier: `${desc.type}:${desc.name || key}`, + namespace, + source, + specifier: `${type}:${name || key}`, }; } } diff --git a/packages/@ember/-internals/runtime/tests/inject_test.js b/packages/@ember/-internals/runtime/tests/inject_test.js index 91747604105..79375e0fcc5 100644 --- a/packages/@ember/-internals/runtime/tests/inject_test.js +++ b/packages/@ember/-internals/runtime/tests/inject_test.js @@ -1,4 +1,4 @@ -import { InjectedProperty } from '@ember/-internals/metal'; +import { inject } from '@ember/-internals/metal'; import { DEBUG } from '@glimmer/env'; import EmberObject from '../lib/system/object'; import { buildOwner } from 'internal-test-helpers'; @@ -10,7 +10,7 @@ moduleFor( ['@test attempting to inject a nonexistent container key should error']() { let owner = buildOwner(); let AnObject = EmberObject.extend({ - foo: new InjectedProperty('bar', 'baz'), + foo: inject('bar', 'baz'), }); owner.register('foo:main', AnObject); @@ -23,8 +23,8 @@ moduleFor( ['@test factories should return a list of lazy injection full names'](assert) { if (DEBUG) { let AnObject = EmberObject.extend({ - foo: new InjectedProperty('foo', 'bar'), - bar: new InjectedProperty('quux'), + foo: inject('foo', 'bar'), + bar: inject('quux'), }); assert.deepEqual( diff --git a/packages/@ember/-internals/utils/tests/guid_for_test.js b/packages/@ember/-internals/utils/tests/guid_for_test.js index f38259f290b..3bfa49810cc 100644 --- a/packages/@ember/-internals/utils/tests/guid_for_test.js +++ b/packages/@ember/-internals/utils/tests/guid_for_test.js @@ -1,4 +1,4 @@ -import { guidFor } from '..'; +import { guidFor, HAS_NATIVE_SYMBOL } from '..'; import { moduleFor, AbstractTestCase as TestCase } from 'internal-test-helpers'; function sameGuid(assert, a, b, message) { @@ -49,7 +49,7 @@ moduleFor( } ['@test symbols'](assert) { - if (typeof Symbol === 'undefined') { + if (HAS_NATIVE_SYMBOL) { assert.ok(true, 'symbols are not supported on this browser'); return; } diff --git a/packages/@ember/-internals/views/lib/mixins/child_views_support.js b/packages/@ember/-internals/views/lib/mixins/child_views_support.js index 1cd92bff43a..1af78716c2f 100644 --- a/packages/@ember/-internals/views/lib/mixins/child_views_support.js +++ b/packages/@ember/-internals/views/lib/mixins/child_views_support.js @@ -1,7 +1,7 @@ /** @module ember */ -import { Mixin, descriptor } from '@ember/-internals/metal'; +import { Mixin, nativeDescDecorator } from '@ember/-internals/metal'; import { getChildViews, addChildView } from '../system/utils'; export default Mixin.create({ @@ -13,7 +13,7 @@ export default Mixin.create({ @default [] @private */ - childViews: descriptor({ + childViews: nativeDescDecorator({ configurable: false, enumerable: false, get() { diff --git a/packages/@ember/-internals/views/lib/mixins/class_names_support.js b/packages/@ember/-internals/views/lib/mixins/class_names_support.js index e03fa190b80..0beaa12dd5b 100644 --- a/packages/@ember/-internals/views/lib/mixins/class_names_support.js +++ b/packages/@ember/-internals/views/lib/mixins/class_names_support.js @@ -1,8 +1,7 @@ /** @module ember */ -import { descriptorFor } from '@ember/-internals/meta'; -import { Mixin } from '@ember/-internals/metal'; +import { descriptorForProperty, Mixin } from '@ember/-internals/metal'; import { assert } from '@ember/debug'; const EMPTY_ARRAY = Object.freeze([]); @@ -20,12 +19,12 @@ export default Mixin.create({ assert( `Only arrays are allowed for 'classNameBindings'`, - descriptorFor(this, 'classNameBindings') === undefined && + descriptorForProperty(this, 'classNameBindings') === undefined && Array.isArray(this.classNameBindings) ); assert( `Only arrays of static class strings are allowed for 'classNames'. For dynamic classes, use 'classNameBindings'.`, - descriptorFor(this, 'classNames') === undefined && Array.isArray(this.classNames) + descriptorForProperty(this, 'classNames') === undefined && Array.isArray(this.classNames) ); }, diff --git a/packages/@ember/-internals/views/lib/mixins/view_support.js b/packages/@ember/-internals/views/lib/mixins/view_support.js index 4b12662c7f6..4aeed30daea 100644 --- a/packages/@ember/-internals/views/lib/mixins/view_support.js +++ b/packages/@ember/-internals/views/lib/mixins/view_support.js @@ -1,6 +1,5 @@ import { guidFor } from '@ember/-internals/utils'; -import { descriptorFor } from '@ember/-internals/meta'; -import { descriptor, Mixin } from '@ember/-internals/metal'; +import { descriptorForProperty, Mixin, nativeDescDecorator } from '@ember/-internals/metal'; import { assert } from '@ember/debug'; import { hasDOM } from '@ember/-internals/browser-environment'; import { matches } from '../system/utils'; @@ -143,11 +142,11 @@ let mixin = { /** Returns the current DOM element for the view. - @property element - @type DOMElement - @public - */ - element: descriptor({ + @property element + @type DOMElement + @public + */ + element: nativeDescDecorator({ configurable: false, enumerable: false, get() { @@ -399,13 +398,13 @@ let mixin = { // tslint:disable-next-line:max-line-length assert( `You cannot use a computed property for the component's \`elementId\` (${this}).`, - descriptorFor(this, 'elementId') === undefined + descriptorForProperty(this, 'elementId') === undefined ); // tslint:disable-next-line:max-line-length assert( `You cannot use a computed property for the component's \`tagName\` (${this}).`, - descriptorFor(this, 'tagName') === undefined + descriptorForProperty(this, 'tagName') === undefined ); if (!this.elementId && this.tagName !== '') { diff --git a/packages/@ember/canary-features/index.ts b/packages/@ember/canary-features/index.ts index 992c99b629f..0512a60d074 100644 --- a/packages/@ember/canary-features/index.ts +++ b/packages/@ember/canary-features/index.ts @@ -25,6 +25,7 @@ export const DEFAULT_FEATURES = { EMBER_GLIMMER_ANGLE_BRACKET_INVOCATION: true, EMBER_GLIMMER_ARRAY_HELPER: true, EMBER_ROUTING_BUILD_ROUTEINFO_METADATA: null, + EMBER_NATIVE_DECORATOR_SUPPORT: null, }; /** @@ -92,3 +93,4 @@ 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 f4e09d9f850..f31947f957b 100644 --- a/packages/@ember/controller/index.js +++ b/packages/@ember/controller/index.js @@ -1,6 +1,6 @@ import { Object as EmberObject } from '@ember/-internals/runtime'; import ControllerMixin from './lib/controller_mixin'; -import { InjectedProperty } from '@ember/-internals/metal'; +import { inject as metalInject } from '@ember/-internals/metal'; /** @module @ember/controller @@ -44,7 +44,7 @@ const Controller = EmberObject.extend(ControllerMixin); @public */ export function inject(name, options) { - return new InjectedProperty('controller', name, options); + return metalInject('controller', name, options); } export default Controller; diff --git a/packages/@ember/object/index.js b/packages/@ember/object/index.js new file mode 100644 index 00000000000..7f747f59cf5 --- /dev/null +++ b/packages/@ember/object/index.js @@ -0,0 +1,95 @@ +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 7f3a11fe2b6..f904bfa07fa 100644 --- a/packages/@ember/object/lib/computed/computed_macros.js +++ b/packages/@ember/object/lib/computed/computed_macros.js @@ -2,7 +2,6 @@ import { get, set, computed, - ComputedProperty, isEmpty, isNone, alias, @@ -38,21 +37,18 @@ function generateComputedWithPredicate(name, predicate) { return (...properties) => { let dependentKeys = expandPropertiesToArray(name, properties); - let computedFunc = new ComputedProperty( - function() { - let lastIdx = dependentKeys.length - 1; + let computedFunc = computed(...dependentKeys, function() { + let lastIdx = dependentKeys.length - 1; - for (let i = 0; i < lastIdx; i++) { - let value = get(this, dependentKeys[i]); - if (!predicate(value)) { - return value; - } + for (let i = 0; i < lastIdx; i++) { + let value = get(this, dependentKeys[i]); + if (!predicate(value)) { + return value; } + } - return get(this, dependentKeys[lastIdx]); - }, - { dependentKeys } - ); + return get(this, dependentKeys[lastIdx]); + }); return computedFunc; }; diff --git a/packages/@ember/object/lib/computed/reduce_computed_macros.js b/packages/@ember/object/lib/computed/reduce_computed_macros.js index 4f5a864bd40..63aeef51ac7 100644 --- a/packages/@ember/object/lib/computed/reduce_computed_macros.js +++ b/packages/@ember/object/lib/computed/reduce_computed_macros.js @@ -3,13 +3,7 @@ */ import { DEBUG } from '@glimmer/env'; import { assert } from '@ember/debug'; -import { - get, - computed, - ComputedProperty, - addObserver, - removeObserver, -} from '@ember/-internals/metal'; +import { get, computed, addObserver, removeObserver } from '@ember/-internals/metal'; import { compare, isArray, A as emberA, uniqBy as uniqByArray } from '@ember/-internals/runtime'; function reduceMacro(dependentKey, callback, initialValue, name) { @@ -18,18 +12,13 @@ function reduceMacro(dependentKey, callback, initialValue, name) { !/[\[\]\{\}]/g.test(dependentKey) ); - let cp = new ComputedProperty( - function() { - let arr = get(this, dependentKey); - if (arr === null || typeof arr !== 'object') { - return initialValue; - } - return arr.reduce(callback, initialValue, this); - }, - { dependentKeys: [`${dependentKey}.[]`], readOnly: true } - ); - - return cp; + return computed(`${dependentKey}.[]`, function() { + let arr = get(this, dependentKey); + if (arr === null || typeof arr !== 'object') { + return initialValue; + } + return arr.reduce(callback, initialValue, this); + }).readOnly(); } function arrayMacro(dependentKey, additionalDependentKeys, callback) { @@ -59,14 +48,9 @@ function multiArrayMacro(_dependentKeys, callback, name) { ); let dependentKeys = _dependentKeys.map(key => `${key}.[]`); - let cp = new ComputedProperty( - function() { - return emberA(callback.call(this, _dependentKeys)); - }, - { dependentKeys, readOnly: true } - ); - - return cp; + return computed(...dependentKeys, function() { + return emberA(callback.call(this, _dependentKeys)); + }).readOnly(); } /** @@ -579,15 +563,10 @@ export function uniqBy(dependentKey, propertyKey) { !/[\[\]\{\}]/g.test(dependentKey) ); - let cp = new ComputedProperty( - function() { - let list = get(this, dependentKey); - return isArray(list) ? uniqByArray(list, propertyKey) : emberA(); - }, - { dependentKeys: [`${dependentKey}.[]`], readOnly: true } - ); - - return cp; + return computed(`${dependentKey}.[]`, function() { + let list = get(this, dependentKey); + return isArray(list) ? uniqByArray(list, propertyKey) : emberA(); + }).readOnly(); } /** @@ -737,27 +716,19 @@ export function setDiff(setAProperty, setBProperty) { !/[\[\]\{\}]/g.test(setAProperty) && !/[\[\]\{\}]/g.test(setBProperty) ); - let cp = new ComputedProperty( - function() { - let setA = this.get(setAProperty); - let setB = this.get(setBProperty); + return computed(`${setAProperty}.[]`, `${setBProperty}.[]`, function() { + let setA = this.get(setAProperty); + let setB = this.get(setBProperty); - if (!isArray(setA)) { - return emberA(); - } - if (!isArray(setB)) { - return emberA(setA); - } - - return setA.filter(x => setB.indexOf(x) === -1); - }, - { - dependentKeys: [`${setAProperty}.[]`, `${setBProperty}.[]`], - readOnly: true, + if (!isArray(setA)) { + return emberA(); + } + if (!isArray(setB)) { + return emberA(setA); } - ); - return cp; + return setA.filter(x => setB.indexOf(x) === -1); + }).readOnly(); } /** @@ -981,68 +952,59 @@ function customSort(itemsKey, additionalDependentKeys, comparator) { // This one needs to dynamically set up and tear down observers on the itemsKey // depending on the sortProperties function propertySort(itemsKey, sortPropertiesKey) { - let cp = new ComputedProperty( - function(key) { - let sortProperties = get(this, sortPropertiesKey); - - assert( - `The sort definition for '${key}' on ${this} must be a function or an array of strings`, - isArray(sortProperties) && sortProperties.every(s => typeof s === 'string') - ); - - // Add/remove property observers as required. - let activeObserversMap = cp._activeObserverMap || (cp._activeObserverMap = new WeakMap()); - let activeObservers = activeObserversMap.get(this); - - let sortPropertyDidChangeMap = - cp._sortPropertyDidChangeMap || (cp._sortPropertyDidChangeMap = new WeakMap()); - - if (!sortPropertyDidChangeMap.has(this)) { - sortPropertyDidChangeMap.set(this, function() { - this.notifyPropertyChange(key); - }); - } + let activeObserversMap = new WeakMap(); + let sortPropertyDidChangeMap = new WeakMap(); - let sortPropertyDidChange = sortPropertyDidChangeMap.get(this); + return computed(`${sortPropertiesKey}.[]`, function(key) { + let sortProperties = get(this, sortPropertiesKey); - if (activeObservers !== undefined) { - activeObservers.forEach(path => removeObserver(this, path, sortPropertyDidChange)); - } + assert( + `The sort definition for '${key}' on ${this} must be a function or an array of strings`, + isArray(sortProperties) && sortProperties.every(s => typeof s === 'string') + ); - let itemsKeyIsAtThis = itemsKey === '@this'; - let normalizedSortProperties = normalizeSortProperties(sortProperties); - if (normalizedSortProperties.length === 0) { - let path = itemsKeyIsAtThis ? `[]` : `${itemsKey}.[]`; - addObserver(this, path, sortPropertyDidChange); - activeObservers = [path]; - } else { - activeObservers = normalizedSortProperties.map(([prop]) => { - let path = itemsKeyIsAtThis ? `@each.${prop}` : `${itemsKey}.@each.${prop}`; - addObserver(this, path, sortPropertyDidChange); - return path; - }); - } + // Add/remove property observers as required. + let activeObservers = activeObserversMap.get(this); - activeObserversMap.set(this, activeObservers); + if (!sortPropertyDidChangeMap.has(this)) { + sortPropertyDidChangeMap.set(this, function() { + this.notifyPropertyChange(key); + }); + } - let items = itemsKeyIsAtThis ? this : get(this, itemsKey); - if (!isArray(items)) { - return emberA(); - } + let sortPropertyDidChange = sortPropertyDidChangeMap.get(this); - if (normalizedSortProperties.length === 0) { - return emberA(items.slice()); - } else { - return sortByNormalizedSortProperties(items, normalizedSortProperties); - } - }, - { dependentKeys: [`${sortPropertiesKey}.[]`], readOnly: true } - ); + if (activeObservers !== undefined) { + activeObservers.forEach(path => removeObserver(this, path, sortPropertyDidChange)); + } + + let itemsKeyIsAtThis = itemsKey === '@this'; + let normalizedSortProperties = normalizeSortProperties(sortProperties); + if (normalizedSortProperties.length === 0) { + let path = itemsKeyIsAtThis ? `[]` : `${itemsKey}.[]`; + addObserver(this, path, sortPropertyDidChange); + activeObservers = [path]; + } else { + activeObservers = normalizedSortProperties.map(([prop]) => { + let path = itemsKeyIsAtThis ? `@each.${prop}` : `${itemsKey}.@each.${prop}`; + addObserver(this, path, sortPropertyDidChange); + return path; + }); + } - cp._activeObserverMap = undefined; - cp._sortPropertyDidChangeMap = undefined; + activeObserversMap.set(this, activeObservers); - return cp; + let items = itemsKeyIsAtThis ? this : get(this, itemsKey); + if (!isArray(items)) { + return emberA(); + } + + if (normalizedSortProperties.length === 0) { + return emberA(items.slice()); + } else { + return sortByNormalizedSortProperties(items, normalizedSortProperties); + } + }).readOnly(); } function normalizeSortProperties(sortProperties) { diff --git a/packages/@ember/object/tests/action_test.js b/packages/@ember/object/tests/action_test.js new file mode 100644 index 00000000000..11c13f55de4 --- /dev/null +++ b/packages/@ember/object/tests/action_test.js @@ -0,0 +1,234 @@ +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 abe9a3ef0fa..90155e2008e 100644 --- a/packages/@ember/service/index.js +++ b/packages/@ember/service/index.js @@ -1,5 +1,5 @@ import { Object as EmberObject } from '@ember/-internals/runtime'; -import { InjectedProperty } from '@ember/-internals/metal'; +import { inject as metalInject } from '@ember/-internals/metal'; /** @module @ember/service @@ -39,7 +39,7 @@ import { InjectedProperty } from '@ember/-internals/metal'; @public */ export function inject(name, options) { - return new InjectedProperty('service', name, options); + return metalInject('service', name, options); } /** diff --git a/packages/ember/index.js b/packages/ember/index.js index 736423efee8..34a22d63a8f 100644 --- a/packages/ember/index.js +++ b/packages/ember/index.js @@ -28,6 +28,8 @@ import { } from '@ember/string'; import Service, { inject as injectService } from '@ember/service'; +import { action } from '@ember/object'; + import { and, bool, @@ -271,8 +273,9 @@ Object.defineProperty(Ember.run, 'currentRunLoop', { const computed = metal._globalsComputed; Ember.computed = computed; computed.alias = metal.alias; -Ember.ComputedProperty = metal.ComputedProperty; Ember.cacheFor = metal.getCachedValueFor; +Ember.ComputedProperty = metal.ComputedProperty; +Ember._setComputedDecorator = metal.setComputedDecorator; Ember.meta = meta; Ember.get = metal.get; Ember.getWithDefault = metal.getWithDefault; @@ -432,6 +435,8 @@ 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 8e3043cdd0a..1e8b6a5acaf 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 } from '@ember/canary-features'; +import { FEATURES, EMBER_NATIVE_DECORATOR_SUPPORT } from '@ember/canary-features'; import { confirmExport } from 'internal-test-helpers'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; import { jQueryDisabled, jQuery } from '@ember/-internals/views'; @@ -111,6 +111,7 @@ let allExports = [ ['computed', '@ember/-internals/metal', '_globalsComputed'], ['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'], @@ -268,6 +269,7 @@ 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 004952f2c50..55150bfbe12 100644 --- a/packages/external-helpers/lib/external-helpers.js +++ b/packages/external-helpers/lib/external-helpers.js @@ -2,6 +2,32 @@ 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 9274d51512d..3a9e419fca3 100644 --- a/tests/docs/expected.js +++ b/tests/docs/expected.js @@ -292,7 +292,7 @@ module.exports = { 'isArray', 'isBlank', 'isBrowser', - 'isDescriptor', + 'isComputedDecorator', 'isDestroyed', 'isDestroying', 'isEmpty', @@ -492,6 +492,7 @@ module.exports = { 'serializeQueryParam', 'serializeQueryParamKey', 'set', + 'setComputedDecorator', 'setDiff', 'setEach', 'setEngineParent', diff --git a/tests/index.html b/tests/index.html index 26e41591b30..8c06ce87852 100644 --- a/tests/index.html +++ b/tests/index.html @@ -9,6 +9,7 @@ display: none; } + diff --git a/yarn.lock b/yarn.lock index e425c77e6bd..5d7cb0085d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -40,6 +40,17 @@ source-map "^0.5.0" trim-right "^1.0.1" +"@babel/generator@^7.2.2": + version "7.3.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.0.tgz#f663838cd7b542366de3aa608a657b8ccb2a99eb" + integrity sha512-dZTwMvTgWfhmibq4V9X+LMf6Bgl7zAodRn9PvcPdhlzFMbvUutx74dbEv7Atz3ToeEpevYEJtAwfxq/bDCzHWg== + dependencies: + "@babel/types" "^7.3.0" + jsesc "^2.5.1" + lodash "^4.17.10" + source-map "^0.5.0" + trim-right "^1.0.1" + "@babel/helper-annotate-as-pure@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" @@ -64,6 +75,17 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.0.0" +"@babel/helper-create-class-features-plugin@^7.2.1": + version "7.2.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.2.1.tgz#f6e8027291669ef64433220dc8327531233f1161" + integrity sha512-EsEP7XLFmcJHjcuFYBxYD1FkP0irC8C9fsrt2tX/jrAi/eTnFI6DOPgVFb+WREeg1GboF+Ib+nCHbGBodyAXSg== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-member-expression-to-functions" "^7.0.0" + "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.1.0" + "@babel/helper-define-map@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz#3b74caec329b3c80c116290887c0dd9ae468c20c" @@ -213,6 +235,11 @@ esutils "^2.0.2" js-tokens "^4.0.0" +"@babel/parser@^7.0.0", "@babel/parser@^7.2.3": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.1.tgz#8f4ffd45f779e6132780835ffa7a215fa0b2d181" + integrity sha512-ATz6yX/L8LEnC3dtLQnIx4ydcPxhLcoy9Vl6re00zb2w5lG6itY6Vhnr1KFRPq/FHNsgl/gh2mjNN20f9iJTTA== + "@babel/parser@^7.1.2", "@babel/parser@^7.1.6", "@babel/parser@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.2.0.tgz#02d01dbc330b6cbf36b76ac93c50752c69027065" @@ -227,6 +254,24 @@ "@babel/helper-remap-async-to-generator" "^7.1.0" "@babel/plugin-syntax-async-generators" "^7.2.0" +"@babel/plugin-proposal-class-properties@^7.2.1": + version "7.2.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.2.1.tgz#c734a53e0a1ec40fe5c22ee5069d26da3b187d05" + integrity sha512-/4FKFChkQ2Jgb8lBDsvFX496YTi7UWTetVgS8oJUpX1e/DlaoeEK57At27ug8Hu2zI2g8bzkJ+8k9qrHZRPGPA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.2.1" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-proposal-decorators@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.2.0.tgz#6b4278282a6f5dd08b5d89b94f21aa1671fea071" + integrity sha512-yrDmvCsOMvNPpjCC6HMseiac2rUuQdeNqUyPU+3QbW7gLg/APX0c/7l9i/aulSICJQOkP6/4EHxkcB4d4DqZhg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + "@babel/plugin-syntax-decorators" "^7.2.0" + "@babel/plugin-proposal-json-strings@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317" @@ -267,6 +312,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-syntax-decorators@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz#c50b1b957dcc69e4b1127b65e1c33eef61570c1b" + integrity sha512-38QdqVoXdHUQfTpZo3rQwqQdWtCn5tMv4uV6r2RMfTqNBuv4ZBhz79SfaQWKTVmxHjeFv/DnXVC/+agHCklYWA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-json-strings@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz#72bd13f6ffe1d25938129d2a186b11fd62951470" @@ -591,6 +643,21 @@ "@babel/parser" "^7.1.2" "@babel/types" "^7.1.2" +"@babel/traverse@^7.0.0": + version "7.2.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8" + integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.2.2" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + "@babel/parser" "^7.2.3" + "@babel/types" "^7.2.2" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.10" + "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.1.6": version "7.1.6" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.1.6.tgz#c8db9963ab4ce5b894222435482bd8ea854b7b5c" @@ -615,6 +682,15 @@ lodash "^4.17.10" to-fast-properties "^2.0.0" +"@babel/types@^7.2.2", "@babel/types@^7.3.0": + version "7.3.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.0.tgz#61dc0b336a93badc02bf5f69c4cd8e1353f2ffc0" + integrity sha512-QkFPw68QqWU1/RVPyBe8SO7lXbPfjtqAxRYQKpFpaB8yMq7X2qAqfwK5LKoQufEkSmO5NQ70O6Kc3Afk03RwXw== + dependencies: + esutils "^2.0.2" + lodash "^4.17.10" + to-fast-properties "^2.0.0" + "@glimmer/compiler@^0.37.1": version "0.37.1" resolved "https://registry.yarnpkg.com/@glimmer/compiler/-/compiler-0.37.1.tgz#a3d59e0b1e51341314d3f8202aa0d4b70dc55173" @@ -1156,6 +1232,18 @@ babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: esutils "^2.0.2" js-tokens "^3.0.2" +babel-eslint@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed" + integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.0.0" + "@babel/traverse" "^7.0.0" + "@babel/types" "^7.0.0" + eslint-scope "3.7.1" + eslint-visitor-keys "^1.0.0" + babel-messages@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" @@ -1907,6 +1995,11 @@ builtin-modules@^1.0.0, builtin-modules@^1.1.1: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= +builtin-modules@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.0.0.tgz#1e587d44b006620d90286cc7a9238bbc6129cab1" + integrity sha512-hMIeU4K2ilbXV6Uv93ZZ0Avg/M91RaKXucQ+4me2Do1txxBDyDZWCBa5bJSLqoNTRpXTLwEzIk1KmloenDDjhg== + builtins@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" @@ -2444,6 +2537,11 @@ core-js@^2.4.0, core-js@^2.5.7: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.0.tgz#1e30793e9ee5782b307e37ffa22da0eacddd84d4" integrity sha512-kLRC6ncVpuEW/1kwrOXYX6KQASCVtrh1gQr/UiaVgFlf9WE5Vp+lNe5+h3LuMr5PAucWnnEXwH0nQHRH/gpGtw== +core-js@^2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.3.tgz#4b70938bdffdaf64931e66e2db158f0892289c49" + integrity sha512-l00tmFFZOBHtYhN4Cz7k32VM7vTn3rE2ANjQDxdEN6zmXZ/xq1jQuutnmHvMG1ZJ7xd72+TA5YpUK8wz3rWsfQ== + core-object@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/core-object/-/core-object-3.1.5.tgz#fa627b87502adc98045e44678e9a8ec3b9c0d2a9" @@ -3246,6 +3344,14 @@ eslint-plugin-qunit@^4.0.0: resolved "https://registry.yarnpkg.com/eslint-plugin-qunit/-/eslint-plugin-qunit-4.0.0.tgz#5945ba3434bfe8879bea195192e906701051cf01" integrity sha512-+0i2xcYryUoLawi47Lp0iJKzkP931G5GXwIOq1KBKQc2pknV1VPjfE6b4mI2mR2RnL7WRoS30YjwC9SjQgJDXQ== +eslint-scope@3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug= + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + eslint-scope@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" @@ -3355,6 +3461,11 @@ estree-walker@^0.3.0: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.3.1.tgz#e6b1a51cf7292524e7237c312e5fe6660c1ce1aa" integrity sha1-5rGlHPcpJSTnI3wxLl/mZgwc4ao= +estree-walker@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.5.2.tgz#d3850be7529c9580d815600b53126515e146dd39" + integrity sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig== + esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" @@ -4772,6 +4883,11 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= + is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" @@ -5668,6 +5784,13 @@ magic-string@^0.24.0: dependencies: sourcemap-codec "^1.4.1" +magic-string@^0.25.1: + version "0.25.2" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.2.tgz#139c3a729515ec55e96e69e82a11fe890a293ad9" + integrity sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg== + dependencies: + sourcemap-codec "^1.4.4" + make-dir@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51" @@ -7201,6 +7324,25 @@ rimraf@~2.5.2: dependencies: glob "^7.0.5" +rollup-plugin-commonjs@^9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.2.0.tgz#4604e25069e0c78a09e08faa95dc32dec27f7c89" + integrity sha512-0RM5U4Vd6iHjL6rLvr3lKBwnPsaVml+qxOGaaNUWN1lSq6S33KhITOfHmvxV3z2vy9Mk4t0g4rNlVaJJsNQPWA== + dependencies: + estree-walker "^0.5.2" + magic-string "^0.25.1" + resolve "^1.8.1" + rollup-pluginutils "^2.3.3" + +rollup-plugin-node-resolve@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-4.0.0.tgz#9bc6b8205e9936cc0e26bba2415f1ecf1e64d9b2" + integrity sha512-7Ni+/M5RPSUBfUaP9alwYQiIKnKeXCOHiqBpKUl9kwp3jX5ZJtgXAait1cne6pGEVUUztPD6skIKH9Kq9sNtfw== + dependencies: + builtin-modules "^3.0.0" + is-module "^1.0.0" + resolve "^1.8.1" + rollup-pluginutils@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.0.1.tgz#7ec95b3573f6543a46a6461bd9a7c544525d0fc0" @@ -7209,6 +7351,14 @@ rollup-pluginutils@^2.0.1: estree-walker "^0.3.0" micromatch "^2.3.11" +rollup-pluginutils@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.3.3.tgz#3aad9b1eb3e7fe8262820818840bf091e5ae6794" + integrity sha512-2XZwja7b6P5q4RZ5FhyX1+f46xi1Z3qBKigLRZ6VTZjwbN0K1IFGMlwm06Uu0Emcre2Z63l77nq/pzn+KxIEoA== + dependencies: + estree-walker "^0.5.2" + micromatch "^2.3.11" + rollup@^0.57.1: version "0.57.1" resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.57.1.tgz#0bb28be6151d253f67cf4a00fea48fb823c74027" @@ -7645,6 +7795,11 @@ sourcemap-codec@^1.4.1: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.1.tgz#c8fd92d91889e902a07aee392bdd2c5863958ba2" integrity sha512-hX1eNBNuilj8yfFnECh0DzLgwKpBLMIvmhgEhixXNui8lMLBInTI8Kyxt++RwJnMNu7cAUo635L2+N1TxMJCzA== +sourcemap-codec@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz#c63ea927c029dd6bd9a2b7fa03b3fec02ad56e9f" + integrity sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg== + sourcemap-validator@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/sourcemap-validator/-/sourcemap-validator-1.1.0.tgz#00454547d1682186e1498a7208e022e8dfa8738f"