ember-legacy-class-transform
Version:
The default blueprint for ember-cli addons.
295 lines • 10 kB
JavaScript
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;