ember-legacy-class-transform
Version:
The default blueprint for ember-cli addons.
257 lines • 9.58 kB
JavaScript
import { CLASS_META } from '@glimmer/object-reference';
import { dict, assign } from '@glimmer/util';
import { ClassMeta, InstanceMeta, turbocharge } from './object';
import { ROOT } from './utils';
const { isArray } = Array;
export const DESCRIPTOR = "5d90f84f-908e-4a42-9749-3d0f523c262c";
export const BLUEPRINT = "8d97cf5f-db9e-48d8-a6b2-7a75b7170805";
export class Descriptor {
constructor() {
this["5d90f84f-908e-4a42-9749-3d0f523c262c"] = true;
}
}
export class Blueprint {
constructor() {
this["8d97cf5f-db9e-48d8-a6b2-7a75b7170805"] = true;
}
}
export class Mixin {
constructor(extensions, mixins) {
this.extensions = null;
this.concatenatedProperties = [];
this.mergedProperties = [];
this.dependencies = [];
this.reopen(extensions);
this.dependencies.push(...mixins);
}
static create(...args) {
let extensions = args[args.length - 1];
if (args.length === 0) {
return new this({}, []);
} else if (extensions instanceof Mixin) {
return new this({}, args);
} else {
let deps = args.slice(0, -1).map(toMixin);
return new this(extensions, deps);
}
}
static mixins(obj) {
if (typeof obj !== 'object' || obj === null) return [];
let meta = ClassMeta.for(obj);
if (!meta) return [];
return meta.getAppliedMixins();
}
detect(obj) {
if (typeof obj !== 'object' || obj === null) return false;
if (obj instanceof Mixin) {
return obj.dependencies.indexOf(this) !== -1;
}
let meta = ClassMeta.for(obj);
return !!meta && meta.hasAppliedMixin(this);
}
reopen(extensions) {
if (this.extensions) {
this.dependencies.push(toMixin(this.extensions));
}
if (typeof extensions === 'object' && 'concatenatedProperties' in extensions) {
let concat;
let rawConcat = extensions.concatenatedProperties;
if (isArray(rawConcat)) {
concat = rawConcat.slice();
} else if (rawConcat === null || rawConcat === undefined) {
concat = [];
} else {
concat = [rawConcat];
}
delete extensions.concatenatedProperties;
this.concatenatedProperties = concat;
}
if (typeof extensions === 'object' && 'mergedProperties' in extensions) {
let merged;
let rawMerged = extensions.mergedProperties;
if (isArray(rawMerged)) {
merged = rawMerged.slice();
} else if (rawMerged === null || rawMerged === undefined) {
merged = [];
} else {
merged = [rawMerged];
}
delete extensions.mergedProperties;
this.mergedProperties = merged;
}
let normalized = Object.keys(extensions).reduce((obj, key) => {
let value = extensions[key];
switch (typeof value) {
case 'function':
obj[key] = new MethodBlueprint({ value });
break;
case 'object':
if (value && BLUEPRINT in value) {
obj[key] = value;
break;
}
/* falls through */
default:
obj[key] = new DataBlueprint({ value });
}
return obj;
}, dict());
this.extensions = dict();
assign(this.extensions, turbocharge(normalized));
}
apply(target) {
let meta = target[CLASS_META] = target[CLASS_META] || new ClassMeta();
this.dependencies.forEach(m => m.apply(target));
this.mergeProperties(target, target, meta);
meta.addMixin(this);
meta.seal();
meta.reseal(target);
return target;
}
extendPrototype(Original) {
Original.prototype = Object.create(Original.prototype);
this.dependencies.forEach(m => m.extendPrototype(Original));
this.extendPrototypeOnto(Original, Original);
}
extendPrototypeOnto(Subclass, Parent) {
this.dependencies.forEach(m => m.extendPrototypeOnto(Subclass, Parent));
this.mergeProperties(Subclass.prototype, Parent.prototype, Subclass[CLASS_META]);
Subclass[CLASS_META].addMixin(this);
}
extendStatic(Target) {
this.dependencies.forEach(m => m.extendStatic(Target));
this.mergeProperties(Target, Object.getPrototypeOf(Target), Target[CLASS_META][CLASS_META]);
Target[CLASS_META].addStaticMixin(this);
}
mergeProperties(target, parent, meta) {
if (meta.hasAppliedMixin(this)) return;
meta.addAppliedMixin(this);
this.mergedProperties.forEach(k => meta.addMergedProperty(k, parent[k]));
this.concatenatedProperties.forEach(k => meta.addConcatenatedProperty(k, []));
new ValueDescriptor({ value: meta.getConcatenatedProperties() }).define(target, 'concatenatedProperties');
new ValueDescriptor({ value: meta.getMergedProperties() }).define(target, 'mergedProperties');
Object.keys(this.extensions).forEach(key => {
let extension = this.extensions[key];
let desc = extension.descriptor(target, key, meta);
desc.define(target, key, parent);
});
new ValueDescriptor({ value: ROOT }).define(target, '_super');
}
}
export function extend(Parent, ...extensions) {
let Super = Parent;
let Subclass = class extends Super {};
Subclass[CLASS_META] = InstanceMeta.fromParent(Parent[CLASS_META]);
let mixins = extensions.map(toMixin);
Parent[CLASS_META].addSubclass(Subclass);
mixins.forEach(m => Subclass[CLASS_META].addMixin(m));
ClassMeta.applyAllMixins(Subclass, Parent);
return Subclass;
}
export function relinkSubclasses(Parent) {
Parent[CLASS_META].getSubclasses().forEach(Subclass => {
Subclass[CLASS_META].reset(Parent[CLASS_META]);
Subclass.prototype = Object.create(Parent.prototype);
ClassMeta.applyAllMixins(Subclass, Parent);
// recurse into sub-subclasses
relinkSubclasses(Subclass);
});
}
export function toMixin(extension) {
if (extension instanceof Mixin) return extension;else return new Mixin(extension, []);
}
class ValueDescriptor extends Descriptor {
constructor({ enumerable = true, configurable = true, writable = true, value }) {
super();
this.enumerable = enumerable;
this.configurable = configurable;
this.writable = writable;
this.value = value;
}
define(target, key, _home) {
Object.defineProperty(target, key, {
enumerable: this.enumerable,
configurable: this.configurable,
writable: this.writable,
value: this.value
});
}
}
export class DataBlueprint extends Blueprint {
constructor({ enumerable = true, configurable = true, writable = true, value }) {
super();
this.enumerable = enumerable;
this.configurable = configurable;
this.value = value;
this.writable = writable;
}
descriptor(_target, key, classMeta) {
let { enumerable, configurable, writable, value } = this;
if (classMeta.hasConcatenatedProperty(key)) {
classMeta.addConcatenatedProperty(key, value);
value = classMeta.getConcatenatedProperty(key);
} else if (classMeta.hasMergedProperty(key)) {
classMeta.addMergedProperty(key, value);
value = classMeta.getMergedProperty(key);
}
return new ValueDescriptor({ enumerable, configurable, writable, value });
}
}
export class AccessorBlueprint extends Blueprint {
constructor({ enumerable = true, configurable = true, get, set }) {
super();
this.enumerable = enumerable;
this.configurable = configurable;
this.get = get;
this.set = set;
}
descriptor(_target, _key, _classMeta) {
return new ValueDescriptor({
enumerable: this.enumerable,
configurable: this.configurable,
get: this.get,
set: this.set
});
}
}
class MethodDescriptor extends ValueDescriptor {
define(target, key, home) {
this.value = wrapMethod(home, key, this.value);
super.define(target, key, home);
}
}
class MethodBlueprint extends DataBlueprint {
descriptor(target, key, classMeta) {
let desc = super.descriptor(target, key, classMeta);
return new MethodDescriptor(desc);
}
}
export function wrapMethod(home, methodName, original) {
if (!(methodName in home)) return maybeWrap(original);
let superMethod = home[methodName];
let func = function (...args) {
if (!this) return original.apply(this, args);
let lastSuper = this._super;
this._super = superMethod;
try {
return original.apply(this, args);
} finally {
this._super = lastSuper;
}
};
func.__wrapped = true;
return func;
}
function maybeWrap(original) {
if ('__wrapped' in original) return original;
return function (...args) {
if (!this) return original.apply(this, args);
let lastSuper = this._super;
this._super = ROOT;
try {
return original.apply(this, args);
} finally {
this._super = lastSuper;
}
};
}