From a707c2fe8ea7599faf219fa3e3393d7e964b01b6 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Fri, 19 Apr 2013 00:12:22 -0700 Subject: [PATCH] Ensure isType methods return `false` for subclassed values. Former-commit-id: e300d12eb506c6ae4949bd37cf8eb33c3a4be2e1 --- build.js | 45 ++++++++++++++++++++++++------------------ lodash.js | 49 ++++++++++++++++++++++------------------------ test/test-build.js | 5 ----- test/test.js | 9 +++++++++ 4 files changed, 58 insertions(+), 50 deletions(-) diff --git a/build.js b/build.js index a50dff763..458a82fd9 100755 --- a/build.js +++ b/build.js @@ -786,6 +786,19 @@ return (source.match(/(?:\s*\/\/.*)*\n( *)if *\((?:!support\.argsClass|!isArguments)[\s\S]+?};\n\1}/) || [''])[0]; } + /** + * Gets the `_.isArray` fallback from `source`. + * + * @private + * @param {String} source The source to inspect. + * @returns {String} Returns the `isArray` fallback. + */ + function getIsArrayFallback(source) { + return matchFunction(source, 'isArray') + .replace(/^[\s\S]+?=\s*nativeIsArray\b/, '') + .replace(/[;\s]+$/, ''); + } + /** * Gets the `_.isFunction` fallback from `source`. * @@ -1016,6 +1029,17 @@ return source.replace(getIsArgumentsFallback(source), ''); } + /** + * Removes the `_.isArray` fallback from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeIsArrayFallback(source) { + return source.replace(getIsArrayFallback(source), ''); + } + /** * Removes the `_.isFunction` fallback from `source`. * @@ -1074,11 +1098,6 @@ function removeSupportArgsObject(source) { source = removeSupportProp(source, 'argsObject'); - // remove `argsAreObjects` from `_.isArray` - source = source.replace(matchFunction(source, 'isArray'), function(match) { - return match.replace(/\(support\.argsObject\s*&&\s*([^)]+)\)/g, '$1'); - }); - // remove `argsAreObjects` from `_.isEqual` source = source.replace(matchFunction(source, 'isEqual'), function(match) { return match.replace(/!support.\argsObject[^:]+:\s*/g, ''); @@ -1867,7 +1886,7 @@ // remove native `Array.isArray` branch in `_.isArray` source = source.replace(matchFunction(source, 'isArray'), function(match) { - return match.replace(/\s*\(nativeIsArray.+/, ' toString.call(value) == arrayClass;'); + return match.replace(/\bnativeIsArray\s*\|\|\s*/, ''); }); // replace `_.keys` with `shimKeys` @@ -1919,6 +1938,7 @@ source = removeSupportNodeClass(source); if (!isMobile) { + source = removeIsArrayFallback(source); source = removeSupportEnumPrototypes(source); source = removeSupportNonEnumArgs(source); @@ -2006,11 +2026,6 @@ source = source.replace(/^( *)var eachIteratorOptions *= *[\s\S]+?\n\1};\n/m, function(match) { return match.replace(/(^ *'arrays':)[^,]+/m, '$1 false'); }); - - // remove `toString.call` use from `_.isArray` - source = source.replace(matchFunction(source, 'isArray'), function(match) { - return match.replace(/\s*\(nativeIsArray.+/, ' nativeIsArray(value);'); - }); } } if (isUnderscore) { @@ -2535,14 +2550,6 @@ return match.replace(/\bisEqual\(([^,]+), *([^,]+)[^)]+\)/, '$1 === $2'); }); - // remove `instanceof` use from `_.isDate`, `_.isFunction`, and `_.isRegExp` - _.each(['isDate', 'isFunction', 'isRegExp'], function(methodName) { - var snippet = methodName == 'isFunction' ? getIsFunctionFallback(source) : matchFunction(source, methodName); - source = source.replace(snippet, function(match) { - return match.replace(/\w+\s+instanceof\s+\w+\s*\|\|\s*/g, ''); - }); - }); - // remove conditional `charCodeCallback` use from `_.max` and `_.min` _.each(['max', 'min'], function(methodName) { source = source.replace(matchFunction(source, methodName), function(match) { diff --git a/lodash.js b/lodash.js index 63ee29cac..9bfcb5ce3 100644 --- a/lodash.js +++ b/lodash.js @@ -943,6 +943,26 @@ }; } + /** + * Checks if `value` is an array. + * + * @static + * @memberOf _ + * @category Objects + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true`, if the `value` is an array, else `false`. + * @example + * + * (function() { return _.isArray(arguments); })(); + * // => false + * + * _.isArray([1, 2, 3]); + * // => true + */ + var isArray = nativeIsArray || function(value) { + return value && typeof value == 'object' && toString.call(value) == arrayClass; + }; + /** * A fallback implementation of `Object.keys` which produces an array of the * given object's own enumerable property names. @@ -1416,29 +1436,6 @@ return result; } - /** - * Checks if `value` is an array. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true`, if the `value` is an array, else `false`. - * @example - * - * (function() { return _.isArray(arguments); })(); - * // => false - * - * _.isArray([1, 2, 3]); - * // => true - */ - function isArray(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 (support.argsObject && value instanceof Array) || - (nativeIsArray ? nativeIsArray(value) : toString.call(value) == arrayClass); - } - /** * Checks if `value` is a boolean value. * @@ -1470,7 +1467,7 @@ * // => true */ function isDate(value) { - return value instanceof Date || toString.call(value) == dateClass; + return value ? typeof value == 'object' && toString.call(value) == dateClass : false; } /** @@ -1774,7 +1771,7 @@ // fallback for older versions of Chrome and Safari if (isFunction(/x/)) { isFunction = function(value) { - return value instanceof Function || toString.call(value) == funcClass; + return typeof value == 'function' && toString.call(value) == funcClass; }; } @@ -1924,7 +1921,7 @@ * // => true */ function isRegExp(value) { - return value instanceof RegExp || toString.call(value) == regexpClass; + return value ? typeof value == 'object' && toString.call(value) == regexpClass : false; } /** diff --git a/test/test-build.js b/test/test-build.js index f61d5e75e..939f237af 100644 --- a/test/test-build.js +++ b/test/test-build.js @@ -961,11 +961,6 @@ strictEqual(actual, false, '_.isEqual should ignore `callback` and `thisArg`: ' + basename); - _.each(['isArray', 'isBoolean', 'isDate', 'isFunction', 'isNumber', 'isRegExp', 'isString'], function(methodName) { - var proto = global[methodName.slice(2)].prototype; - strictEqual(lodash[methodName](Object.create(proto)), false, '_.' + methodName + ' returns `false` for subclassed values: ' + basename); - }); - equal(lodash.max('abc'), -Infinity, '_.max should return `-Infinity` for strings: ' + basename); equal(lodash.min('abc'), Infinity, '_.min should return `Infinity` for strings: ' + basename); diff --git a/test/test.js b/test/test.js index ffdaa1836..683923a98 100644 --- a/test/test.js +++ b/test/test.js @@ -1611,6 +1611,15 @@ equal(typeof func({ 'a': 1 }), expected); equal(typeof func('a'), expected); }); + + test('should return `false` for subclassed values', function() { + _.each(['isArray', 'isBoolean', 'isDate', 'isFunction', 'isNumber', 'isRegExp', 'isString'], function(methodName) { + function Foo() {} + Foo.prototype = window[methodName.slice(2)].prototype; + + strictEqual(_[methodName](new Foo), false, '_.' + methodName + ' returns `false`'); + }); + }); }); /*--------------------------------------------------------------------------*/