UNPKG

ember-legacy-class-transform

Version:
257 lines 9.58 kB
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; } }; }