diff --git a/build.js b/build.js index e677ca85f..b862eb6e0 100755 --- a/build.js +++ b/build.js @@ -1385,6 +1385,9 @@ function removeRunInContext(source) { source = removeVar(source, 'contextProps'); + // replace reference in `reThis` assignment + source = source.replace(/\btest\(runInContext\)/, 'test(function() { return this; })'); + // remove function scaffolding, leaving most of its content source = source.replace(matchFunction(source, 'runInContext'), function(match) { return match diff --git a/lodash.js b/lodash.js index 8a7ff787d..e00d47118 100644 --- a/lodash.js +++ b/lodash.js @@ -55,6 +55,9 @@ /** Used to match "interpolate" template delimiters */ var reInterpolate = /<%=([\s\S]+?)%>/g; + /** Used to detect functions containing a `this` reference */ + var reThis = (reThis = /\bthis\b/) && reThis.test(runInContext) && reThis; + /** Used to detect and test whitespace */ var whitespace = ( // whitespace @@ -188,6 +191,7 @@ clearTimeout = context.clearTimeout, concat = arrayProto.concat, floor = Math.floor, + fnToString = Function.prototype.toString, getPrototypeOf = reNative.test(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf, hasOwnProperty = objectProto.hasOwnProperty, push = arrayProto.push, @@ -4578,27 +4582,27 @@ return result; }; } - if (typeof thisArg != 'undefined') { - if (argCount === 1) { - return function(value) { - return func.call(thisArg, value); - }; - } - if (argCount === 2) { - return function(a, b) { - return func.call(thisArg, a, b); - }; - } - if (argCount === 4) { - return function(accumulator, value, index, collection) { - return func.call(thisArg, accumulator, value, index, collection); - }; - } - return function(value, index, collection) { - return func.call(thisArg, value, index, collection); + if (typeof thisArg == 'undefined' || (reThis && !reThis.test(fnToString.call(func)))) { + return func; + } + if (argCount === 1) { + return function(value) { + return func.call(thisArg, value); }; } - return func; + if (argCount === 2) { + return function(a, b) { + return func.call(thisArg, a, b); + }; + } + if (argCount === 4) { + return function(accumulator, value, index, collection) { + return func.call(thisArg, accumulator, value, index, collection); + }; + } + return function(value, index, collection) { + return func.call(thisArg, value, index, collection); + }; } /** diff --git a/test/test-build.js b/test/test-build.js index e97e50125..fe9467023 100644 --- a/test/test-build.js +++ b/test/test-build.js @@ -1129,6 +1129,80 @@ /*--------------------------------------------------------------------------*/ + QUnit.module('exclude command'); + + (function() { + var commands = [ + 'exclude', + 'minus' + ]; + + commands.forEach(function(command) { + asyncTest('`lodash ' + command + '=runInContext`', function() { + var start = _.after(2, _.once(QUnit.start)); + + build(['-s', command + '=runInContext'], function(data) { + var basename = path.basename(data.outputPath, '.js'), + context = createContext(), + source = data.source; + + vm.runInContext(data.source, context); + + var lodash = context._, + array = [0]; + + var actual = lodash.map(array, function() { + return String(this[0]); + }, array); + + deepEqual(actual, ['0'], basename); + equal('runInContext' in lodash, false, basename); + start(); + }); + }); + + asyncTest('`lodash ' + command + '=mixin`', function() { + var start = _.after(2, _.once(QUnit.start)); + + build(['-s', command + '=mixin'], function(data) { + var basename = path.basename(data.outputPath, '.js'), + context = createContext(), + source = data.source; + + vm.runInContext(data.source, context); + var lodash = context._; + + var actual = lodash([1, 2, 3]) + .map(function(num) { return num * num; }) + .value(); + + deepEqual(actual, [1, 4, 9], basename); + equal('mixin' in lodash, false, basename); + start(); + }); + }); + + asyncTest('`lodash ' + command + '=value`', function() { + var start = _.after(2, _.once(QUnit.start)); + + build(['-s', command + '=value'], function(data) { + var basename = path.basename(data.outputPath, '.js'), + context = createContext(), + source = data.source; + + vm.runInContext(data.source, context); + var lodash = context._; + + strictEqual(lodash([1]), undefined, basename); + deepEqual(_.keys(lodash.prototype), [], basename); + start(); + }); + }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + QUnit.module('exports command'); (function() { @@ -1440,19 +1514,19 @@ var command = origCommand; if (index == 1) { - if (/legacy|mobile/.test(command)) { + if (/\b(?:legacy|mobile)\b/.test(command)) { return; } command = 'mobile ' + command; } else if (index == 2) { - if (/legacy|modern/.test(command)) { + if (/\b(?:legacy|modern)\b/.test(command)) { return; } command = 'modern ' + command; } else if (index == 3) { - if (/category|legacy|underscore/.test(command)) { + if (/\b(?:category|legacy|underscore)\b/.test(command)) { return; } command = 'underscore ' + command; @@ -1464,8 +1538,8 @@ var methodNames, basename = path.basename(data.outputPath, '.js'), context = createContext(), - isBackbone = /backbone/.test(command), - isUnderscore = isBackbone || /underscore/.test(command), + isBackbone = /\bbackbone\b/.test(command), + isUnderscore = isBackbone || /\bunderscore\b/.test(command), exposeAssign = !isUnderscore, exposeZipObject = !isUnderscore; @@ -1475,11 +1549,11 @@ console.log(e); } // add method names explicitly - if (/include/.test(command)) { - methodNames = command.match(/include=(\S*)/)[1].split(/, */); + if (/\binclude=/.test(command)) { + methodNames = command.match(/\binclude=(\S*)/)[1].split(/, */); } // add method names required by Backbone and Underscore builds - if (/backbone/.test(command) && !methodNames) { + if (/\bbackbone\b/.test(command) && !methodNames) { methodNames = backboneDependencies.slice(); } if (isUnderscore) { @@ -1490,20 +1564,20 @@ methodNames = underscoreMethods.slice(); } } - if (/category/.test(command)) { - methodNames = (methodNames || []).concat(command.match(/category=(\S*)/)[1].split(/, */).map(capitalize)); + if (/\bcategory=/.test(command)) { + methodNames = (methodNames || []).concat(command.match(/\bcategory=(\S*)/)[1].split(/, */).map(capitalize)); } if (!methodNames) { methodNames = lodashMethods.slice(); } - if (/plus/.test(command)) { - methodNames = methodNames.concat(command.match(/plus=(\S*)/)[1].split(/, */)); + if (/\bplus=/.test(command)) { + methodNames = methodNames.concat(command.match(/\bplus=(\S*)/)[1].split(/, */)); } - if (/minus/.test(command)) { - methodNames = _.without.apply(_, [methodNames].concat(expandMethodNames(command.match(/minus=(\S*)/)[1].split(/, */)))); + if (/\bminus=/.test(command)) { + methodNames = _.without.apply(_, [methodNames].concat(expandMethodNames(command.match(/\bminus=(\S*)/)[1].split(/, */)))); } - if (/exclude/.test(command)) { - methodNames = _.without.apply(_, [methodNames].concat(expandMethodNames(command.match(/exclude=(\S*)/)[1].split(/, */)))); + if (/\bexclude=/.test(command)) { + methodNames = _.without.apply(_, [methodNames].concat(expandMethodNames(command.match(/\bexclude=(\S*)/)[1].split(/, */)))); } // expand categories to real method names