Skip to content

Commit

Permalink
[FEAT] Implements Decorators
Browse files Browse the repository at this point in the history
This PR refactors the Descriptor system in Ember metal, mostly replacing
it with a Decorator system instead. `defineProperty` has been updated to
apply decorators following the currently implemented spec. This means
that the value returned by `computed` goes through the exact same flow
when applied as a decorator using decorator syntax, and applied using
classic syntax.
  • Loading branch information
NullVoxPopuli authored and Chris Garrett committed Feb 5, 2019
1 parent 1f5438b commit 4359400
Show file tree
Hide file tree
Showing 40 changed files with 1,211 additions and 627 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const path = require('path');

module.exports = {
root: true,
parser: 'babel-eslint',
extends: [
'eslint:recommended',
'prettier',
Expand Down
10 changes: 9 additions & 1 deletion broccoli/packages.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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'],
});
Expand All @@ -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,
});

Expand Down
13 changes: 13 additions & 0 deletions broccoli/transforms/transform-babel-plugins.js
Original file line number Diff line number Diff line change
@@ -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);
};
11 changes: 11 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2016",
"experimentalDecorators": true
},
"exclude": [
"node_modules",
"**/node_modules/*"
]
}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -101,6 +103,7 @@
"@types/rsvp": "^4.0.2",
"auto-dist-tag": "^1.0.0",
"aws-sdk": "^2.395.0",
"babel-eslint": "^10.0.1",
"babel-plugin-debug-macros": "^0.3.0",
"babel-plugin-filter-imports": "^2.0.4",
"babel-plugin-module-resolver": "^3.1.3",
Expand Down
2 changes: 0 additions & 2 deletions packages/@ember/-internals/meta/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
export {
counters,
deleteMeta,
descriptorFor,
isDescriptor,
Meta,
meta,
MetaCounters,
Expand Down
42 changes: 0 additions & 42 deletions packages/@ember/-internals/meta/lib/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -978,48 +978,6 @@ if (DEBUG) {
meta._counters = counters;
}

/**
Returns the CP descriptor assocaited with `obj` and `keyName`, if any.
@method descriptorFor
@param {Object} obj the object to check
@param {String} keyName the key to check
@return {Descriptor}
@private
*/
export function descriptorFor(obj: object, keyName: string, _meta?: Meta | null) {
assert('Cannot call `descriptorFor` on null', obj !== null);
assert('Cannot call `descriptorFor` on undefined', obj !== undefined);
assert(
`Cannot call \`descriptorFor\` on ${typeof obj}`,
typeof obj === 'object' || typeof obj === 'function'
);

let meta = _meta === undefined ? peekMeta(obj) : _meta;

if (meta !== null) {
return meta.peekDescriptors(keyName);
}
}

/**
Check whether a value is a CP descriptor.
@method isDescriptor
@param {any} possibleDesc the value to check
@return {boolean}
@private
*/
export function isDescriptor(possibleDesc: any | undefined | null): boolean {
// TODO make this return `possibleDesc is Descriptor`
return (
possibleDesc !== undefined &&
possibleDesc !== null &&
typeof possibleDesc === 'object' &&
possibleDesc.isDescriptor === true
);
}

export { counters };

function indexOfListener(
Expand Down
13 changes: 9 additions & 4 deletions packages/@ember/-internals/metal/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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';
Expand All @@ -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 {
Expand Down
56 changes: 38 additions & 18 deletions packages/@ember/-internals/metal/lib/alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,50 @@ import { Meta, meta as metaFor } from '@ember/-internals/meta';
import { inspect } from '@ember/-internals/utils';
import { assert } from '@ember/debug';
import EmberError from '@ember/error';
import { ComputedProperty } from './computed';
import { getCachedValueFor, getCacheFor } from './computed_cache';
import {
addDependentKeys,
DescriptorWithDependentKeys,
ComputedDescriptor,
Decorator,
descriptorForDecorator,
makeComputedDecorator,
removeDependentKeys,
} from './dependent_keys';
import { defineProperty, Descriptor } from './properties';
} from './decorator';
import { defineProperty } from './properties';
import { get } from './property_get';
import { set } from './property_set';

const CONSUMED = Object.freeze({});

export default function alias(altKey: string): AliasedProperty {
return new AliasedProperty(altKey);
export type AliasDecorator = Decorator & PropertyDecorator & AliasDecoratorImpl;

export default function alias(altKey: string): AliasDecorator {
return makeComputedDecorator(new AliasedProperty(altKey), AliasDecoratorImpl) as AliasDecorator;
}

class AliasDecoratorImpl {
readOnly(this: Decorator) {
descriptorForDecorator(this).readOnly();
return this;
}

oneWay(this: Decorator) {
descriptorForDecorator(this).oneWay();
return this;
}

meta(this: Decorator, meta?: any): any {
let prop = descriptorForDecorator(this);

if (arguments.length === 0) {
return prop._meta || {};
} else {
prop._meta = meta;
}
}
}

export class AliasedProperty extends Descriptor implements DescriptorWithDependentKeys {
readonly _dependentKeys: string[];
export class AliasedProperty extends ComputedDescriptor {
readonly altKey: string;

constructor(altKey: string) {
Expand All @@ -29,9 +54,10 @@ export class AliasedProperty extends Descriptor implements DescriptorWithDepende
this._dependentKeys = [altKey];
}

setup(obj: object, keyName: string, meta: Meta): void {
setup(obj: object, keyName: string, propertyDesc: PropertyDescriptor, meta: Meta): void {
assert(`Setting alias '${keyName}' on self`, this.altKey !== keyName);
super.setup(obj, keyName, meta);
super.setup(obj, keyName, propertyDesc, meta);

if (meta.peekWatching(keyName) > 0) {
this.consume(obj, keyName, meta);
}
Expand Down Expand Up @@ -74,14 +100,12 @@ export class AliasedProperty extends Descriptor implements DescriptorWithDepende
return set(obj, this.altKey, value);
}

readOnly(): this {
readOnly(): void {
this.set = AliasedProperty_readOnlySet;
return this;
}

oneWay(): this {
oneWay(): void {
this.set = AliasedProperty_oneWaySet;
return this;
}
}

Expand All @@ -94,7 +118,3 @@ function AliasedProperty_oneWaySet(obj: object, keyName: string, value: any): an
defineProperty(obj, keyName, null);
return set(obj, keyName, value);
}

// Backwards compatibility with Ember Data.
(AliasedProperty.prototype as any)._meta = undefined;
(AliasedProperty.prototype as any).meta = ComputedProperty.prototype.meta;
5 changes: 3 additions & 2 deletions packages/@ember/-internals/metal/lib/chains.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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);
}

Expand Down
Loading

0 comments on commit 4359400

Please sign in to comment.