From 952afa05ce966e2f4a3bdd40bc14c65422dd0ad0 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Thu, 7 Mar 2013 09:02:44 -0800 Subject: [PATCH] Add the `_.support` object. Former-commit-id: b81ec9e5dbd41c729b3ad71187cb5e77e0755b9f --- build.js | 486 +++++++++++++++++++++++-------------------- build/pre-compile.js | 20 +- lodash.js | 281 +++++++++++++++---------- 3 files changed, 445 insertions(+), 342 deletions(-) diff --git a/build.js b/build.js index 34baebc1f..cb9b3e4c7 100755 --- a/build.js +++ b/build.js @@ -188,13 +188,9 @@ 'arrays', 'bottom', 'firstArg', - 'hasDontEnumBug', - 'hasEnumPrototype', - 'isKeysFast', 'loop', - 'nonEnumArgs', - 'noCharByIndex', 'shadowedProps', + 'support', 'top', 'useHas' ]; @@ -416,7 +412,7 @@ '', ' // avoid array-like object bugs with `Array#shift` and `Array#splice`', ' // in Firefox < 10 and IE < 9', - ' if (hasObjectSpliceBug && value.length === 0) {', + ' if (!support.spliceObjects && value.length === 0) {', ' delete value[0];', ' }', ' return this;', @@ -774,7 +770,7 @@ * @returns {String} Returns the `isArguments` fallback. */ function getIsArgumentsFallback(source) { - return (source.match(/(?:\s*\/\/.*)*\n( *)if *\((?:noArgsClass|!isArguments)[\s\S]+?};\n\1}/) || [''])[0]; + return (source.match(/(?:\s*\/\/.*)*\n( *)if *\((?:!support\.argsClass|!isArguments)[\s\S]+?};\n\1}/) || [''])[0]; } /** @@ -921,29 +917,6 @@ return _.uniq(_.intersection(allMethods, methodNames)); } - /** - * Removes all `argsAreObjects` references from `source`. - * - * @private - * @param {String} source The source to process. - * @returns {String} Returns the modified source. - */ - function removeArgsAreObjects(source) { - source = removeVar(source, 'argsAreObjects'); - - // remove `argsAreObjects` from `_.isArray` - source = source.replace(matchFunction(source, 'isArray'), function(match) { - return match.replace(/\(argsAreObjects && *([^)]+)\)/g, '$1'); - }); - - // remove `argsAreObjects` from `_.isEqual` - source = source.replace(matchFunction(source, 'isEqual'), function(match) { - return match.replace(/!argsAreObjects[^:]+:\s*/g, ''); - }); - - return source; - } - /** * Removes the all references to `varName` from `createIterator` in `source`. * @@ -958,7 +931,7 @@ return source; } // remove data object property assignment - var modified = snippet.replace(RegExp("^ *'" + varName + "': *" + varName + '.+\\n', 'm'), ''); + var modified = snippet.replace(RegExp("^(?: *\\/\\/.*\\n)* *'" + varName + "': *" + varName + '.+\\n+', 'm'), ''); source = source.replace(snippet, function() { return modified; }); @@ -1015,75 +988,6 @@ return removeFromCreateIterator(source, funcName); } - /** - * Removes all `hasDontEnumBug` references from `source`. - * - * @private - * @param {String} source The source to process. - * @returns {String} Returns the modified source. - */ - function removeHasDontEnumBug(source) { - source = removeVar(source, 'shadowedProps'); - source = removeFromCreateIterator(source, 'hasDontEnumBug'); - source = removeFromCreateIterator(source, 'shadowedProps'); - - // remove `hasDontEnumBug` declaration and assignment - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasDontEnumBug\b.*|.+?hasDontEnumBug *=.+/g, ''); - - // remove `hasDontEnumBug` from `iteratorTemplate` - source = source.replace(getIteratorTemplate(source), function(match) { - return match.replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(hasDontEnumBug[\s\S]+?["']\1<% *} *%>.+/, ''); - }); - - return source; - } - - /** - * Removes all `hasEnumPrototype` references from `source`. - * - * @private - * @param {String} source The source to process. - * @returns {String} Returns the modified source. - */ - function removeHasEnumPrototype(source) { - source = removeFromCreateIterator(source, 'hasEnumPrototype'); - - // remove `hasEnumPrototype` declaration and assignment - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasEnumPrototype\b.*|.+?hasEnumPrototype *=.+/g, ''); - - // remove `hasEnumPrototype` from `_.keys` - source = source.replace(matchFunction(source, 'keys'), function(match) { - return match - .replace(/\(hasEnumPrototype[^)]+\)(?:\s*\|\|\s*)?/, '') - .replace(/\s*if *\(\s*\)[^}]+}/, ''); - }); - - // remove `hasEnumPrototype` from `iteratorTemplate` - source = source.replace(getIteratorTemplate(source), function(match) { - return match - .replace(/(?: *\/\/.*\n)* *["'] *(?:<% *)?if *\(hasEnumPrototype *(?:&&|\))[\s\S]+?<% *} *(?:%>|["']).+/g, '') - .replace(/hasEnumPrototype *\|\|\s*/g, ''); - }); - - return source; - } - - /** - * Removes all `hasObjectSpliceBug` references from `source`. - * - * @private - * @param {String} source The source to process. - * @returns {String} Returns the modified source. - */ - function removeHasObjectSpliceBug(source) { - source = removeVar(source, 'hasObjectSpliceBug') - - // remove `hasObjectSpliceBug` fix from the `Array` function mixins - source = source.replace(/(?:\s*\/\/.*)*\n( *)if *\(hasObjectSpliceBug[\s\S]+?(?:{\s*}|\n\1})/, ''); - - return source; - } - /** * Removes the `_.isArguments` fallback from `source`. * @@ -1106,25 +1010,6 @@ return source.replace(getIsFunctionFallback(source), ''); } - /** - * Removes all `iteratesOwnLast` references from `source`. - * - * @private - * @param {String} source The source to process. - * @returns {String} Returns the modified source. - */ - function removeIteratesOwnLast(source) { - // remove `iteratesOwnLast` declaration and assignment - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var iteratesOwnLast\b.*|.+?iteratesOwnLast *=.+/g, ''); - - // remove `iteratesOwnLast` from `shimIsPlainObject` - source = source.replace(matchFunction(source, 'shimIsPlainObject'), function(match) { - return match.replace(/(?:\s*\/\/.*)*\n( *)if *\(iteratesOwnLast[\s\S]+?\n\1}/, ''); - }); - - return source; - } - /** * Removes the `Object.keys` object iteration optimization from `source`. * @@ -1134,11 +1019,11 @@ */ function removeKeysOptimization(source) { source = removeVar(source, 'isJSC'); - source = removeVar(source, 'isKeysFast'); + source = removeSupportProp(source, 'fastKeys'); // remove optimized branch in `iteratorTemplate` source = source.replace(getIteratorTemplate(source), function(match) { - return match.replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(isKeysFast[\s\S]+?["']\1<% *} *else *{ *%>.+\n([\s\S]+?) *["']\1<% *} *%>.+/, "'\\n' +\n$2"); + return match.replace(/^(?: *\/\/.*\n)* *["']( *)<% *if *\(support\.fastKeys[\s\S]+?["']\1<% *} *else *{ *%>.+\n([\s\S]+?) *["']\1<% *} *%>.+/m, "'\\n' +\n$2"); }); return source; @@ -1164,117 +1049,217 @@ } /** - * Removes all `noArgsClass` references from `source`. + * Removes all `support.argsObject` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeNoArgsClass(source) { - source = removeVar(source, 'noArgsClass'); + function removeSupportArgsObject(source) { + source = removeSupportProp(source, 'argsObject'); - // replace `noArgsClass` in the `_.isArguments` fallback + // remove `argsAreObjects` from `_.isArray` + source = source.replace(matchFunction(source, 'isArray'), function(match) { + return match.replace(/\(support\.argsObject && *([^)]+)\)/g, '$1'); + }); + + // remove `argsAreObjects` from `_.isEqual` + source = source.replace(matchFunction(source, 'isEqual'), function(match) { + return match.replace(/!support.\argsObject[^:]+:\s*/g, ''); + }); + + return source; + } + + /** + * Removes all `support.argsClass` references from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeSupportArgsClass(source) { + source = removeSupportProp(source, 'argsClass'); + + // replace `support.argsClass` in the `_.isArguments` fallback source = source.replace(getIsArgumentsFallback(source), function(match) { - return match.replace(/noArgsClass/g, '!isArguments(arguments)'); + return match.replace(/!support\.argsClass/g, '!isArguments(arguments)'); }); - // remove `noArgsClass` from `_.isEmpty` + // remove `support.argsClass` from `_.isEmpty` source = source.replace(matchFunction(source, 'isEmpty'), function(match) { - return match.replace(/ *\|\|\s*\(noArgsClass *&&[^)]+?\)\)/g, ''); + return match.replace(/\s*\(support\.argsClass *\?([^:]+):.+?\)\)/g, '$1'); }); return source; } /** - * Removes all `noCharByIndex` references from `source`. + * Removes all `support.enumPrototypes` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeNoCharByIndex(source) { - source = removeVar(source, 'noCharByIndex'); + function removeSupportEnumPrototypes(source) { + source = removeSupportProp(source, 'enumPrototypes'); - // remove `noCharByIndex` from `_.at` - source = source.replace(matchFunction(source, 'at'), function(match) { - return match.replace(/^ *if *\(noCharByIndex[^}]+}\n+/m, ''); - }); - - // remove `noCharByIndex` from `_.reduceRight` - source = source.replace(matchFunction(source, 'reduceRight'), function(match) { - return match.replace(/}\s*else if *\(noCharByIndex[^}]+/, ''); - }); - - // remove `noCharByIndex` from `_.toArray` - source = source.replace(matchFunction(source, 'toArray'), function(match) { - return match.replace(/(return\b).+?noCharByIndex[^:]+:\s*/, '$1 '); - }); - - // `noCharByIndex` from `iteratorTemplate` - source = source.replace(getIteratorTemplate(source), function(match) { - return match - .replace(/'if *\(<%= *arrays *%>[^']*/, '$&\\n') - .replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(noCharByIndex[\s\S]+?["']\1<% *} *%>.+/, ''); - }); - - return source; - } - - /** - * Removes all `nonEnumArgs` references from `source`. - * - * @private - * @param {String} source The source to process. - * @returns {String} Returns the modified source. - */ - function removeNonEnumArgs(source) { - source = removeFromCreateIterator(source, 'nonEnumArgs'); - - // remove `nonEnumArgs` declaration and assignment - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var nonEnumArgs\b.*|.+?nonEnumArgs *=.+/g, ''); - - // remove `nonEnumArgs` from `_.keys` + // remove `support.enumPrototypes` from `_.keys` source = source.replace(matchFunction(source, 'keys'), function(match) { return match - .replace(/(?:\s*\|\|\s*)?\(nonEnumArgs[^)]+\)\)/, '') + .replace(/\(support\.enumPrototypes[^)]+\)(?:\s*\|\|\s*)?/, '') + .replace(/\s*if *\(\s*\)[^}]+}/, ''); + }); + + // remove `support.enumPrototypes` from `iteratorTemplate` + source = source.replace(getIteratorTemplate(source), function(match) { + return match + .replace(/(?: *\/\/.*\n)* *["'] *(?:<% *)?if *\(support\.enumPrototypes *(?:&&|\))[\s\S]+?<% *} *(?:%>|["']).+/g, '') + .replace(/support\.enumPrototypes *\|\|\s*/g, ''); + }); + + return source; + } + + /** + * Removes all `support.nodeClass` references from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeSupportNodeClass(source) { + source = removeSupportProp(source, 'nodeClass'); + + // remove `support.nodeClass` from `shimIsPlainObject` + source = source.replace(matchFunction(source, 'shimIsPlainObject'), function(match) { + return match.replace(/ *&& *\(support\.nodeClass[\s\S]+?\)\)/, ''); + }); + + // remove `support.nodeClass` from `_.clone` + source = source.replace(matchFunction(source, 'clone'), function(match) { + return match.replace(/ *\|\|\s*\(!support\.nodeClass[\s\S]+?\)\)/, ''); + }); + + // remove `support.nodeClass` from `_.isEqual` + source = source.replace(matchFunction(source, 'isEqual'), function(match) { + return match.replace(/ *\|\|\s*\(!support\.nodeClass[\s\S]+?\)\)\)/, ''); + }); + + return source; + } + + /** + * Removes all `support.nonEnumArgs` references from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeSupportNonEnumArgs(source) { + source = removeSupportProp(source, 'nonEnumArgs'); + + // remove `support.nonEnumArgs` from `_.keys` + source = source.replace(matchFunction(source, 'keys'), function(match) { + return match + .replace(/(?:\s*\|\|\s*)?\(support\.nonEnumArgs[^)]+\)\)/, '') .replace(/\s*if *\(\s*\)[^}]+}/, ''); }); // remove `nonEnumArgs` from `iteratorTemplate` source = source.replace(getIteratorTemplate(source), function(match) { return match - .replace(/(?: *\/\/.*\n)*( *["'] *)<% *} *else *if *\(nonEnumArgs[\s\S]+?(\1<% *} *%>.+)/, '$2') - .replace(/ *\|\|\s*nonEnumArgs/, ''); + .replace(/(?: *\/\/.*\n)*( *["'] *)<% *} *else *if *\(support\.nonEnumArgs[\s\S]+?(\1<% *} *%>.+)/, '$2') + .replace(/ *\|\|\s*support\.nonEnumArgs/, ''); }); return source; } /** - * Removes all `noNodeClass` references from `source`. + * Removes all `support.nonEnumShadows` references from `source`. * * @private * @param {String} source The source to process. * @returns {String} Returns the modified source. */ - function removeNoNodeClass(source) { - // remove `noNodeClass` assignment - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *{(?:\s*\/\/.*)*\n *var noNodeClass[\s\S]+?catch[^}]+}\n/, ''); + function removeSupportNonEnumShadows(source) { + source = removeSupportProp(source, 'nonEnumShadows'); + source = removeVar(source, 'shadowedProps'); + source = removeFromCreateIterator(source, 'shadowedProps'); - // remove `noNodeClass` from `shimIsPlainObject` + // remove `support.nonEnumShadows` from `iteratorTemplate` + source = source.replace(getIteratorTemplate(source), function(match) { + return match.replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(support\.nonEnumShadows[\s\S]+?["']\1<% *} *%>.+/, ''); + }); + + return source; + } + + /** + * Removes all `support.ownLast` references from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeSupportOwnLast(source) { + source = removeSupportProp(source, 'ownLast'); + + // remove `support.ownLast` from `shimIsPlainObject` source = source.replace(matchFunction(source, 'shimIsPlainObject'), function(match) { - return match.replace(/ *&& *\(!noNodeClass[\s\S]+?\)\)/, ''); + return match.replace(/(?:\s*\/\/.*)*\n( *)if *\(support\.ownLast[\s\S]+?\n\1}/, ''); }); - // remove `noNodeClass` from `_.clone` - source = source.replace(matchFunction(source, 'clone'), function(match) { - return match.replace(/ *\|\|\s*\(noNodeClass[\s\S]+?\)\)/, ''); + return source; + } + + /** + * Removes all `support.spliceObjects` references from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeSupportSpliceObjects(source) { + source = removeSupportProp(source, 'spliceObjects'); + + // remove `support.spliceObjects` fix from the `Array` function mixins + source = source.replace(/(?:\s*\/\/.*)*\n( *)if *\(!support\.spliceObjects[\s\S]+?(?:{\s*}|\n\1})/, ''); + + return source; + } + + /** + * Removes all `support.unindexedChars` references from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeSupportUnindexedChars(source) { + source = removeSupportProp(source, 'unindexedChars'); + + // remove `support.unindexedChars` from `_.at` + source = source.replace(matchFunction(source, 'at'), function(match) { + return match.replace(/^ *if *\(support\.unindexedChars[^}]+}\n+/m, ''); }); - // remove `noNodeClass` from `_.isEqual` - source = source.replace(matchFunction(source, 'isEqual'), function(match) { - return match.replace(/ *\|\|\s*\(noNodeClass[\s\S]+?\)\)\)/, ''); + // remove `support.unindexedChars` from `_.reduceRight` + source = source.replace(matchFunction(source, 'reduceRight'), function(match) { + return match.replace(/}\s*else if *\(support\.unindexedChars[^}]+/, ''); + }); + + // remove `support.unindexedChars` from `_.toArray` + source = source.replace(matchFunction(source, 'toArray'), function(match) { + return match.replace(/(return\b).+?support\.unindexedChars[^:]+:\s*/, '$1 '); + }); + + // remove `support.unindexedChars` from `iteratorTemplate` + source = source.replace(getIteratorTemplate(source), function(match) { + return match + .replace(/'if *\(<%= *arrays *%>[^']*/, '$&\\n') + .replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(support\.unindexedChars[\s\S]+?["']\1<% *} *%>.+/, ''); }); return source; @@ -1323,6 +1308,27 @@ return source; } + /** + * Removes a given property from the `support` object in `source`. + * + * @private + * @param {String} source The source to process. + * @param {String} varName The name of the `support` property to remove. + * @returns {String} Returns the modified source. + */ + function removeSupportProp(source, propName) { + return source.replace(RegExp( + // match multi-line comment block + '(?:\\n +/\\*[^*]*\\*+(?:[^/][^*]*\\*+)*/)?\\n' + + // match a `try` block + '(?: *try\\b.+\\n)?' + + // match the `support` property assignment + ' *support\\.' + propName + ' *=.+\\n' + + // match `catch` block + '(?:( *).+?catch\\b[\\s\\S]+?\\n\\1}\\n)?' + ), ''); + } + /** * Removes a given variable from `source`. * @@ -1333,7 +1339,7 @@ */ function removeVar(source, varName) { // simplify complex variable assignments - if (/^(?:cloneableClasses|contextProps|ctorByClass|hasObjectSpliceBug|shadowedProps)$/.test(varName)) { + if (/^(?:cloneableClasses|contextProps|ctorByClass|shadowedProps)$/.test(varName)) { source = source.replace(RegExp('(var ' + varName + ' *=)[\\s\\S]+?\\n\\n'), '$1=null;\n\n'); } source = source.replace(RegExp( @@ -1359,7 +1365,7 @@ * Replaces the `funcName` function body in `source` with `funcValue`. * * @private - * @param {String} source The source to inspect. + * @param {String} source The source to process. * @param {String} varName The name of the function to replace. * @returns {String} Returns the modified source. */ @@ -1380,6 +1386,28 @@ return source; } + + /** + * Replaces the `support` object `propName` property value in `source` with `propValue`. + * + * @private + * @param {String} source The source to process. + * @param {String} varName The name of the `support` property to replace. + * @returns {String} Returns the modified source. + */ + function replaceSupportProp(source, propName, propValue) { + return source.replace(RegExp( + // match a `try` block + '(?: *try\\b.+\\n)?' + + // match the `support` property assignment + '( *support\\.' + propName + ' *=).+\\n' + + // match `catch` block + '(?:( *).+?catch\\b[\\s\\S]+?\\n\\2}\\n)?' + ), function(match, left) { + return left + ' ' + propValue + ';\n'; + }); + } + /** * Replaces the `varName` variable declaration value in `source` with `varValue`. * @@ -1391,22 +1419,22 @@ function replaceVar(source, varName, varValue) { // replace a variable that's not part of a declaration list var result = source.replace(RegExp( - '(( *)var ' + varName + ' *= *)' + + '(( *)var ' + varName + ' *=)' + '(?:.+?;|(?:Function\\(.+?|.*?[^,])\\n[\\s\\S]+?\\n\\2.+?;)\\n' - ), function(match, captured) { - return captured + varValue + ';\n'; + ), function(match, left) { + return left + ' ' + varValue + ';\n'; }); if (source == result) { // replace a varaible at the start or middle of a declaration list - result = source.replace(RegExp('((?:var|\\n) +' + varName + ' *=).+?,'), function(match, captured) { - return captured + ' ' + varValue + ','; + result = source.replace(RegExp('((?:var|\\n) +' + varName + ' *=).+?,'), function(match, left) { + return left + ' ' + varValue + ','; }); } if (source == result) { // replace a variable at the end of a variable declaration list - result = source.replace(RegExp('(,\\s*' + varName + ' *=).+?;'), function(match, captured) { - return captured + ' ' + varValue + ';'; + result = source.replace(RegExp('(,\\s*' + varName + ' *=).+?;'), function(match, left) { + return left + ' ' + varValue + ';'; }); } return result; @@ -1753,11 +1781,14 @@ source = setUseStrictOption(source, isStrict); if (isLegacy) { - _.each(['getPrototypeOf', 'isBindFast', 'isKeysFast', 'nativeBind', 'nativeIsArray', 'nativeKeys'], function(varName) { + _.each(['getPrototypeOf', 'nativeBind', 'nativeIsArray', 'nativeKeys'], function(varName) { source = replaceVar(source, varName, 'false'); }); - source = replaceVar(source, 'noArgsClass', 'true'); + _.each(['argsClass', 'fastBind', 'fastKeys'], function(propName) { + source = replaceSupportProp(source, propName, 'false'); + }); + source = removeKeysOptimization(source); } if (isMobile || isUnderscore) { @@ -1765,14 +1796,14 @@ source = removeSetImmediate(source); } if (isModern || isUnderscore) { - source = removeHasDontEnumBug(source); - source = removeHasEnumPrototype(source); - source = removeIteratesOwnLast(source); - source = removeNoCharByIndex(source); - source = removeNoNodeClass(source); + source = removeSupportNonEnumShadows(source); + source = removeSupportEnumPrototypes(source); + source = removeSupportOwnLast(source); + source = removeSupportUnindexedChars(source); + source = removeSupportNodeClass(source); if (!isMobile) { - source = removeNonEnumArgs(source); + source = removeSupportNonEnumArgs(source); } } if (isModern) { @@ -2300,14 +2331,15 @@ if (isLegacy) { source = removeSetImmediate(source); + source = removeSupportProp(source, 'fastBind'); - _.each(['isBindFast', 'isIeOpera', 'isV8', 'nativeBind', 'nativeIsArray', 'nativeKeys', 'reNative'], function(varName) { + _.each(['isIeOpera', 'isV8', 'nativeBind', 'nativeIsArray', 'nativeKeys', 'reNative'], function(varName) { source = removeVar(source, varName); }); // remove native `Function#bind` branch in `_.bind` source = source.replace(matchFunction(source, 'bind'), function(match) { - return match.replace(/(?:\s*\/\/.*)*\s*return isBindFast[^:]+:\s*/, 'return '); + return match.replace(/(?:\s*\/\/.*)*\s*return support\.fastBind[^:]+:\s*/, 'return '); }); // remove native `Array.isArray` branch in `_.isArray` @@ -2335,13 +2367,13 @@ } } if (isModern) { - source = removeArgsAreObjects(source); - source = removeHasObjectSpliceBug(source); + source = removeSupportArgsObject(source); + source = removeSupportSpliceObjects(source); source = removeIsArgumentsFallback(source); } if (isModern || isUnderscore) { - source = removeNoArgsClass(source); - source = removeNoNodeClass(source); + source = removeSupportArgsClass(source); + source = removeSupportNodeClass(source); } if (isMobile || isUnderscore) { source = removeVar(source, 'iteratorTemplate'); @@ -2432,6 +2464,8 @@ }); } if (!(isMobile || isUnderscore)) { + source = removeFromCreateIterator(source, 'support'); + // inline `iteratorTemplate` template source = source.replace(getIteratorTemplate(source), function(match) { var indent = getIndent(match), @@ -2451,7 +2485,8 @@ .replace(/__p *\+= *' *';/g, '') .replace(/(__p *\+= *)' *' *\+/g, '$1') .replace(/({) *;|; *(})/g, '$1$2') - .replace(/\(\(__t *= *\( *([^)]+) *\)\) *== *null *\? *'' *: *__t\)/g, '($1)'); + .replace(/\(\(__t *= *\( *([^)]+) *\)\) *== *null *\? *'' *: *__t\)/g, '($1)') + .replace(/obj\.(?=support)/g, '') // remove the with-statement snippet = snippet.replace(/ *with *\(.+?\) *{/, '\n').replace(/}([^}]*}[^}]*$)/, '$1'); @@ -2543,7 +2578,7 @@ }); } if (isRemoved(source, 'value')) { - source = removeHasObjectSpliceBug(source); + source = removeSupportSpliceObjects(source); source = removeLodashWrapper(source); // simplify the `lodash` function @@ -2581,7 +2616,7 @@ } if (isRemoved(source, 'isPlainObject')) { source = removeVar(source, 'getPrototypeOf'); - source = removeIteratesOwnLast(source); + source = removeSupportOwnLast(source); } if (isRemoved(source, 'keys')) { source = removeFunction(source, 'shimKeys'); @@ -2594,10 +2629,10 @@ source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *lodash\.templateSettings[\s\S]+?};\n/, ''); } if (isRemoved(source, 'isArguments', 'isEmpty')) { - source = removeNoArgsClass(source); + source = removeSupportArgsClass(source); } if (isRemoved(source, 'clone', 'isEqual', 'isPlainObject')) { - source = removeNoNodeClass(source); + source = removeSupportNodeClass(source); } if ((source.match(/\bcreateIterator\b/g) || []).length < 2) { source = removeFunction(source, 'createIterator'); @@ -2605,28 +2640,35 @@ source = removeVar(source, 'eachIteratorOptions'); source = removeVar(source, 'forOwnIteratorOptions'); source = removeVar(source, 'templateIterator'); - source = removeHasDontEnumBug(source); - source = removeHasEnumPrototype(source); + source = removeSupportNonEnumShadows(source); + source = removeSupportEnumPrototypes(source); } if (isRemoved(source, 'createIterator', 'bind', 'keys')) { - source = removeVar(source, 'isBindFast'); + source = removeSupportProp(source, 'fastBind'); source = removeVar(source, 'isV8'); source = removeVar(source, 'nativeBind'); } if (isRemoved(source, 'createIterator', 'keys')) { source = removeVar(source, 'nativeKeys'); source = removeKeysOptimization(source); - source = removeNonEnumArgs(source); + source = removeSupportNonEnumArgs(source); } - if (!source.match(/var (?:hasDontEnumBug|hasEnumPrototype|iteratesOwnLast|nonEnumArgs)\b/g)) { - // remove IIFE used to assign `hasDontEnumBug`, `hasEnumPrototype`, `iteratesOwnLast`, and `nonEnumArgs` - source = source.replace(/^ *\(function\(\) *{[\s\S]+?}\(1\)\);\n+/m, ''); + if (!/support\.(?:enumPrototypes|nonEnumShadows|ownLast)\b/.test(source)) { + // remove code used to resolve unneeded `support` properties + source = source.replace(/^ *\(function[\s\S]+?\n(( *)var ctor *= *function[\s\S]+?\n *for.+\n)([\s\S]+?)}\(1\)\);\n/m, function(match, setup, indent, body) { + if (/support\.spliceObjects\b/.test(match)) { + return match.replace(setup, indent + "var object = { '0': 1, 'length': 1 };\n"); + } else if (/support\.nonEnumArgs\b/.test(match)) { + return match.replace(setup, ''); + } + return body.replace(/^ {4}/gm, ' '); + }); } } - if ((source.match(/\bfreeModule\b/g) || []).length < 2) { + if (_.size(source.match(/\bfreeModule\b/g)) < 2) { source = removeVar(source, 'freeModule'); } - if ((source.match(/\bfreeExports\b/g) || []).length < 2) { + if (_.size(source.match(/\bfreeExports\b/g)) < 2) { source = removeVar(source, 'freeExports'); } diff --git a/build/pre-compile.js b/build/pre-compile.js index e9ece35ca..d8aa9dae8 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -33,19 +33,15 @@ 'thisArg' ]; - /** Used to minify `compileIterator` option properties */ + /** Used to minify `iteratorTemplate` data properties */ var iteratorOptions = [ 'args', 'arrays', 'bottom', 'firstArg', - 'hasDontEnumBug', - 'hasEnumPrototype', - 'isKeysFast', 'loop', - 'nonEnumArgs', - 'noCharByIndex', 'shadowedProps', + 'support', 'top', 'useHas' ]; @@ -74,6 +70,8 @@ 'all', 'amd', 'any', + 'argsClass', + 'argsObject', 'assign', 'at', 'attachEvent', @@ -98,12 +96,15 @@ 'difference', 'drop', 'each', + 'enumPrototypes', 'environment', 'escape', 'evaluate', 'every', 'exports', 'extend', + 'fastBind', + 'fastKeys', 'filter', 'find', 'first', @@ -159,9 +160,13 @@ 'min', 'mixin', 'noConflict', + 'nodeClass', + 'nonEnumArgs', + 'nonEnumShadows', 'object', 'omit', 'once', + 'ownLast', 'pairs', 'parseInt', 'partial', @@ -185,6 +190,8 @@ 'sortBy', 'sortedIndex', 'source', + 'spliceObjects', + 'support', 'tail', 'take', 'tap', @@ -194,6 +201,7 @@ 'times', 'toArray', 'unescape', + 'unindexedChars', 'union', 'uniq', 'unique', diff --git a/lodash.js b/lodash.js index 2d2934350..0b0e7f87d 100644 --- a/lodash.js +++ b/lodash.js @@ -188,84 +188,6 @@ isJSC = !/\n{2,}/.test(Function()), isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera); - /* Detect if `Function#bind` exists and is inferred to be fast (all but V8) */ - var isBindFast = nativeBind && !isV8; - - /* Detect if `Object.keys` exists and is inferred to be fast (Firefox, IE, Opera, V8) */ - var isKeysFast = nativeKeys && (isIeOpera || isV8 || !isJSC); - - /** - * Detect the JScript [[DontEnum]] bug: - * - * In IE < 9 an objects own properties, shadowing non-enumerable ones, are - * made non-enumerable as well. - */ - var hasDontEnumBug; - - /** - * Detect if a `prototype` properties are enumerable by default: - * - * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 - * (if the prototype or a property on the prototype has been set) - * incorrectly sets a function's `prototype` property [[Enumerable]] - * value to `true`. - */ - var hasEnumPrototype; - - /** Detect if own properties are iterated after inherited properties (IE < 9) */ - var iteratesOwnLast; - - /** - * Detect if `Array#shift` and `Array#splice` augment array-like objects - * incorrectly: - * - * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()` - * and `splice()` functions that fail to remove the last element, `value[0]`, - * of array-like objects even though the `length` property is set to `0`. - * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` - * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. - */ - var hasObjectSpliceBug = (hasObjectSpliceBug = { '0': 1, 'length': 1 }, - arrayRef.splice.call(hasObjectSpliceBug, 0, 1), hasObjectSpliceBug[0]); - - /** Detect if `arguments` object indexes are non-enumerable (Firefox < 4, IE < 9, PhantomJS, Safari < 5.1) */ - var nonEnumArgs = true; - - (function() { - var props = []; - function ctor() { this.x = 1; } - ctor.prototype = { 'valueOf': 1, 'y': 1 }; - for (var prop in new ctor) { props.push(prop); } - for (prop in arguments) { nonEnumArgs = !prop; } - - hasDontEnumBug = !/valueOf/.test(props); - hasEnumPrototype = ctor.propertyIsEnumerable('prototype'); - iteratesOwnLast = props[0] != 'x'; - }(1)); - - /** Detect if `arguments` objects are `Object` objects (all but Opera < 10.5) */ - var argsAreObjects = arguments.constructor == Object; - - /** Detect if `arguments` objects [[Class]] is unresolvable (Firefox < 4, IE < 9) */ - var noArgsClass = !isArguments(arguments); - - /** - * Detect lack of support for accessing string characters by index: - * - * IE < 8 can't access characters by index and IE 8 can only access - * characters by index on string literals. - */ - var noCharByIndex = ('x'[0] + Object('x')[0]) != 'xx'; - - /** - * Detect if a DOM node's [[Class]] is unresolvable (IE < 9) - * and that the JS engine won't error when attempting to coerce an object to - * a string without a `toString` function. - */ - try { - var noNodeClass = toString.call(document) == objectClass && !({ 'toString': 0 } + ''); - } catch(e) { } - /** Used to lookup a built-in constructor by [[Class]] */ var ctorByClass = {}; ctorByClass[arrayClass] = Array; @@ -324,6 +246,141 @@ : new lodashWrapper(value); } + /** + * An object used to flag environments features. + * + * @static + * @memberOf _ + * @type Object + */ + var support = lodash.support = {}; + + (function() { + var ctor = function() { this.x = 1; }, + object = { '0': 1, 'length': 1 }, + props = []; + + ctor.prototype = { 'valueOf': 1, 'y': 1 }; + for (var prop in new ctor) { props.push(prop); } + + /** + * Detect if `arguments` objects are `Object` objects + * (all but Opera < 10.5). + * + * @memberOf _.support + * @type Boolean + */ + support.argsObject = arguments.constructor == Object; + + /** + * Detect if `arguments` objects [[Class]] are resolvable + * (all but Firefox < 4, IE < 9). + * + * @memberOf _.support + * @type Boolean + */ + support.argsClass = isArguments(arguments); + + /** + * Detect if `prototype` properties are enumerable by default. + * + * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 + * (if the prototype or a property on the prototype has been set) + * incorrectly sets a function's `prototype` property [[Enumerable]] + * value to `true`. + * + * @memberOf _.support + * @type Boolean + */ + support.enumPrototypes = ctor.propertyIsEnumerable('prototype'); + + /** + * Detect if `Function#bind` exists and is inferred to be fast (all but V8). + * + * @memberOf _.support + * @type Boolean + */ + support.fastBind = nativeBind && !isV8; + + /** + * Detect if `Object.keys` exists and is inferred to be fast + * (Firefox, IE, Opera, V8). + * + * @memberOf _.support + * @type Boolean + */ + support.fastKeys = nativeKeys && (isIeOpera || isV8 || !isJSC); + + /** + * Detect if own properties are iterated after inherited properties + * (all but IE < 9). + * + * @memberOf _.support + * @type Boolean + */ + support.ownLast = props[0] != 'x'; + + /** + * Detect if `arguments` object indexes are non-enumerable + * (Firefox < 4, IE < 9, PhantomJS, Safari < 5.1). + * + * @memberOf _.support + * @type Boolean + */ + support.nonEnumArgs = !arguments.propertyIsEnumerable(0); + + /** + * Detect if properties shadowing those on `Object.prototype` are non-enumerable. + * + * In IE < 9 an objects own properties, shadowing non-enumerable ones, are + * made non-enumerable as well (a.k.a the JScript [[DontEnum]] bug). + * + * @memberOf _.support + * @type Boolean + */ + support.nonEnumShadows = !/valueOf/.test(props); + + /** + * Detect if `Array#shift` and `Array#splice` augment array-like + * objects correctly. + * + * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()` + * and `splice()` functions that fail to remove the last element, `value[0]`, + * of array-like objects even though the `length` property is set to `0`. + * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` + * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. + * + * @memberOf _.support + * @type Boolean + */ + support.spliceObjects = (arrayRef.splice.call(object, 0, 1), !object[0]); + + /** + * Detect lack of support for accessing string characters by index. + * + * IE < 8 can't access characters by index and IE 8 can only access + * characters by index on string literals. + * + * @memberOf _.support + * @type Boolean + */ + support.unindexedChars = ('x'[0] + Object('x')[0]) != 'xx'; + + /** + * Detect if a DOM node's [[Class]] is resolvable (all but IE < 9) + * and that the JS engine errors when attempting to coerce an object to + * a string without a `toString` function. + * + * @memberOf _.support + * @type Boolean + */ + try { + support.nodeClass = !(toString.call(document) == objectClass && !({ 'toString': 0 } + '')); + } catch(e) { + support.nodeClass = true; + } + }(1)); + /** * By default, the template delimiters used by Lo-Dash are similar to those in * embedded Ruby (ERB). Change the following template settings to use alternative @@ -410,7 +467,7 @@ 'if (<%= arrays %>) {' + // add support for accessing string characters by index if needed - ' <% if (noCharByIndex) { %>\n' + + ' <% if (support.unindexedChars) { %>\n' + ' if (isString(iterable)) {\n' + " iterable = iterable.split('')\n" + ' }' + @@ -425,7 +482,7 @@ // object iteration: // add support for iterating over `arguments` objects if needed - ' <% } else if (nonEnumArgs) { %>\n' + + ' <% } else if (support.nonEnumArgs) { %>\n' + ' var length = iterable.length; index = -1;\n' + ' if (length && isArguments(iterable)) {\n' + ' while (++index < length) {\n' + @@ -436,33 +493,33 @@ ' <% } %>' + // avoid iterating over `prototype` properties in older Firefox, Opera, and Safari - ' <% if (hasEnumPrototype) { %>\n' + + ' <% if (support.enumPrototypes) { %>\n' + " var skipProto = typeof iterable == 'function';\n" + ' <% } %>' + // iterate own properties using `Object.keys` if it's fast - ' <% if (isKeysFast && useHas) { %>\n' + + ' <% if (support.fastKeys && useHas) { %>\n' + ' var ownIndex = -1,\n' + ' ownProps = objectTypes[typeof iterable] ? nativeKeys(iterable) : [],\n' + ' length = ownProps.length;\n\n' + ' while (++ownIndex < length) {\n' + ' index = ownProps[ownIndex];\n' + - " <% if (hasEnumPrototype) { %>if (!(skipProto && index == 'prototype')) {\n <% } %>" + + " <% if (support.enumPrototypes) { %>if (!(skipProto && index == 'prototype')) {\n <% } %>" + ' <%= loop %>\n' + - ' <% if (hasEnumPrototype) { %>}\n<% } %>' + + ' <% if (support.enumPrototypes) { %>}\n<% } %>' + ' }' + // else using a for-in loop ' <% } else { %>\n' + ' for (index in iterable) {<%' + - ' if (hasEnumPrototype || useHas) { %>\n if (<%' + - " if (hasEnumPrototype) { %>!(skipProto && index == 'prototype')<% }" + - ' if (hasEnumPrototype && useHas) { %> && <% }' + + ' if (support.enumPrototypes || useHas) { %>\n if (<%' + + " if (support.enumPrototypes) { %>!(skipProto && index == 'prototype')<% }" + + ' if (support.enumPrototypes && useHas) { %> && <% }' + ' if (useHas) { %>hasOwnProperty.call(iterable, index)<% }' + ' %>) {' + ' <% } %>\n' + ' <%= loop %>;' + - ' <% if (hasEnumPrototype || useHas) { %>\n }<% } %>\n' + + ' <% if (support.enumPrototypes || useHas) { %>\n }<% } %>\n' + ' }' + ' <% } %>' + @@ -470,7 +527,7 @@ // existing property and the `constructor` property of a prototype // defaults to non-enumerable, Lo-Dash skips the `constructor` // property when it infers it's iterating over a `prototype` object. - ' <% if (hasDontEnumBug) { %>\n\n' + + ' <% if (support.nonEnumShadows) { %>\n\n' + ' var ctor = iterable.constructor;\n' + ' <% for (var k = 0; k < 7; k++) { %>\n' + " index = '<%= shadowedProps[k] %>';\n" + @@ -482,7 +539,7 @@ ' }' + ' <% } %>' + ' <% } %>' + - ' <% if (arrays || nonEnumArgs) { %>\n}<% } %>\n' + + ' <% if (arrays || support.nonEnumArgs) { %>\n}<% } %>\n' + // add code to the bottom of the iteration function '<%= bottom %>;\n' + @@ -668,13 +725,9 @@ */ function createIterator() { var data = { - // support properties - 'hasDontEnumBug': hasDontEnumBug, - 'hasEnumPrototype': hasEnumPrototype, - 'isKeysFast': isKeysFast, - 'nonEnumArgs': nonEnumArgs, - 'noCharByIndex': noCharByIndex, + // data properties 'shadowedProps': shadowedProps, + 'support': support, // iterator options 'arrays': 'isArray(iterable)', @@ -841,7 +894,7 @@ return toString.call(value) == argsClass; } // fallback for browsers that can't detect `arguments` objects by [[Class]] - if (noArgsClass) { + if (!support.argsClass) { isArguments = function(value) { return value ? hasOwnProperty.call(value, 'callee') : false; }; @@ -922,7 +975,7 @@ var isArray = nativeIsArray || function(value) { // `instanceof` may cause a memory leak in IE 7 if `value` is a host object // http://ajaxian.com/archives/working-aroung-the-instanceof-memory-leak - return (argsAreObjects && value instanceof Array) || toString.call(value) == arrayClass; + return (support.argsObject && value instanceof Array) || toString.call(value) == arrayClass; }; /** @@ -942,8 +995,8 @@ if (!isObject(object)) { return []; } - if ((hasEnumPrototype && typeof object == 'function') || - (nonEnumArgs && object.length && isArguments(object))) { + if ((support.enumPrototypes && typeof object == 'function') || + (support.nonEnumArgs && object.length && isArguments(object))) { return shimKeys(object); } return nativeKeys(object); @@ -967,16 +1020,16 @@ } // check that the constructor is `Object` (i.e. `Object instanceof Object`) var ctor = value.constructor; - if ((!isFunction(ctor) && (!noNodeClass || !isNode(value))) || ctor instanceof ctor) { + if ((!isFunction(ctor) && (support.nodeClass || !isNode(value))) || ctor instanceof ctor) { // IE < 9 iterates inherited properties before own properties. If the first // iterated property is an object's own property then there are no inherited // enumerable properties. - if (iteratesOwnLast) { + if (support.ownLast) { forIn(value, function(value, key, object) { - result = !hasOwnProperty.call(object, key); + result = hasOwnProperty.call(object, key); return false; }); - return result === false; + return result === true; } // In most environments an object's own properties are iterated before // its inherited properties. If the last iterated property is an object's @@ -1136,7 +1189,7 @@ var isObj = isObject(result); if (isObj) { var className = toString.call(result); - if (!cloneableClasses[className] || (noNodeClass && isNode(result))) { + if (!cloneableClasses[className] || (!support.nodeClass && isNode(result))) { return result; } var isArr = isArray(result); @@ -1413,7 +1466,7 @@ length = value.length; if ((className == arrayClass || className == stringClass || - className == argsClass || (noArgsClass && isArguments(value))) || + (support.argsClass ? className == argsClass : isArguments(value))) || (className == objectClass && typeof length == 'number' && isFunction(value.splice))) { return !length; } @@ -1535,12 +1588,12 @@ return isEqual(a.__wrapped__ || a, b.__wrapped__ || b, callback, thisArg, stackA, stackB); } // exit for functions and DOM nodes - if (className != objectClass || (noNodeClass && (isNode(a) || isNode(b)))) { + if (className != objectClass || (!support.nodeClass && (isNode(a) || isNode(b)))) { return false; } // in older versions of Opera, `arguments` objects have `Array` constructors - var ctorA = !argsAreObjects && isArguments(a) ? Object : a.constructor, - ctorB = !argsAreObjects && isArguments(b) ? Object : b.constructor; + var ctorA = !support.argsObject && isArguments(a) ? Object : a.constructor, + ctorB = !support.argsObject && isArguments(b) ? Object : b.constructor; // non `Object` object instances with different constructors are not equal if (ctorA != ctorB && !( @@ -2191,7 +2244,7 @@ length = props.length, result = Array(length); - if (noCharByIndex && isString(collection)) { + if (support.unindexedChars && isString(collection)) { collection = collection.split(''); } while(++index < length) { @@ -2890,7 +2943,7 @@ if (typeof length != 'number') { var props = keys(collection); length = props.length; - } else if (noCharByIndex && isString(collection)) { + } else if (support.unindexedChars && isString(collection)) { iterable = collection.split(''); } callback = lodash.createCallback(callback, thisArg, 4); @@ -3136,7 +3189,7 @@ */ function toArray(collection) { if (collection && typeof collection.length == 'number') { - return (noCharByIndex && isString(collection)) + return (support.unindexedChars && isString(collection)) ? collection.split('') : slice(collection); } @@ -4099,7 +4152,7 @@ function bind(func, thisArg) { // use `Function#bind` if it exists and is fast // (in V8 `Function#bind` is slower except when partially applied) - return isBindFast || (nativeBind && arguments.length > 2) + return support.fastBind || (nativeBind && arguments.length > 2) ? nativeBind.call.apply(nativeBind, arguments) : createBound(func, thisArg, slice(arguments, 2)); } @@ -5233,7 +5286,7 @@ // avoid array-like object bugs with `Array#shift` and `Array#splice` // in Firefox < 10 and IE < 9 - if (hasObjectSpliceBug) { + if (!support.spliceObjects) { each(['pop', 'shift', 'splice'], function(methodName) { var func = arrayRef[methodName], isSplice = methodName == 'splice';