UNPKG

ember-legacy-class-transform

Version:
295 lines 10 kB
import { Meta, PropertyReference } from '@glimmer/object-reference'; import { dict, assign, initializeGuid } from '@glimmer/util'; import { extend as extendClass, toMixin, relinkSubclasses, wrapMethod } from './mixin'; const { isArray } = Array; import { ROOT } from './utils'; export const EMPTY_CACHE = function EMPTY_CACHE() {}; const CLASS_META = "df8be4c8-4e89-44e2-a8f9-550c8dacdca7"; export function turbocharge(obj) { // function Dummy() {} // Dummy.prototype = obj; return obj; } class SealedMeta extends Meta { addReferenceTypeFor(..._args) { throw new Error("Cannot modify reference types on a sealed meta"); } } export class ClassMeta { constructor() { this.referenceTypes = dict(); this.propertyMetadata = dict(); this.concatenatedProperties = dict(); this.hasConcatenatedProperties = false; this.mergedProperties = dict(); this.hasMergedProperties = false; this.mixins = []; this.appliedMixins = []; this.staticMixins = []; this.subclasses = []; this.slots = []; this.InstanceMetaConstructor = null; } static fromParent(parent) { let meta = new this(); meta.reset(parent); return meta; } static for(object) { if (CLASS_META in object) return object[CLASS_META];else if (object.constructor) return object.constructor[CLASS_META] || null;else return null; } init(object, attrs) { if (typeof attrs !== 'object' || attrs === null) return; if (this.hasConcatenatedProperties) { let concatProps = this.concatenatedProperties; for (let prop in concatProps) { if (prop in attrs) { let concat = concatProps[prop].slice(); object[prop] = concat.concat(attrs[prop]); } } } if (this.hasMergedProperties) { let mergedProps = this.mergedProperties; for (let prop in mergedProps) { if (prop in attrs) { let merged = assign({}, mergedProps[prop]); object[prop] = assign(merged, attrs[prop]); } } } } addStaticMixin(mixin) { this.staticMixins.push(mixin); } addMixin(mixin) { this.mixins.push(mixin); } getStaticMixins() { return this.staticMixins; } getMixins() { return this.mixins; } addAppliedMixin(mixin) { this.appliedMixins.push(mixin); } hasAppliedMixin(mixin) { return this.appliedMixins.indexOf(mixin) !== -1; } getAppliedMixins() { return this.appliedMixins; } hasStaticMixin(mixin) { return this.staticMixins.indexOf(mixin) !== -1; } static applyAllMixins(Subclass, Parent) { Subclass[CLASS_META].getMixins().forEach(m => m.extendPrototypeOnto(Subclass, Parent)); Subclass[CLASS_META].getStaticMixins().forEach(m => m.extendStatic(Subclass)); Subclass[CLASS_META].seal(); } addSubclass(constructor) { this.subclasses.push(constructor); } getSubclasses() { return this.subclasses; } addPropertyMetadata(property, value) { this.propertyMetadata[property] = value; } metadataForProperty(property) { return this.propertyMetadata[property]; } addReferenceTypeFor(property, type) { this.referenceTypes[property] = type; } addSlotFor(property) { this.slots.push(property); } hasConcatenatedProperty(property) { if (!this.hasConcatenatedProperties) return false; return property in this.concatenatedProperties; } getConcatenatedProperty(property) { return this.concatenatedProperties[property]; } getConcatenatedProperties() { return Object.keys(this.concatenatedProperties); } addConcatenatedProperty(property, value) { this.hasConcatenatedProperties = true; if (property in this.concatenatedProperties) { let val = this.concatenatedProperties[property].concat(value); this.concatenatedProperties[property] = val; } else { this.concatenatedProperties[property] = value; } } hasMergedProperty(property) { if (!this.hasMergedProperties) return false; return property in this.mergedProperties; } getMergedProperty(property) { return this.mergedProperties[property]; } getMergedProperties() { return Object.keys(this.mergedProperties); } addMergedProperty(property, value) { this.hasMergedProperties = true; if (isArray(value)) { throw new Error(`You passed in \`${JSON.stringify(value)}\` as the value for \`foo\` but \`foo\` cannot be an Array`); } if (property in this.mergedProperties && this.mergedProperties[property] && value) { this.mergedProperties[property] = mergeMergedProperties(value, this.mergedProperties[property]); } else { value = value === null ? value : value || {}; this.mergedProperties[property] = value; } } getReferenceTypes() { return this.referenceTypes; } getPropertyMetadata() { return this.propertyMetadata; } reset(parent) { this.referenceTypes = dict(); this.propertyMetadata = dict(); this.concatenatedProperties = dict(); this.mergedProperties = dict(); if (parent) { this.hasConcatenatedProperties = parent.hasConcatenatedProperties; for (let prop in parent.concatenatedProperties) { this.concatenatedProperties[prop] = parent.concatenatedProperties[prop].slice(); } this.hasMergedProperties = parent.hasMergedProperties; for (let prop in parent.mergedProperties) { this.mergedProperties[prop] = assign({}, parent.mergedProperties[prop]); } assign(this.referenceTypes, parent.referenceTypes); assign(this.propertyMetadata, parent.propertyMetadata); } } reseal(obj) { let meta = Meta.for(obj); let fresh = new this.InstanceMetaConstructor(obj, {}); let referenceTypes = meta.getReferenceTypes(); let slots = meta.getSlots(); turbocharge(assign(referenceTypes, this.referenceTypes)); turbocharge(assign(slots, fresh.getSlots())); } seal() { let referenceTypes = turbocharge(assign({}, this.referenceTypes)); turbocharge(this.concatenatedProperties); turbocharge(this.mergedProperties); if (!this.hasMergedProperties && !this.hasConcatenatedProperties) { this.init = function () {}; } let slots = this.slots; class Slots { constructor() { slots.forEach(name => { this[name] = EMPTY_CACHE; }); } } this.InstanceMetaConstructor = class extends SealedMeta { constructor() { super(...arguments); this.slots = new Slots(); this.referenceTypes = referenceTypes; } getReferenceTypes() { return this.referenceTypes; } referenceTypeFor(property) { return this.referenceTypes[property] || PropertyReference; } getSlots() { return this.slots; } }; turbocharge(this); } } function mergeMergedProperties(attrs, parent) { let merged = assign({}, parent); for (let prop in attrs) { if (prop in parent && typeof parent[prop] === 'function' && typeof attrs[prop] === 'function') { let wrapped = wrapMethod(parent, prop, attrs[prop]); merged[prop] = wrapped; } else { merged[prop] = attrs[prop]; } } return merged; } export class InstanceMeta extends ClassMeta { constructor() { super(...arguments); this["df8be4c8-4e89-44e2-a8f9-550c8dacdca7"] = ClassMeta.fromParent(null); } static fromParent(parent) { return super.fromParent(parent); } reset(parent) { super.reset(parent); if (parent) this[CLASS_META].reset(parent[CLASS_META]); } seal() { super.seal(); this[CLASS_META].seal(); } } export default class GlimmerObject { constructor(attrs) { this._super = ROOT; this._meta = null; if (attrs) assign(this, attrs); this.constructor[CLASS_META].init(this, attrs || null); this._super = ROOT; initializeGuid(this); this.init(); } static extend(...extensions) { return extendClass(this, ...extensions); } static create(attrs) { return new this(attrs); } static reopen(extensions) { toMixin(extensions).extendPrototype(this); this[CLASS_META].seal(); relinkSubclasses(this); } static reopenClass(extensions) { toMixin(extensions).extendStatic(this); this[CLASS_META].seal(); } static metaForProperty(property) { let value = this[CLASS_META].metadataForProperty(property); if (!value) throw new Error(`metaForProperty() could not find a computed property with key '${property}'.`); return value; } static eachComputedProperty(callback) { let metadata = this[CLASS_META].getPropertyMetadata(); if (!metadata) return; for (let prop in metadata) { callback(prop, metadata[prop]); } } init() {} get(key) { return this[key]; } set(key, value) { this[key] = value; } setProperties(attrs) { assign(this, attrs); } destroy() {} } GlimmerObject["df8be4c8-4e89-44e2-a8f9-550c8dacdca7"] = InstanceMeta.fromParent(null); GlimmerObject.isClass = true;