michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: module.metadata = { michael@0: "stability": "deprecated" michael@0: }; michael@0: michael@0: const { michael@0: compose: _compose, michael@0: override: _override, michael@0: resolve: _resolve, michael@0: trait: _trait, michael@0: //create: _create, michael@0: required, michael@0: } = require('./traits/core'); michael@0: michael@0: const defineProperties = Object.defineProperties, michael@0: freeze = Object.freeze, michael@0: create = Object.create; michael@0: michael@0: /** michael@0: * Work around bug 608959 by defining the _create function here instead of michael@0: * importing it from traits/core. For docs on this function, see the create michael@0: * function in that module. michael@0: * michael@0: * FIXME: remove this workaround in favor of importing the function once that michael@0: * bug has been fixed. michael@0: */ michael@0: function _create(proto, trait) { michael@0: let properties = {}, michael@0: keys = Object.getOwnPropertyNames(trait); michael@0: for each(let key in keys) { michael@0: let descriptor = trait[key]; michael@0: if (descriptor.required && michael@0: !Object.prototype.hasOwnProperty.call(proto, key)) michael@0: throw new Error('Missing required property: ' + key); michael@0: else if (descriptor.conflict) michael@0: throw new Error('Remaining conflicting property: ' + key); michael@0: else michael@0: properties[key] = descriptor; michael@0: } michael@0: return Object.create(proto, properties); michael@0: } michael@0: michael@0: /** michael@0: * Placeholder for `Trait.prototype` michael@0: */ michael@0: let TraitProto = Object.prototype; michael@0: michael@0: function Get(key) this[key] michael@0: function Set(key, value) this[key] = value michael@0: michael@0: /** michael@0: * Creates anonymous trait descriptor from the passed argument, unless argument michael@0: * is a trait constructor. In later case trait's already existing properties michael@0: * descriptor is returned. michael@0: * This is module's internal function and is used as a gateway to a trait's michael@0: * internal properties descriptor. michael@0: * @param {Function} $ michael@0: * Composed trait's constructor. michael@0: * @returns {Object} michael@0: * Private trait property of the composition. michael@0: */ michael@0: function TraitDescriptor(object) michael@0: ( michael@0: 'function' == typeof object && michael@0: (object.prototype == TraitProto || object.prototype instanceof Trait) michael@0: ) ? object._trait(TraitDescriptor) : _trait(object) michael@0: michael@0: function Public(instance, trait) { michael@0: let result = {}, michael@0: keys = Object.getOwnPropertyNames(trait); michael@0: for each (let key in keys) { michael@0: if ('_' === key.charAt(0) && '__iterator__' !== key ) michael@0: continue; michael@0: let property = trait[key], michael@0: descriptor = { michael@0: configurable: property.configurable, michael@0: enumerable: property.enumerable michael@0: }; michael@0: if (property.get) michael@0: descriptor.get = property.get.bind(instance); michael@0: if (property.set) michael@0: descriptor.set = property.set.bind(instance); michael@0: if ('value' in property) { michael@0: let value = property.value; michael@0: if ('function' === typeof value) { michael@0: descriptor.value = property.value.bind(instance); michael@0: descriptor.writable = property.writable; michael@0: } else { michael@0: descriptor.get = Get.bind(instance, key); michael@0: descriptor.set = Set.bind(instance, key); michael@0: } michael@0: } michael@0: result[key] = descriptor; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: /** michael@0: * This is private function that composes new trait with privates. michael@0: */ michael@0: function Composition(trait) { michael@0: function Trait() { michael@0: let self = _create({}, trait); michael@0: self._public = create(Trait.prototype, Public(self, trait)); michael@0: delete self._public.constructor; michael@0: if (Object === self.constructor) michael@0: self.constructor = Trait; michael@0: else michael@0: return self.constructor.apply(self, arguments) || self._public; michael@0: return self._public; michael@0: } michael@0: defineProperties(Trait, { michael@0: prototype: { value: freeze(create(TraitProto, { michael@0: constructor: { value: constructor, writable: true } michael@0: }))}, // writable is `true` to avoid getters in custom ES5 michael@0: displayName: { value: (trait.constructor || constructor).name }, michael@0: compose: { value: compose, enumerable: true }, michael@0: override: { value: override, enumerable: true }, michael@0: resolve: { value: resolve, enumerable: true }, michael@0: required: { value: required, enumerable: true }, michael@0: _trait: { value: function _trait(caller) michael@0: caller === TraitDescriptor ? trait : undefined michael@0: } michael@0: }); michael@0: return freeze(Trait); michael@0: } michael@0: michael@0: /** michael@0: * Composes new trait out of itself and traits / property maps passed as an michael@0: * arguments. If two or more traits / property maps have properties with the michael@0: * same name, the new trait will contain a "conflict" property for that name. michael@0: * This is a commutative and associative operation, and the order of its michael@0: * arguments is not significant. michael@0: * @params {Object|Function} michael@0: * List of Traits or property maps to create traits from. michael@0: * @returns {Function} michael@0: * New trait containing the combined properties of all the traits. michael@0: */ michael@0: function compose() { michael@0: let traits = Array.slice(arguments, 0); michael@0: traits.push(this); michael@0: return Composition(_compose.apply(null, traits.map(TraitDescriptor))); michael@0: } michael@0: michael@0: /** michael@0: * Composes a new trait with all of the combined properties of `this` and the michael@0: * argument traits. In contrast to `compose`, `override` immediately resolves michael@0: * all conflicts resulting from this composition by overriding the properties of michael@0: * later traits. Trait priority is from left to right. I.e. the properties of michael@0: * the leftmost trait are never overridden. michael@0: * @params {Object} trait michael@0: * @returns {Object} michael@0: */ michael@0: function override() { michael@0: let traits = Array.slice(arguments, 0); michael@0: traits.push(this); michael@0: return Composition(_override.apply(null, traits.map(TraitDescriptor))); michael@0: } michael@0: michael@0: /** michael@0: * Composes new resolved trait, with all the same properties as this michael@0: * trait, except that all properties whose name is an own property of michael@0: * `resolutions` will be renamed to `resolutions[name]`. If it is michael@0: * `resolutions[name]` is `null` value is changed into a required property michael@0: * descriptor. michael@0: */ michael@0: function resolve(resolutions) michael@0: Composition(_resolve(resolutions, TraitDescriptor(this))) michael@0: michael@0: /** michael@0: * Base Trait, that all the traits are composed of. michael@0: */ michael@0: const Trait = Composition({ michael@0: /** michael@0: * Internal property holding public API of this instance. michael@0: */ michael@0: _public: { value: null, configurable: true, writable: true }, michael@0: toString: { value: function() '[object ' + this.constructor.name + ']' } michael@0: }); michael@0: TraitProto = Trait.prototype; michael@0: exports.Trait = Trait; michael@0: