Skip to content

Commit

Permalink
update to latest ember-decorators implementation of the RFC
Browse files Browse the repository at this point in the history
  • Loading branch information
NullVoxPopuli committed Jan 15, 2019
1 parent 658459b commit a7265d0
Show file tree
Hide file tree
Showing 6 changed files with 455 additions and 192 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// import { NEEDS_STAGE_1_DECORATORS } from 'ember-decorators-flags';

// let isStage1FieldDescriptor;

// if (NEEDS_STAGE_1_DECORATORS) {
// isStage1FieldDescriptor = function isStage1FieldDescriptor(possibleDesc) {
// if (possibleDesc.length === 3) {
// let [target, key, desc] = possibleDesc;

// return (
// typeof target === 'object' &&
// target !== null &&
// typeof key === 'string' &&
// ((typeof desc === 'object' &&
// desc !== null &&
// 'enumerable' in desc &&
// 'configurable' in desc) ||
// desc === undefined) // TS compatibility
// );
// } else if (possibleDesc.length === 1) {
// let [target] = possibleDesc;

// return typeof target === 'function' && 'prototype' in target;
// }

// return false;
// }
// }

export function isFieldDescriptor(possibleDesc) {
let isDescriptor = isStage2FieldDescriptor(possibleDesc);

// if (NEEDS_STAGE_1_DECORATORS) {
// isDescriptor = isDescriptor || isStage1FieldDescriptor(possibleDesc);
// }

return isDescriptor;
}



export function isStage2FieldDescriptor(possibleDesc) {
return possibleDesc && possibleDesc.toString() === '[object Descriptor]';
}
141 changes: 141 additions & 0 deletions packages/@ember/-internals/metal/lib/-private/decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { assert } from '@ember/debug';

// import { NEEDS_STAGE_1_DECORATORS } from 'ember-decorators-flags';
// import { deprecate } from '@ember/application/deprecations';

import { isFieldDescriptor, isStage2FieldDescriptor } from './class-field-descriptor';

function kindForDesc(desc) {
if ('value' in desc && desc.enumerable === true) {
return 'field';
} else {
return 'method';
}
}

function placementForKind(kind) {
return kind === 'method' ? 'prototype' : 'own';
}

function convertStage1ToStage2(desc) {
if (desc.length === 3) {
// Class element decorator
let [, key, descriptor] = desc;

let kind = kindForDesc(desc);
let placement = placementForKind(kind);

let initializer = descriptor !== undefined ? descriptor.initializer : undefined;

return {
descriptor,
key,
kind,
placement,
initializer,
toString: () => '[object Descriptor]',
};
} else {
// Class decorator
return {
kind: 'class',
elements: [],
};
}
}

export function decorator(fn) {
// if (NEEDS_STAGE_1_DECORATORS) {
// return function(...params) {
// if (isStage2FieldDescriptor(params)) {
// let desc = params[0];

// return deprecateDirectDescriptorMutation(fn, desc);
// } else {
// let desc = convertStage1ToStage2(params);

// desc = deprecateDirectDescriptorMutation(fn, desc);

// if (typeof desc.finisher === 'function') {
// // Finishers are supposed to run at the end of class finalization,
// // but we don't get that with stage 1 transforms. We have to be careful
// // to make sure that we aren't doing any operations which would change
// // due to timing.
// let [target] = params;

// desc.finisher(target.prototype ? target : target.constructor);
// }

// if (typeof desc.initializer === 'function') {
// // Babel 6 / the legacy decorator transform needs the initializer back
// // on the property descriptor/ In case the user has set a new
// // initializer on the member descriptor, we transfer it back to
// // original descriptor.
// desc.descriptor.initializer = desc.initializer;
// }

// return desc.descriptor;
// }
// };
// } else {
return fn;
// }
}

/**
* A macro that takes a decorator function and allows it to optionally
* receive parameters
*
* ```js
* let foo = decoratorWithParams((target, desc, key, params) => {
* console.log(params);
* });
*
* class {
* @foo bar; // undefined
* @foo('bar') baz; // ['bar']
* }
* ```
*
* @param {Function} fn - decorator function
*/
export function decoratorWithParams(fn) {
return function(...params) {
// determine if user called as @computed('blah', 'blah') or @computed
if (isFieldDescriptor(params)) {
return decorator(fn)(...params);
} else {
return decorator(desc => fn(desc, params));
}
};
}

/**
* A macro that takes a decorator function and requires it to receive
* parameters:
*
* ```js
* let foo = decoratorWithRequiredParams((target, desc, key, params) => {
* console.log(params);
* });
*
* class {
* @foo('bar') baz; // ['bar']
* @foo bar; // Error
* }
* ```
*
* @param {Function} fn - decorator function
*/
export function decoratorWithRequiredParams(fn, name) {
return function(...params) {
assert(
`The @${name || fn.name} decorator requires parameters`,
!isFieldDescriptor(params) && params.length > 0
);

return decorator(desc => {
return fn(desc, params);
});
};
}
124 changes: 124 additions & 0 deletions packages/@ember/-internals/metal/lib/-private/decorator_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { assert } from '@ember/debug';

import { defineProperty } from '@ember/-internals/metal';
import { DecoratorDescriptor } from '../computed';
import { isFieldDescriptor } from './class-field-descriptor';
import { decorator } from './decorator';
import { computedDescriptorFor, isComputedDescriptor } from './descriptor';

export const DECORATOR_COMPUTED_FN = new WeakMap();
export const DECORATOR_PARAMS = new WeakMap();
export const DECORATOR_MODIFIERS = new WeakMap();

export function collapseProto(target) {
// We must collapse the superclass prototype to make sure that the `actions`
// object will exist. Since collapsing doesn't generally happen until a class is
// instantiated, we have to do it manually.
if (typeof target.constructor.proto === 'function') {
target.constructor.proto();
}
}

export function buildComputedDesc(dec, desc) {
let fn = DECORATOR_COMPUTED_FN.get(dec);
let params = DECORATOR_PARAMS.get(dec);
let modifiers = DECORATOR_MODIFIERS.get(dec);

let computedDesc = fn(desc, params);

assert(`computed decorators must return an instance of an Ember ComputedProperty descriptor, received ${computedDesc}`, isComputedDescriptor(computedDesc));

if (modifiers) {
modifiers.forEach(m => {
if (Array.isArray(m)) {
computedDesc[m[0]](...m[1]);
} else {
computedDesc[m]();
}
});
}

return computedDesc;
}

export function computedDecoratorWithParams(fn) {
return function(...params) {
if (isFieldDescriptor(params)) {
// Funkiness of application call here is due to `...params` transpiling to
// use `apply`, which is no longer on the prototype of the computedDecorator
// since it has had it's prototype changed :upside_down_face:
return Function.apply.call(computedDecorator(fn), undefined, params);
} else {
return computedDecorator(fn, params);
}
}
}

export function computedDecoratorWithRequiredParams(fn, name) {
return function(...params) {
assert(
`The @${name || fn.name} decorator requires parameters`,
!isFieldDescriptor(params) && params.length > 0
);

return computedDecorator(fn, params);
};
}


/**
* A macro that receives a decorator function which returns a ComputedProperty,
* and defines that property using `Ember.defineProperty`. Conceptually, CPs
* are custom property descriptors that require Ember's intervention to apply
* correctly. In the future, we will use finishers to define the CPs rather than
* directly defining them in the decorator function.
*
* @param {Function} fn - decorator function
*/
export function computedDecorator(fn, params) {
let dec = decorator((desc) => {
// All computeds are methods
desc.kind = 'method';
desc.placement = 'prototype';

desc.finisher = function initializeComputedProperty(target) {
let { prototype } = target;
let { key } = desc;

assert(`ES6 property getters/setters only need to be decorated once, '${key}' was decorated on both the getter and the setter`, !computedDescriptorFor(prototype, key));

let computedDesc = buildComputedDesc(dec, desc);

// if (!HAS_NATIVE_COMPUTED_GETTERS) {
// // Until recent versions of Ember, computed properties would be defined
// // by just setting them. We need to blow away any predefined properties
// // (getters/setters, etc.) to allow Ember.defineProperty to work correctly.
// Object.defineProperty(prototype, key, {
// configurable: true,
// writable: true,
// enumerable: true,
// value: undefined
// });
// }

defineProperty(prototype, key, computedDesc);

// if (NEEDS_STAGE_1_DECORATORS) {
// // There's currently no way to disable redefining the property when decorators
// // are run, so return the property descriptor we just assigned
// desc.descriptor = Object.getOwnPropertyDescriptor(prototype, key);
// }

return target;
}

return desc;
});

Object.setPrototypeOf(dec, DecoratorDescriptor.prototype);

DECORATOR_COMPUTED_FN.set(dec, fn);
DECORATOR_PARAMS.set(dec, params);

return dec;
}
62 changes: 62 additions & 0 deletions packages/@ember/-internals/metal/lib/-private/descriptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// import { DEBUG } from '@glimmer/env';
// import { HAS_NATIVE_COMPUTED_GETTERS, HAS_DESCRIPTOR_TRAP, gte } from 'ember-compatibility-helpers';

import { assert } from '@ember/debug';

// const DESCRIPTOR = '__DESCRIPTOR__';

function isCPGetter(getter) {
// Hack for descriptor traps, we want to be able to tell if the function
// is a descriptor trap before we call it at all
return (
getter !== null &&
typeof getter === 'function' &&
getter.toString().indexOf('CPGETTER_FUNCTION') !== -1
);
}

function isDescriptorTrap(possibleDesc) {
// if (HAS_DESCRIPTOR_TRAP && DEBUG) {
// return possibleDesc !== null && typeof possibleDesc === 'object' && possibleDesc[DESCRIPTOR] !== undefined;
// } else {
throw new Error('Cannot call `isDescriptorTrap` in production');
// }
}

export function isComputedDescriptor(possibleDesc) {
return possibleDesc !== null && typeof possibleDesc === 'object' && possibleDesc.isDescriptor;
}

export function computedDescriptorFor(obj, keyName) {
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'
);

// if (HAS_NATIVE_COMPUTED_GETTERS) {
let meta = Ember.meta(obj);

if (meta !== undefined && typeof meta._descriptors === 'object') {
// TODO: Just return the standard descriptor
// if (gte('3.8.0')) {
return meta._descriptors.get(keyName);
// } else {
// return meta._descriptors[keyName];
// }
}
// } else if (Object.hasOwnProperty.call(obj, keyName)) {
// let { value: possibleDesc, get: possibleCPGetter } = Object.getOwnPropertyDescriptor(obj, keyName);

// if (DEBUG && HAS_DESCRIPTOR_TRAP && isCPGetter(possibleCPGetter)) {
// possibleDesc = possibleCPGetter.call(obj);

// if(isDescriptorTrap(possibleDesc)) {
// return possibleDesc[DESCRIPTOR];
// }
// }

// return isComputedDescriptor(possibleDesc) ? possibleDesc : undefined;
// }
}
Loading

0 comments on commit a7265d0

Please sign in to comment.