Files
Zos/Skills/@be/node_modules/traits/lib/traits.js

663 lines
23 KiB
JavaScript

// Copyright (C) 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// See http://code.google.com/p/es-lab/wiki/Traits
// for background on traits and a description of this library
var Trait = (function(){
// == Ancillary functions ==
var SUPPORTS_DEFINEPROP = (function() {
try {
var test = {};
Object.defineProperty(test, 'x', {get: function() { return 0; } } );
return test.x === 0;
} catch(e) {
return false;
}
})();
// IE8 implements Object.defineProperty and Object.getOwnPropertyDescriptor
// only for DOM objects. These methods don't work on plain objects.
// Hence, we need a more elaborate feature-test to see whether the
// browser truly supports these methods:
function supportsGOPD() {
try {
if (Object.getOwnPropertyDescriptor) {
var test = {x:0};
return !!Object.getOwnPropertyDescriptor(test,'x');
}
} catch(e) {}
return false;
};
function supportsDP() {
try {
if (Object.defineProperty) {
var test = {};
Object.defineProperty(test,'x',{value:0});
return test.x === 0;
}
} catch(e) {}
return false;
};
var call = Function.prototype.call;
/**
* An ad hoc version of bind that only binds the 'this' parameter.
*/
var bindThis = Function.prototype.bind ?
function(fun, self) { return Function.prototype.bind.call(fun, self); } :
function(fun, self) {
function funcBound(var_args) {
return fun.apply(self, arguments);
}
return funcBound;
};
var hasOwnProperty = bindThis(call, Object.prototype.hasOwnProperty);
var slice = bindThis(call, Array.prototype.slice);
// feature testing such that traits.js runs on both ES3 and ES5
var forEach = Array.prototype.forEach ?
bindThis(call, Array.prototype.forEach) :
function(arr, fun) {
for (var i = 0, len = arr.length; i < len; i++) { fun(arr[i]); }
};
var freeze = Object.freeze || function(obj) { return obj; };
var getPrototypeOf = Object.getPrototypeOf || function(obj) {
return Object.prototype;
};
var getOwnPropertyNames = Object.getOwnPropertyNames ||
function(obj) {
var props = [];
for (var p in obj) { if (hasOwnProperty(obj,p)) { props.push(p); } }
return props;
};
var getOwnPropertyDescriptor = supportsGOPD() ?
Object.getOwnPropertyDescriptor :
function(obj, name) {
return {
value: obj[name],
enumerable: true,
writable: true,
configurable: true
};
};
var defineProperty = supportsDP() ? Object.defineProperty :
function(obj, name, pd) {
obj[name] = pd.value;
};
var defineProperties = Object.defineProperties ||
function(obj, propMap) {
for (var name in propMap) {
if (hasOwnProperty(propMap, name)) {
defineProperty(obj, name, propMap[name]);
}
}
};
var Object_create = Object.create ||
function(proto, propMap) {
var self;
function dummy() {};
dummy.prototype = proto || Object.prototype;
self = new dummy();
if (propMap) {
defineProperties(self, propMap);
}
return self;
};
var getOwnProperties = Object.getOwnProperties ||
function(obj) {
var map = {};
forEach(getOwnPropertyNames(obj), function (name) {
map[name] = getOwnPropertyDescriptor(obj, name);
});
return map;
};
// end of ES3 - ES5 compatibility functions
function makeConflictAccessor(name) {
var accessor = function(var_args) {
throw new Error("Conflicting property: "+name);
};
freeze(accessor.prototype);
return freeze(accessor);
};
function makeRequiredPropDesc(name) {
return freeze({
value: undefined,
enumerable: false,
required: true
});
}
function makeConflictingPropDesc(name) {
var conflict = makeConflictAccessor(name);
if (SUPPORTS_DEFINEPROP) {
return freeze({
get: conflict,
set: conflict,
enumerable: false,
conflict: true
});
} else {
return freeze({
value: conflict,
enumerable: false,
conflict: true
});
}
}
/**
* Are x and y not observably distinguishable?
*/
function identical(x, y) {
if (x === y) {
// 0 === -0, but they are not identical
return x !== 0 || 1/x === 1/y;
} else {
// NaN !== NaN, but they are identical.
// NaNs are the only non-reflexive value, i.e., if x !== x,
// then x is a NaN.
return x !== x && y !== y;
}
}
// Note: isSameDesc should return true if both
// desc1 and desc2 represent a 'required' property
// (otherwise two composed required properties would be turned into
// a conflict)
function isSameDesc(desc1, desc2) {
// for conflicting properties, don't compare values because
// the conflicting property values are never equal
if (desc1.conflict && desc2.conflict) {
return true;
} else {
return ( desc1.get === desc2.get
&& desc1.set === desc2.set
&& identical(desc1.value, desc2.value)
&& desc1.enumerable === desc2.enumerable
&& desc1.required === desc2.required
&& desc1.conflict === desc2.conflict);
}
}
function freezeAndBind(meth, self) {
return freeze(bindThis(meth, self));
}
/* makeSet(['foo', ...]) => { foo: true, ...}
*
* makeSet returns an object whose own properties represent a set.
*
* Each string in the names array is added to the set.
*
* To test whether an element is in the set, perform:
* hasOwnProperty(set, element)
*/
function makeSet(names) {
var set = {};
forEach(names, function (name) {
set[name] = true;
});
return freeze(set);
}
// == singleton object to be used as the placeholder for a required
// property ==
var required = freeze({
toString: function() { return '<Trait.required>'; }
});
// == The public API methods ==
/**
* var newTrait = trait({ foo:required, ... })
*
* @param object an object record (in principle an object literal)
* @returns a new trait describing all of the own properties of the object
* (both enumerable and non-enumerable)
*
* As a general rule, 'trait' should be invoked with an object
* literal, since the object merely serves as a record
* descriptor. Both its identity and its prototype chain are
* irrelevant.
*
* Data properties bound to function objects in the argument will be
* flagged as 'method' properties. The prototype of these function
* objects is frozen.
*
* Data properties bound to the 'required' singleton exported by
* this module will be marked as 'required' properties.
*
* The <tt>trait</tt> function is pure if no other code can witness
* the side-effects of freezing the prototypes of the methods. If
* <tt>trait</tt> is invoked with an object literal whose methods
* are represented as in-place anonymous functions, this should
* normally be the case.
*/
function trait(obj) {
var map = {};
forEach(getOwnPropertyNames(obj), function (name) {
var pd = getOwnPropertyDescriptor(obj, name);
if (pd.value === required) {
pd = makeRequiredPropDesc(name);
} else if (typeof pd.value === 'function') {
pd.method = true;
if ('prototype' in pd.value) {
freeze(pd.value.prototype);
}
} else {
if (pd.get && pd.get.prototype) { freeze(pd.get.prototype); }
if (pd.set && pd.set.prototype) { freeze(pd.set.prototype); }
}
map[name] = pd;
});
return map;
}
/**
* var newTrait = compose(trait_1, trait_2, ..., trait_N)
*
* @param trait_i a trait object
* @returns a new trait containing the combined own properties of
* all the trait_i.
*
* If two or more traits have own properties with the same name, the new
* trait will contain a 'conflict' property for that name. 'compose' is
* a commutative and associative operation, and the order of its
* arguments is not significant.
*
* If 'compose' is invoked with < 2 arguments, then:
* compose(trait_1) returns a trait equivalent to trait_1
* compose() returns an empty trait
*/
function compose(var_args) {
var traits = slice(arguments, 0);
var newTrait = {};
forEach(traits, function (trait) {
forEach(getOwnPropertyNames(trait), function (name) {
var pd = trait[name];
if (hasOwnProperty(newTrait, name) &&
!newTrait[name].required) {
// a non-required property with the same name was previously
// defined this is not a conflict if pd represents a
// 'required' property itself:
if (pd.required) {
return; // skip this property, the required property is
// now present
}
if (!isSameDesc(newTrait[name], pd)) {
// a distinct, non-required property with the same name
// was previously defined by another trait => mark as
// conflicting property
newTrait[name] = makeConflictingPropDesc(name);
} // else,
// properties are not in conflict if they refer to the same value
} else {
newTrait[name] = pd;
}
});
});
return freeze(newTrait);
}
/* var newTrait = exclude(['name', ...], trait)
*
* @param names a list of strings denoting property names.
* @param trait a trait some properties of which should be excluded.
* @returns a new trait with the same own properties as the original trait,
* except that all property names appearing in the first argument
* are replaced by required property descriptors.
*
* Note: exclude(A, exclude(B,t)) is equivalent to exclude(A U B, t)
*/
function exclude(names, trait) {
var exclusions = makeSet(names);
var newTrait = {};
forEach(getOwnPropertyNames(trait), function (name) {
// required properties are not excluded but ignored
if (!hasOwnProperty(exclusions, name) || trait[name].required) {
newTrait[name] = trait[name];
} else {
// excluded properties are replaced by required properties
newTrait[name] = makeRequiredPropDesc(name);
}
});
return freeze(newTrait);
}
/**
* var newTrait = override(trait_1, trait_2, ..., trait_N)
*
* @returns a new trait with all of the combined properties of the
* argument traits. In contrast to 'compose', 'override'
* immediately resolves all conflicts resulting from this
* composition by overriding the properties of later
* traits. Trait priority is from left to right. I.e. the
* properties of the leftmost trait are never overridden.
*
* override is associative:
* override(t1,t2,t3) is equivalent to override(t1, override(t2, t3)) or
* to override(override(t1, t2), t3)
* override is not commutative: override(t1,t2) is not equivalent
* to override(t2,t1)
*
* override() returns an empty trait
* override(trait_1) returns a trait equivalent to trait_1
*/
function override(var_args) {
var traits = slice(arguments, 0);
var newTrait = {};
forEach(traits, function (trait) {
forEach(getOwnPropertyNames(trait), function (name) {
var pd = trait[name];
// add this trait's property to the composite trait only if
// - the trait does not yet have this property
// - or, the trait does have the property, but it's a required property
if (!hasOwnProperty(newTrait, name) || newTrait[name].required) {
newTrait[name] = pd;
}
});
});
return freeze(newTrait);
}
/**
* var newTrait = override(dominantTrait, recessiveTrait)
*
* @returns a new trait with all of the properties of dominantTrait
* and all of the properties of recessiveTrait not in dominantTrait
*
* Note: override is associative:
* override(t1, override(t2, t3)) is equivalent to
* override(override(t1, t2), t3)
*/
/*function override(frontT, backT) {
var newTrait = {};
// first copy all of backT's properties into newTrait
forEach(getOwnPropertyNames(backT), function (name) {
newTrait[name] = backT[name];
});
// now override all these properties with frontT's properties
forEach(getOwnPropertyNames(frontT), function (name) {
var pd = frontT[name];
// frontT's required property does not override the provided property
if (!(pd.required && hasOwnProperty(newTrait, name))) {
newTrait[name] = pd;
}
});
return freeze(newTrait);
}*/
/**
* var newTrait = rename(map, trait)
*
* @param map an object whose own properties serve as a mapping from
old names to new names.
* @param trait a trait object
* @returns a new trait with the same properties as the original trait,
* except that all properties whose name is an own property
* of map will be renamed to map[name], and a 'required' property
* for name will be added instead.
*
* rename({a: 'b'}, t) eqv compose(exclude(['a'],t),
* { a: { required: true },
* b: t[a] })
*
* For each renamed property, a required property is generated. If
* the map renames two properties to the same name, a conflict is
* generated. If the map renames a property to an existing
* unrenamed property, a conflict is generated.
*
* Note: rename(A, rename(B, t)) is equivalent to rename(\n ->
* A(B(n)), t) Note: rename({...},exclude([...], t)) is not eqv to
* exclude([...],rename({...}, t))
*/
function rename(map, trait) {
var renamedTrait = {};
forEach(getOwnPropertyNames(trait), function (name) {
// required props are never renamed
if (hasOwnProperty(map, name) && !trait[name].required) {
var alias = map[name]; // alias defined in map
if (hasOwnProperty(renamedTrait, alias) &&
!renamedTrait[alias].required) {
// could happen if 2 props are mapped to the same alias
renamedTrait[alias] = makeConflictingPropDesc(alias);
} else {
// add the property under an alias
renamedTrait[alias] = trait[name];
}
// add a required property under the original name
// but only if a property under the original name does not exist
// such a prop could exist if an earlier prop in the trait was
// previously aliased to this name
if (!hasOwnProperty(renamedTrait, name)) {
renamedTrait[name] = makeRequiredPropDesc(name);
}
} else { // no alias defined
if (hasOwnProperty(renamedTrait, name)) {
// could happen if another prop was previously aliased to name
if (!trait[name].required) {
renamedTrait[name] = makeConflictingPropDesc(name);
}
// else required property overridden by a previously aliased
// property and otherwise ignored
} else {
renamedTrait[name] = trait[name];
}
}
});
return freeze(renamedTrait);
}
/**
* var newTrait = resolve({ oldName: 'newName', excludeName:
* undefined, ... }, trait)
*
* This is a convenience function combining renaming and
* exclusion. It can be implemented as <tt>rename(map,
* exclude(exclusions, trait))</tt> where map is the subset of
* mappings from oldName to newName and exclusions is an array of
* all the keys that map to undefined (or another falsy value).
*
* @param resolutions an object whose own properties serve as a
mapping from old names to new names, or to undefined if
the property should be excluded
* @param trait a trait object
* @returns a resolved trait with the same own properties as the
* original trait.
*
* In a resolved trait, all own properties whose name is an own property
* of resolutions will be renamed to resolutions[name] if it is truthy,
* or their value is changed into a required property descriptor if
* resolutions[name] is falsy.
*
* Note, it's important to _first_ exclude, _then_ rename, since exclude
* and rename are not associative, for example:
* rename({a: 'b'}, exclude(['b'], trait({ a:1,b:2 }))) eqv trait({b:1})
* exclude(['b'], rename({a: 'b'}, trait({ a:1,b:2 }))) eqv
* trait({b:Trait.required})
*
* writing resolve({a:'b', b: undefined},trait({a:1,b:2})) makes it
* clear that what is meant is to simply drop the old 'b' and rename
* 'a' to 'b'
*/
function resolve(resolutions, trait) {
var renames = {};
var exclusions = [];
// preprocess renamed and excluded properties
for (var name in resolutions) {
if (hasOwnProperty(resolutions, name)) {
if (resolutions[name]) { // old name -> new name
renames[name] = resolutions[name];
} else { // name -> undefined
exclusions.push(name);
}
}
}
return rename(renames, exclude(exclusions, trait));
}
/**
* var obj = create(proto, trait)
*
* @param proto denotes the prototype of the completed object
* @param trait a trait object to be turned into a complete object
* @returns an object with all of the properties described by the trait.
* @throws 'Missing required property' the trait still contains a
* required property.
* @throws 'Remaining conflicting property' if the trait still
* contains a conflicting property.
*
* Trait.create is like Object.create, except that it generates
* high-integrity or final objects. In addition to creating a new object
* from a trait, it also ensures that:
* - an exception is thrown if 'trait' still contains required properties
* - an exception is thrown if 'trait' still contains conflicting
* properties
* - the object is and all of its accessor and method properties are frozen
* - the 'this' pseudovariable in all accessors and methods of
* the object is bound to the composed object.
*
* Use Object.create instead of Trait.create if you want to create
* abstract or malleable objects. Keep in mind that for such objects:
* - no exception is thrown if 'trait' still contains required properties
* (the properties are simply dropped from the composite object)
* - no exception is thrown if 'trait' still contains conflicting
* properties (these properties remain as conflicting
* properties in the composite object)
* - neither the object nor its accessor and method properties are frozen
* - the 'this' pseudovariable in all accessors and methods of
* the object is left unbound.
*/
function create(proto, trait) {
var self = Object_create(proto);
var properties = {};
forEach(getOwnPropertyNames(trait), function (name) {
var pd = trait[name];
// check for remaining 'required' properties
// Note: it's OK for the prototype to provide the properties
if (pd.required) {
if (!(name in proto)) {
throw new Error('Missing required property: '+name);
}
} else if (pd.conflict) { // check for remaining conflicting properties
throw new Error('Remaining conflicting property: '+name);
} else if ('value' in pd) { // data property
// freeze all function properties and their prototype
if (pd.method) { // the property is meant to be used as a method
// bind 'this' in trait method to the composite object
properties[name] = {
value: freezeAndBind(pd.value, self),
enumerable: pd.enumerable,
configurable: pd.configurable,
writable: pd.writable
};
} else {
properties[name] = pd;
}
} else { // accessor property
properties[name] = {
get: pd.get ? freezeAndBind(pd.get, self) : undefined,
set: pd.set ? freezeAndBind(pd.set, self) : undefined,
enumerable: pd.enumerable,
configurable: pd.configurable
};
}
});
defineProperties(self, properties);
return freeze(self);
}
/** A shorthand for create(Object.prototype, trait({...}), options) */
function object(record, options) {
return create(Object.prototype, trait(record), options);
}
/**
* Tests whether two traits are equivalent. T1 is equivalent to T2 iff
* both describe the same set of property names and for all property
* names n, T1[n] is equivalent to T2[n]. Two property descriptors are
* equivalent if they have the same value, accessors and attributes.
*
* @return a boolean indicating whether the two argument traits are
* equivalent.
*/
function eqv(trait1, trait2) {
var names1 = getOwnPropertyNames(trait1);
var names2 = getOwnPropertyNames(trait2);
var name;
if (names1.length !== names2.length) {
return false;
}
for (var i = 0; i < names1.length; i++) {
name = names1[i];
if (!trait2[name] || !isSameDesc(trait1[name], trait2[name])) {
return false;
}
}
return true;
}
// if this code is ran in ES3 without an Object.create function, this
// library will define it on Object:
if (!Object.create) {
Object.create = Object_create;
}
// ES5 does not by default provide Object.getOwnProperties
// if it's not defined, the Traits library defines this utility
// function on Object
if(!Object.getOwnProperties) {
Object.getOwnProperties = getOwnProperties;
}
// expose the public API of this module
function Trait(record) {
// calling Trait as a function creates a new atomic trait
return trait(record);
}
Trait.required = freeze(required);
Trait.compose = freeze(compose);
Trait.resolve = freeze(resolve);
Trait.override = freeze(override);
Trait.create = freeze(create);
Trait.eqv = freeze(eqv);
Trait.object = freeze(object); // not essential, cf. create + trait
return freeze(Trait);
})();
if (typeof exports !== "undefined") { // CommonJS module support
exports.Trait = Trait;
}