From 1ca26ce6768dc947fb2321a9e8670dbbc25ec47b Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Sat, 22 Sep 2012 15:39:37 -0700 Subject: [PATCH] Ensure methods accepting a `thisArg` argument allow `null` values. Former-commit-id: 368b943687291f0d7ed02304284ac076ef86e02b --- build/pre-compile.js | 1 + lodash.js | 31 ++++++++++++------------ test/test.js | 56 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 72 insertions(+), 16 deletions(-) diff --git a/build/pre-compile.js b/build/pre-compile.js index e564f75a7..7b2c582ed 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -33,6 +33,7 @@ 'stringClass', 'thisArg', 'toString', + 'undefined', 'value', // lesser used variables diff --git a/lodash.js b/lodash.js index 8595b3e38..b1811a625 100644 --- a/lodash.js +++ b/lodash.js @@ -456,8 +456,8 @@ /** * Reusable iterator options shared by - * `every`, `filter`, `find`, `forEach`, `forIn`, `forOwn`, `groupBy`, `map`, - * `reject`, `some`, and `sortBy`. + * `countBy`, `every`, `filter`, `find`, `forEach`, `forIn`, `forOwn`, `groupBy`, + * `map`, `reject`, `some`, and `sortBy`. */ var baseIteratorOptions = { 'args': 'collection, callback, thisArg', @@ -466,7 +466,7 @@ 'if (!callback) {\n' + ' callback = identity\n' + '}\n' + - 'else if (thisArg) {\n' + + 'else if (thisArg !== undefined) {\n' + ' callback = bindIterator(callback, thisArg)\n' + '}', 'inLoop': 'if (callback(value, index, collection) === false) return result' @@ -481,7 +481,7 @@ ' var valueProp = callback;\n' + ' callback = function(value) { return value[valueProp] }\n' + '}\n' + - 'else if (thisArg) {\n' + + 'else if (thisArg !== undefined) {\n' + ' callback = bindIterator(callback, thisArg)\n' + '}', 'inLoop': @@ -516,7 +516,7 @@ /** Reusable iterator options for `find`, `forEach`, `forIn`, and `forOwn` */ var forEachIteratorOptions = { - 'top': 'if (thisArg) callback = bindIterator(callback, thisArg)' + 'top': 'if (thisArg !== undefined) callback = bindIterator(callback, thisArg)' }; /** Reusable iterator options for `forIn` and `forOwn` */ @@ -549,7 +549,7 @@ 'var isFunc = typeof callback == \'function\';\n' + 'if (!isFunc) {\n' + ' var props = concat.apply(ArrayProto, arguments)\n' + - '} else if (thisArg) {\n' + + '} else if (thisArg !== undefined) {\n' + ' callback = bindIterator(callback, thisArg)\n' + '}', 'inLoop': @@ -792,7 +792,7 @@ 'arrayLikeClasses, ArrayProto, bind, bindIterator, compareAscending, concat, ' + 'forIn, hasOwnProperty, identity, indexOf, isArguments, isArray, isFunction, ' + 'isPlainObject, objectClass, objectTypes, nativeKeys, propertyIsEnumerable, ' + - 'slice, stringClass, toString', + 'slice, stringClass, toString, undefined', 'var callee = function(' + args + ') {\n' + iteratorTemplate(data) + '\n};\n' + 'return callee' ); @@ -1862,7 +1862,7 @@ ' if (prop in object) result[prop] = object[prop]\n' + ' }\n' + '} else {\n' + - ' if (thisArg) callback = bindIterator(callback, thisArg)', + ' if (thisArg !== undefined) callback = bindIterator(callback, thisArg)', 'inLoop': 'if (callback(value, index, object)) result[index] = value', 'bottom': '}' @@ -2184,7 +2184,7 @@ 'init': 'accumulator', 'top': 'var noaccum = arguments.length < 3;\n' + - 'if (thisArg) callback = bindIterator(callback, thisArg)', + 'if (thisArg !== undefined) callback = bindIterator(callback, thisArg)', 'beforeLoop': { 'array': 'if (noaccum) result = iteratee[++index]' }, @@ -2763,7 +2763,7 @@ } return result; } - if (thisArg) { + if (thisArg !== undefined) { callback = bindIterator(callback, thisArg); } while (++index < length) { @@ -2813,7 +2813,7 @@ } return result; } - if (thisArg) { + if (thisArg !== undefined) { callback = bindIterator(callback, thisArg); } while (++index < length) { @@ -3011,7 +3011,7 @@ high = array.length; if (callback) { - if (thisArg) { + if (thisArg !== undefined) { callback = bind(callback, thisArg); } value = callback(value); @@ -3105,7 +3105,7 @@ } if (!callback) { callback = identity; - } else if (thisArg) { + } else if (thisArg !== undefined) { callback = bindIterator(callback, thisArg); } while (++index < length) { @@ -3954,10 +3954,11 @@ * // => also calls `mage.castSpell(n)` three times */ function times(n, callback, thisArg) { + n = +n || 0; var index = -1, - result = Array(n || 0); + result = Array(n); - if (thisArg) { + if (thisArg !== undefined) { while (++index < n) { result[index] = callback.call(thisArg, index); } diff --git a/test/test.js b/test/test.js index badf87995..e6c752b07 100644 --- a/test/test.js +++ b/test/test.js @@ -1713,7 +1713,61 @@ } }); - ok(pass, methodName + ' allows falsey arguments'); + ok(pass, '_.' + methodName + ' allows falsey arguments'); + }); + }); + + test('should handle `null` `thisArg` arguments', function() { + var thisArg, + array = ['a'], + callback = function() { thisArg = this; }, + useStrict = Function('"use strict";return this')() === undefined; + + var funcs = [ + 'countBy', + 'every', + 'filter', + 'find', + 'forEach', + 'forIn', + 'forOwn', + 'groupBy', + 'map', + 'max', + 'min', + 'omit', + 'pick', + 'reduce', + 'reduceRight', + 'reject', + 'some', + 'sortBy', + 'sortedIndex', + 'times', + 'uniq' + ]; + + _.each(funcs, function(methodName) { + var func = _[methodName], + message = '_.' + methodName + ' handles `null` `thisArg` arguments'; + + thisArg = undefined; + + if (/^reduce/.test(methodName)) { + func(array, callback, 0, null); + } else if (methodName == 'sortedIndex') { + func(array, 'a', callback, null); + } else if (methodName == 'times') { + func(1, callback, null); + } else { + func(array, callback, null); + } + + if (useStrict) { + deepEqual(thisArg, null, message); + } else { + equal(thisArg, window, message); + } }); }); }());