ion
Version:
ion language ========================
1,485 lines (1,290 loc) • 283 kB
JavaScript
/*
* Sugar Custom 2017.04.21
*
* Freely distributable and licensed under the MIT-style license.
* Copyright (c) Andrew Plummer
* https://sugarjs.com/
*
* ---------------------------- */
(function() {
'use strict';
/***
* @module Core
* @description Core functionality including the ability to define methods and
* extend onto natives.
*
***/
// The global to export.
var Sugar;
// The name of Sugar in the global namespace.
var SUGAR_GLOBAL = 'Sugar';
// Natives available on initialization. Letting Object go first to ensure its
// global is set by the time the rest are checking for chainable Object methods.
var NATIVE_NAMES = 'Object Number String Array Date RegExp Function';
// Static method flag
var STATIC = 0x1;
// Instance method flag
var INSTANCE = 0x2;
// IE8 has a broken defineProperty but no defineProperties so this saves a try/catch.
var PROPERTY_DESCRIPTOR_SUPPORT = !!(Object.defineProperty && Object.defineProperties);
// The global context. Rhino uses a different "global" keyword so
// do an extra check to be sure that it's actually the global context.
var globalContext = typeof global !== 'undefined' && global.Object === Object ? global : this;
// Is the environment node?
var hasExports = typeof module !== 'undefined' && module.exports;
// Whether object instance methods can be mapped to the prototype.
var allowObjectPrototype = false;
// A map from Array to SugarArray.
var namespacesByName = {};
// A map from [object Object] to namespace.
var namespacesByClassString = {};
// Defining properties.
var defineProperty = PROPERTY_DESCRIPTOR_SUPPORT ? Object.defineProperty : definePropertyShim;
// A default chainable class for unknown types.
var DefaultChainable = getNewChainableClass('Chainable');
// Global methods
function setupGlobal() {
Sugar = globalContext[SUGAR_GLOBAL];
// istanbul ignore if
if (Sugar) {
// Reuse already defined Sugar global object.
return;
}
Sugar = function(arg) {
forEachProperty(Sugar, function(sugarNamespace, name) {
// Although only the only enumerable properties on the global
// object are Sugar namespaces, environments that can't set
// non-enumerable properties will step through the utility methods
// as well here, so use this check to only allow true namespaces.
if (hasOwn(namespacesByName, name)) {
sugarNamespace.extend(arg);
}
});
return Sugar;
};
// istanbul ignore else
if (hasExports) {
module.exports = Sugar;
} else {
try {
globalContext[SUGAR_GLOBAL] = Sugar;
} catch (e) {
// Contexts such as QML have a read-only global context.
}
}
forEachProperty(NATIVE_NAMES.split(' '), function(name) {
createNamespace(name);
});
setGlobalProperties();
}
/***
* @method createNamespace(name)
* @returns SugarNamespace
* @namespace Sugar
* @short Creates a new Sugar namespace.
* @extra This method is for plugin developers who want to define methods to be
* used with natives that Sugar does not handle by default. The new
* namespace will appear on the `Sugar` global with all the methods of
* normal namespaces, including the ability to define new methods. When
* extended, any defined methods will be mapped to `name` in the global
* context.
*
* @example
*
* Sugar.createNamespace('Boolean');
*
* @param {string} name - The namespace name.
*
***/
function createNamespace(name) {
// Is the current namespace Object?
var isObject = name === 'Object';
// A Sugar namespace is also a chainable class: Sugar.Array, etc.
var sugarNamespace = getNewChainableClass(name, true);
/***
* @method extend([opts])
* @returns Sugar
* @namespace Sugar
* @short Extends Sugar defined methods onto natives.
* @extra This method can be called on individual namespaces like
* `Sugar.Array` or on the `Sugar` global itself, in which case
* [opts] will be forwarded to each `extend` call. For more,
* see `extending`.
*
* @options
*
* methods An array of method names to explicitly extend.
*
* except An array of method names or global namespaces (`Array`,
* `String`) to explicitly exclude. Namespaces should be the
* actual global objects, not strings.
*
* namespaces An array of global namespaces (`Array`, `String`) to
* explicitly extend. Namespaces should be the actual
* global objects, not strings.
*
* enhance A shortcut to disallow all "enhance" flags at once
* (flags listed below). For more, see `enhanced methods`.
* Default is `true`.
*
* enhanceString A boolean allowing String enhancements. Default is `true`.
*
* enhanceArray A boolean allowing Array enhancements. Default is `true`.
*
* objectPrototype A boolean allowing Sugar to extend Object.prototype
* with instance methods. This option is off by default
* and should generally not be used except with caution.
* For more, see `object methods`.
*
* @example
*
* Sugar.Array.extend();
* Sugar.extend();
*
* @option {Array<string>} [methods]
* @option {Array<string|NativeConstructor>} [except]
* @option {Array<NativeConstructor>} [namespaces]
* @option {boolean} [enhance]
* @option {boolean} [enhanceString]
* @option {boolean} [enhanceArray]
* @option {boolean} [objectPrototype]
* @param {ExtendOptions} [opts]
*
***
* @method extend([opts])
* @returns SugarNamespace
* @namespace SugarNamespace
* @short Extends Sugar defined methods for a specific namespace onto natives.
* @param {ExtendOptions} [opts]
*
***/
var extend = function (opts) {
var nativeClass = globalContext[name], nativeProto = nativeClass.prototype;
var staticMethods = {}, instanceMethods = {}, methodsByName;
function objectRestricted(name, target) {
return isObject && target === nativeProto &&
(!allowObjectPrototype || name === 'get' || name === 'set');
}
function arrayOptionExists(field, val) {
var arr = opts[field];
if (arr) {
for (var i = 0, el; el = arr[i]; i++) {
if (el === val) {
return true;
}
}
}
return false;
}
function arrayOptionExcludes(field, val) {
return opts[field] && !arrayOptionExists(field, val);
}
function disallowedByFlags(methodName, target, flags) {
// Disallowing methods by flag currently only applies if methods already
// exist to avoid enhancing native methods, as aliases should still be
// extended (i.e. Array#all should still be extended even if Array#every
// is being disallowed by a flag).
if (!target[methodName] || !flags) {
return false;
}
for (var i = 0; i < flags.length; i++) {
if (opts[flags[i]] === false) {
return true;
}
}
}
function namespaceIsExcepted() {
return arrayOptionExists('except', nativeClass) ||
arrayOptionExcludes('namespaces', nativeClass);
}
function methodIsExcepted(methodName) {
return arrayOptionExists('except', methodName);
}
function canExtend(methodName, method, target) {
return !objectRestricted(methodName, target) &&
!disallowedByFlags(methodName, target, method.flags) &&
!methodIsExcepted(methodName);
}
opts = opts || {};
methodsByName = opts.methods;
if (namespaceIsExcepted()) {
return;
} else if (isObject && typeof opts.objectPrototype === 'boolean') {
// Store "objectPrototype" flag for future reference.
allowObjectPrototype = opts.objectPrototype;
}
forEachProperty(methodsByName || sugarNamespace, function(method, methodName) {
if (methodsByName) {
// If we have method names passed in an array,
// then we need to flip the key and value here
// and find the method in the Sugar namespace.
methodName = method;
method = sugarNamespace[methodName];
}
if (hasOwn(method, 'instance') && canExtend(methodName, method, nativeProto)) {
instanceMethods[methodName] = method.instance;
}
if(hasOwn(method, 'static') && canExtend(methodName, method, nativeClass)) {
staticMethods[methodName] = method;
}
});
// Accessing the extend target each time instead of holding a reference as
// it may have been overwritten (for example Date by Sinon). Also need to
// access through the global to allow extension of user-defined namespaces.
extendNative(nativeClass, staticMethods);
extendNative(nativeProto, instanceMethods);
if (!methodsByName) {
// If there are no method names passed, then
// all methods in the namespace will be extended
// to the native. This includes all future defined
// methods, so add a flag here to check later.
setProperty(sugarNamespace, 'active', true);
}
return sugarNamespace;
};
function defineWithOptionCollect(methodName, instance, args) {
setProperty(sugarNamespace, methodName, function(arg1, arg2, arg3) {
var opts = collectDefineOptions(arg1, arg2, arg3);
defineMethods(sugarNamespace, opts.methods, instance, args, opts.last);
return sugarNamespace;
});
}
/***
* @method defineStatic(methods)
* @returns SugarNamespace
* @namespace SugarNamespace
* @short Defines static methods on the namespace that can later be extended
* onto the native globals.
* @extra Accepts either a single object mapping names to functions, or name
* and function as two arguments. If `extend` was previously called
* with no arguments, the method will be immediately mapped to its
* native when defined.
*
* @example
*
* Sugar.Number.defineStatic({
* isOdd: function (num) {
* return num % 2 === 1;
* }
* });
*
* @signature defineStatic(methodName, methodFn)
* @param {Object} methods - Methods to be defined.
* @param {string} methodName - Name of a single method to be defined.
* @param {Function} methodFn - Function body of a single method to be defined.
***/
defineWithOptionCollect('defineStatic', STATIC);
/***
* @method defineInstance(methods)
* @returns SugarNamespace
* @namespace SugarNamespace
* @short Defines methods on the namespace that can later be extended as
* instance methods onto the native prototype.
* @extra Accepts either a single object mapping names to functions, or name
* and function as two arguments. All functions should accept the
* native for which they are mapped as their first argument, and should
* never refer to `this`. If `extend` was previously called with no
* arguments, the method will be immediately mapped to its native when
* defined.
*
* Methods cannot accept more than 4 arguments in addition to the
* native (5 arguments total). Any additional arguments will not be
* mapped. If the method needs to accept unlimited arguments, use
* `defineInstanceWithArguments`. Otherwise if more options are
* required, use an options object instead.
*
* @example
*
* Sugar.Number.defineInstance({
* square: function (num) {
* return num * num;
* }
* });
*
* @signature defineInstance(methodName, methodFn)
* @param {Object} methods - Methods to be defined.
* @param {string} methodName - Name of a single method to be defined.
* @param {Function} methodFn - Function body of a single method to be defined.
***/
defineWithOptionCollect('defineInstance', INSTANCE);
/***
* @method defineInstanceAndStatic(methods)
* @returns SugarNamespace
* @namespace SugarNamespace
* @short A shortcut to define both static and instance methods on the namespace.
* @extra This method is intended for use with `Object` instance methods. Sugar
* will not map any methods to `Object.prototype` by default, so defining
* instance methods as static helps facilitate their proper use.
*
* @example
*
* Sugar.Object.defineInstanceAndStatic({
* isAwesome: function (obj) {
* // check if obj is awesome!
* }
* });
*
* @signature defineInstanceAndStatic(methodName, methodFn)
* @param {Object} methods - Methods to be defined.
* @param {string} methodName - Name of a single method to be defined.
* @param {Function} methodFn - Function body of a single method to be defined.
***/
defineWithOptionCollect('defineInstanceAndStatic', INSTANCE | STATIC);
/***
* @method defineStaticWithArguments(methods)
* @returns SugarNamespace
* @namespace SugarNamespace
* @short Defines static methods that collect arguments.
* @extra This method is identical to `defineStatic`, except that when defined
* methods are called, they will collect any arguments past `n - 1`,
* where `n` is the number of arguments that the method accepts.
* Collected arguments will be passed to the method in an array
* as the last argument defined on the function.
*
* @example
*
* Sugar.Number.defineStaticWithArguments({
* addAll: function (num, args) {
* for (var i = 0; i < args.length; i++) {
* num += args[i];
* }
* return num;
* }
* });
*
* @signature defineStaticWithArguments(methodName, methodFn)
* @param {Object} methods - Methods to be defined.
* @param {string} methodName - Name of a single method to be defined.
* @param {Function} methodFn - Function body of a single method to be defined.
***/
defineWithOptionCollect('defineStaticWithArguments', STATIC, true);
/***
* @method defineInstanceWithArguments(methods)
* @returns SugarNamespace
* @namespace SugarNamespace
* @short Defines instance methods that collect arguments.
* @extra This method is identical to `defineInstance`, except that when
* defined methods are called, they will collect any arguments past
* `n - 1`, where `n` is the number of arguments that the method
* accepts. Collected arguments will be passed to the method as the
* last argument defined on the function.
*
* @example
*
* Sugar.Number.defineInstanceWithArguments({
* addAll: function (num, args) {
* for (var i = 0; i < args.length; i++) {
* num += args[i];
* }
* return num;
* }
* });
*
* @signature defineInstanceWithArguments(methodName, methodFn)
* @param {Object} methods - Methods to be defined.
* @param {string} methodName - Name of a single method to be defined.
* @param {Function} methodFn - Function body of a single method to be defined.
***/
defineWithOptionCollect('defineInstanceWithArguments', INSTANCE, true);
/***
* @method defineStaticPolyfill(methods)
* @returns SugarNamespace
* @namespace SugarNamespace
* @short Defines static methods that are mapped onto the native if they do
* not already exist.
* @extra Intended only for use creating polyfills that follow the ECMAScript
* spec. Accepts either a single object mapping names to functions, or
* name and function as two arguments. Note that polyfill methods will
* be immediately mapped onto their native prototype regardless of the
* use of `extend`.
*
* @example
*
* Sugar.Object.defineStaticPolyfill({
* keys: function (obj) {
* // get keys!
* }
* });
*
* @signature defineStaticPolyfill(methodName, methodFn)
* @param {Object} methods - Methods to be defined.
* @param {string} methodName - Name of a single method to be defined.
* @param {Function} methodFn - Function body of a single method to be defined.
***/
setProperty(sugarNamespace, 'defineStaticPolyfill', function(arg1, arg2, arg3) {
var opts = collectDefineOptions(arg1, arg2, arg3);
extendNative(globalContext[name], opts.methods, true, opts.last);
return sugarNamespace;
});
/***
* @method defineInstancePolyfill(methods)
* @returns SugarNamespace
* @namespace SugarNamespace
* @short Defines instance methods that are mapped onto the native prototype
* if they do not already exist.
* @extra Intended only for use creating polyfills that follow the ECMAScript
* spec. Accepts either a single object mapping names to functions, or
* name and function as two arguments. This method differs from
* `defineInstance` as there is no static signature (as the method
* is mapped as-is to the native), so it should refer to its `this`
* object. Note that polyfill methods will be immediately mapped onto
* their native prototype regardless of the use of `extend`.
*
* @example
*
* Sugar.Array.defineInstancePolyfill({
* indexOf: function (arr, el) {
* // index finding code here!
* }
* });
*
* @signature defineInstancePolyfill(methodName, methodFn)
* @param {Object} methods - Methods to be defined.
* @param {string} methodName - Name of a single method to be defined.
* @param {Function} methodFn - Function body of a single method to be defined.
***/
setProperty(sugarNamespace, 'defineInstancePolyfill', function(arg1, arg2, arg3) {
var opts = collectDefineOptions(arg1, arg2, arg3);
extendNative(globalContext[name].prototype, opts.methods, true, opts.last);
// Map instance polyfills to chainable as well.
forEachProperty(opts.methods, function(fn, methodName) {
defineChainableMethod(sugarNamespace, methodName, fn);
});
return sugarNamespace;
});
/***
* @method alias(toName, from)
* @returns SugarNamespace
* @namespace SugarNamespace
* @short Aliases one Sugar method to another.
*
* @example
*
* Sugar.Array.alias('all', 'every');
*
* @signature alias(toName, fn)
* @param {string} toName - Name for new method.
* @param {string|Function} from - Method to alias, or string shortcut.
***/
setProperty(sugarNamespace, 'alias', function(name, source) {
var method = typeof source === 'string' ? sugarNamespace[source] : source;
setMethod(sugarNamespace, name, method);
return sugarNamespace;
});
// Each namespace can extend only itself through its .extend method.
setProperty(sugarNamespace, 'extend', extend);
// Cache the class to namespace relationship for later use.
namespacesByName[name] = sugarNamespace;
namespacesByClassString['[object ' + name + ']'] = sugarNamespace;
mapNativeToChainable(name);
mapObjectChainablesToNamespace(sugarNamespace);
// Export
return Sugar[name] = sugarNamespace;
}
function setGlobalProperties() {
setProperty(Sugar, 'extend', Sugar);
setProperty(Sugar, 'toString', toString);
setProperty(Sugar, 'createNamespace', createNamespace);
setProperty(Sugar, 'util', {
'hasOwn': hasOwn,
'getOwn': getOwn,
'setProperty': setProperty,
'classToString': classToString,
'defineProperty': defineProperty,
'forEachProperty': forEachProperty,
'mapNativeToChainable': mapNativeToChainable
});
}
function toString() {
return SUGAR_GLOBAL;
}
// Defining Methods
function defineMethods(sugarNamespace, methods, type, args, flags) {
forEachProperty(methods, function(method, methodName) {
var instanceMethod, staticMethod = method;
if (args) {
staticMethod = wrapMethodWithArguments(method);
}
if (flags) {
staticMethod.flags = flags;
}
// A method may define its own custom implementation, so
// make sure that's not the case before creating one.
if (type & INSTANCE && !method.instance) {
instanceMethod = wrapInstanceMethod(method, args);
setProperty(staticMethod, 'instance', instanceMethod);
}
if (type & STATIC) {
setProperty(staticMethod, 'static', true);
}
setMethod(sugarNamespace, methodName, staticMethod);
if (sugarNamespace.active) {
// If the namespace has been activated (.extend has been called),
// then map this method as well.
sugarNamespace.extend(methodName);
}
});
}
function collectDefineOptions(arg1, arg2, arg3) {
var methods, last;
if (typeof arg1 === 'string') {
methods = {};
methods[arg1] = arg2;
last = arg3;
} else {
methods = arg1;
last = arg2;
}
return {
last: last,
methods: methods
};
}
function wrapInstanceMethod(fn, args) {
return args ? wrapMethodWithArguments(fn, true) : wrapInstanceMethodFixed(fn);
}
function wrapMethodWithArguments(fn, instance) {
// Functions accepting enumerated arguments will always have "args" as the
// last argument, so subtract one from the function length to get the point
// at which to start collecting arguments. If this is an instance method on
// a prototype, then "this" will be pushed into the arguments array so start
// collecting 1 argument earlier.
var startCollect = fn.length - 1 - (instance ? 1 : 0);
return function() {
var args = [], collectedArgs = [], len;
if (instance) {
args.push(this);
}
len = Math.max(arguments.length, startCollect);
// Optimized: no leaking arguments
for (var i = 0; i < len; i++) {
if (i < startCollect) {
args.push(arguments[i]);
} else {
collectedArgs.push(arguments[i]);
}
}
args.push(collectedArgs);
return fn.apply(this, args);
};
}
function wrapInstanceMethodFixed(fn) {
switch(fn.length) {
// Wrapped instance methods will always be passed the instance
// as the first argument, but requiring the argument to be defined
// may cause confusion here, so return the same wrapped function regardless.
case 0:
case 1:
return function() {
return fn(this);
};
case 2:
return function(a) {
return fn(this, a);
};
case 3:
return function(a, b) {
return fn(this, a, b);
};
case 4:
return function(a, b, c) {
return fn(this, a, b, c);
};
case 5:
return function(a, b, c, d) {
return fn(this, a, b, c, d);
};
}
}
// Method helpers
function extendNative(target, source, polyfill, override) {
forEachProperty(source, function(method, name) {
if (polyfill && !override && target[name]) {
// Method exists, so bail.
return;
}
setProperty(target, name, method);
});
}
function setMethod(sugarNamespace, methodName, method) {
sugarNamespace[methodName] = method;
if (method.instance) {
defineChainableMethod(sugarNamespace, methodName, method.instance, true);
}
}
// Chainables
function getNewChainableClass(name) {
var fn = function SugarChainable(obj, arg) {
if (!(this instanceof fn)) {
return new fn(obj, arg);
}
if (this.constructor !== fn) {
// Allow modules to define their own constructors.
obj = this.constructor.apply(obj, arguments);
}
this.raw = obj;
};
setProperty(fn, 'toString', function() {
return SUGAR_GLOBAL + name;
});
setProperty(fn.prototype, 'valueOf', function() {
return this.raw;
});
return fn;
}
function defineChainableMethod(sugarNamespace, methodName, fn) {
var wrapped = wrapWithChainableResult(fn), existing, collision, dcp;
dcp = DefaultChainable.prototype;
existing = dcp[methodName];
// If the method was previously defined on the default chainable, then a
// collision exists, so set the method to a disambiguation function that will
// lazily evaluate the object and find it's associated chainable. An extra
// check is required to avoid false positives from Object inherited methods.
collision = existing && existing !== Object.prototype[methodName];
// The disambiguation function is only required once.
if (!existing || !existing.disambiguate) {
dcp[methodName] = collision ? disambiguateMethod(methodName) : wrapped;
}
// The target chainable always receives the wrapped method. Additionally,
// if the target chainable is Sugar.Object, then map the wrapped method
// to all other namespaces as well if they do not define their own method
// of the same name. This way, a Sugar.Number will have methods like
// isEqual that can be called on any object without having to traverse up
// the prototype chain and perform disambiguation, which costs cycles.
// Note that the "if" block below actually does nothing on init as Object
// goes first and no other namespaces exist yet. However it needs to be
// here as Object instance methods defined later also need to be mapped
// back onto existing namespaces.
sugarNamespace.prototype[methodName] = wrapped;
if (sugarNamespace === Sugar.Object) {
mapObjectChainableToAllNamespaces(methodName, wrapped);
}
}
function mapObjectChainablesToNamespace(sugarNamespace) {
forEachProperty(Sugar.Object && Sugar.Object.prototype, function(val, methodName) {
if (typeof val === 'function') {
setObjectChainableOnNamespace(sugarNamespace, methodName, val);
}
});
}
function mapObjectChainableToAllNamespaces(methodName, fn) {
forEachProperty(namespacesByName, function(sugarNamespace) {
setObjectChainableOnNamespace(sugarNamespace, methodName, fn);
});
}
function setObjectChainableOnNamespace(sugarNamespace, methodName, fn) {
var proto = sugarNamespace.prototype;
if (!hasOwn(proto, methodName)) {
proto[methodName] = fn;
}
}
function wrapWithChainableResult(fn) {
return function() {
return new DefaultChainable(fn.apply(this.raw, arguments));
};
}
function disambiguateMethod(methodName) {
var fn = function() {
var raw = this.raw, sugarNamespace;
if (raw != null) {
// Find the Sugar namespace for this unknown.
sugarNamespace = namespacesByClassString[classToString(raw)];
}
if (!sugarNamespace) {
// If no sugarNamespace can be resolved, then default
// back to Sugar.Object so that undefined and other
// non-supported types can still have basic object
// methods called on them, such as type checks.
sugarNamespace = Sugar.Object;
}
return new sugarNamespace(raw)[methodName].apply(this, arguments);
};
fn.disambiguate = true;
return fn;
}
function mapNativeToChainable(name, methodNames) {
var sugarNamespace = namespacesByName[name],
nativeProto = globalContext[name].prototype;
if (!methodNames && ownPropertyNames) {
methodNames = ownPropertyNames(nativeProto);
}
forEachProperty(methodNames, function(methodName) {
if (nativeMethodProhibited(methodName)) {
// Sugar chainables have their own constructors as well as "valueOf"
// methods, so exclude them here. The __proto__ argument should be trapped
// by the function check below, however simply accessing this property on
// Object.prototype causes QML to segfault, so pre-emptively excluding it.
return;
}
try {
var fn = nativeProto[methodName];
if (typeof fn !== 'function') {
// Bail on anything not a function.
return;
}
} catch (e) {
// Function.prototype has properties that
// will throw errors when accessed.
return;
}
defineChainableMethod(sugarNamespace, methodName, fn);
});
}
function nativeMethodProhibited(methodName) {
return methodName === 'constructor' ||
methodName === 'valueOf' ||
methodName === '__proto__';
}
// Util
// Internal references
var ownPropertyNames = Object.getOwnPropertyNames,
internalToString = Object.prototype.toString,
internalHasOwnProperty = Object.prototype.hasOwnProperty;
// Defining this as a variable here as the ES5 module
// overwrites it to patch DONTENUM.
var forEachProperty = function (obj, fn) {
for(var key in obj) {
if (!hasOwn(obj, key)) continue;
if (fn.call(obj, obj[key], key, obj) === false) break;
}
};
// istanbul ignore next
function definePropertyShim(obj, prop, descriptor) {
obj[prop] = descriptor.value;
}
function setProperty(target, name, value, enumerable) {
defineProperty(target, name, {
value: value,
enumerable: !!enumerable,
configurable: true,
writable: true
});
}
// PERF: Attempts to speed this method up get very Heisenbergy. Quickly
// returning based on typeof works for primitives, but slows down object
// types. Even === checks on null and undefined (no typeof) will end up
// basically breaking even. This seems to be as fast as it can go.
function classToString(obj) {
return internalToString.call(obj);
}
function hasOwn(obj, prop) {
return !!obj && internalHasOwnProperty.call(obj, prop);
}
function getOwn(obj, prop) {
if (hasOwn(obj, prop)) {
return obj[prop];
}
}
setupGlobal();
/***
* @module Common
* @description Internal utility and common methods.
***/
// Flag allowing native methods to be enhanced
var ENHANCEMENTS_FLAG = 'enhance';
// For type checking, etc. Excludes object as this is more nuanced.
var NATIVE_TYPES = 'Boolean Number String Date RegExp Function Array Error Set Map';
// Do strings have no keys?
var NO_KEYS_IN_STRING_OBJECTS = !('0' in Object('a'));
// Prefix for private properties
var PRIVATE_PROP_PREFIX = '_sugar_';
// Matches 1..2 style ranges in properties
var PROPERTY_RANGE_REG = /^(.*?)\[([-\d]*)\.\.([-\d]*)\](.*)$/;
// WhiteSpace/LineTerminator as defined in ES5.1 plus Unicode characters in the Space, Separator category.
var TRIM_CHARS = '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF';
// Regex for matching a formatted string
var STRING_FORMAT_REG = /([{}])\1|\{([^}]*)\}|(%)%|(%(\w*))/g;
// Common chars
var HALF_WIDTH_ZERO = 0x30,
FULL_WIDTH_ZERO = 0xff10,
HALF_WIDTH_PERIOD = '.',
FULL_WIDTH_PERIOD = '.',
HALF_WIDTH_COMMA = ',',
OPEN_BRACE = '{',
CLOSE_BRACE = '}';
// Namespace aliases
var sugarObject = Sugar.Object,
sugarArray = Sugar.Array,
sugarDate = Sugar.Date,
sugarString = Sugar.String,
sugarNumber = Sugar.Number,
sugarFunction = Sugar.Function,
sugarRegExp = Sugar.RegExp;
// Core utility aliases
var hasOwn = Sugar.util.hasOwn,
getOwn = Sugar.util.getOwn,
setProperty = Sugar.util.setProperty,
classToString = Sugar.util.classToString,
defineProperty = Sugar.util.defineProperty,
forEachProperty = Sugar.util.forEachProperty,
mapNativeToChainable = Sugar.util.mapNativeToChainable;
// Class checks
var isSerializable,
isBoolean, isNumber, isString,
isDate, isRegExp, isFunction,
isArray, isSet, isMap, isError;
function buildClassChecks() {
var knownTypes = {};
function addCoreTypes() {
var names = spaceSplit(NATIVE_TYPES);
isBoolean = buildPrimitiveClassCheck(names[0]);
isNumber = buildPrimitiveClassCheck(names[1]);
isString = buildPrimitiveClassCheck(names[2]);
isDate = buildClassCheck(names[3]);
isRegExp = buildClassCheck(names[4]);
// Wanted to enhance performance here by using simply "typeof"
// but Firefox has two major issues that make this impossible,
// one fixed, the other not, so perform a full class check here.
//
// 1. Regexes can be typeof "function" in FF < 3
// https://bugzilla.mozilla.org/show_bug.cgi?id=61911 (fixed)
//
// 2. HTMLEmbedElement and HTMLObjectElement are be typeof "function"
// https://bugzilla.mozilla.org/show_bug.cgi?id=268945 (won't fix)
isFunction = buildClassCheck(names[5]);
isArray = Array.isArray || buildClassCheck(names[6]);
isError = buildClassCheck(names[7]);
isSet = buildClassCheck(names[8], typeof Set !== 'undefined' && Set);
isMap = buildClassCheck(names[9], typeof Map !== 'undefined' && Map);
// Add core types as known so that they can be checked by value below,
// notably excluding Functions and adding Arguments and Error.
addKnownType('Arguments');
addKnownType(names[0]);
addKnownType(names[1]);
addKnownType(names[2]);
addKnownType(names[3]);
addKnownType(names[4]);
addKnownType(names[6]);
}
function addArrayTypes() {
var types = 'Int8 Uint8 Uint8Clamped Int16 Uint16 Int32 Uint32 Float32 Float64';
forEach(spaceSplit(types), function(str) {
addKnownType(str + 'Array');
});
}
function addKnownType(className) {
var str = '[object '+ className +']';
knownTypes[str] = true;
}
function isKnownType(className) {
return knownTypes[className];
}
function buildClassCheck(className, globalObject) {
// istanbul ignore if
if (globalObject && isClass(new globalObject, 'Object')) {
return getConstructorClassCheck(globalObject);
} else {
return getToStringClassCheck(className);
}
}
// Map and Set may be [object Object] in certain IE environments.
// In this case we need to perform a check using the constructor
// instead of Object.prototype.toString.
// istanbul ignore next
function getConstructorClassCheck(obj) {
var ctorStr = String(obj);
return function(obj) {
return String(obj.constructor) === ctorStr;
};
}
function getToStringClassCheck(className) {
return function(obj, str) {
// perf: Returning up front on instanceof appears to be slower.
return isClass(obj, className, str);
};
}
function buildPrimitiveClassCheck(className) {
var type = className.toLowerCase();
return function(obj) {
var t = typeof obj;
return t === type || t === 'object' && isClass(obj, className);
};
}
addCoreTypes();
addArrayTypes();
isSerializable = function(obj, className) {
// Only known objects can be serialized. This notably excludes functions,
// host objects, Symbols (which are matched by reference), and instances
// of classes. The latter can arguably be matched by value, but
// distinguishing between these and host objects -- which should never be
// compared by value -- is very tricky so not dealing with it here.
className = className || classToString(obj);
return isKnownType(className) || isPlainObject(obj, className);
};
}
function isClass(obj, className, str) {
if (!str) {
str = classToString(obj);
}
return str === '[object '+ className +']';
}
// Wrapping the core's "define" methods to
// save a few bytes in the minified script.
function wrapNamespace(method) {
return function(sugarNamespace, arg1, arg2) {
sugarNamespace[method](arg1, arg2);
};
}
// Method define aliases
var alias = wrapNamespace('alias'),
defineStatic = wrapNamespace('defineStatic'),
defineInstance = wrapNamespace('defineInstance'),
defineStaticPolyfill = wrapNamespace('defineStaticPolyfill'),
defineInstancePolyfill = wrapNamespace('defineInstancePolyfill'),
defineInstanceAndStatic = wrapNamespace('defineInstanceAndStatic'),
defineInstanceWithArguments = wrapNamespace('defineInstanceWithArguments');
function defineInstanceSimilar(sugarNamespace, set, fn, flags) {
defineInstance(sugarNamespace, collectSimilarMethods(set, fn), flags);
}
function collectSimilarMethods(set, fn) {
var methods = {};
if (isString(set)) {
set = spaceSplit(set);
}
forEach(set, function(el, i) {
fn(methods, el, i);
});
return methods;
}
// This song and dance is to fix methods to a different length
// from what they actually accept in order to stay in line with
// spec. Additionally passing argument length, as some methods
// throw assertion errors based on this (undefined check is not
// enough). Fortunately for now spec is such that passing 3
// actual arguments covers all requirements. Note that passing
// the argument length also forces the compiler to not rewrite
// length of the compiled function.
function fixArgumentLength(fn) {
var staticFn = function(a) {
var args = arguments;
return fn(a, args[1], args[2], args.length - 1);
};
staticFn.instance = function(b) {
var args = arguments;
return fn(this, b, args[1], args.length);
};
return staticFn;
}
function defineAccessor(namespace, name, fn) {
setProperty(namespace, name, fn);
}
function defineOptionsAccessor(namespace, defaults) {
var obj = simpleClone(defaults);
function getOption(name) {
return obj[name];
}
function setOption(arg1, arg2) {
var options;
if (arguments.length === 1) {
options = arg1;
} else {
options = {};
options[arg1] = arg2;
}
forEachProperty(options, function(val, name) {
if (val === null) {
val = defaults[name];
}
obj[name] = val;
});
}
defineAccessor(namespace, 'getOption', getOption);
defineAccessor(namespace, 'setOption', setOption);
return getOption;
}
function assertCallable(obj) {
if (!isFunction(obj)) {
throw new TypeError('Function is not callable');
}
}
function assertArray(obj) {
if (!isArray(obj)) {
throw new TypeError('Array required');
}
}
function assertWritable(obj) {
if (isPrimitive(obj)) {
// If strict mode is active then primitives will throw an
// error when attempting to write properties. We can't be
// sure if strict mode is available, so pre-emptively
// throw an error here to ensure consistent behavior.
throw new TypeError('Property cannot be written');
}
}
// Coerces an object to a positive integer.
// Does not allow Infinity.
function coercePositiveInteger(n) {
n = +n || 0;
if (n < 0 || !isNumber(n) || !isFinite(n)) {
throw new RangeError('Invalid number');
}
return trunc(n);
}
function isDefined(o) {
return o !== undefined;
}
function isUndefined(o) {
return o === undefined;
}
function privatePropertyAccessor(key) {
var privateKey = PRIVATE_PROP_PREFIX + key;
return function(obj, val) {
if (arguments.length > 1) {
setProperty(obj, privateKey, val);
return obj;
}
return obj[privateKey];
};
}
function setChainableConstructor(sugarNamespace, createFn) {
sugarNamespace.prototype.constructor = function() {
return createFn.apply(this, arguments);
};
}
function getKeys(obj) {
return Object.keys(obj);
}
function deepGetProperty(obj, key, any) {
return handleDeepProperty(obj, key, any, false);
}
function handleDeepProperty(obj, key, any, has, fill, fillLast, val) {
var ns, bs, ps, cbi, set, isLast, isPush, isIndex, nextIsIndex, exists;
ns = obj || undefined;
if (key == null) return;
if (isObjectType(key)) {
// Allow array and array-like accessors
bs = [key];
} else {
key = String(key);
if (key.indexOf('..') !== -1) {
return handleArrayIndexRange(obj, key, any, val);
}
bs = key.split('[');
}
set = isDefined(val);
for (var i = 0, blen = bs.length; i < blen; i++) {
ps = bs[i];
if (isString(ps)) {
ps = periodSplit(ps);
}
for (var j = 0, plen = ps.length; j < plen; j++) {
key = ps[j];
// Is this the last key?
isLast = i === blen - 1 && j === plen - 1;
// Index of the closing ]
cbi = key.indexOf(']');
// Is the key an array index?
isIndex = cbi !== -1;
// Is this array push syntax "[]"?
isPush = set && cbi === 0;
// If the bracket split was successful and this is the last element
// in the dot split, then we know the next key will be an array index.
nextIsIndex = blen > 1 && j === plen - 1;
if (isPush) {
// Set the index to the end of the array
key = ns.length;
} else if (isIndex) {
// Remove the closing ]
key = key.slice(0, -1);
}
// If the array index is less than 0, then
// add its length to allow negative indexes.
if (isIndex && key < 0) {
key = +key + ns.length;
}
// Bracket keys may look like users[5] or just [5], so the leading
// characters are optional. We can enter the namespace if this is the
// 2nd part, if there is only 1 part, or if there is an explicit key.
if (i || key || blen === 1) {
exists = any ? key in ns : hasOwn(ns, key);
// Non-existent namespaces are only filled if they are intermediate
// (not at the end) or explicitly filling the last.
if (fill && (!isLast || fillLast) && !exists) {
// For our purposes, last only needs to be an array.
ns = ns[key] = nextIsIndex || (fillLast && isLast) ? [] : {};
continue;
}
if (has) {
if (isLast || !exists) {
return exists;
}
} else if (set && isLast) {
assertWritable(ns);
ns[key] = val;
}
ns = exists ? ns[key] : undefined;
}
}
}
return ns;
}
// Get object property with support for 0..1 style range notation.
function handleArrayIndexRange(obj, key, any, val) {
var match, start, end, leading, trailing, arr, set;
match = key.match(PROPERTY_RANGE_REG);
if (!match) {
return;
}
set = isDefined(val);
leading = match[1];
if (leading) {
arr = handleDeepProperty(obj, leading, any, false, set ? true : false, true);
} else {
arr = obj;
}
assertArray(arr);
trailing = match[4];
start = match[2] ? +match[2] : 0;
end = match[3] ? +match[3] : arr.length;
// A range of 0..1 is inclusive, so we need to add 1 to the end. If this
// pushes the index from -1 to 0, then set it to the full length of the
// array, otherwise it will return nothing.
end = end === -1 ? arr.length : end + 1;
if (set) {
for (var i = start; i < end; i++) {
handleDeepProperty(arr, i + trailing, any, false, true, false, val);
}
} else {
arr = arr.slice(start, end);
// If there are trailing properties, then they need to be mapped for each
// element in the array.
if (trailing) {
if (trailing.charAt(0) === HALF_WIDTH_PERIOD) {
// Need to chomp the period if one is trailing after the range. We
// can't do this at the regex level because it will be required if
// we're setting the value as it needs to be concatentated together
// with the array index to be set.
trailing = trailing.slice(1);
}
return arr.map(function(el) {
return handleDeepProperty(el, trailing);
});
}
}
return arr;
}
function getOwnKey(obj, key) {
if (hasOwn(obj, key)) {
return key;
}
}
function isObjectType(obj, type) {
return !!obj && (type || typeof obj) === 'object';
}
function isPrimitive(obj, type) {
type = type || typeof obj;
return obj == null || type === 'string' || type === 'number' || type === 'boolean';
}
function isPlainObject(obj, className) {
return isObjectType(obj) &&
isClass(obj, 'Object', className) &&
hasValidPlainObjectPrototype(obj) &&
hasOwnEnumeratedProperties(obj);
}
function hasValidPlainObjectPrototype(obj) {
var hasToString = 'toString' in obj;
var hasConstructor = 'constructor' in obj;
// An object created with Object.create(null) has no methods in the
// prototype chain, so check if any are missing. The additional hasToString
// check is for false positives on some host objects in old IE which have
// toString but no constructor. If the object has an inherited constructor,
// then check if it is Object (the "isPrototypeOf" tapdance here is a more
// robust way of ensuring this if the global has been hijacked). Note that
// accessing the constructor directly (without "in" or "hasOwnProperty")
// will throw a permissions error in IE8 on cross-domain windows.
return (!hasConstructor && !hasToString) ||
(hasConstructor && !hasOwn(obj, 'constructor') &&
hasOwn(obj.constructor.prototype, 'isPrototypeOf'));
}
function hasOwnEnumeratedProperties(obj) {
// Plain objects are generally defined as having enumerated properties
// all their own, however in early IE environments without defineProperty,
// there may also be enumerated methods in the prototype chain, so check
// for both of these cases.
var objectProto = Object.prototype;
for (var key in obj) {
var val = obj[key];
if (!hasOwn(obj, key) && val !== objectProto[key]) {
return false;
}
}
return true;
}
function simpleClone(obj) {
return simpleMerge({}, obj);
}
function simpleMerge(target, source) {
forEachProperty(source, function(val, key) {
target[key] = val;
});
return target;
}
// Make primtives types like strings into objects.
function coercePrimitiveToObject(obj) {
if (isPrimitive(obj)) {
obj = Object(obj);
}
// istanbul ignore if
if (NO_KEYS_IN_STRING_OBJECTS && isString(obj)) {
forceStringCoercion(obj);
}
return obj;
}
// Force strings to have their indexes set in
// environments that don't do this automatically.
// istanbul ignore next
function forceStringCoercion(obj) {
var i = 0, chr;
while (chr = obj.charAt(i)) {
obj[i++] = chr;
}
}
// Serializes an object in a way that will provide a token unique
// to the type, class, and value of an object. Host objects, class
// instances etc, are not serializable, and are held in an array
// of references that will return the index as a unique identifier
// for the object. This array is passed from outside so that the
// calling function can decide when to dispose of this array.
function serializeInternal(obj, refs, stack) {
var type = typeof obj, className, value, ref;
// Return quickly for primitives to save cycles
if (isPrimitive(obj, type) && !isRealNaN(obj)) {
return type + obj;
}
className = classToString(obj);
if (!isSerializable(obj, className)) {
ref = indexOf(refs, obj);
if (ref === -1) {
ref = refs.length;
refs.push(obj);
}
return ref;
} else if (isObjectType(obj)) {
value = serializeDeep(obj, refs, stack) + obj.toString();
} else if (1 / obj === -Infinity) {
value = '-0';
} else if (obj.valueOf) {
value = obj.valueOf();
}
return type + className + value;
}
function serializeDeep(obj, refs, stack) {
var result = '';
iterateWithCyclicCheck(obj, true, stack, function(key, val, cyc, stack) {
result += cyc ? 'CYC' : key + serializeInternal(val, refs, stack);
});
return result;
}
function iterateWithCyclicCheck(obj, sortedKeys, stack, fn) {
function next(val, key) {
var cyc = false;
// Allowing a step into the structure before triggering this check to save
// cycles on standard JSON structures and also to try as hard as possible to
// catch basic properties that may have been modified.
if (stack.length > 1) {
var i = stack.length;
while (i--) {
if (stack[i] === val) {
cyc = true;
}
}