trusktr-dummy-test-pkg
Version:
JavaScript/TypeScript class inheritance tools.
114 lines • 4.17 kB
JavaScript
// TODO no any types
// TODO no @ts-ignore
import Class from './Class.js';
export function Mixin(mixinFn, DefaultBase) {
// XXX Maybe Cached should go last.
// @ts-ignore
mixinFn = Cached(mixinFn);
mixinFn = HasInstance(mixinFn);
mixinFn = Dedupe(mixinFn);
mixinFn = WithDefault(mixinFn, DefaultBase || Class());
mixinFn = ApplyDefault(mixinFn);
// @ts-ignore
return mixinFn();
}
export default Mixin;
export { WithDefault, Cached, HasInstance, ApplyDefault, Dedupe };
// TODO remove WithDefault, we can use default argument syntax instead, which is more clear and conventional
function WithDefault(classFactory, Default) {
// @ts-ignore
return named(classFactory.name, (Base) => {
Base = Base || Default;
return classFactory(Base);
});
}
function Cached(classFactory) {
const classCache = new WeakMap();
// @ts-ignore
return named(classFactory.name, (Base) => {
let Class = classCache.get(Base);
if (!Class) {
classCache.set(Base, (Class = classFactory(Base)));
}
return Class;
});
}
function HasInstance(classFactory) {
let instanceofSymbol;
// @ts-ignore
return named(classFactory.name, (Base) => {
const Class = classFactory(Base);
if (typeof Symbol === 'undefined' || !Symbol.hasInstance)
return Class;
if (Object.getOwnPropertySymbols(Class).includes(Symbol.hasInstance))
return Class;
if (!instanceofSymbol)
instanceofSymbol = Symbol('instanceofSymbol');
Class[instanceofSymbol] = true;
Object.defineProperty(Class, Symbol.hasInstance, {
value: function hasInstance(obj) {
// we do this check because a subclass of `Class` may not have
// it's own `[Symbol.hasInstance]()` method, therefore `this`
// will be the subclass, not this `Class`, when the prototype
// lookup on the subclass finds the `[Symbol.hasInstance]()`
// method of this `Class`. In this case, we don't want to run
// our logic here, so we delegate to the super class of this
// `Class` to take over with the instanceof check. In many
// cases, the super class `[Symbol.hasInstance]()` method will
// be `Function.prototype[Symbol.hasInstance]` which will
// perform the standard check.
if (this !== Class)
// This is effectively a `super` call.
return Class.__proto__[Symbol.hasInstance].call(this, obj);
let currentProto = obj;
while (currentProto) {
const descriptor = Object.getOwnPropertyDescriptor(currentProto, 'constructor');
if (descriptor && descriptor.value && descriptor.value.hasOwnProperty(instanceofSymbol))
return true;
currentProto = currentProto.__proto__;
}
return false;
},
});
return Class;
});
}
// requires WithDefault or a classFactory that can accept no args
function ApplyDefault(classFactory) {
const DefaultClass = classFactory();
DefaultClass.mixin = classFactory;
return classFactory;
}
// requires Cached
function Dedupe(classFactory) {
const map = new WeakMap();
// @ts-ignore
return named(classFactory.name, (Base) => {
if (hasMixin(Base, classFactory, map))
return Base;
const Class = classFactory(Base);
map.set(Class, classFactory);
return Class;
});
}
function hasMixin(Class, mixin, map) {
while (Class) {
if (map.get(Class) === mixin)
return true;
Class = Class.__proto__;
}
return false;
}
function named(name, func) {
try {
Object.defineProperty(func, 'name', {
...Object.getOwnPropertyDescriptor(func, 'name'),
value: name,
});
}
catch (e) {
// do nohing in case the property is non-configurable.
}
return func;
}
//# sourceMappingURL=Mixin.js.map