|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 module.metadata = { |
|
8 "stability": "deprecated" |
|
9 }; |
|
10 |
|
11 const { |
|
12 compose: _compose, |
|
13 override: _override, |
|
14 resolve: _resolve, |
|
15 trait: _trait, |
|
16 //create: _create, |
|
17 required, |
|
18 } = require('./traits/core'); |
|
19 |
|
20 const defineProperties = Object.defineProperties, |
|
21 freeze = Object.freeze, |
|
22 create = Object.create; |
|
23 |
|
24 /** |
|
25 * Work around bug 608959 by defining the _create function here instead of |
|
26 * importing it from traits/core. For docs on this function, see the create |
|
27 * function in that module. |
|
28 * |
|
29 * FIXME: remove this workaround in favor of importing the function once that |
|
30 * bug has been fixed. |
|
31 */ |
|
32 function _create(proto, trait) { |
|
33 let properties = {}, |
|
34 keys = Object.getOwnPropertyNames(trait); |
|
35 for each(let key in keys) { |
|
36 let descriptor = trait[key]; |
|
37 if (descriptor.required && |
|
38 !Object.prototype.hasOwnProperty.call(proto, key)) |
|
39 throw new Error('Missing required property: ' + key); |
|
40 else if (descriptor.conflict) |
|
41 throw new Error('Remaining conflicting property: ' + key); |
|
42 else |
|
43 properties[key] = descriptor; |
|
44 } |
|
45 return Object.create(proto, properties); |
|
46 } |
|
47 |
|
48 /** |
|
49 * Placeholder for `Trait.prototype` |
|
50 */ |
|
51 let TraitProto = Object.prototype; |
|
52 |
|
53 function Get(key) this[key] |
|
54 function Set(key, value) this[key] = value |
|
55 |
|
56 /** |
|
57 * Creates anonymous trait descriptor from the passed argument, unless argument |
|
58 * is a trait constructor. In later case trait's already existing properties |
|
59 * descriptor is returned. |
|
60 * This is module's internal function and is used as a gateway to a trait's |
|
61 * internal properties descriptor. |
|
62 * @param {Function} $ |
|
63 * Composed trait's constructor. |
|
64 * @returns {Object} |
|
65 * Private trait property of the composition. |
|
66 */ |
|
67 function TraitDescriptor(object) |
|
68 ( |
|
69 'function' == typeof object && |
|
70 (object.prototype == TraitProto || object.prototype instanceof Trait) |
|
71 ) ? object._trait(TraitDescriptor) : _trait(object) |
|
72 |
|
73 function Public(instance, trait) { |
|
74 let result = {}, |
|
75 keys = Object.getOwnPropertyNames(trait); |
|
76 for each (let key in keys) { |
|
77 if ('_' === key.charAt(0) && '__iterator__' !== key ) |
|
78 continue; |
|
79 let property = trait[key], |
|
80 descriptor = { |
|
81 configurable: property.configurable, |
|
82 enumerable: property.enumerable |
|
83 }; |
|
84 if (property.get) |
|
85 descriptor.get = property.get.bind(instance); |
|
86 if (property.set) |
|
87 descriptor.set = property.set.bind(instance); |
|
88 if ('value' in property) { |
|
89 let value = property.value; |
|
90 if ('function' === typeof value) { |
|
91 descriptor.value = property.value.bind(instance); |
|
92 descriptor.writable = property.writable; |
|
93 } else { |
|
94 descriptor.get = Get.bind(instance, key); |
|
95 descriptor.set = Set.bind(instance, key); |
|
96 } |
|
97 } |
|
98 result[key] = descriptor; |
|
99 } |
|
100 return result; |
|
101 } |
|
102 |
|
103 /** |
|
104 * This is private function that composes new trait with privates. |
|
105 */ |
|
106 function Composition(trait) { |
|
107 function Trait() { |
|
108 let self = _create({}, trait); |
|
109 self._public = create(Trait.prototype, Public(self, trait)); |
|
110 delete self._public.constructor; |
|
111 if (Object === self.constructor) |
|
112 self.constructor = Trait; |
|
113 else |
|
114 return self.constructor.apply(self, arguments) || self._public; |
|
115 return self._public; |
|
116 } |
|
117 defineProperties(Trait, { |
|
118 prototype: { value: freeze(create(TraitProto, { |
|
119 constructor: { value: constructor, writable: true } |
|
120 }))}, // writable is `true` to avoid getters in custom ES5 |
|
121 displayName: { value: (trait.constructor || constructor).name }, |
|
122 compose: { value: compose, enumerable: true }, |
|
123 override: { value: override, enumerable: true }, |
|
124 resolve: { value: resolve, enumerable: true }, |
|
125 required: { value: required, enumerable: true }, |
|
126 _trait: { value: function _trait(caller) |
|
127 caller === TraitDescriptor ? trait : undefined |
|
128 } |
|
129 }); |
|
130 return freeze(Trait); |
|
131 } |
|
132 |
|
133 /** |
|
134 * Composes new trait out of itself and traits / property maps passed as an |
|
135 * arguments. If two or more traits / property maps have properties with the |
|
136 * same name, the new trait will contain a "conflict" property for that name. |
|
137 * This is a commutative and associative operation, and the order of its |
|
138 * arguments is not significant. |
|
139 * @params {Object|Function} |
|
140 * List of Traits or property maps to create traits from. |
|
141 * @returns {Function} |
|
142 * New trait containing the combined properties of all the traits. |
|
143 */ |
|
144 function compose() { |
|
145 let traits = Array.slice(arguments, 0); |
|
146 traits.push(this); |
|
147 return Composition(_compose.apply(null, traits.map(TraitDescriptor))); |
|
148 } |
|
149 |
|
150 /** |
|
151 * Composes a new trait with all of the combined properties of `this` and the |
|
152 * argument traits. In contrast to `compose`, `override` immediately resolves |
|
153 * all conflicts resulting from this composition by overriding the properties of |
|
154 * later traits. Trait priority is from left to right. I.e. the properties of |
|
155 * the leftmost trait are never overridden. |
|
156 * @params {Object} trait |
|
157 * @returns {Object} |
|
158 */ |
|
159 function override() { |
|
160 let traits = Array.slice(arguments, 0); |
|
161 traits.push(this); |
|
162 return Composition(_override.apply(null, traits.map(TraitDescriptor))); |
|
163 } |
|
164 |
|
165 /** |
|
166 * Composes new resolved trait, with all the same properties as this |
|
167 * trait, except that all properties whose name is an own property of |
|
168 * `resolutions` will be renamed to `resolutions[name]`. If it is |
|
169 * `resolutions[name]` is `null` value is changed into a required property |
|
170 * descriptor. |
|
171 */ |
|
172 function resolve(resolutions) |
|
173 Composition(_resolve(resolutions, TraitDescriptor(this))) |
|
174 |
|
175 /** |
|
176 * Base Trait, that all the traits are composed of. |
|
177 */ |
|
178 const Trait = Composition({ |
|
179 /** |
|
180 * Internal property holding public API of this instance. |
|
181 */ |
|
182 _public: { value: null, configurable: true, writable: true }, |
|
183 toString: { value: function() '[object ' + this.constructor.name + ']' } |
|
184 }); |
|
185 TraitProto = Trait.prototype; |
|
186 exports.Trait = Trait; |
|
187 |