UNPKG

ion

Version:

ion language ========================

1,485 lines (1,290 loc) 283 kB
/* * 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; } }