diff --git a/lib/ace/mode/javascript/jshint.js b/lib/ace/mode/javascript/jshint.js index c390116e..894b78cc 100644 --- a/lib/ace/mode/javascript/jshint.js +++ b/lib/ace/mode/javascript/jshint.js @@ -27,9 +27,9 @@ module.exports = { }, {}], 2:[function(_dereq_,module,exports){ -// Underscore.js 1.6.0 +// Underscore.js 1.8.2 // http://underscorejs.org -// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. (function() { @@ -43,9 +43,6 @@ module.exports = { // Save the previous value of the `_` variable. var previousUnderscore = root._; - // Establish the object that gets returned to break out of a loop iteration. - var breaker = {}; - // Save bytes in the minified (but not gzipped) version: var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; @@ -53,25 +50,19 @@ module.exports = { var push = ArrayProto.push, slice = ArrayProto.slice, - concat = ArrayProto.concat, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; // All **ECMAScript 5** native function implementations that we hope to use // are declared here. var - nativeForEach = ArrayProto.forEach, - nativeMap = ArrayProto.map, - nativeReduce = ArrayProto.reduce, - nativeReduceRight = ArrayProto.reduceRight, - nativeFilter = ArrayProto.filter, - nativeEvery = ArrayProto.every, - nativeSome = ArrayProto.some, - nativeIndexOf = ArrayProto.indexOf, - nativeLastIndexOf = ArrayProto.lastIndexOf, nativeIsArray = Array.isArray, nativeKeys = Object.keys, - nativeBind = FuncProto.bind; + nativeBind = FuncProto.bind, + nativeCreate = Object.create; + + // Naked function reference for surrogate-prototype-swapping. + var Ctor = function(){}; // Create a safe reference to the Underscore object for use below. var _ = function(obj) { @@ -82,8 +73,7 @@ module.exports = { // Export the Underscore object for **Node.js**, with // backwards-compatibility for the old `require()` API. If we're in - // the browser, add `_` as a global object via a string identifier, - // for Closure Compiler "advanced" mode. + // the browser, add `_` as a global object. if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = _; @@ -94,161 +84,208 @@ module.exports = { } // Current version. - _.VERSION = '1.6.0'; + _.VERSION = '1.8.2'; + + // Internal function that returns an efficient (for current engines) version + // of the passed-in callback, to be repeatedly applied in other Underscore + // functions. + var optimizeCb = function(func, context, argCount) { + if (context === void 0) return func; + switch (argCount == null ? 3 : argCount) { + case 1: return function(value) { + return func.call(context, value); + }; + case 2: return function(value, other) { + return func.call(context, value, other); + }; + case 3: return function(value, index, collection) { + return func.call(context, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(context, accumulator, value, index, collection); + }; + } + return function() { + return func.apply(context, arguments); + }; + }; + + // A mostly-internal function to generate callbacks that can be applied + // to each element in a collection, returning the desired result — either + // identity, an arbitrary callback, a property matcher, or a property accessor. + var cb = function(value, context, argCount) { + if (value == null) return _.identity; + if (_.isFunction(value)) return optimizeCb(value, context, argCount); + if (_.isObject(value)) return _.matcher(value); + return _.property(value); + }; + _.iteratee = function(value, context) { + return cb(value, context, Infinity); + }; + + // An internal function for creating assigner functions. + var createAssigner = function(keysFunc, undefinedOnly) { + return function(obj) { + var length = arguments.length; + if (length < 2 || obj == null) return obj; + for (var index = 1; index < length; index++) { + var source = arguments[index], + keys = keysFunc(source), + l = keys.length; + for (var i = 0; i < l; i++) { + var key = keys[i]; + if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key]; + } + } + return obj; + }; + }; + + // An internal function for creating a new object that inherits from another. + var baseCreate = function(prototype) { + if (!_.isObject(prototype)) return {}; + if (nativeCreate) return nativeCreate(prototype); + Ctor.prototype = prototype; + var result = new Ctor; + Ctor.prototype = null; + return result; + }; + + // Helper for collection methods to determine whether a collection + // should be iterated as an array or as an object + // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength + var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; + var isArrayLike = function(collection) { + var length = collection && collection.length; + return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; + }; // Collection Functions // -------------------- // The cornerstone, an `each` implementation, aka `forEach`. - // Handles objects with the built-in `forEach`, arrays, and raw objects. - // Delegates to **ECMAScript 5**'s native `forEach` if available. - var each = _.each = _.forEach = function(obj, iterator, context) { - if (obj == null) return obj; - if (nativeForEach && obj.forEach === nativeForEach) { - obj.forEach(iterator, context); - } else if (obj.length === +obj.length) { - for (var i = 0, length = obj.length; i < length; i++) { - if (iterator.call(context, obj[i], i, obj) === breaker) return; + // Handles raw objects in addition to array-likes. Treats all + // sparse array-likes as if they were dense. + _.each = _.forEach = function(obj, iteratee, context) { + iteratee = optimizeCb(iteratee, context); + var i, length; + if (isArrayLike(obj)) { + for (i = 0, length = obj.length; i < length; i++) { + iteratee(obj[i], i, obj); } } else { var keys = _.keys(obj); - for (var i = 0, length = keys.length; i < length; i++) { - if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; + for (i = 0, length = keys.length; i < length; i++) { + iteratee(obj[keys[i]], keys[i], obj); } } return obj; }; - // Return the results of applying the iterator to each element. - // Delegates to **ECMAScript 5**'s native `map` if available. - _.map = _.collect = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); - each(obj, function(value, index, list) { - results.push(iterator.call(context, value, index, list)); - }); + // Return the results of applying the iteratee to each element. + _.map = _.collect = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length, + results = Array(length); + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + results[index] = iteratee(obj[currentKey], currentKey, obj); + } return results; }; - var reduceError = 'Reduce of empty array with no initial value'; + // Create a reducing function iterating left or right. + function createReduce(dir) { + // Optimized iterator function as using arguments.length + // in the main function will deoptimize the, see #1991. + function iterator(obj, iteratee, memo, keys, index, length) { + for (; index >= 0 && index < length; index += dir) { + var currentKey = keys ? keys[index] : index; + memo = iteratee(memo, obj[currentKey], currentKey, obj); + } + return memo; + } + + return function(obj, iteratee, memo, context) { + iteratee = optimizeCb(iteratee, context, 4); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length, + index = dir > 0 ? 0 : length - 1; + // Determine the initial value if none is provided. + if (arguments.length < 3) { + memo = obj[keys ? keys[index] : index]; + index += dir; + } + return iterator(obj, iteratee, memo, keys, index, length); + }; + } // **Reduce** builds up a single result from a list of values, aka `inject`, - // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. - _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; - if (obj == null) obj = []; - if (nativeReduce && obj.reduce === nativeReduce) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); - } - each(obj, function(value, index, list) { - if (!initial) { - memo = value; - initial = true; - } else { - memo = iterator.call(context, memo, value, index, list); - } - }); - if (!initial) throw new TypeError(reduceError); - return memo; - }; + // or `foldl`. + _.reduce = _.foldl = _.inject = createReduce(1); // The right-associative version of reduce, also known as `foldr`. - // Delegates to **ECMAScript 5**'s native `reduceRight` if available. - _.reduceRight = _.foldr = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; - if (obj == null) obj = []; - if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); - } - var length = obj.length; - if (length !== +length) { - var keys = _.keys(obj); - length = keys.length; - } - each(obj, function(value, index, list) { - index = keys ? keys[--length] : --length; - if (!initial) { - memo = obj[index]; - initial = true; - } else { - memo = iterator.call(context, memo, obj[index], index, list); - } - }); - if (!initial) throw new TypeError(reduceError); - return memo; - }; + _.reduceRight = _.foldr = createReduce(-1); // Return the first value which passes a truth test. Aliased as `detect`. _.find = _.detect = function(obj, predicate, context) { - var result; - any(obj, function(value, index, list) { - if (predicate.call(context, value, index, list)) { - result = value; - return true; - } - }); - return result; + var key; + if (isArrayLike(obj)) { + key = _.findIndex(obj, predicate, context); + } else { + key = _.findKey(obj, predicate, context); + } + if (key !== void 0 && key !== -1) return obj[key]; }; // Return all the elements that pass a truth test. - // Delegates to **ECMAScript 5**'s native `filter` if available. // Aliased as `select`. _.filter = _.select = function(obj, predicate, context) { var results = []; - if (obj == null) return results; - if (nativeFilter && obj.filter === nativeFilter) return obj.filter(predicate, context); - each(obj, function(value, index, list) { - if (predicate.call(context, value, index, list)) results.push(value); + predicate = cb(predicate, context); + _.each(obj, function(value, index, list) { + if (predicate(value, index, list)) results.push(value); }); return results; }; // Return all the elements for which a truth test fails. _.reject = function(obj, predicate, context) { - return _.filter(obj, function(value, index, list) { - return !predicate.call(context, value, index, list); - }, context); + return _.filter(obj, _.negate(cb(predicate)), context); }; // Determine whether all of the elements match a truth test. - // Delegates to **ECMAScript 5**'s native `every` if available. // Aliased as `all`. _.every = _.all = function(obj, predicate, context) { - predicate || (predicate = _.identity); - var result = true; - if (obj == null) return result; - if (nativeEvery && obj.every === nativeEvery) return obj.every(predicate, context); - each(obj, function(value, index, list) { - if (!(result = result && predicate.call(context, value, index, list))) return breaker; - }); - return !!result; + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + if (!predicate(obj[currentKey], currentKey, obj)) return false; + } + return true; }; // Determine if at least one element in the object matches a truth test. - // Delegates to **ECMAScript 5**'s native `some` if available. // Aliased as `any`. - var any = _.some = _.any = function(obj, predicate, context) { - predicate || (predicate = _.identity); - var result = false; - if (obj == null) return result; - if (nativeSome && obj.some === nativeSome) return obj.some(predicate, context); - each(obj, function(value, index, list) { - if (result || (result = predicate.call(context, value, index, list))) return breaker; - }); - return !!result; + _.some = _.any = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + if (predicate(obj[currentKey], currentKey, obj)) return true; + } + return false; }; // Determine if the array or object contains a given value (using `===`). - // Aliased as `include`. - _.contains = _.include = function(obj, target) { - if (obj == null) return false; - if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; - return any(obj, function(value) { - return value === target; - }); + // Aliased as `includes` and `include`. + _.contains = _.includes = _.include = function(obj, target, fromIndex) { + if (!isArrayLike(obj)) obj = _.values(obj); + return _.indexOf(obj, target, typeof fromIndex == 'number' && fromIndex) >= 0; }; // Invoke a method (with arguments) on every item in a collection. @@ -256,7 +293,8 @@ module.exports = { var args = slice.call(arguments, 2); var isFunc = _.isFunction(method); return _.map(obj, function(value) { - return (isFunc ? method : value[method]).apply(value, args); + var func = isFunc ? method : value[method]; + return func == null ? func : func.apply(value, args); }); }; @@ -268,60 +306,76 @@ module.exports = { // Convenience version of a common use case of `filter`: selecting only objects // containing specific `key:value` pairs. _.where = function(obj, attrs) { - return _.filter(obj, _.matches(attrs)); + return _.filter(obj, _.matcher(attrs)); }; // Convenience version of a common use case of `find`: getting the first object // containing specific `key:value` pairs. _.findWhere = function(obj, attrs) { - return _.find(obj, _.matches(attrs)); + return _.find(obj, _.matcher(attrs)); }; - // Return the maximum element or (element-based computation). - // Can't optimize arrays of integers longer than 65,535 elements. - // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797) - _.max = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { - return Math.max.apply(Math, obj); - } - var result = -Infinity, lastComputed = -Infinity; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - if (computed > lastComputed) { - result = value; - lastComputed = computed; + // Return the maximum element (or element-based computation). + _.max = function(obj, iteratee, context) { + var result = -Infinity, lastComputed = -Infinity, + value, computed; + if (iteratee == null && obj != null) { + obj = isArrayLike(obj) ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value > result) { + result = value; + } } - }); + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(value, index, list) { + computed = iteratee(value, index, list); + if (computed > lastComputed || computed === -Infinity && result === -Infinity) { + result = value; + lastComputed = computed; + } + }); + } return result; }; // Return the minimum element (or element-based computation). - _.min = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { - return Math.min.apply(Math, obj); - } - var result = Infinity, lastComputed = Infinity; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - if (computed < lastComputed) { - result = value; - lastComputed = computed; + _.min = function(obj, iteratee, context) { + var result = Infinity, lastComputed = Infinity, + value, computed; + if (iteratee == null && obj != null) { + obj = isArrayLike(obj) ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value < result) { + result = value; + } } - }); + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(value, index, list) { + computed = iteratee(value, index, list); + if (computed < lastComputed || computed === Infinity && result === Infinity) { + result = value; + lastComputed = computed; + } + }); + } return result; }; - // Shuffle an array, using the modern version of the + // Shuffle a collection, using the modern version of the // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). _.shuffle = function(obj) { - var rand; - var index = 0; - var shuffled = []; - each(obj, function(value) { - rand = _.random(index++); - shuffled[index - 1] = shuffled[rand]; - shuffled[rand] = value; - }); + var set = isArrayLike(obj) ? obj : _.values(obj); + var length = set.length; + var shuffled = Array(length); + for (var index = 0, rand; index < length; index++) { + rand = _.random(0, index); + if (rand !== index) shuffled[index] = shuffled[rand]; + shuffled[rand] = set[index]; + } return shuffled; }; @@ -330,27 +384,20 @@ module.exports = { // The internal `guard` argument allows it to work with `map`. _.sample = function(obj, n, guard) { if (n == null || guard) { - if (obj.length !== +obj.length) obj = _.values(obj); + if (!isArrayLike(obj)) obj = _.values(obj); return obj[_.random(obj.length - 1)]; } return _.shuffle(obj).slice(0, Math.max(0, n)); }; - // An internal function to generate lookup iterators. - var lookupIterator = function(value) { - if (value == null) return _.identity; - if (_.isFunction(value)) return value; - return _.property(value); - }; - - // Sort the object's values by a criterion produced by an iterator. - _.sortBy = function(obj, iterator, context) { - iterator = lookupIterator(iterator); + // Sort the object's values by a criterion produced by an iteratee. + _.sortBy = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); return _.pluck(_.map(obj, function(value, index, list) { return { value: value, index: index, - criteria: iterator.call(context, value, index, list) + criteria: iteratee(value, index, list) }; }).sort(function(left, right) { var a = left.criteria; @@ -365,12 +412,12 @@ module.exports = { // An internal function used for aggregate "group by" operations. var group = function(behavior) { - return function(obj, iterator, context) { + return function(obj, iteratee, context) { var result = {}; - iterator = lookupIterator(iterator); - each(obj, function(value, index) { - var key = iterator.call(context, value, index, obj); - behavior(result, key, value); + iteratee = cb(iteratee, context); + _.each(obj, function(value, index) { + var key = iteratee(value, index, obj); + behavior(result, value, key); }); return result; }; @@ -378,48 +425,46 @@ module.exports = { // Groups the object's values by a criterion. Pass either a string attribute // to group by, or a function that returns the criterion. - _.groupBy = group(function(result, key, value) { - _.has(result, key) ? result[key].push(value) : result[key] = [value]; + _.groupBy = group(function(result, value, key) { + if (_.has(result, key)) result[key].push(value); else result[key] = [value]; }); // Indexes the object's values by a criterion, similar to `groupBy`, but for // when you know that your index values will be unique. - _.indexBy = group(function(result, key, value) { + _.indexBy = group(function(result, value, key) { result[key] = value; }); // Counts instances of an object that group by a certain criterion. Pass // either a string attribute to count by, or a function that returns the // criterion. - _.countBy = group(function(result, key) { - _.has(result, key) ? result[key]++ : result[key] = 1; + _.countBy = group(function(result, value, key) { + if (_.has(result, key)) result[key]++; else result[key] = 1; }); - // Use a comparator function to figure out the smallest index at which - // an object should be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iterator, context) { - iterator = lookupIterator(iterator); - var value = iterator.call(context, obj); - var low = 0, high = array.length; - while (low < high) { - var mid = (low + high) >>> 1; - iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; - } - return low; - }; - // Safely create a real, live array from anything iterable. _.toArray = function(obj) { if (!obj) return []; if (_.isArray(obj)) return slice.call(obj); - if (obj.length === +obj.length) return _.map(obj, _.identity); + if (isArrayLike(obj)) return _.map(obj, _.identity); return _.values(obj); }; // Return the number of elements in an object. _.size = function(obj) { if (obj == null) return 0; - return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; + return isArrayLike(obj) ? obj.length : _.keys(obj).length; + }; + + // Split a collection into two arrays: one whose elements all satisfy the given + // predicate, and one whose elements all do not satisfy the predicate. + _.partition = function(obj, predicate, context) { + predicate = cb(predicate, context); + var pass = [], fail = []; + _.each(obj, function(value, key, obj) { + (predicate(value, key, obj) ? pass : fail).push(value); + }); + return [pass, fail]; }; // Array Functions @@ -430,33 +475,30 @@ module.exports = { // allows it to work with `_.map`. _.first = _.head = _.take = function(array, n, guard) { if (array == null) return void 0; - if ((n == null) || guard) return array[0]; - if (n < 0) return []; - return slice.call(array, 0, n); + if (n == null || guard) return array[0]; + return _.initial(array, array.length - n); }; // Returns everything but the last entry of the array. Especially useful on // the arguments object. Passing **n** will return all the values in - // the array, excluding the last N. The **guard** check allows it to work with - // `_.map`. + // the array, excluding the last N. _.initial = function(array, n, guard) { - return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); + return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); }; // Get the last element of an array. Passing **n** will return the last N - // values in the array. The **guard** check allows it to work with `_.map`. + // values in the array. _.last = function(array, n, guard) { if (array == null) return void 0; - if ((n == null) || guard) return array[array.length - 1]; - return slice.call(array, Math.max(array.length - n, 0)); + if (n == null || guard) return array[array.length - 1]; + return _.rest(array, Math.max(0, array.length - n)); }; // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. // Especially useful on the arguments object. Passing an **n** will return - // the rest N values in the array. The **guard** - // check allows it to work with `_.map`. + // the rest N values in the array. _.rest = _.tail = _.drop = function(array, n, guard) { - return slice.call(array, (n == null) || guard ? 1 : n); + return slice.call(array, n == null || guard ? 1 : n); }; // Trim out all falsy values from an array. @@ -465,23 +507,28 @@ module.exports = { }; // Internal implementation of a recursive `flatten` function. - var flatten = function(input, shallow, output) { - if (shallow && _.every(input, _.isArray)) { - return concat.apply(output, input); - } - each(input, function(value) { - if (_.isArray(value) || _.isArguments(value)) { - shallow ? push.apply(output, value) : flatten(value, shallow, output); - } else { - output.push(value); + var flatten = function(input, shallow, strict, startIndex) { + var output = [], idx = 0; + for (var i = startIndex || 0, length = input && input.length; i < length; i++) { + var value = input[i]; + if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { + //flatten current level of array or arguments object + if (!shallow) value = flatten(value, shallow, strict); + var j = 0, len = value.length; + output.length += len; + while (j < len) { + output[idx++] = value[j++]; + } + } else if (!strict) { + output[idx++] = value; } - }); + } return output; }; // Flatten out an array, either recursively (by default), or just one level. _.flatten = function(array, shallow) { - return flatten(array, shallow, []); + return flatten(array, shallow, false); }; // Return a version of the array that does not contain the specified value(s). @@ -489,79 +536,93 @@ module.exports = { return _.difference(array, slice.call(arguments, 1)); }; - // Split an array into two arrays: one whose elements all satisfy the given - // predicate, and one whose elements all do not satisfy the predicate. - _.partition = function(array, predicate) { - var pass = [], fail = []; - each(array, function(elem) { - (predicate(elem) ? pass : fail).push(elem); - }); - return [pass, fail]; - }; - // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. // Aliased as `unique`. - _.uniq = _.unique = function(array, isSorted, iterator, context) { - if (_.isFunction(isSorted)) { - context = iterator; - iterator = isSorted; + _.uniq = _.unique = function(array, isSorted, iteratee, context) { + if (array == null) return []; + if (!_.isBoolean(isSorted)) { + context = iteratee; + iteratee = isSorted; isSorted = false; } - var initial = iterator ? _.map(array, iterator, context) : array; - var results = []; + if (iteratee != null) iteratee = cb(iteratee, context); + var result = []; var seen = []; - each(initial, function(value, index) { - if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { - seen.push(value); - results.push(array[index]); + for (var i = 0, length = array.length; i < length; i++) { + var value = array[i], + computed = iteratee ? iteratee(value, i, array) : value; + if (isSorted) { + if (!i || seen !== computed) result.push(value); + seen = computed; + } else if (iteratee) { + if (!_.contains(seen, computed)) { + seen.push(computed); + result.push(value); + } + } else if (!_.contains(result, value)) { + result.push(value); } - }); - return results; + } + return result; }; // Produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { - return _.uniq(_.flatten(arguments, true)); + return _.uniq(flatten(arguments, true, true)); }; // Produce an array that contains every item shared between all the // passed-in arrays. _.intersection = function(array) { - var rest = slice.call(arguments, 1); - return _.filter(_.uniq(array), function(item) { - return _.every(rest, function(other) { - return _.contains(other, item); - }); - }); + if (array == null) return []; + var result = []; + var argsLength = arguments.length; + for (var i = 0, length = array.length; i < length; i++) { + var item = array[i]; + if (_.contains(result, item)) continue; + for (var j = 1; j < argsLength; j++) { + if (!_.contains(arguments[j], item)) break; + } + if (j === argsLength) result.push(item); + } + return result; }; // Take the difference between one array and a number of other arrays. // Only the elements present in just the first array will remain. _.difference = function(array) { - var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); - return _.filter(array, function(value){ return !_.contains(rest, value); }); + var rest = flatten(arguments, true, true, 1); + return _.filter(array, function(value){ + return !_.contains(rest, value); + }); }; // Zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { - var length = _.max(_.pluck(arguments, 'length').concat(0)); - var results = new Array(length); - for (var i = 0; i < length; i++) { - results[i] = _.pluck(arguments, '' + i); + return _.unzip(arguments); + }; + + // Complement of _.zip. Unzip accepts an array of arrays and groups + // each array's elements on shared indices + _.unzip = function(array) { + var length = array && _.max(array, 'length').length || 0; + var result = Array(length); + + for (var index = 0; index < length; index++) { + result[index] = _.pluck(array, index); } - return results; + return result; }; // Converts lists into objects. Pass either a single array of `[key, value]` // pairs, or two parallel arrays of the same length -- one of keys, and one of // the corresponding values. _.object = function(list, values) { - if (list == null) return {}; var result = {}; - for (var i = 0, length = list.length; i < length; i++) { + for (var i = 0, length = list && list.length; i < length; i++) { if (values) { result[list[i]] = values[i]; } else { @@ -571,40 +632,68 @@ module.exports = { return result; }; - // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), - // we need this function. Return the position of the first occurrence of an - // item in an array, or -1 if the item is not included in the array. - // Delegates to **ECMAScript 5**'s native `indexOf` if available. + // Return the position of the first occurrence of an item in an array, + // or -1 if the item is not included in the array. // If the array is large and already in sort order, pass `true` // for **isSorted** to use binary search. _.indexOf = function(array, item, isSorted) { - if (array == null) return -1; - var i = 0, length = array.length; - if (isSorted) { - if (typeof isSorted == 'number') { - i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted); - } else { - i = _.sortedIndex(array, item); - return array[i] === item ? i : -1; - } + var i = 0, length = array && array.length; + if (typeof isSorted == 'number') { + i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted; + } else if (isSorted && length) { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } + if (item !== item) { + return _.findIndex(slice.call(array, i), _.isNaN); } - if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); for (; i < length; i++) if (array[i] === item) return i; return -1; }; - // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. _.lastIndexOf = function(array, item, from) { - if (array == null) return -1; - var hasIndex = from != null; - if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { - return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); + var idx = array ? array.length : 0; + if (typeof from == 'number') { + idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1); } - var i = (hasIndex ? from : array.length); - while (i--) if (array[i] === item) return i; + if (item !== item) { + return _.findLastIndex(slice.call(array, 0, idx), _.isNaN); + } + while (--idx >= 0) if (array[idx] === item) return idx; return -1; }; + // Generator function to create the findIndex and findLastIndex functions + function createIndexFinder(dir) { + return function(array, predicate, context) { + predicate = cb(predicate, context); + var length = array != null && array.length; + var index = dir > 0 ? 0 : length - 1; + for (; index >= 0 && index < length; index += dir) { + if (predicate(array[index], index, array)) return index; + } + return -1; + }; + } + + // Returns the first index on an array-like that passes a predicate test + _.findIndex = createIndexFinder(1); + + _.findLastIndex = createIndexFinder(-1); + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iteratee, context) { + iteratee = cb(iteratee, context, 1); + var value = iteratee(obj); + var low = 0, high = array.length; + while (low < high) { + var mid = Math.floor((low + high) / 2); + if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; + } + return low; + }; + // Generate an integer Array containing an arithmetic progression. A port of // the native Python `range()` function. See // [the Python documentation](http://docs.python.org/library/functions.html#range). @@ -613,15 +702,13 @@ module.exports = { stop = start || 0; start = 0; } - step = arguments[2] || 1; + step = step || 1; var length = Math.max(Math.ceil((stop - start) / step), 0); - var idx = 0; - var range = new Array(length); + var range = Array(length); - while(idx < length) { - range[idx++] = start; - start += step; + for (var idx = 0; idx < length; idx++, start += step) { + range[idx] = start; } return range; @@ -630,26 +717,27 @@ module.exports = { // Function (ahem) Functions // ------------------ - // Reusable constructor function for prototype setting. - var ctor = function(){}; + // Determines whether to execute a function as a constructor + // or a normal function with the provided arguments + var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { + if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); + var self = baseCreate(sourceFunc.prototype); + var result = sourceFunc.apply(self, args); + if (_.isObject(result)) return result; + return self; + }; // Create a function bound to a given object (assigning `this`, and arguments, // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if // available. _.bind = function(func, context) { - var args, bound; if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - if (!_.isFunction(func)) throw new TypeError; - args = slice.call(arguments, 2); - return bound = function() { - if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); - ctor.prototype = func.prototype; - var self = new ctor; - ctor.prototype = null; - var result = func.apply(self, args.concat(slice.call(arguments))); - if (Object(result) === result) return result; - return self; + if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); + var args = slice.call(arguments, 2); + var bound = function() { + return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); }; + return bound; }; // Partially apply a function by creating a version that has had some of its @@ -657,49 +745,55 @@ module.exports = { // as a placeholder, allowing any combination of arguments to be pre-filled. _.partial = function(func) { var boundArgs = slice.call(arguments, 1); - return function() { - var position = 0; - var args = boundArgs.slice(); - for (var i = 0, length = args.length; i < length; i++) { - if (args[i] === _) args[i] = arguments[position++]; + var bound = function() { + var position = 0, length = boundArgs.length; + var args = Array(length); + for (var i = 0; i < length; i++) { + args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; } while (position < arguments.length) args.push(arguments[position++]); - return func.apply(this, args); + return executeBound(func, bound, this, this, args); }; + return bound; }; // Bind a number of an object's methods to that object. Remaining arguments // are the method names to be bound. Useful for ensuring that all callbacks // defined on an object belong to it. _.bindAll = function(obj) { - var funcs = slice.call(arguments, 1); - if (funcs.length === 0) throw new Error('bindAll must be passed function names'); - each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); + var i, length = arguments.length, key; + if (length <= 1) throw new Error('bindAll must be passed function names'); + for (i = 1; i < length; i++) { + key = arguments[i]; + obj[key] = _.bind(obj[key], obj); + } return obj; }; // Memoize an expensive function by storing its results. _.memoize = function(func, hasher) { - var memo = {}; - hasher || (hasher = _.identity); - return function() { - var key = hasher.apply(this, arguments); - return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); + var memoize = function(key) { + var cache = memoize.cache; + var address = '' + (hasher ? hasher.apply(this, arguments) : key); + if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); + return cache[address]; }; + memoize.cache = {}; + return memoize; }; // Delays a function for the given number of milliseconds, and then calls // it with the arguments supplied. _.delay = function(func, wait) { var args = slice.call(arguments, 2); - return setTimeout(function(){ return func.apply(null, args); }, wait); + return setTimeout(function(){ + return func.apply(null, args); + }, wait); }; // Defers a function, scheduling it to run after the current call stack has // cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); - }; + _.defer = _.partial(_.delay, _, 1); // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. Normally, the throttled function will run @@ -710,12 +804,12 @@ module.exports = { var context, args, result; var timeout = null; var previous = 0; - options || (options = {}); + if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); - context = args = null; + if (!timeout) context = args = null; }; return function() { var now = _.now(); @@ -723,12 +817,14 @@ module.exports = { var remaining = wait - (now - previous); context = this; args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } previous = now; result = func.apply(context, args); - context = args = null; + if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } @@ -745,13 +841,14 @@ module.exports = { var later = function() { var last = _.now() - timestamp; - if (last < wait) { + + if (last < wait && last >= 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; if (!immediate) { result = func.apply(context, args); - context = args = null; + if (!timeout) context = args = null; } } }; @@ -761,9 +858,7 @@ module.exports = { args = arguments; timestamp = _.now(); var callNow = immediate && !timeout; - if (!timeout) { - timeout = setTimeout(later, wait); - } + if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; @@ -773,19 +868,6 @@ module.exports = { }; }; - // Returns a function that will be executed at most one time, no matter how - // often you call it. Useful for lazy initialization. - _.once = function(func) { - var ran = false, memo; - return function() { - if (ran) return memo; - ran = true; - memo = func.apply(this, arguments); - func = null; - return memo; - }; - }; - // Returns the first function passed as an argument to the second, // allowing you to adjust arguments, run code before and after, and // conditionally execute the original function. @@ -793,20 +875,27 @@ module.exports = { return _.partial(wrapper, func); }; - // Returns a function that is the composition of a list of functions, each - // consuming the return value of the function that follows. - _.compose = function() { - var funcs = arguments; + // Returns a negated version of the passed-in predicate. + _.negate = function(predicate) { return function() { - var args = arguments; - for (var i = funcs.length - 1; i >= 0; i--) { - args = [funcs[i].apply(this, args)]; - } - return args[0]; + return !predicate.apply(this, arguments); }; }; - // Returns a function that will only be executed after being called N times. + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var args = arguments; + var start = args.length - 1; + return function() { + var i = start; + var result = args[start].apply(this, arguments); + while (i--) result = args[i].call(this, result); + return result; + }; + }; + + // Returns a function that will only be executed on and after the Nth call. _.after = function(times, func) { return function() { if (--times < 1) { @@ -815,16 +904,66 @@ module.exports = { }; }; + // Returns a function that will only be executed up to (but not including) the Nth call. + _.before = function(times, func) { + var memo; + return function() { + if (--times > 0) { + memo = func.apply(this, arguments); + } + if (times <= 1) func = null; + return memo; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = _.partial(_.before, 2); + // Object Functions // ---------------- - // Retrieve the names of an object's properties. + // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. + var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); + var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', + 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; + + function collectNonEnumProps(obj, keys) { + var nonEnumIdx = nonEnumerableProps.length; + var constructor = obj.constructor; + var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; + + // Constructor is a special case. + var prop = 'constructor'; + if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); + + while (nonEnumIdx--) { + prop = nonEnumerableProps[nonEnumIdx]; + if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { + keys.push(prop); + } + } + } + + // Retrieve the names of an object's own properties. // Delegates to **ECMAScript 5**'s native `Object.keys` _.keys = function(obj) { if (!_.isObject(obj)) return []; if (nativeKeys) return nativeKeys(obj); var keys = []; for (var key in obj) if (_.has(obj, key)) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + }; + + // Retrieve all the property names of an object. + _.allKeys = function(obj) { + if (!_.isObject(obj)) return []; + var keys = []; + for (var key in obj) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; }; @@ -832,18 +971,33 @@ module.exports = { _.values = function(obj) { var keys = _.keys(obj); var length = keys.length; - var values = new Array(length); + var values = Array(length); for (var i = 0; i < length; i++) { values[i] = obj[keys[i]]; } return values; }; + // Returns the results of applying the iteratee to each element of the object + // In contrast to _.map it returns an object + _.mapObject = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var keys = _.keys(obj), + length = keys.length, + results = {}, + currentKey; + for (var index = 0; index < length; index++) { + currentKey = keys[index]; + results[currentKey] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + }; + // Convert an object into a list of `[key, value]` pairs. _.pairs = function(obj) { var keys = _.keys(obj); var length = keys.length; - var pairs = new Array(length); + var pairs = Array(length); for (var i = 0; i < length; i++) { pairs[i] = [keys[i], obj[keys[i]]]; } @@ -871,48 +1025,57 @@ module.exports = { }; // Extend a given object with all the properties in passed-in object(s). - _.extend = function(obj) { - each(slice.call(arguments, 1), function(source) { - if (source) { - for (var prop in source) { - obj[prop] = source[prop]; - } - } - }); - return obj; + _.extend = createAssigner(_.allKeys); + + // Assigns a given object with all the own properties in the passed-in object(s) + // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) + _.extendOwn = _.assign = createAssigner(_.keys); + + // Returns the first key on an object that passes a predicate test + _.findKey = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = _.keys(obj), key; + for (var i = 0, length = keys.length; i < length; i++) { + key = keys[i]; + if (predicate(obj[key], key, obj)) return key; + } }; // Return a copy of the object only containing the whitelisted properties. - _.pick = function(obj) { - var copy = {}; - var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); - each(keys, function(key) { - if (key in obj) copy[key] = obj[key]; - }); - return copy; + _.pick = function(object, oiteratee, context) { + var result = {}, obj = object, iteratee, keys; + if (obj == null) return result; + if (_.isFunction(oiteratee)) { + keys = _.allKeys(obj); + iteratee = optimizeCb(oiteratee, context); + } else { + keys = flatten(arguments, false, false, 1); + iteratee = function(value, key, obj) { return key in obj; }; + obj = Object(obj); + } + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i]; + var value = obj[key]; + if (iteratee(value, key, obj)) result[key] = value; + } + return result; }; // Return a copy of the object without the blacklisted properties. - _.omit = function(obj) { - var copy = {}; - var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); - for (var key in obj) { - if (!_.contains(keys, key)) copy[key] = obj[key]; + _.omit = function(obj, iteratee, context) { + if (_.isFunction(iteratee)) { + iteratee = _.negate(iteratee); + } else { + var keys = _.map(flatten(arguments, false, false, 1), String); + iteratee = function(value, key) { + return !_.contains(keys, key); + }; } - return copy; + return _.pick(obj, iteratee, context); }; // Fill in a given object with default properties. - _.defaults = function(obj) { - each(slice.call(arguments, 1), function(source) { - if (source) { - for (var prop in source) { - if (obj[prop] === void 0) obj[prop] = source[prop]; - } - } - }); - return obj; - }; + _.defaults = createAssigner(_.allKeys, true); // Create a (shallow-cloned) duplicate of an object. _.clone = function(obj) { @@ -928,11 +1091,24 @@ module.exports = { return obj; }; + // Returns whether an object has a given set of `key:value` pairs. + _.isMatch = function(object, attrs) { + var keys = _.keys(attrs), length = keys.length; + if (object == null) return !length; + var obj = Object(object); + for (var i = 0; i < length; i++) { + var key = keys[i]; + if (attrs[key] !== obj[key] || !(key in obj)) return false; + } + return true; + }; + + // Internal recursive comparison function for `isEqual`. var eq = function(a, b, aStack, bStack) { // Identical objects are equal. `0 === -0`, but they aren't identical. // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). - if (a === b) return a !== 0 || 1 / a == 1 / b; + if (a === b) return a !== 0 || 1 / a === 1 / b; // A strict comparison is necessary because `null == undefined`. if (a == null || b == null) return a === b; // Unwrap any wrapped objects. @@ -940,98 +1116,98 @@ module.exports = { if (b instanceof _) b = b._wrapped; // Compare `[[Class]]` names. var className = toString.call(a); - if (className != toString.call(b)) return false; + if (className !== toString.call(b)) return false; switch (className) { - // Strings, numbers, dates, and booleans are compared by value. + // Strings, numbers, regular expressions, dates, and booleans are compared by value. + case '[object RegExp]': + // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. - return a == String(b); + return '' + a === '' + b; case '[object Number]': - // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for - // other numeric values. - return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); + // `NaN`s are equivalent, but non-reflexive. + // Object(NaN) is equivalent to NaN + if (+a !== +a) return +b !== +b; + // An `egal` comparison is performed for other numeric values. + return +a === 0 ? 1 / +a === 1 / b : +a === +b; case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. - return +a == +b; - // RegExps are compared by their source patterns and flags. - case '[object RegExp]': - return a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; + return +a === +b; + } + + var areArrays = className === '[object Array]'; + if (!areArrays) { + if (typeof a != 'object' || typeof b != 'object') return false; + + // Objects with different constructors are not equivalent, but `Object`s or `Array`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && + _.isFunction(bCtor) && bCtor instanceof bCtor) + && ('constructor' in a && 'constructor' in b)) { + return false; + } } - if (typeof a != 'object' || typeof b != 'object') return false; // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + + // Initializing stack of traversed objects. + // It's done here since we only need them for objects and arrays comparison. + aStack = aStack || []; + bStack = bStack || []; var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. - if (aStack[length] == a) return bStack[length] == b; - } - // Objects with different constructors are not equivalent, but `Object`s - // from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && - _.isFunction(bCtor) && (bCtor instanceof bCtor)) - && ('constructor' in a && 'constructor' in b)) { - return false; + if (aStack[length] === a) return bStack[length] === b; } + // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); - var size = 0, result = true; + // Recursively compare objects and arrays. - if (className == '[object Array]') { + if (areArrays) { // Compare array lengths to determine if a deep comparison is necessary. - size = a.length; - result = size == b.length; - if (result) { - // Deep compare the contents, ignoring non-numeric properties. - while (size--) { - if (!(result = eq(a[size], b[size], aStack, bStack))) break; - } + length = a.length; + if (length !== b.length) return false; + // Deep compare the contents, ignoring non-numeric properties. + while (length--) { + if (!eq(a[length], b[length], aStack, bStack)) return false; } } else { // Deep compare objects. - for (var key in a) { - if (_.has(a, key)) { - // Count the expected number of properties. - size++; - // Deep compare each member. - if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; - } - } - // Ensure that both objects contain the same number of properties. - if (result) { - for (key in b) { - if (_.has(b, key) && !(size--)) break; - } - result = !size; + var keys = _.keys(a), key; + length = keys.length; + // Ensure that both objects contain the same number of properties before comparing deep equality. + if (_.keys(b).length !== length) return false; + while (length--) { + // Deep compare each member + key = keys[length]; + if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; } } // Remove the first object from the stack of traversed objects. aStack.pop(); bStack.pop(); - return result; + return true; }; // Perform a deep comparison to check if two objects are equal. _.isEqual = function(a, b) { - return eq(a, b, [], []); + return eq(a, b); }; // Is a given array, string, or object empty? // An "empty" object has no enumerable own-properties. _.isEmpty = function(obj) { if (obj == null) return true; - if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; - for (var key in obj) if (_.has(obj, key)) return false; - return true; + if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; + return _.keys(obj).length === 0; }; // Is a given value a DOM element? @@ -1042,33 +1218,35 @@ module.exports = { // Is a given value an array? // Delegates to ECMA5's native Array.isArray _.isArray = nativeIsArray || function(obj) { - return toString.call(obj) == '[object Array]'; + return toString.call(obj) === '[object Array]'; }; // Is a given variable an object? _.isObject = function(obj) { - return obj === Object(obj); + var type = typeof obj; + return type === 'function' || type === 'object' && !!obj; }; - // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. - each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. + _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { _['is' + name] = function(obj) { - return toString.call(obj) == '[object ' + name + ']'; + return toString.call(obj) === '[object ' + name + ']'; }; }); - // Define a fallback version of the method in browsers (ahem, IE), where + // Define a fallback version of the method in browsers (ahem, IE < 9), where // there isn't any inspectable "Arguments" type. if (!_.isArguments(arguments)) { _.isArguments = function(obj) { - return !!(obj && _.has(obj, 'callee')); + return _.has(obj, 'callee'); }; } - // Optimize `isFunction` if appropriate. - if (typeof (/./) !== 'function') { + // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, + // IE 11 (#1621), and in Safari 8 (#1929). + if (typeof /./ != 'function' && typeof Int8Array != 'object') { _.isFunction = function(obj) { - return typeof obj === 'function'; + return typeof obj == 'function' || false; }; } @@ -1079,12 +1257,12 @@ module.exports = { // Is the given value `NaN`? (NaN is the only number which does not equal itself). _.isNaN = function(obj) { - return _.isNumber(obj) && obj != +obj; + return _.isNumber(obj) && obj !== +obj; }; // Is a given value a boolean? _.isBoolean = function(obj) { - return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; + return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; }; // Is a given value equal to null? @@ -1100,7 +1278,7 @@ module.exports = { // Shortcut function for checking if an object has a given property directly // on itself (in other words, not on a prototype). _.has = function(obj, key) { - return hasOwnProperty.call(obj, key); + return obj != null && hasOwnProperty.call(obj, key); }; // Utility Functions @@ -1113,39 +1291,47 @@ module.exports = { return this; }; - // Keep the identity function around for default iterators. + // Keep the identity function around for default iteratees. _.identity = function(value) { return value; }; + // Predicate-generating functions. Often useful outside of Underscore. _.constant = function(value) { - return function () { + return function() { return value; }; }; + _.noop = function(){}; + _.property = function(key) { return function(obj) { + return obj == null ? void 0 : obj[key]; + }; + }; + + // Generates a function for a given object that returns a given property. + _.propertyOf = function(obj) { + return obj == null ? function(){} : function(key) { return obj[key]; }; }; - // Returns a predicate for checking whether an object has a given set of `key:value` pairs. - _.matches = function(attrs) { + // Returns a predicate for checking whether an object has a given set of + // `key:value` pairs. + _.matcher = _.matches = function(attrs) { + attrs = _.extendOwn({}, attrs); return function(obj) { - if (obj === attrs) return true; //avoid comparing an object to itself. - for (var key in attrs) { - if (attrs[key] !== obj[key]) - return false; - } - return true; - } + return _.isMatch(obj, attrs); + }; }; // Run a function **n** times. - _.times = function(n, iterator, context) { + _.times = function(n, iteratee, context) { var accum = Array(Math.max(0, n)); - for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); + iteratee = optimizeCb(iteratee, context, 1); + for (var i = 0; i < n; i++) accum[i] = iteratee(i); return accum; }; @@ -1159,56 +1345,48 @@ module.exports = { }; // A (possibly faster) way to get the current timestamp as an integer. - _.now = Date.now || function() { return new Date().getTime(); }; - - // List of HTML entities for escaping. - var entityMap = { - escape: { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - } + _.now = Date.now || function() { + return new Date().getTime(); }; - entityMap.unescape = _.invert(entityMap.escape); - // Regexes containing the keys and values listed immediately above. - var entityRegexes = { - escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), - unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') + // List of HTML entities for escaping. + var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`' }; + var unescapeMap = _.invert(escapeMap); // Functions for escaping and unescaping strings to/from HTML interpolation. - _.each(['escape', 'unescape'], function(method) { - _[method] = function(string) { - if (string == null) return ''; - return ('' + string).replace(entityRegexes[method], function(match) { - return entityMap[method][match]; - }); + var createEscaper = function(map) { + var escaper = function(match) { + return map[match]; }; - }); + // Regexes for identifying a key that needs to be escaped + var source = '(?:' + _.keys(map).join('|') + ')'; + var testRegexp = RegExp(source); + var replaceRegexp = RegExp(source, 'g'); + return function(string) { + string = string == null ? '' : '' + string; + return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; + }; + }; + _.escape = createEscaper(escapeMap); + _.unescape = createEscaper(unescapeMap); // If the value of the named `property` is a function then invoke it with the // `object` as context; otherwise, return it. - _.result = function(object, property) { - if (object == null) return void 0; - var value = object[property]; + _.result = function(object, property, fallback) { + var value = object == null ? void 0 : object[property]; + if (value === void 0) { + value = fallback; + } return _.isFunction(value) ? value.call(object) : value; }; - // Add your own custom functions to the Underscore object. - _.mixin = function(obj) { - each(_.functions(obj), function(name) { - var func = _[name] = obj[name]; - _.prototype[name] = function() { - var args = [this._wrapped]; - push.apply(args, arguments); - return result.call(this, func.apply(_, args)); - }; - }); - }; - // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. var idCounter = 0; @@ -1237,22 +1415,26 @@ module.exports = { '\\': '\\', '\r': 'r', '\n': 'n', - '\t': 't', '\u2028': 'u2028', '\u2029': 'u2029' }; - var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + var escaper = /\\|'|\r|\n|\u2028|\u2029/g; + + var escapeChar = function(match) { + return '\\' + escapes[match]; + }; // JavaScript micro-templating, similar to John Resig's implementation. // Underscore templating handles arbitrary delimiters, preserves whitespace, // and correctly escapes quotes within interpolated code. - _.template = function(text, data, settings) { - var render; + // NB: `oldSettings` only exists for backwards compatibility. + _.template = function(text, settings, oldSettings) { + if (!settings && oldSettings) settings = oldSettings; settings = _.defaults({}, settings, _.templateSettings); // Combine delimiters into one regular expression via alternation. - var matcher = new RegExp([ + var matcher = RegExp([ (settings.escape || noMatch).source, (settings.interpolate || noMatch).source, (settings.evaluate || noMatch).source @@ -1262,19 +1444,18 @@ module.exports = { var index = 0; var source = "__p+='"; text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { - source += text.slice(index, offset) - .replace(escaper, function(match) { return '\\' + escapes[match]; }); + source += text.slice(index, offset).replace(escaper, escapeChar); + index = offset + match.length; if (escape) { source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; - } - if (interpolate) { + } else if (interpolate) { source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; - } - if (evaluate) { + } else if (evaluate) { source += "';\n" + evaluate + "\n__p+='"; } - index = offset + match.length; + + // Adobe VMs need the match returned to produce the correct offest. return match; }); source += "';\n"; @@ -1284,29 +1465,31 @@ module.exports = { source = "var __t,__p='',__j=Array.prototype.join," + "print=function(){__p+=__j.call(arguments,'');};\n" + - source + "return __p;\n"; + source + 'return __p;\n'; try { - render = new Function(settings.variable || 'obj', '_', source); + var render = new Function(settings.variable || 'obj', '_', source); } catch (e) { e.source = source; throw e; } - if (data) return render(data, _); var template = function(data) { return render.call(this, data, _); }; - // Provide the compiled function source as a convenience for precompilation. - template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; + // Provide the compiled source as a convenience for precompilation. + var argument = settings.variable || 'obj'; + template.source = 'function(' + argument + '){\n' + source + '}'; return template; }; - // Add a "chain" function, which will delegate to the wrapper. + // Add a "chain" function. Start chaining a wrapped Underscore object. _.chain = function(obj) { - return _(obj).chain(); + var instance = _(obj); + instance._chain = true; + return instance; }; // OOP @@ -1316,46 +1499,56 @@ module.exports = { // underscore functions. Wrapped objects may be chained. // Helper function to continue chaining intermediate results. - var result = function(obj) { - return this._chain ? _(obj).chain() : obj; + var result = function(instance, obj) { + return instance._chain ? _(obj).chain() : obj; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + _.each(_.functions(obj), function(name) { + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result(this, func.apply(_, args)); + }; + }); }; // Add all of the Underscore functions to the wrapper object. _.mixin(_); // Add all mutator Array functions to the wrapper. - each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { var obj = this._wrapped; method.apply(obj, arguments); - if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; - return result.call(this, obj); + if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; + return result(this, obj); }; }); // Add all accessor Array functions to the wrapper. - each(['concat', 'join', 'slice'], function(name) { + _.each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { - return result.call(this, method.apply(this._wrapped, arguments)); + return result(this, method.apply(this._wrapped, arguments)); }; }); - _.extend(_.prototype, { + // Extracts the result from a wrapped and chained object. + _.prototype.value = function() { + return this._wrapped; + }; - // Start chaining a wrapped Underscore object. - chain: function() { - this._chain = true; - return this; - }, - - // Extracts the result from a wrapped and chained object. - value: function() { - return this._wrapped; - } - - }); + // Provide unwrapping proxy for some methods used in engine operations + // such as arithmetic and JSON stringification. + _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; + + _.prototype.toString = function() { + return '' + this._wrapped; + }; // AMD registration happens at the end for compatibility with AMD loaders // that may not enforce next-turn semantics on modules. Even though general @@ -1369,7 +1562,7 @@ module.exports = { return _; }); } -}).call(this); +}.call(this)); }, {}], @@ -1422,7 +1615,7 @@ var options = _dereq_("./options.js"); // variable. That function will be invoked immediately, and its return value is // the JSHINT function itself. -var JSHINT = (function () { +var JSHINT = (function() { "use strict"; var api, // Extension API @@ -1533,14 +1726,14 @@ var JSHINT = (function () { } function supplant(str, data) { - return str.replace(/\{([^{}]*)\}/g, function (a, b) { + return str.replace(/\{([^{}]*)\}/g, function(a, b) { var r = data[b]; return typeof r === "string" || typeof r === "number" ? r : a; }); } function combine(dest, src) { - Object.keys(src).forEach(function (name) { + Object.keys(src).forEach(function(name) { if (_.has(JSHINT.blacklist, name)) return; dest[name] = src[name]; }); @@ -1551,7 +1744,7 @@ var JSHINT = (function () { for (var enforceopt in options.bool.enforcing) { if (state.option[enforceopt] === undefined) { state.option[enforceopt] = true; - } + } } for (var relaxopt in options.bool.relaxing) { if (state.option[relaxopt] === undefined) { @@ -1564,8 +1757,12 @@ var JSHINT = (function () { function assume() { processenforceall(); + if (!state.option.es3) { + combine(predefined, vars.ecmaIdentifiers[5]); + } + if (state.option.esnext) { - combine(predefined, vars.newEcmaIdentifiers); + combine(predefined, vars.ecmaIdentifiers[6]); } if (state.option.couch) { @@ -1658,19 +1855,19 @@ var JSHINT = (function () { // Let's assume that chronologically ES3 < ES5 < ES6/ESNext < Moz - state.option.inMoz = function (strict) { + state.option.inMoz = function(strict) { return state.option.moz; }; - state.option.inESNext = function (strict) { + state.option.inESNext = function(strict) { return state.option.moz || state.option.esnext; }; - state.option.inES5 = function (/* strict */) { + state.option.inES5 = function(/* strict */) { return !state.option.es3; }; - state.option.inES3 = function (strict) { + state.option.inES3 = function(strict) { if (strict) { return !state.option.moz && !state.option.esnext && state.option.es3; } @@ -1694,14 +1891,16 @@ var JSHINT = (function () { } function isundef(scope, code, token, a) { - return JSHINT.undefs.push([scope, code, token, a]); + if (!state.ignored[code] && state.option.undef !== false) { + JSHINT.undefs.push([scope, code, token, a]); + } } function removeIgnoredMessages() { var ignored = state.ignoredLines; if (_.isEmpty(ignored)) return; - JSHINT.errors = _.reject(JSHINT.errors, function (err) { return ignored[err.line] }); + JSHINT.errors = _.reject(JSHINT.errors, function(err) { return ignored[err.line] }); } function warning(code, t, a, b, c, d) { @@ -1861,7 +2060,7 @@ var JSHINT = (function () { var predef = {}; if (nt.type === "globals") { - body.forEach(function (g) { + body.forEach(function(g) { g = g.split(":"); var key = (g[0] || "").trim(); var val = (g[1] || "").trim(); @@ -1887,7 +2086,7 @@ var JSHINT = (function () { } if (nt.type === "exported") { - body.forEach(function (e) { + body.forEach(function(e) { exported[e] = true; }); } @@ -1895,7 +2094,7 @@ var JSHINT = (function () { if (nt.type === "members") { membersOnly = membersOnly || {}; - body.forEach(function (m) { + body.forEach(function(m) { var ch1 = m.charAt(0); var ch2 = m.charAt(m.length - 1); @@ -1920,7 +2119,7 @@ var JSHINT = (function () { ]; if (nt.type === "jshint" || nt.type === "jslint") { - body.forEach(function (g) { + body.forEach(function(g) { g = g.split(":"); var key = (g[0] || "").trim(); var val = (g[1] || "").trim(); @@ -2138,14 +2337,7 @@ var JSHINT = (function () { error("E020", state.tokens.next, id, t.id, t.line, state.tokens.next.value); } } else if (state.tokens.next.type !== "(identifier)" || state.tokens.next.value !== id) { - // parameter destructuring with rest operator - if (state.tokens.next.value === "...") { - if (!state.option.esnext) { - warning("W119", state.tokens.next, "spread/rest operator"); - } - } else { - warning("W116", state.tokens.next, id, state.tokens.next.value); - } + warning("W116", state.tokens.next, id, state.tokens.next.value); } } @@ -2177,7 +2369,7 @@ var JSHINT = (function () { } function isInfix(token) { - return token.infix || (!token.identifier && !!token.led); + return token.infix || (!token.identifier && !token.template && !!token.led); } function isEndOfExpr() { @@ -2187,11 +2379,15 @@ var JSHINT = (function () { return true; } if (isInfix(next) === isInfix(curr) || (curr.id === "yield" && state.option.inMoz(true))) { - return curr.line !== next.line; + return curr.line !== startLine(next); } return false; } + function isBeginOfExpr(prev) { + return !prev.left && prev.arity !== "unary"; + } + // This is the heart of JSHINT, the Pratt parser. In addition to parsing, it // is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is // like .nud except that it is only used on the first token of a statement. @@ -2207,8 +2403,7 @@ var JSHINT = (function () { // They are elements of the parsing method called Top Down Operator Precedence. function expression(rbp, initial) { - var left, isArray = false, isObject = false, isLetExpr = false, - isFatArrowBody = state.tokens.curr.value === "=>"; + var left, isArray = false, isObject = false, isLetExpr = false; state.nameStack.push(); @@ -2222,20 +2417,16 @@ var JSHINT = (function () { funct["(blockscope)"].stack(); advance("let"); advance("("); - state.syntax["let"].fud.call(state.syntax["let"].fud, false); + state.tokens.prev.fud(); advance(")"); } if (state.tokens.next.id === "(end)") error("E006", state.tokens.curr); - if (state.tokens.next.type === "(template)") { - doTemplateLiteral(); - } - var isDangerous = state.option.asi && - state.tokens.prev.line < state.tokens.curr.line && + state.tokens.prev.line !== startLine(state.tokens.curr) && _.contains(["]", ")"], state.tokens.prev.id) && _.contains(["[", "("], state.tokens.curr.id); @@ -2246,6 +2437,7 @@ var JSHINT = (function () { if (initial) { funct["(verb)"] = state.tokens.curr.value; + state.tokens.curr.beginsStmt = true; } if (initial === true && state.tokens.curr.fud) { @@ -2257,7 +2449,9 @@ var JSHINT = (function () { error("E030", state.tokens.curr, state.tokens.curr.id); } - while (rbp < state.tokens.next.lbp && !isEndOfExpr()) { + // TODO: use pratt mechanics rather than special casing template tokens + while ((rbp < state.tokens.next.lbp || state.tokens.next.type === "(template)") && + !isEndOfExpr()) { isArray = state.tokens.curr.value === "Array"; isObject = state.tokens.curr.value === "Object"; @@ -2300,11 +2494,6 @@ var JSHINT = (function () { funct["(blockscope)"].unstack(); } - if (state.option.singleGroups && left && left.paren && !left.exprs && - !isFatArrowBody && !left.triggerFnExpr) { - warning("W126"); - } - state.nameStack.pop(); return left; @@ -2313,23 +2502,27 @@ var JSHINT = (function () { // Functions for conformance of style. + function startLine(token) { + return token.startLine || token.line; + } + function nobreaknonadjacent(left, right) { left = left || state.tokens.curr; right = right || state.tokens.next; - if (!state.option.laxbreak && left.line !== right.line) { + if (!state.option.laxbreak && left.line !== startLine(right)) { warning("W014", right, right.value); } } function nolinebreak(t) { t = t || state.tokens.curr; - if (t.line !== state.tokens.next.line) { + if (t.line !== startLine(state.tokens.next)) { warning("E022", t, t.value); } } function nobreakcomma(left, right) { - if (left.line !== right.line) { + if (left.line !== startLine(right)) { if (!state.option.laxcomma) { if (comma.first) { warning("I001"); @@ -2343,10 +2536,6 @@ var JSHINT = (function () { function comma(opts) { opts = opts || {}; - if (state.option.nocomma) { - warning("W127"); - } - if (!opts.peek) { nobreakcomma(state.tokens.curr, state.tokens.next); advance(","); @@ -2416,7 +2605,9 @@ var JSHINT = (function () { } function delim(s) { - return symbol(s, 0); + var x = symbol(s, 0); + x.delim = true; + return x; } function stmt(s, f) { @@ -2444,9 +2635,9 @@ var JSHINT = (function () { var x = symbol(s, 150); reserveName(x); - x.nud = (typeof f === "function") ? f : function () { - this.right = expression(150); + x.nud = (typeof f === "function") ? f : function() { this.arity = "unary"; + this.right = expression(150); if (this.id === "++" || this.id === "--") { if (state.option.plusplus) { @@ -2478,7 +2669,7 @@ var JSHINT = (function () { } function FutureReservedWord(name, meta) { - var x = type(name, (meta && meta.nud) || function () { + var x = type(name, (meta && meta.nud) || function() { return this; }); @@ -2494,7 +2685,7 @@ var JSHINT = (function () { } function reservevar(s, v) { - return reserve(s, function () { + return reserve(s, function() { if (typeof v === "function") { v(this); } @@ -2506,7 +2697,7 @@ var JSHINT = (function () { var x = symbol(s, p); reserveName(x); x.infix = true; - x.led = function (left) { + x.led = function(left) { if (!w) { nobreaknonadjacent(state.tokens.prev, state.tokens.curr); } @@ -2528,11 +2719,11 @@ var JSHINT = (function () { function application(s) { var x = symbol(s, 42); - x.led = function (left) { + x.led = function(left) { nobreaknonadjacent(state.tokens.prev, state.tokens.curr); this.left = left; - this.right = doFunction(undefined, undefined, false, { loneArg: left }); + this.right = doFunction({ type: "arrow", loneArg: left }); return this; }; return x; @@ -2541,7 +2732,7 @@ var JSHINT = (function () { function relation(s, f) { var x = symbol(s, 100); - x.led = function (left) { + x.led = function(left) { nobreaknonadjacent(state.tokens.prev, state.tokens.curr); var right = expression(100); @@ -2593,7 +2784,7 @@ var JSHINT = (function () { var values = [ "undefined", "object", "boolean", "number", - "string", "function", "xml", "object", "unknown" + "string", "function", "xml", "object", "unknown", "symbol" ]; if (right.type === "(identifier)" && right.value === "typeof" && left.type === "(string)") @@ -2602,6 +2793,27 @@ var JSHINT = (function () { return false; } + function isGlobalEval(left, state, funct) { + var isGlobal = false; + + // permit methods to refer to an "eval" key in their own context + if (left.type === "this" && funct["(context)"] === null) { + isGlobal = true; + } + // permit use of "eval" members of objects + else if (left.type === "(identifier)") { + if (state.option.node && left.value === "global") { + isGlobal = true; + } + + else if (state.option.browser && (left.value === "window" || left.value === "document")) { + isGlobal = true; + } + } + + return isGlobal; + } + function findNativePrototype(left) { var natives = [ "Array", "ArrayBuffer", "Boolean", "Collator", "DataView", "Date", @@ -2631,7 +2843,7 @@ var JSHINT = (function () { } function assignop(s, f, p) { - var x = infix(s, typeof f === "function" ? f : function (left, that) { + var x = infix(s, typeof f === "function" ? f : function(left, that) { that.left = left; if (left) { @@ -2664,7 +2876,7 @@ var JSHINT = (function () { return that; } else if (left.id === "[") { if (state.tokens.curr.left.first) { - state.tokens.curr.left.first.forEach(function (t) { + state.tokens.curr.left.first.forEach(function(t) { if (t && funct[t.value] === "const") { error("E013", t, t.value); } @@ -2705,7 +2917,7 @@ var JSHINT = (function () { function bitwise(s, f, p) { var x = symbol(s, p); reserveName(x); - x.led = (typeof f === "function") ? f : function (left) { + x.led = (typeof f === "function") ? f : function(left) { if (state.option.bitwise) { warning("W016", this, this.id); } @@ -2718,7 +2930,7 @@ var JSHINT = (function () { function bitwiseassignop(s) { - return assignop(s, function (left, that) { + return assignop(s, function(left, that) { if (state.option.bitwise) { warning("W016", that, that.id); } @@ -2742,7 +2954,7 @@ var JSHINT = (function () { function suffix(s) { var x = symbol(s, 150); - x.led = function (left) { + x.led = function(left) { if (state.option.plusplus) { warning("W016", this, this.id); } else if ((!left.identifier || isReserved(left)) && left.id !== "." && left.id !== "[") { @@ -2758,9 +2970,8 @@ var JSHINT = (function () { // fnparam means that this identifier is being defined as a function // argument (see identifier()) // prop means that this identifier is that of an object property - // exported means that the identifier is part of a valid ES6 `export` declaration - function optionalidentifier(fnparam, prop, preserve, exported) { + function optionalidentifier(fnparam, prop, preserve) { if (!state.tokens.next.identifier) { return; } @@ -2772,10 +2983,6 @@ var JSHINT = (function () { var curr = state.tokens.curr; var val = state.tokens.curr.value; - if (exported) { - state.tokens.curr.exported = true; - } - if (!isReserved(curr)) { return val; } @@ -2797,9 +3004,8 @@ var JSHINT = (function () { // fnparam means that this identifier is being defined as a function // argument // prop means that this identifier is that of an object property - // `exported` means that the identifier token should be exported. - function identifier(fnparam, prop, exported) { - var i = optionalidentifier(fnparam, prop, false, exported); + function identifier(fnparam, prop) { + var i = optionalidentifier(fnparam, prop, false); if (i) { return i; } @@ -2809,6 +3015,21 @@ var JSHINT = (function () { if (!state.option.esnext) { warning("W119", state.tokens.next, "spread/rest operator"); } + advance(); + + if (checkPunctuators(state.tokens.next, ["..."])) { + warning("E024", state.tokens.next, "..."); + while (checkPunctuators(state.tokens.next, ["..."])) { + advance(); + } + } + + if (!state.tokens.next.identifier) { + warning("E024", state.tokens.curr, "..."); + return; + } + + return identifier(fnparam, prop); } else { error("E030", state.tokens.next, state.tokens.next.value); @@ -2853,12 +3074,14 @@ var JSHINT = (function () { function parseFinalSemicolon() { if (state.tokens.next.id !== ";") { + // don't complain about unclosed templates / strings + if (state.tokens.next.isUnclosed) return advance(); if (!state.option.asi) { // If this is the last statement in a block that ends on // the same line *and* option lastsemic is on, ignore the warning. // Otherwise, complain about missing semicolon. if (!state.option.lastsemic || state.tokens.next.id !== "}" || - state.tokens.next.line !== state.tokens.curr.line) { + startLine(state.tokens.next) !== state.tokens.curr.line) { warningAt("W033", state.tokens.curr.line, state.tokens.curr.character); } } @@ -2868,7 +3091,6 @@ var JSHINT = (function () { } function statement() { - var values; var i = indent, r, s = scope, t = state.tokens.next; if (t.id === ";") { @@ -2905,22 +3127,6 @@ var JSHINT = (function () { } } - // detect a destructuring assignment - if (_.has(["[", "{"], t.value)) { - if (lookupBlockType().isDestAssign) { - if (!state.option.inESNext()) { - warning("W104", state.tokens.curr, "destructuring expression"); - } - values = destructuringExpression(); - values.forEach(function (tok) { - isundef(funct, "W117", tok.token, tok.id); - }); - advance("="); - destructuringExpressionMatch(values, expression(10, true)); - advance(";"); - return; - } - } if (t.identifier && !res && peek().id === ":") { advance(); advance(":"); @@ -3222,11 +3428,11 @@ var JSHINT = (function () { // Build the syntax table by declaring the syntactic elements of the language. - type("(number)", function () { + type("(number)", function() { return this; }); - type("(string)", function () { + type("(string)", function() { return this; }); @@ -3235,7 +3441,7 @@ var JSHINT = (function () { lbp: 0, identifier: true, - nud: function () { + nud: function() { var v = this.value; var s = scope[v]; var f; @@ -3359,27 +3565,44 @@ var JSHINT = (function () { return this; }, - led: function () { + led: function() { error("E033", state.tokens.next, state.tokens.next.value); } }; - state.syntax["(template)"] = { - type: "(template)", + var baseTemplateSyntax = { lbp: 0, identifier: false, - fud: doTemplateLiteral + template: true, }; + state.syntax["(template)"] = _.extend({ + type: "(template)", + nud: doTemplateLiteral, + led: doTemplateLiteral, + noSubst: false + }, baseTemplateSyntax); - type("(template middle)", function () { - return this; - }); + state.syntax["(template middle)"] = _.extend({ + type: "(template middle)", + middle: true, + noSubst: false + }, baseTemplateSyntax); - type("(template tail)", function () { - return this; - }); + state.syntax["(template tail)"] = _.extend({ + type: "(template tail)", + tail: true, + noSubst: false + }, baseTemplateSyntax); - type("(regexp)", function () { + state.syntax["(no subst template)"] = _.extend({ + type: "(template)", + nud: doTemplateLiteral, + led: doTemplateLiteral, + noSubst: true, + tail: true // mark as tail, since it's always the last component + }, baseTemplateSyntax); + + type("(regexp)", function() { return this; }); @@ -3403,7 +3626,7 @@ var JSHINT = (function () { reserve("catch"); reserve("default").reach = true; reserve("finally"); - reservevar("arguments", function (x) { + reservevar("arguments", function(x) { if (state.directive["use strict"] && funct["(global)"]) { warning("E008", x); } @@ -3412,7 +3635,7 @@ var JSHINT = (function () { reservevar("false"); reservevar("Infinity"); reservevar("null"); - reservevar("this", function (x) { + reservevar("this", function(x) { if (state.directive["use strict"] && !isMethod() && !state.option.validthis && ((funct["(statement)"] && funct["(name)"].charAt(0) > "Z") || funct["(global)"])) { @@ -3426,7 +3649,7 @@ var JSHINT = (function () { assignop("+=", "assignadd", 20); assignop("-=", "assignsub", 20); assignop("*=", "assignmult", 20); - assignop("/=", "assigndiv", 20).nud = function () { + assignop("/=", "assigndiv", 20).nud = function() { error("E014"); }; assignop("%=", "assignmod", 20); @@ -3437,14 +3660,19 @@ var JSHINT = (function () { bitwiseassignop("<<="); bitwiseassignop(">>="); bitwiseassignop(">>>="); - infix(",", function (left, that) { + infix(",", function(left, that) { var expr; that.exprs = [left]; - if (!comma({peek: true})) { + + if (state.option.nocomma) { + warning("W127"); + } + + if (!comma({ peek: true })) { return that; } while (true) { - if (!(expr = expression(10))) { + if (!(expr = expression(10))) { break; } that.exprs.push(expr); @@ -3455,7 +3683,7 @@ var JSHINT = (function () { return that; }, 10, true); - infix("?", function (left, that) { + infix("?", function(left, that) { increaseComplexityCount(); that.left = left; that.right = expression(10); @@ -3465,7 +3693,7 @@ var JSHINT = (function () { }, 30); var orPrecendence = 40; - infix("||", function (left, that) { + infix("||", function(left, that) { increaseComplexityCount(); that.left = left; that.right = expression(orPrecendence); @@ -3475,7 +3703,7 @@ var JSHINT = (function () { bitwise("|", "bitor", 70); bitwise("^", "bitxor", 80); bitwise("&", "bitand", 90); - relation("==", function (left, right) { + relation("==", function(left, right) { var eqnull = state.option.eqnull && (left.value === "null" || right.value === "null"); switch (true) { @@ -3499,7 +3727,7 @@ var JSHINT = (function () { return this; }); - relation("===", function (left, right) { + relation("===", function(left, right) { if (isTypoTypeof(right, left)) { warning("W122", this, right.value); } else if (isTypoTypeof(left, right)) { @@ -3507,7 +3735,7 @@ var JSHINT = (function () { } return this; }); - relation("!=", function (left, right) { + relation("!=", function(left, right) { var eqnull = state.option.eqnull && (left.value === "null" || right.value === "null"); @@ -3525,7 +3753,7 @@ var JSHINT = (function () { } return this; }); - relation("!==", function (left, right) { + relation("!==", function(left, right) { if (isTypoTypeof(right, left)) { warning("W122", this, right.value); } else if (isTypoTypeof(left, right)) { @@ -3542,8 +3770,11 @@ var JSHINT = (function () { bitwise(">>>", "shiftrightunsigned", 120); infix("in", "in", 120); infix("instanceof", "instanceof", 120); - infix("+", function (left, that) { - var right = expression(130); + infix("+", function(left, that) { + var right; + that.left = left; + that.right = right = expression(130); + if (left && right && left.id === "(string)" && right.id === "(string)") { left.value += right.value; left.character = right.character; @@ -3552,18 +3783,17 @@ var JSHINT = (function () { } return left; } - that.left = left; - that.right = right; + return that; }, 130); prefix("+", "num"); - prefix("+++", function () { + prefix("+++", function() { warning("W007"); - this.right = expression(150); this.arity = "unary"; + this.right = expression(150); return this; }); - infix("+++", function (left) { + infix("+++", function(left) { warning("W007"); this.left = left; this.right = expression(130); @@ -3571,13 +3801,13 @@ var JSHINT = (function () { }, 130); infix("-", "sub", 130); prefix("-", "neg"); - prefix("---", function () { + prefix("---", function() { warning("W006"); - this.right = expression(150); this.arity = "unary"; + this.right = expression(150); return this; }); - infix("---", function (left) { + infix("---", function(left) { warning("W006"); this.left = left; this.right = expression(130); @@ -3594,9 +3824,13 @@ var JSHINT = (function () { suffix("--"); prefix("--", "predec"); state.syntax["--"].exps = true; - prefix("delete", function () { + prefix("delete", function() { var p = expression(10); - if (!p || (p.id !== "." && p.id !== "[")) { + if (!p) { + return this; + } + + if (p.id !== "." && p.id !== "[") { warning("W051"); } this.first = p; @@ -3609,28 +3843,64 @@ var JSHINT = (function () { return this; }).exps = true; - prefix("~", function () { + prefix("~", function() { if (state.option.bitwise) { - warning("W052", this, "~"); + warning("W016", this, "~"); } + this.arity = "unary"; expression(150); return this; }); - prefix("...", function () { + prefix("...", function() { if (!state.option.esnext) { warning("W119", this, "spread/rest operator"); } - if (!state.tokens.next.identifier) { + + // TODO: Allow all AssignmentExpression + // once parsing permits. + // + // How to handle eg. number, boolean when the built-in + // prototype of may have an @@iterator definition? + // + // Number.prototype[Symbol.iterator] = function * () { + // yield this.valueOf(); + // }; + // + // var a = [ ...1 ]; + // console.log(a); // [1]; + // + // for (let n of [...10]) { + // console.log(n); + // } + // // 10 + // + // + // Boolean.prototype[Symbol.iterator] = function * () { + // yield this.valueOf(); + // }; + // + // var a = [ ...true ]; + // console.log(a); // [true]; + // + // for (let n of [...false]) { + // console.log(n); + // } + // // false + // + if (!state.tokens.next.identifier && + state.tokens.next.type !== "(string)" && + !checkPunctuators(state.tokens.next, ["[", "("])) { + error("E030", state.tokens.next, state.tokens.next.value); } expression(150); return this; }); - prefix("!", function () { - this.right = expression(150); + prefix("!", function() { this.arity = "unary"; + this.right = expression(150); if (!this.right) { // '!' followed by nothing? Give up. quit("E041", this.line || 0); @@ -3642,7 +3912,7 @@ var JSHINT = (function () { return this; }); - prefix("typeof", (function () { + prefix("typeof", (function() { var p = expression(150); this.first = p; @@ -3653,7 +3923,7 @@ var JSHINT = (function () { } return this; })); - prefix("new", function () { + prefix("new", function() { var c = expression(155), i; if (c && c.id !== "function") { if (c.identifier) { @@ -3707,7 +3977,7 @@ var JSHINT = (function () { prefix("void").exps = true; - infix(".", function (left, that) { + infix(".", function(left, that) { var m = identifier(false, true); if (typeof m === "string") { @@ -3732,13 +4002,15 @@ var JSHINT = (function () { } if (!state.option.evil && (m === "eval" || m === "execScript")) { - warning("W061"); + if (isGlobalEval(left, state, funct)) { + warning("W061"); + } } return that; }, 160, true); - infix("(", function (left, that) { + infix("(", function(left, that) { if (state.option.immed && left && !left.immed && left.id === "function") { warning("W062"); } @@ -3812,11 +4084,13 @@ var JSHINT = (function () { return that; }, 155, true).exps = true; - prefix("(", function () { - var bracket, brackets = []; + prefix("(", function() { var pn = state.tokens.next, pn1, i = -1; - var ret, triggerFnExpr; + var ret, triggerFnExpr, first, last; var parens = 1; + var opening = state.tokens.curr; + var preceeding = state.tokens.prev; + var isNecessary = !state.option.singleGroups; do { if (pn.value === "(") { @@ -3828,8 +4102,7 @@ var JSHINT = (function () { i += 1; pn1 = pn; pn = peek(i); - } while (!(parens === 0 && pn1.value === ")") && - pn.value !== ";" && pn.type !== "(end)"); + } while (!(parens === 0 && pn1.value === ")") && pn.value !== ";" && pn.type !== "(end)"); if (state.tokens.next.id === "function") { triggerFnExpr = state.tokens.next.immed = true; @@ -3839,26 +4112,23 @@ var JSHINT = (function () { // current token marks the beginning of a "fat arrow" function and parsing // should proceed accordingly. if (pn.value === "=>") { - return doFunction(null, null, null, { parsedParen: true }); + return doFunction({ type: "arrow", parsedOpening: true }); } var exprs = []; if (state.tokens.next.id !== ")") { for (;;) { - if (pn.value === "=>" && _.contains(["{", "["], state.tokens.next.value)) { - bracket = state.tokens.next; - bracket.left = destructuringExpression(); - brackets.push(bracket); - for (var t in bracket.left) { - exprs.push(bracket.left[t].token); - } - } else { - exprs.push(expression(10)); - } + exprs.push(expression(10)); + if (state.tokens.next.id !== ",") { break; } + + if (state.option.nocomma) { + warning("W127"); + } + comma(); } } @@ -3871,32 +4141,64 @@ var JSHINT = (function () { } } - if (state.tokens.next.value === "=>") { - return exprs; - } if (!exprs.length) { return; } if (exprs.length > 1) { ret = Object.create(state.syntax[","]); ret.exprs = exprs; + + first = exprs[0]; + last = exprs[exprs.length - 1]; + + if (!isNecessary) { + isNecessary = preceeding.assign || preceeding.delim; + } } else { - ret = exprs[0]; + ret = first = last = exprs[0]; + + if (!isNecessary) { + isNecessary = + // Used to distinguish from an ExpressionStatement which may not + // begin with the `{` and `function` tokens + (opening.beginsStmt && (ret.id === "{" || triggerFnExpr || isFunctor(ret))) || + // Used as the return value of a single-statement arrow function + (ret.id === "{" && preceeding.id === "=>") || + // Used to prevent left-to-right application of adjacent addition + // operators (the order of which effect type) + (first.id === "+" && preceeding.id === "+"); + } } + if (ret) { + // The operator may be necessary to override the default binding power of + // neighboring operators (whenever there is an operator in use within the + // first expression *or* the current group contains multiple expressions) + if (!isNecessary && (first.left || ret.exprs)) { + isNecessary = + (!isBeginOfExpr(preceeding) && first.lbp < preceeding.lbp) || + (!isEndOfExpr() && last.lbp < state.tokens.next.lbp); + } + + if (!isNecessary) { + warning("W126", opening); + } + ret.paren = true; - ret.triggerFnExpr = triggerFnExpr; } + return ret; }); application("=>"); - infix("[", function (left, that) { + infix("[", function(left, that) { var e = expression(10), s; if (e && e.type === "(string)") { if (!state.option.evil && (e.value === "eval" || e.value === "execScript")) { - warning("W061", that); + if (isGlobalEval(left, state, funct)) { + warning("W061"); + } } countMember(e.value); @@ -3971,7 +4273,7 @@ var JSHINT = (function () { return res; } - prefix("[", function () { + prefix("[", function() { var blocktype = lookupBlockType(); if (blocktype.isCompArray) { if (!state.option.inESNext()) { @@ -3981,7 +4283,7 @@ var JSHINT = (function () { } else if (blocktype.isDestAssign && !state.option.inESNext()) { warning("W104", state.tokens.curr, "destructuring assignment"); } - var b = state.tokens.curr.line !== state.tokens.next.line; + var b = state.tokens.curr.line !== startLine(state.tokens.next); this.first = []; if (b) { indent += state.option.indent; @@ -4000,7 +4302,7 @@ var JSHINT = (function () { warning("W128"); do { advance(","); - } while(state.tokens.next.id === ","); + } while (state.tokens.next.id === ","); continue; } } @@ -4075,14 +4377,23 @@ var JSHINT = (function () { return id; } - function functionparams(fatarrow) { + /** + * @param {Object} [options] + * @param {token} [options.loneArg] The argument to the function in cases + * where it was defined using the + * single-argument shorthand. + * @param {bool} [options.parsedOpening] Whether the opening parenthesis has + * already been parsed. + */ + function functionparams(options) { var next; var params = []; var ident; var tokens = []; var t; var pastDefault = false; - var loneArg = fatarrow && fatarrow.loneArg; + var pastRest = false; + var loneArg = options && options.loneArg; if (loneArg && loneArg.identifier === true) { addlabel(loneArg.value, { type: "unused", token: loneArg }); @@ -4091,7 +4402,7 @@ var JSHINT = (function () { next = state.tokens.next; - if (!fatarrow || !fatarrow.parsedParen) { + if (!options || !options.parsedOpening) { advance("("); } @@ -4110,18 +4421,16 @@ var JSHINT = (function () { addlabel(t.id, { type: "unused", token: t.token }); } } - } else if (state.tokens.next.value === "...") { - if (!state.option.esnext) { - warning("W119", state.tokens.next, "spread/rest operator"); - } - advance("..."); - ident = identifier(true); - params.push(ident); - addlabel(ident, { type: "unused", token: state.tokens.curr }); } else { + if (checkPunctuators(state.tokens.next, ["..."])) pastRest = true; ident = identifier(true); - params.push(ident); - addlabel(ident, { type: "unused", token: state.tokens.curr }); + if (ident) { + params.push(ident); + addlabel(ident, { type: "unused", token: state.tokens.curr }); + } else { + // Skip invalid parameter. + while (!checkPunctuators(state.tokens.next, [",", ")"])) advance(); + } } // it is a syntax error to have a regular argument after a default argument @@ -4139,6 +4448,9 @@ var JSHINT = (function () { expression(10); } if (state.tokens.next.id === ",") { + if (pastRest) { + warning("W131", state.tokens.next); + } comma(); } else { advance(")", next); @@ -4203,39 +4515,76 @@ var JSHINT = (function () { return funct; } - function doTemplateLiteral() { - while (state.tokens.next.type !== "(template tail)" && state.tokens.next.id !== "(end)") { - advance(); - if (state.tokens.next.type === "(template tail)") { - break; - } else if (state.tokens.next.type !== "(template middle)" && - state.tokens.next.type !== "(end)") { - expression(10); // should probably have different rbp? + function isFunctor(token) { + return "(scope)" in token; + } + + function doTemplateLiteral(left) { + // ASSERT: this.type === "(template)" + // jshint validthis: true + var ctx = this.context; + var noSubst = this.noSubst; + var depth = this.depth; + + if (!noSubst) { + while (!end() && state.tokens.next.id !== "(end)") { + if (!state.tokens.next.template || state.tokens.next.depth > depth) { + expression(0); // should probably have different rbp? + } else { + // skip template start / middle + advance(); + } } } + return { id: "(template)", - type: "(template)" + type: "(template)", + tag: left }; + + function end() { + if (state.tokens.curr.template && state.tokens.curr.tail && + state.tokens.curr.context === ctx) return true; + var complete = (state.tokens.next.template && state.tokens.next.tail && + state.tokens.next.context === ctx); + if (complete) advance(); + return complete || state.tokens.next.isUnclosed; + } } /** - * @param {Object} [fatarrow] In the case that the function being parsed - * takes the "fat arrow" form, this object will - * contain details about the in-progress parsing - * operation. - * @param {Token} [fatarrow.loneArg] The argument to the function in cases - * where it was defined using the single- - * argument shorthand. - * @param {bool} [fatarrow.parsedParen] Whether the opening parenthesis has - * already been parsed. + * @param {Object} [options] + * @param {token} [options.name] The identifier belonging to the function (if + * any) + * @param {boolean} [options.statement] The statement that triggered creation + * of the current function. + * @param {string} [options.type] If specified, either "generator" or "arrow" + * @param {token} [options.loneArg] The argument to the function in cases + * where it was defined using the + * single-argument shorthand + * @param {bool} [options.parsedOpening] Whether the opening parenthesis has + * already been parsed + * @param {token} [options.classExprBinding] Define a function with this + * identifier in the new function's + * scope, mimicking the bahavior of + * class expression names within + * the body of member functions. */ - function doFunction(name, statement, generator, fatarrow) { - var f; + function doFunction(options) { + var f, name, statement, classExprBinding, isGenerator, isArrow; var oldOption = state.option; var oldIgnored = state.ignored; var oldScope = scope; + if (options) { + name = options.name; + statement = options.statement; + classExprBinding = options.classExprBinding; + isGenerator = options.type === "generator"; + isArrow = options.type === "arrow"; + } + state.option = Object.create(state.option); state.ignored = Object.create(state.ignored); scope = Object.create(scope); @@ -4243,7 +4592,7 @@ var JSHINT = (function () { funct = functor(name || state.nameStack.infer(), state.tokens.next, scope, { "(statement)": statement, "(context)": funct, - "(generator)": generator ? true : null + "(generator)": isGenerator }); f = funct; @@ -4255,22 +4604,26 @@ var JSHINT = (function () { addlabel(name, { type: "function" }); } - funct["(params)"] = functionparams(fatarrow); + if (classExprBinding) { + addlabel(classExprBinding, { type: "function" }); + } + + funct["(params)"] = functionparams(options); funct["(metrics)"].verifyMaxParametersPerFunction(funct["(params)"]); - if (fatarrow) { + if (isArrow) { if (!state.option.esnext) { warning("W119", state.tokens.curr, "arrow function syntax (=>)"); } - if (!fatarrow.loneArg) { + if (!options.loneArg) { advance("=>"); } } - block(false, true, true, !!fatarrow); + block(false, true, true, isArrow); - if (!state.option.noyield && generator && + if (!state.option.noyield && isGenerator && funct["(generator)"] !== "yielded") { warning("W124", state.tokens.curr); } @@ -4285,7 +4638,7 @@ var JSHINT = (function () { funct["(last)"] = state.tokens.curr.line; funct["(lastcharacter)"] = state.tokens.curr.character; - _.map(Object.keys(funct), function (key) { + _.map(Object.keys(funct), function(key) { if (key[0] === "(") return; funct["(blockscope)"].unshadow(key); }); @@ -4301,14 +4654,14 @@ var JSHINT = (function () { nestedBlockDepth: -1, ComplexityCount: 1, - verifyMaxStatementsPerFunction: function () { + verifyMaxStatementsPerFunction: function() { if (state.option.maxstatements && this.statementCount > state.option.maxstatements) { warning("W071", functionStartToken, this.statementCount); } }, - verifyMaxParametersPerFunction: function (params) { + verifyMaxParametersPerFunction: function(params) { params = params || []; if (state.option.maxparams && params.length > state.option.maxparams) { @@ -4316,7 +4669,7 @@ var JSHINT = (function () { } }, - verifyMaxNestedBlockDepthPerFunction: function () { + verifyMaxNestedBlockDepthPerFunction: function() { if (state.option.maxdepth && this.nestedBlockDepth > 0 && this.nestedBlockDepth === state.option.maxdepth + 1) { @@ -4324,7 +4677,7 @@ var JSHINT = (function () { } }, - verifyMaxComplexityPerFunction: function () { + verifyMaxComplexityPerFunction: function() { var max = state.option.maxcomplexity; var cc = this.ComplexityCount; if (max && cc > max) { @@ -4382,12 +4735,12 @@ var JSHINT = (function () { } } - (function (x) { - x.nud = function () { + (function(x) { + x.nud = function() { var b, f, i, p, t, g, nextVal; var props = {}; // All properties, including accessors - b = state.tokens.curr.line !== state.tokens.next.line; + b = state.tokens.curr.line !== startLine(state.tokens.next); if (b) { indent += state.option.indent; if (state.tokens.next.from === indent + state.option.indent) { @@ -4434,7 +4787,6 @@ var JSHINT = (function () { warning("W077", t, i); } } else { - g = false; if (state.tokens.next.value === "*" && state.tokens.next.type === "(punctuator)") { if (!state.option.inESNext()) { warning("W104", state.tokens.next, "generator functions"); @@ -4469,7 +4821,7 @@ var JSHINT = (function () { if (!state.option.inESNext()) { warning("W104", state.tokens.curr, "concise methods"); } - doFunction(null, undefined, g); + doFunction({ type: g ? "generator" : null }); } else { advance(":"); expression(10); @@ -4499,7 +4851,7 @@ var JSHINT = (function () { return this; }; - x.fud = function () { + x.fud = function() { error("E036", state.tokens.curr); }; }(delim("{"))); @@ -4510,47 +4862,68 @@ var JSHINT = (function () { if (!state.option.inESNext()) { warning("W104", state.tokens.curr, "destructuring expression"); } - var nextInnerDE = function () { + var nextInnerDE = function() { var ident; - if (_.contains(["[", "{"], state.tokens.next.value)) { + if (checkPunctuators(state.tokens.next, ["[", "{"])) { ids = destructuringExpression(); for (var id in ids) { id = ids[id]; identifiers.push({ id: id.id, token: id.token }); } - } else if (state.tokens.next.value === ",") { + } else if (checkPunctuators(state.tokens.next, [","])) { identifiers.push({ id: null, token: state.tokens.curr }); - } else if (state.tokens.next.value === "(") { + } else if (checkPunctuators(state.tokens.next, ["("])) { advance("("); nextInnerDE(); advance(")"); } else { + var is_rest = checkPunctuators(state.tokens.next, ["..."]); ident = identifier(); if (ident) identifiers.push({ id: ident, token: state.tokens.curr }); + return is_rest; } + return false; }; - if (state.tokens.next.value === "[") { + if (checkPunctuators(state.tokens.next, ["["])) { advance("["); - nextInnerDE(); - while (state.tokens.next.value !== "]") { + var element_after_rest = false; + if (nextInnerDE() && checkPunctuators(state.tokens.next, [","]) && + !element_after_rest) { + warning("W130", state.tokens.next); + element_after_rest = true; + } + while (!checkPunctuators(state.tokens.next, ["]"])) { advance(","); - nextInnerDE(); + if (checkPunctuators(state.tokens.next, ["]"])) { + // Trailing comma + break; + } + if (nextInnerDE() && checkPunctuators(state.tokens.next, [","]) && + !element_after_rest) { + warning("W130", state.tokens.next); + element_after_rest = true; + } } advance("]"); - } else if (state.tokens.next.value === "{") { + } else if (checkPunctuators(state.tokens.next, ["{"])) { advance("{"); id = identifier(); - if (state.tokens.next.value === ":") { + if (checkPunctuators(state.tokens.next, [":"])) { advance(":"); nextInnerDE(); } else { identifiers.push({ id: id, token: state.tokens.curr }); } - while (state.tokens.next.value !== "}") { + while (!checkPunctuators(state.tokens.next, ["}"])) { advance(","); + if (checkPunctuators(state.tokens.next, ["}"])) { + // Trailing comma + // ObjectBindingPattern: { BindingPropertyList , } + break; + } id = identifier(); - if (state.tokens.next.value === ":") { + if (checkPunctuators(state.tokens.next, [":"])) { advance(":"); nextInnerDE(); } else { @@ -4568,7 +4941,7 @@ var JSHINT = (function () { if (!first) return; - _.zip(tokens, Array.isArray(first) ? first : [ first ]).forEach(function (val) { + _.zip(tokens, Array.isArray(first) ? first : [ first ]).forEach(function(val) { var token = val[0]; var value = val[1]; @@ -4579,7 +4952,9 @@ var JSHINT = (function () { }); } - var conststatement = stmt("const", function (prefix) { + var conststatement = stmt("const", function(context) { + var prefix = context && context.prefix; + var inexport = context && context.inexport; var tokens; var value; var lone; // State variable to know if it is a lone identifier, or a destructuring statement. @@ -4596,6 +4971,10 @@ var JSHINT = (function () { } else { tokens = [ { id: identifier(), token: state.tokens.curr } ]; lone = true; + if (inexport) { + exported[state.tokens.curr.value] = true; + state.tokens.curr.exported = true; + } } for (var t in tokens) { if (tokens.hasOwnProperty(t)) { @@ -4647,9 +5026,11 @@ var JSHINT = (function () { }); conststatement.exps = true; - var varstatement = stmt("var", function (prefix) { + var varstatement = stmt("var", function(context) { // JavaScript does not have block scope. It only has function scope. So, // declaring a variable in a block can have unexpected consequences. + var prefix = context && context.prefix; + var inexport = context && context.inexport; var tokens, lone, value; this.first = []; @@ -4661,6 +5042,10 @@ var JSHINT = (function () { } else { tokens = [ { id: identifier(), token: state.tokens.curr } ]; lone = true; + if (inexport) { + exported[state.tokens.curr.value] = true; + state.tokens.curr.exported = true; + } } for (var t in tokens) { if (tokens.hasOwnProperty(t)) { @@ -4668,8 +5053,15 @@ var JSHINT = (function () { if (state.option.inESNext() && funct[t.id] === "const") { warning("E011", null, t.id); } - if (funct["(global)"] && predefined[t.id] === false) { - warning("W079", t.token, t.id); + if (funct["(global)"]) { + if (predefined[t.id] === false) { + warning("W079", t.token, t.id); + } else if (state.option.futurehostile === false) { + if ((!state.option.inES5() && vars.ecmaIdentifiers[5][t.id] === false) || + (!state.option.inESNext() && vars.ecmaIdentifiers[6][t.id] === false)) { + warning("W129", t.token, t.id); + } + } } if (t.id) { addlabel(t.id, { type: "unused", token: t.token }); @@ -4711,7 +5103,9 @@ var JSHINT = (function () { }); varstatement.exps = true; - var letstatement = stmt("let", function (prefix) { + var letstatement = stmt("let", function(context) { + var prefix = context && context.prefix; + var inexport = context && context.inexport; var tokens, lone, value, letblock; if (!state.option.inESNext()) { @@ -4738,6 +5132,10 @@ var JSHINT = (function () { } else { tokens = [ { id: identifier(), token: state.tokens.curr.value } ]; lone = true; + if (inexport) { + exported[state.tokens.curr.value] = true; + state.tokens.curr.exported = true; + } } for (var t in tokens) { if (tokens.hasOwnProperty(t)) { @@ -4792,22 +5190,24 @@ var JSHINT = (function () { }); letstatement.exps = true; - blockstmt("class", function () { + blockstmt("class", function() { return classdef.call(this, true); }); - function classdef(stmt) { + function classdef(isStatement) { + /*jshint validthis:true */ if (!state.option.inESNext()) { warning("W104", state.tokens.curr, "class"); } - if (stmt) { + if (isStatement) { // BindingIdentifier this.name = identifier(); addlabel(this.name, { type: "unused", token: state.tokens.curr }); } else if (state.tokens.next.identifier && state.tokens.next.value !== "extends") { // BindingIdentifier(opt) this.name = identifier(); + this.namedExpr = true; } else { this.name = state.nameStack.infer(); } @@ -4836,6 +5236,7 @@ var JSHINT = (function () { function classbody(c) { var name; var isStatic; + var isGenerator; var getset; var props = {}; var staticProps = {}; @@ -4843,7 +5244,13 @@ var JSHINT = (function () { for (var i = 0; state.tokens.next.id !== "}"; ++i) { name = state.tokens.next; isStatic = false; + isGenerator = false; getset = null; + if (name.id === "*") { + isGenerator = true; + advance("*"); + name = state.tokens.next; + } if (name.id === "[") { name = computedPropertyName(); } else if (isPropertyName(name)) { @@ -4851,6 +5258,10 @@ var JSHINT = (function () { advance(); computed = false; if (name.identifier && name.value === "static") { + if (checkPunctuators(state.tokens.next, ["*"])) { + isGenerator = true; + advance("*"); + } if (isPropertyName(state.tokens.next) || state.tokens.next.id === "[") { computed = state.tokens.next.id === "["; isStatic = true; @@ -4885,7 +5296,7 @@ var JSHINT = (function () { advance(); } if (state.tokens.next.value !== "(") { - doFunction(undefined, c, false, null); + doFunction({ statement: c }); } } @@ -4913,13 +5324,17 @@ var JSHINT = (function () { propertyName(name); - doFunction(null, c, false, null); + doFunction({ + statement: c, + type: isGenerator ? "generator" : null, + classExprBinding: c.namedExpr ? c.name : null + }); } checkProperties(props); } - blockstmt("function", function () { + blockstmt("function", function() { var generator = false; if (state.tokens.next.value === "*") { advance("*"); @@ -4944,14 +5359,18 @@ var JSHINT = (function () { } addlabel(i, { type: "unction", token: state.tokens.curr }); - doFunction(i, { statement: true }, generator); + doFunction({ + name: i, + statement: this, + type: generator ? "generator" : null + }); if (state.tokens.next.id === "(" && state.tokens.next.line === state.tokens.curr.line) { error("E039"); } return this; }); - prefix("function", function () { + prefix("function", function() { var generator = false; if (state.tokens.next.value === "*") { @@ -4963,7 +5382,7 @@ var JSHINT = (function () { } var i = optionalidentifier(); - var fn = doFunction(i, undefined, generator); + var fn = doFunction({ name: i, type: generator ? "generator" : null }); function isVariable(name) { return name[0] !== "("; } function isLocal(name) { return fn[name] === "var"; } @@ -4972,14 +5391,14 @@ var JSHINT = (function () { // If the function we just parsed accesses any non-local variables // trigger a warning. Otherwise, the function is safe even within // a loop. - if (_.some(fn, function (val, name) { return isVariable(name) && !isLocal(name); })) { + if (_.some(fn, function(val, name) { return isVariable(name) && !isLocal(name); })) { warning("W083"); } } return this; }); - blockstmt("if", function () { + blockstmt("if", function() { var t = state.tokens.next; increaseComplexityCount(); state.condition = true; @@ -5023,7 +5442,7 @@ var JSHINT = (function () { return this; }); - blockstmt("try", function () { + blockstmt("try", function() { var b; function doCatch() { @@ -5101,7 +5520,7 @@ var JSHINT = (function () { return this; }); - blockstmt("while", function () { + blockstmt("while", function() { var t = state.tokens.next; funct["(breakage)"] += 1; funct["(loopage)"] += 1; @@ -5115,7 +5534,7 @@ var JSHINT = (function () { return this; }).labelled = true; - blockstmt("with", function () { + blockstmt("with", function() { var t = state.tokens.next; if (state.directive["use strict"]) { error("E010", state.tokens.curr); @@ -5131,7 +5550,7 @@ var JSHINT = (function () { return this; }); - blockstmt("switch", function () { + blockstmt("switch", function() { var t = state.tokens.next; var g = false; var noindent = false; @@ -5242,15 +5661,15 @@ var JSHINT = (function () { } }).labelled = true; - stmt("debugger", function () { + stmt("debugger", function() { if (!state.option.debug) { warning("W087", this); } return this; }).exps = true; - (function () { - var x = stmt("do", function () { + (function() { + var x = stmt("do", function() { funct["(breakage)"] += 1; funct["(loopage)"] += 1; increaseComplexityCount(); @@ -5269,7 +5688,7 @@ var JSHINT = (function () { x.exps = true; }()); - blockstmt("for", function () { + blockstmt("for", function() { var s, t = state.tokens.next; var letscope = false; var foreachtok = null; @@ -5294,8 +5713,7 @@ var JSHINT = (function () { do { nextop = peek(i); ++i; - } while (!_.contains(inof, nextop.value) && nextop.value !== ";" && - nextop.type !== "(end)"); + } while (!_.contains(inof, nextop.value) && nextop.value !== ";" && nextop.type !== "(end)"); // if we're in a for (… in|of …) statement if (_.contains(inof, nextop.value)) { @@ -5305,13 +5723,13 @@ var JSHINT = (function () { if (state.tokens.next.id === "var") { advance("var"); - state.syntax["var"].fud.call(state.syntax["var"].fud, true); + state.tokens.curr.fud({ prefix: true }); } else if (state.tokens.next.id === "let") { advance("let"); // create a new block scope letscope = true; funct["(blockscope)"].stack(); - state.syntax["let"].fud.call(state.syntax["let"].fud, true); + state.tokens.curr.fud({ prefix: true }); } else if (!state.tokens.next.identifier) { error("E030", state.tokens.next, state.tokens.next.type); advance(); @@ -5325,8 +5743,9 @@ var JSHINT = (function () { default: var ident = state.tokens.next.value; if (!funct["(blockscope)"].getlabel(ident) && - !(scope[ident] || {})[ident]) + !(scope[ident] || {})[ident]) { warning("W088", state.tokens.next, state.tokens.next.value); + } } advance(); } @@ -5377,13 +5796,13 @@ var JSHINT = (function () { if (state.tokens.next.id !== ";") { if (state.tokens.next.id === "var") { advance("var"); - state.syntax["var"].fud.call(state.syntax["var"].fud); + state.tokens.curr.fud(); } else if (state.tokens.next.id === "let") { advance("let"); // create a new block scope letscope = true; funct["(blockscope)"].stack(); - state.syntax["let"].fud.call(state.syntax["let"].fud); + state.tokens.curr.fud(); } else { for (;;) { expression(0, "for"); @@ -5427,7 +5846,7 @@ var JSHINT = (function () { }).labelled = true; - stmt("break", function () { + stmt("break", function() { var v = state.tokens.next.value; if (funct["(breakage)"] === 0) @@ -5437,7 +5856,7 @@ var JSHINT = (function () { nolinebreak(this); if (state.tokens.next.id !== ";" && !state.tokens.next.reach) { - if (state.tokens.curr.line === state.tokens.next.line) { + if (state.tokens.curr.line === startLine(state.tokens.next)) { if (funct[v] !== "label") { warning("W090", state.tokens.next, v); } else if (scope[v] !== funct) { @@ -5454,7 +5873,7 @@ var JSHINT = (function () { }).exps = true; - stmt("continue", function () { + stmt("continue", function() { var v = state.tokens.next.value; if (funct["(breakage)"] === 0) @@ -5464,7 +5883,7 @@ var JSHINT = (function () { nolinebreak(this); if (state.tokens.next.id !== ";" && !state.tokens.next.reach) { - if (state.tokens.curr.line === state.tokens.next.line) { + if (state.tokens.curr.line === startLine(state.tokens.next)) { if (funct[v] !== "label") { warning("W090", state.tokens.next, v); } else if (scope[v] !== funct) { @@ -5483,8 +5902,8 @@ var JSHINT = (function () { }).exps = true; - stmt("return", function () { - if (this.line === state.tokens.next.line) { + stmt("return", function() { + if (this.line === startLine(state.tokens.next)) { if (state.tokens.next.id !== ";" && !state.tokens.next.reach) { this.first = expression(0); @@ -5506,10 +5925,10 @@ var JSHINT = (function () { return this; }).exps = true; - (function (x) { + (function(x) { x.exps = true; x.lbp = 25; - }(prefix("yield", function () { + }(prefix("yield", function() { var prev = state.tokens.prev; if (state.option.inESNext(true) && !funct["(generator)"]) { // If it's a yield within a catch clause inside a generator then that's ok @@ -5527,7 +5946,7 @@ var JSHINT = (function () { advance("*"); } - if (this.line === state.tokens.next.line || !state.option.inMoz(true)) { + if (this.line === startLine(state.tokens.next) || !state.option.inMoz(true)) { if (delegatingYield || (state.tokens.next.id !== ";" && !state.tokens.next.reach && state.tokens.next.nud)) { @@ -5551,7 +5970,7 @@ var JSHINT = (function () { }))); - stmt("throw", function () { + stmt("throw", function() { nolinebreak(this); this.first = expression(20); @@ -5560,7 +5979,7 @@ var JSHINT = (function () { return this; }).exps = true; - stmt("import", function () { + stmt("import", function() { if (!state.option.inESNext()) { warning("W119", state.tokens.curr, "import"); } @@ -5575,7 +5994,22 @@ var JSHINT = (function () { // ImportClause :: ImportedDefaultBinding this.name = identifier(); addlabel(this.name, { type: "unused", token: state.tokens.curr }); - } else if (state.tokens.next.id === "*") { + if (state.tokens.next.value === ",") { + // ImportClause :: ImportedDefaultBinding , NameSpaceImport + // ImportClause :: ImportedDefaultBinding , NamedImports + advance(","); + // At this point, we intentionally fall through to continue matching + // either NameSpaceImport or NamedImports. + // Discussion: + // https://github.com/jshint/jshint/pull/2144#discussion_r23978406 + } else { + advance("from"); + advance("(string)"); + return this; + } + } + + if (state.tokens.next.id === "*") { // ImportClause :: NameSpaceImport advance("*"); advance("as"); @@ -5584,6 +6018,7 @@ var JSHINT = (function () { addlabel(this.name, { type: "unused", token: state.tokens.curr }); } } else { + // ImportClause :: NamedImports advance("{"); for (;;) { if (state.tokens.next.value === "}") { @@ -5621,8 +6056,11 @@ var JSHINT = (function () { return this; }).exps = true; - stmt("export", function () { + stmt("export", function() { var ok = true; + var token; + var identifier; + if (!state.option.inESNext()) { warning("W119", state.tokens.curr, "export"); ok = false; @@ -5634,6 +6072,7 @@ var JSHINT = (function () { } if (state.tokens.next.value === "*") { + // ExportDeclaration :: export * FromClause advance("*"); advance("from"); advance("(string)"); @@ -5641,23 +6080,50 @@ var JSHINT = (function () { } if (state.tokens.next.type === "default") { + // ExportDeclaration :: export default HoistableDeclaration + // ExportDeclaration :: export default ClassDeclaration state.nameStack.set(state.tokens.next); advance("default"); if (state.tokens.next.id === "function" || state.tokens.next.id === "class") { this.block = true; } - this.exportee = expression(10); + + token = peek(); + + expression(10); + + if (state.tokens.next.id === "class") { + identifier = token.name; + } else { + identifier = token.value; + } + + addlabel(identifier, { + type: "function", token: token + }); return this; } if (state.tokens.next.value === "{") { + // ExportDeclaration :: export ExportClause advance("{"); + var exportedTokens = []; for (;;) { - var id; - exported[id = identifier(false, false, ok)] = ok; - if (ok) { - funct["(blockscope)"].setExported(id); + if (!state.tokens.next.identifier) { + error("E030", state.tokens.next, state.tokens.next.value); + } + advance(); + + state.tokens.curr.exported = ok; + exportedTokens.push(state.tokens.curr); + + if (state.tokens.next.value === "as") { + advance("as"); + if (!state.tokens.next.identifier) { + error("E030", state.tokens.next, state.tokens.next.value); + } + advance(); } if (state.tokens.next.value === ",") { @@ -5670,31 +6136,43 @@ var JSHINT = (function () { break; } } + if (state.tokens.next.value === "from") { + // ExportDeclaration :: export ExportClause FromClause + advance("from"); + advance("(string)"); + } else if (ok) { + exportedTokens.forEach(function(token) { + if (!funct[token.value]) { + isundef(funct, "W117", token, token.value); + } + exported[token.value] = true; + funct["(blockscope)"].setExported(token.value); + }); + } return this; } if (state.tokens.next.id === "var") { + // ExportDeclaration :: export VariableStatement advance("var"); - exported[state.tokens.next.value] = ok; - state.tokens.next.exported = true; - state.syntax["var"].fud.call(state.syntax["var"].fud); + state.tokens.curr.fud({ inexport:true }); } else if (state.tokens.next.id === "let") { + // ExportDeclaration :: export VariableStatement advance("let"); - exported[state.tokens.next.value] = ok; - state.tokens.next.exported = true; - state.syntax["let"].fud.call(state.syntax["let"].fud); + state.tokens.curr.fud({ inexport:true }); } else if (state.tokens.next.id === "const") { + // ExportDeclaration :: export VariableStatement advance("const"); - exported[state.tokens.next.value] = ok; - state.tokens.next.exported = true; - state.syntax["const"].fud.call(state.syntax["const"].fud); + state.tokens.curr.fud({ inexport:true }); } else if (state.tokens.next.id === "function") { + // ExportDeclaration :: export Declaration this.block = true; advance("function"); exported[state.tokens.next.value] = ok; state.tokens.next.exported = true; state.syntax["function"].fud(); } else if (state.tokens.next.id === "class") { + // ExportDeclaration :: export Declaration this.block = true; advance("class"); exported[state.tokens.next.value] = ok; @@ -5741,7 +6219,7 @@ var JSHINT = (function () { // this function is used to determine whether a squarebracket or a curlybracket // expression is a comprehension array, destructuring assignment or a json value. - var lookupBlockType = function () { + var lookupBlockType = function() { var pn, pn1; var i = -1; var bracketStack = 0; @@ -5776,7 +6254,7 @@ var JSHINT = (function () { ret.isBlock = true; ret.notJson = true; } - } while (bracketStack > 0 && pn.id !== "(end)" && i < 15); + } while (bracketStack > 0 && pn.id !== "(end)"); return ret; }; @@ -5876,15 +6354,15 @@ var JSHINT = (function () { // * "define" which will define the variables local to the list comprehension // * "filter" which will help filter out values - var arrayComprehension = function () { - var CompArray = function () { + var arrayComprehension = function() { + var CompArray = function() { this.mode = "use"; this.variables = []; }; var _carrays = []; var _current; function declare(v) { - var l = _current.variables.filter(function (elt) { + var l = _current.variables.filter(function(elt) { // if it has, change its undef state if (elt.value === v) { elt.undef = false; @@ -5894,7 +6372,7 @@ var JSHINT = (function () { return l !== 0; } function use(v) { - var l = _current.variables.filter(function (elt) { + var l = _current.variables.filter(function(elt) { // and if it has been defined if (elt.value === v && !elt.undef) { if (elt.unused === true) { @@ -5906,12 +6384,12 @@ var JSHINT = (function () { // otherwise we warn about it return (l === 0); } - return {stack: function () { + return { stack: function() { _current = new CompArray(); _carrays.push(_current); }, - unstack: function () { - _current.variables.filter(function (v) { + unstack: function() { + _current.variables.filter(function(v) { if (v.unused) warning("W098", v.token, v.raw_text || v.value); if (v.undef) @@ -5920,11 +6398,11 @@ var JSHINT = (function () { _carrays.splice(-1, 1); _current = _carrays[_carrays.length - 1]; }, - setState: function (s) { + setState: function(s) { if (_.contains(["use", "define", "generate", "filter"], s)) _current.mode = s; }, - check: function (v) { + check: function(v) { if (!_current) { return; } @@ -5991,7 +6469,7 @@ var JSHINT = (function () { warning("W095", state.tokens.next, state.tokens.next.value); } if (o[state.tokens.next.value] === true) { - warning("W075", state.tokens.next, state.tokens.next.value); + warning("W075", state.tokens.next, "key", state.tokens.next.value); } else if ((state.tokens.next.value === "__proto__" && !state.option.proto) || (state.tokens.next.value === "__iterator__" && !state.option.iterator)) { @@ -6057,7 +6535,7 @@ var JSHINT = (function () { } } - var blockScope = function () { + var blockScope = function() { var _current = {}; var _variables = [_current]; @@ -6079,18 +6557,18 @@ var JSHINT = (function () { } return { - stack: function () { + stack: function() { _current = {}; _variables.push(_current); }, - unstack: function () { + unstack: function() { _checkBlockLabels(); _variables.splice(_variables.length - 1, 1); _current = _.last(_variables); }, - getlabel: function (l) { + getlabel: function(l) { for (var i = _variables.length - 1 ; i >= 0; --i) { if (_.has(_variables[i], l) && !_variables[i][l]["(shadowed)"]) { return _variables[i]; @@ -6098,7 +6576,7 @@ var JSHINT = (function () { } }, - shadow: function (name) { + shadow: function(name) { for (var i = _variables.length - 1; i >= 0; i--) { if (_.has(_variables[i], name)) { _variables[i][name]["(shadowed)"] = true; @@ -6106,7 +6584,7 @@ var JSHINT = (function () { } }, - unshadow: function (name) { + unshadow: function(name) { for (var i = _variables.length - 1; i >= 0; i--) { if (_.has(_variables[i], name)) { _variables[i][name]["(shadowed)"] = false; @@ -6114,11 +6592,11 @@ var JSHINT = (function () { } }, - atTop: function () { + atTop: function() { return _variables.length === 1; }, - setExported: function (id) { + setExported: function(id) { if (funct["(blockscope)"].atTop()) { var item = _current[id]; if (item && item["(token)"]) { @@ -6128,11 +6606,11 @@ var JSHINT = (function () { }, current: { - has: function (t) { + has: function(t) { return _.has(_current, t); }, - add: function (t, type, tok) { + add: function(t, type, tok) { _current[t] = { "(type)" : type, "(token)": tok, "(shadowed)": false }; } } @@ -6144,7 +6622,7 @@ var JSHINT = (function () { }; // The actual JSHINT function itself. - var itself = function (s, o, g) { + var itself = function(s, o, g) { var i, k, x, reIgnoreStr, reIgnore; var optionKeys; var newOptionObj = {}; @@ -6164,7 +6642,7 @@ var JSHINT = (function () { } predefined = Object.create(null); - combine(predefined, vars.ecmaIdentifiers); + combine(predefined, vars.ecmaIdentifiers[3]); combine(predefined, vars.reservedVars); combine(predefined, g || {}); @@ -6183,7 +6661,7 @@ var JSHINT = (function () { } if (o) { - each(o.predef || null, function (item) { + each(o.predef || null, function(item) { var slice, prop; if (item[0] === "-") { @@ -6197,7 +6675,7 @@ var JSHINT = (function () { } }); - each(o.exported || null, function (item) { + each(o.exported || null, function(item) { exported[item] = true; }); @@ -6254,31 +6732,31 @@ var JSHINT = (function () { return state.jsonMode; }, - getOption: function (name) { + getOption: function(name) { return state.option[name] || null; }, - getCache: function (name) { + getCache: function(name) { return state.cache[name]; }, - setCache: function (name, value) { + setCache: function(name, value) { state.cache[name] = value; }, - warn: function (code, data) { + warn: function(code, data) { warningAt.apply(null, [ code, data.line, data.char ].concat(data.data)); }, - on: function (names, listener) { - names.split(" ").forEach(function (name) { + on: function(names, listener) { + names.split(" ").forEach(function(name) { emitter.on(name, listener); }.bind(this)); } }; emitter.removeAllListeners(); - (extraModules || []).forEach(function (func) { + (extraModules || []).forEach(function(func) { func(api); }); @@ -6290,7 +6768,7 @@ var JSHINT = (function () { o.ignoreDelimiters = [o.ignoreDelimiters]; } - o.ignoreDelimiters.forEach(function (delimiterPair) { + o.ignoreDelimiters.forEach(function(delimiterPair) { if (!delimiterPair.start || !delimiterPair.end) return; @@ -6308,27 +6786,27 @@ var JSHINT = (function () { lex = new Lexer(s); - lex.on("warning", function (ev) { + lex.on("warning", function(ev) { warningAt.apply(null, [ ev.code, ev.line, ev.character].concat(ev.data)); }); - lex.on("error", function (ev) { + lex.on("error", function(ev) { errorAt.apply(null, [ ev.code, ev.line, ev.character ].concat(ev.data)); }); - lex.on("fatal", function (ev) { + lex.on("fatal", function(ev) { quit("E041", ev.line, ev.from); }); - lex.on("Identifier", function (ev) { + lex.on("Identifier", function(ev) { emitter.emit("Identifier", ev); }); - lex.on("String", function (ev) { + lex.on("String", function(ev) { emitter.emit("String", ev); }); - lex.on("Number", function (ev) { + lex.on("Number", function(ev) { emitter.emit("Number", ev); }); @@ -6372,7 +6850,7 @@ var JSHINT = (function () { advance((state.tokens.next && state.tokens.next.value !== ".") ? "(end)" : undefined); funct["(blockscope)"].unstack(); - var markDefined = function (name, context) { + var markDefined = function(name, context) { do { if (typeof context[name] === "string") { // JSHINT marks unused variables as 'unused' and @@ -6395,7 +6873,7 @@ var JSHINT = (function () { return false; }; - var clearImplied = function (name, line) { + var clearImplied = function(name, line) { if (!implied[name]) return; @@ -6411,7 +6889,7 @@ var JSHINT = (function () { implied[name] = newImplied; }; - var warnUnused = function (name, tkn, type, unused_opt) { + var warnUnused = function(name, tkn, type, unused_opt) { var line = tkn.line; var chr = tkn.from; var raw_name = tkn.raw_text || name; @@ -6445,7 +6923,7 @@ var JSHINT = (function () { }); }; - var checkUnused = function (func, key) { + var checkUnused = function(func, key) { var type = func[key]; var tkn = func["(tokens)"][key]; @@ -6481,7 +6959,7 @@ var JSHINT = (function () { } } - functions.forEach(function (func) { + functions.forEach(function(func) { if (func["(unusedOption)"] === false) { return; } @@ -6504,7 +6982,7 @@ var JSHINT = (function () { unused_opt = func["(unusedOption)"] || state.option.unused; unused_opt = unused_opt === true ? "last-param" : unused_opt; - // 'undefined' is a special case for (function (window, undefined) { ... })(); + // 'undefined' is a special case for (function(window, undefined) { ... })(); // patterns. if (param === "undefined") @@ -6558,14 +7036,14 @@ var JSHINT = (function () { }; // Modules. - itself.addModule = function (func) { + itself.addModule = function(func) { extraModules.push(func); }; itself.addModule(style.register); // Data summary. - itself.data = function () { + itself.data = function() { var data = { functions: [], options: state.option @@ -6694,7 +7172,13 @@ var Token = { RegExp: 9, TemplateHead: 10, TemplateMiddle: 11, - TemplateTail: 12 + TemplateTail: 12, + NoSubstTemplate: 13 +}; + +var Context = { + Block: 1, + Template: 2 }; // Object that handles postponed lexing verifications that checks the parsed @@ -6704,11 +7188,11 @@ function asyncTrigger() { var _checks = []; return { - push: function (fn) { + push: function(fn) { _checks.push(fn); }, - check: function () { + check: function() { for (var check = 0; check < _checks.length; ++check) { _checks[check](); } @@ -6733,7 +7217,7 @@ function asyncTrigger() { * to token() method returning the next token, the Lexer object also * emits events. * - * lex.on("Identifier", function (data) { + * lex.on("Identifier", function(data) { * if (data.name.indexOf("_") >= 0) { * // Produce a warning. * } @@ -6774,9 +7258,8 @@ function Lexer(source) { this.from = 1; this.input = ""; this.inComment = false; - this.inTemplate = false; - this.templateLine = null; - this.templateChar = null; + this.context = []; + this.templateStarts = []; for (var i = 0; i < state.option.indent; i += 1) { state.tab += " "; @@ -6786,12 +7269,32 @@ function Lexer(source) { Lexer.prototype = { _lines: [], - getLines: function () { + inContext: function(ctxType) { + return this.context.length > 0 && this.context[this.context.length - 1].type === ctxType; + }, + + pushContext: function(ctxType) { + this.context.push({ type: ctxType }); + }, + + popContext: function() { + return this.context.pop(); + }, + + isContext: function(context) { + return this.context.length > 0 && this.context[this.context.length - 1] === context; + }, + + currentContext: function() { + return this.context.length > 0 && this.context[this.context.length - 1]; + }, + + getLines: function() { this._lines = state.lines; return this._lines; }, - setLines: function (val) { + setLines: function(val) { this._lines = val; state.lines = this._lines; }, @@ -6800,14 +7303,14 @@ Lexer.prototype = { * Return the next i character without actually moving the * char pointer. */ - peek: function (i) { + peek: function(i) { return this.input.charAt(i || 0); }, /* * Move the char pointer forward i times. */ - skip: function (i) { + skip: function(i) { i = i || 1; this.char += i; this.input = this.input.slice(i); @@ -6818,12 +7321,12 @@ Lexer.prototype = { * Underscore.js i.e. you can subscribe to multiple events with * one call: * - * lex.on("Identifier Number", function (data) { + * lex.on("Identifier Number", function(data) { * // ... * }); */ - on: function (names, listener) { - names.split(" ").forEach(function (name) { + on: function(names, listener) { + names.split(" ").forEach(function(name) { this.emitter.on(name, listener); }.bind(this)); }, @@ -6832,7 +7335,7 @@ Lexer.prototype = { * Trigger a token event. All arguments will be passed to each * listener. */ - trigger: function () { + trigger: function() { this.emitter.emit.apply(this.emitter, Array.prototype.slice.call(arguments)); }, @@ -6843,8 +7346,8 @@ Lexer.prototype = { * by the parser. This avoids parser's peek() to give the lexer * a false context. */ - triggerAsync: function (type, args, checks, fn) { - checks.push(function () { + triggerAsync: function(type, args, checks, fn) { + checks.push(function() { if (fn()) { this.trigger(type, args); } @@ -6858,7 +7361,7 @@ Lexer.prototype = { * This method's implementation was heavily influenced by the * scanPunctuator function in the Esprima parser's source code. */ - scanPunctuator: function () { + scanPunctuator: function() { var ch1 = this.peek(); var ch2, ch3, ch4; @@ -6879,8 +7382,6 @@ Lexer.prototype = { case ")": case ";": case ",": - case "{": - case "}": case "[": case "]": case ":": @@ -6891,6 +7392,24 @@ Lexer.prototype = { value: ch1 }; + // A block/object opener + case "{": + this.pushContext(Context.Block); + return { + type: Token.Punctuator, + value: ch1 + }; + + // A block/object closer + case "}": + if (this.inContext(Context.Block)) { + this.popContext(); + } + return { + type: Token.Punctuator, + value: ch1 + }; + // A pound sign (for Node shebangs) case "#": return { @@ -7015,7 +7534,7 @@ Lexer.prototype = { * also recognizes JSHint- and JSLint-specific comments such as * /*jshint, /*jslint, /*globals and so on. */ - scanComments: function () { + scanComments: function() { var ch1 = this.peek(); var ch2 = this.peek(1); var rest = this.input.substr(2); @@ -7039,7 +7558,7 @@ Lexer.prototype = { body = body.replace(/\n/g, " "); - special.forEach(function (str) { + special.forEach(function(str) { if (isSpecial) { return; } @@ -7155,7 +7674,7 @@ Lexer.prototype = { * Extract a keyword out of the next sequence of characters or * return 'null' if its not possible. */ - scanKeyword: function () { + scanKeyword: function() { var result = /^[a-zA-Z_$][a-zA-Z0-9_$]*/.exec(this.input); var keywords = [ "if", "in", "do", "var", "for", "new", @@ -7184,7 +7703,7 @@ Lexer.prototype = { * to Identifier this method can also produce BooleanLiteral * (true/false) and NullLiteral (null). */ - scanIdentifier: function () { + scanIdentifier: function() { var id = ""; var index = 0; var type, char; @@ -7229,7 +7748,7 @@ Lexer.prototype = { return null; }.bind(this); - var getIdentifierStart = function () { + var getIdentifierStart = function() { /*jshint validthis:true */ var chr = this.peek(index); var code = chr.charCodeAt(0); @@ -7255,7 +7774,7 @@ Lexer.prototype = { return null; }.bind(this); - var getIdentifierPart = function () { + var getIdentifierPart = function() { /*jshint validthis:true */ var chr = this.peek(index); var code = chr.charCodeAt(0); @@ -7332,7 +7851,7 @@ Lexer.prototype = { * This method's implementation was heavily influenced by the * scanNumericLiteral function in the Esprima parser's source code. */ - scanNumericLiteral: function () { + scanNumericLiteral: function() { var index = 0; var value = ""; var length = this.input.length; @@ -7543,7 +8062,7 @@ Lexer.prototype = { // Assumes previously parsed character was \ (=== '\\') and was not skipped. - scanEscapeSequence: function (checks) { + scanEscapeSequence: function(checks) { var allowNewLine = false; var jump = 1; this.skip(); @@ -7556,7 +8075,7 @@ Lexer.prototype = { line: this.line, character: this.char, data: [ "\\'" ] - }, checks, function () {return state.jsonMode; }); + }, checks, function() {return state.jsonMode; }); break; case "b": char = "\\b"; @@ -7584,10 +8103,20 @@ Lexer.prototype = { line: this.line, character: this.char }, checks, - function () { return n >= 0 && n <= 7 && state.directive["use strict"]; }); + function() { return n >= 0 && n <= 7 && state.directive["use strict"]; }); break; case "u": - char = String.fromCharCode(parseInt(this.input.substr(1, 4), 16)); + var hexCode = this.input.substr(1, 4); + var code = parseInt(hexCode, 16); + if (isNaN(code)) { + this.trigger("warning", { + code: "W052", + line: this.line, + character: this.char, + data: [ "u" + hexCode ] + }); + } + char = String.fromCharCode(code); jump = 5; break; case "v": @@ -7596,7 +8125,7 @@ Lexer.prototype = { line: this.line, character: this.char, data: [ "\\v" ] - }, checks, function () { return state.jsonMode; }); + }, checks, function() { return state.jsonMode; }); char = "\v"; break; @@ -7608,7 +8137,7 @@ Lexer.prototype = { line: this.line, character: this.char, data: [ "\\x-" ] - }, checks, function () { return state.jsonMode; }); + }, checks, function() { return state.jsonMode; }); char = String.fromCharCode(x); jump = 3; @@ -7627,7 +8156,7 @@ Lexer.prototype = { break; } - return {char: char, jump: jump, allowNewLine: allowNewLine}; + return { char: char, jump: jump, allowNewLine: allowNewLine }; }, /* @@ -7636,78 +8165,91 @@ Lexer.prototype = { * literals can span across multiple lines, this method has to move * the char pointer. */ - scanTemplateLiteral: function (checks) { + scanTemplateLiteral: function(checks) { var tokenType; - var value = ''; + var value = ""; var ch; + var startLine = this.line; + var startChar = this.char; + var depth = this.templateStarts.length; - // String must start with a backtick. - if (!this.inTemplate) { - if (!state.option.esnext || this.peek() !== "`") { - return null; - } - this.templateLine = this.line; - this.templateChar = this.char; + if (!state.option.esnext) { + // Only lex template strings in ESNext mode. + return null; + } else if (this.peek() === "`") { + // Template must start with a backtick. + tokenType = Token.TemplateHead; + this.templateStarts.push({ line: this.line, char: this.char }); + depth = this.templateStarts.length; this.skip(1); - } else if (this.peek() !== '}') { - // If we're in a template, and we don't have a '}', lex something else instead. + this.pushContext(Context.Template); + } else if (this.inContext(Context.Template) && this.peek() === "}") { + // If we're in a template context, and we have a '}', lex a TemplateMiddle. + tokenType = Token.TemplateMiddle; + } else { + // Go lex something else. return null; } while (this.peek() !== "`") { while ((ch = this.peek()) === "") { + value += "\n"; if (!this.nextLine()) { - // Unclosed template literal --- point to the starting line, or the EOF? - tokenType = this.inTemplate ? Token.TemplateHead : Token.TemplateMiddle; - this.inTemplate = false; + // Unclosed template literal --- point to the starting "`" + var startPos = this.templateStarts.pop(); this.trigger("error", { code: "E052", - line: this.templateLine, - character: this.templateChar + line: startPos.line, + character: startPos.char }); return { type: tokenType, value: value, - isUnclosed: true + startLine: startLine, + startChar: startChar, + isUnclosed: true, + depth: depth, + context: this.popContext() }; } } if (ch === '$' && this.peek(1) === '{') { value += '${'; - tokenType = value.charAt(0) === '}' ? Token.TemplateMiddle : Token.TemplateHead; - // Either TokenHead or TokenMiddle --- depending on if the initial value - // is '}' or not. this.skip(2); - this.inTemplate = true; return { type: tokenType, value: value, - isUnclosed: false + startLine: startLine, + startChar: startChar, + isUnclosed: false, + depth: depth, + context: this.currentContext() }; } else if (ch === '\\') { var escape = this.scanEscapeSequence(checks); value += escape.char; this.skip(escape.jump); - } else if (ch === '`') { - break; - } else { + } else if (ch !== '`') { // Otherwise, append the value and continue. value += ch; this.skip(1); } } - // Final value is either TokenTail or NoSubstititionTemplate --- essentially a string - tokenType = this.inTemplate ? Token.TemplateTail : Token.StringLiteral; - this.inTemplate = false; + // Final value is either NoSubstTemplate or TemplateTail + tokenType = tokenType === Token.TemplateHead ? Token.NoSubstTemplate : Token.TemplateTail; this.skip(1); + this.templateStarts.pop(); return { type: tokenType, value: value, + startLine: startLine, + startChar: startChar, isUnclosed: false, - quote: "`" + depth: depth, + context: this.popContext() }; }, @@ -7722,7 +8264,7 @@ Lexer.prototype = { * var str = "hello\ * world"; */ - scanStringLiteral: function (checks) { + scanStringLiteral: function(checks) { /*jshint loopfunc:true */ var quote = this.peek(); @@ -7736,7 +8278,7 @@ Lexer.prototype = { code: "W108", line: this.line, character: this.char // +1? - }, checks, function () { return state.jsonMode && quote !== "\""; }); + }, checks, function() { return state.jsonMode && quote !== "\""; }); var value = ""; var startLine = this.line; @@ -7771,13 +8313,13 @@ Lexer.prototype = { code: "W043", line: this.line, character: this.char - }, checks, function () { return !state.option.multistr; }); + }, checks, function() { return !state.option.multistr; }); this.triggerAsync("warning", { code: "W042", line: this.line, character: this.char - }, checks, function () { return state.jsonMode && state.option.multistr; }); + }, checks, function() { return state.jsonMode && state.option.multistr; }); } // If we get an EOF inside of an unclosed string, show an @@ -7793,6 +8335,8 @@ Lexer.prototype = { return { type: Token.StringLiteral, value: value, + startLine: startLine, + startChar: startChar, isUnclosed: true, quote: quote }; @@ -7833,6 +8377,8 @@ Lexer.prototype = { return { type: Token.StringLiteral, value: value, + startLine: startLine, + startChar: startChar, isUnclosed: false, quote: quote }; @@ -7848,7 +8394,7 @@ Lexer.prototype = { * rare edge cases where one JavaScript engine complains about * your regular expression while others don't. */ - scanRegExp: function () { + scanRegExp: function() { var index = 0; var length = this.input.length; var char = this.peek(); @@ -7859,7 +8405,7 @@ Lexer.prototype = { var isCharSet = false; var terminated; - var scanUnexpectedChars = function () { + var scanUnexpectedChars = function() { // Unexpected control character if (char < " ") { malformed = true; @@ -8010,7 +8556,7 @@ Lexer.prototype = { * can be mistakenly typed on OS X with option-space. Non UTF-8 web * pages with non-breaking pages produce syntax errors. */ - scanNonBreakingSpaces: function () { + scanNonBreakingSpaces: function() { return state.option.nonbsp ? this.input.search(/(\u00A0)/) : -1; }, @@ -8018,7 +8564,7 @@ Lexer.prototype = { /* * Scan for characters that get silently deleted by one or more browsers. */ - scanUnsafeChars: function () { + scanUnsafeChars: function() { return this.input.search(reg.unsafeChars); }, @@ -8026,7 +8572,7 @@ Lexer.prototype = { * Produce the next raw token or return 'null' if no tokens can be matched. * This method skips over all space characters. */ - next: function (checks) { + next: function(checks) { this.from = this.char; // Move to the next non-space character. @@ -8074,7 +8620,7 @@ Lexer.prototype = { * Switch to the next line and reset all char pointers. Once * switched, this method also checks for other minor warnings. */ - nextLine: function () { + nextLine: function() { var char; if (this.line >= this.getLines().length) { @@ -8088,14 +8634,14 @@ Lexer.prototype = { var inputTrimmed = this.input.trim(); - var startsWith = function () { - return _.some(arguments, function (prefix) { + var startsWith = function() { + return _.some(arguments, function(prefix) { return inputTrimmed.indexOf(prefix) === 0; }); }; - var endsWith = function () { - return _.some(arguments, function (suffix) { + var endsWith = function() { + return _.some(arguments, function(suffix) { return inputTrimmed.indexOf(suffix, inputTrimmed.length - suffix.length) !== -1; }); }; @@ -8142,7 +8688,7 @@ Lexer.prototype = { * This is simply a synonym for nextLine() method with a friendlier * public name. */ - start: function () { + start: function() { this.nextLine(); }, @@ -8150,7 +8696,7 @@ Lexer.prototype = { * Produce the next token. This function is called by advance() to get * the next token. It returns a token in a JSLint-compatible format. */ - token: function () { + token: function() { /*jshint loopfunc:true */ var checks = asyncTrigger(); var token; @@ -8185,7 +8731,7 @@ Lexer.prototype = { } // Produce a token object. - var create = function (type, value, isProperty, token) { + var create = function(type, value, isProperty, token) { /*jshint validthis:true */ var obj; @@ -8237,6 +8783,21 @@ Lexer.prototype = { obj.character = this.char; obj.from = this.from; if (obj.identifier && token) obj.raw_text = token.text || token.value; + if (token && token.startLine && token.startLine !== this.line) { + obj.startLine = token.startLine; + } + if (token && token.context) { + // Context of current token + obj.context = token.context; + } + if (token && token.depth) { + // Nested template depth + obj.depth = token.depth; + } + if (token && token.isUnclosed) { + // Mark token as unclosed string / template literal + obj.isUnclosed = token.isUnclosed; + } if (isProperty && obj.identifier) { obj.isProperty = isProperty; @@ -8276,38 +8837,57 @@ Lexer.prototype = { line: this.line, char: this.char, from: this.from, + startLine: token.startLine, + startChar: token.startChar, value: token.value, quote: token.quote - }, checks, function () { return true; }); + }, checks, function() { return true; }); - return create("(string)", token.value); + return create("(string)", token.value, null, token); case Token.TemplateHead: this.trigger("TemplateHead", { line: this.line, char: this.char, from: this.from, + startLine: token.startLine, + startChar: token.startChar, value: token.value }); - return create("(template)", token.value); + return create("(template)", token.value, null, token); case Token.TemplateMiddle: this.trigger("TemplateMiddle", { line: this.line, char: this.char, from: this.from, + startLine: token.startLine, + startChar: token.startChar, value: token.value }); - return create("(template middle)", token.value); + return create("(template middle)", token.value, null, token); case Token.TemplateTail: this.trigger("TemplateTail", { line: this.line, char: this.char, from: this.from, + startLine: token.startLine, + startChar: token.startChar, value: token.value }); - return create("(template tail)", token.value); + return create("(template tail)", token.value, null, token); + + case Token.NoSubstTemplate: + this.trigger("NoSubstTemplate", { + line: this.line, + char: this.char, + from: this.from, + startLine: token.startLine, + startChar: token.startChar, + value: token.value + }); + return create("(no subst template)", token.value, null, token); case Token.Identifier: this.trigger("Identifier", { @@ -8340,13 +8920,13 @@ Lexer.prototype = { line: this.line, character: this.char, data: [ "0x-" ] - }, checks, function () { return token.base === 16 && state.jsonMode; }); + }, checks, function() { return token.base === 16 && state.jsonMode; }); this.triggerAsync("warning", { code: "W115", line: this.line, character: this.char - }, checks, function () { + }, checks, function() { return state.directive["use strict"] && token.base === 8 && token.isLegacy; }); @@ -8393,6 +8973,7 @@ Lexer.prototype = { }; exports.Lexer = Lexer; +exports.Context = Context; }, {"../data/ascii-identifier-data.js":1,"./reg.js":8,"./state.js":9,"events":12,"underscore":2}], @@ -8599,9 +9180,13 @@ var warnings = { W123: "'{a}' is already defined in outer scope.", W124: "A generator function shall contain a yield statement.", W125: "This line contains non-breaking spaces: http://jshint.com/doc/options/#nonbsp", - W126: "Grouping operator is unnecessary for lone expressions.", + W126: "Unnecessary grouping operator.", W127: "Unexpected use of a comma operator.", - W128: "Empty array elements require elision=true." + W128: "Empty array elements require elision=true.", + W129: "'{a}' is defined in a future version of JavaScript. Use a " + + "different variable name to avoid migration issues.", + W130: "Invalid element after rest element.", + W131: "Invalid parameter after rest parameter." }; var info = { @@ -8614,15 +9199,15 @@ exports.errors = {}; exports.warnings = {}; exports.info = {}; -_.each(errors, function (desc, code) { +_.each(errors, function(desc, code) { exports.errors[code] = { code: code, desc: desc }; }); -_.each(warnings, function (desc, code) { +_.each(warnings, function(desc, code) { exports.warnings[code] = { code: code, desc: desc }; }); -_.each(info, function (desc, code) { +_.each(info, function(desc, code) { exports.info[code] = { code: code, desc: desc }; }); @@ -8734,6 +9319,11 @@ exports.bool = { /** * This option allows you to force all variable names to use either * camelCase style or UPPER_CASE with underscores. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ camelcase : true, @@ -8765,6 +9355,14 @@ exports.bool = { */ eqeqeq : true, + /** + * This option enables warnings about the use of identifiers which are + * defined in future versions of JavaScript. Although overwriting them has + * no effect in contexts where they are not implemented, this practice can + * cause issues when migrating codebases to newer versions of the language. + */ + futurehostile: true, + /** * This option suppresses warnings about invalid `typeof` operator values. * This operator has only [a limited set of possible return @@ -8852,6 +9450,11 @@ exports.bool = { * wrapping them in parentheses. Wrapping parentheses assists readers of * your code in understanding that the expression is the result of a * function, and not the function itself. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ immed : true, @@ -8874,6 +9477,11 @@ exports.bool = { * important because when the function that was intended to be used with * `new` is used without it, `this` will point to the global object instead * of a new object. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ newcap : true, @@ -8898,6 +9506,11 @@ exports.bool = { * originally warning for all empty blocks and we simply made it optional. * There were no studies reporting that empty blocks in JavaScript break * your code in any way. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ noempty : true, @@ -8938,19 +9551,23 @@ exports.bool = { undef : true, /** - * This option prohibits the use of the grouping operator for - * single-expression statements. This unecessary usage commonly reflects - * a misunderstanding of unary operators, for example: + * This option prohibits the use of the grouping operator when it is not + * strictly required. Such usage commonly reflects a misunderstanding of + * unary operators, for example: * * // jshint singleGroups: true * - * delete(obj.attr); // Warning: Grouping operator is unnecessary for lone expressions. + * delete(obj.attr); // Warning: Unnecessary grouping operator. */ singleGroups: false, /** * This option is a short hand for the most strict JSHint configuration. It * enables all enforcing options and disables all relaxing options. + * + * @deprecated The option automatically opts users in to new features which + * can lead to unexpected warnings/errors in when upgrading + * between minor versions of JSHint. */ enforceall : false }, @@ -8992,6 +9609,11 @@ exports.bool = { * * text = "Hello\ * World"; // Warning, there is a space after \ + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ multistr : true, @@ -9069,6 +9691,11 @@ exports.bool = { /** * This option suppresses warnings about using `[]` notation when it can be * expressed in dot notation: `person['name']` vs. `person.name`. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ sub : true, @@ -9090,6 +9717,11 @@ exports.bool = { * This option suppresses most of the warnings about possibly unsafe line * breakings in your code. It doesn't suppress warnings about comma-first * coding style. To suppress those you have to use `laxcomma` (see below). + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ laxbreak : true, @@ -9101,6 +9733,11 @@ exports.bool = { * , handle: 'valueof' * , role: 'SW Engineer' * }; + * + * @deprecated JSHint is limiting its scope to issues of code correctness. + * If you would like to enforce rules relating to code style, + * check out [the JSCS + * project](https://github.com/jscs-dev/node-jscs). */ laxcomma : true, @@ -9374,11 +10011,19 @@ exports.val = { /** * This option lets you set the maximum length of a line. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. If + * you would like to enforce rules relating to code style, check + * out [the JSCS project](https://github.com/jscs-dev/node-jscs). */ maxlen : false, /** * This option sets a specific tab width for your code. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. If + * you would like to enforce rules relating to code style, check + * out [the JSCS project](https://github.com/jscs-dev/node-jscs). */ indent : false, @@ -9412,6 +10057,10 @@ exports.val = { * one particular style but want some consistency, `"single"` if you want to * allow only single quotes and `"double"` if you want to allow only double * quotes. + * + * @deprecated JSHint is limiting its scope to issues of code correctness. If + * you would like to enforce rules relating to code style, check + * out [the JSCS project](https://github.com/jscs-dev/node-jscs). */ quotmark : false, @@ -9642,7 +10291,7 @@ var NameStack = _dereq_("./name-stack.js"); var state = { syntax: {}, - reset: function () { + reset: function() { this.tokens = { prev: null, next: null, @@ -9673,7 +10322,7 @@ exports.state = state; 10:[function(_dereq_,module,exports){ "use strict"; -exports.register = function (linter) { +exports.register = function(linter) { // Check for properties named __proto__. This special property was // deprecated and then re-introduced for ES6. @@ -9729,18 +10378,12 @@ exports.register = function (linter) { linter.on("String", function style_scanQuotes(data) { var quotmark = linter.getOption("quotmark"); - var esnext = linter.getOption("esnext"); var code; if (!quotmark) { return; } - // If quotmark is enabled, return if this is a template literal. - if (esnext && data.quote === "`") { - return; - } - // If quotmark is set to 'single' warn about all double-quotes. if (quotmark === "single" && data.quote !== "'") { @@ -9836,51 +10479,47 @@ exports.reservedVars = { }; exports.ecmaIdentifiers = { - Array : false, - Boolean : false, - Date : false, - decodeURI : false, - decodeURIComponent : false, - encodeURI : false, - encodeURIComponent : false, - Error : false, - "eval" : false, - EvalError : false, - Function : false, - hasOwnProperty : false, - isFinite : false, - isNaN : false, - JSON : false, - Map : false, - Math : false, - Number : false, - Object : false, - Proxy : false, - Promise : false, - parseInt : false, - parseFloat : false, - RangeError : false, - ReferenceError : false, - RegExp : false, - Set : false, - String : false, - SyntaxError : false, - TypeError : false, - URIError : false, - WeakMap : false, - WeakSet : false -}; - -exports.newEcmaIdentifiers = { - Set : false, - Map : false, - WeakMap : false, - WeakSet : false, - Proxy : false, - Promise : false, - Reflect : false, - Symbol : false, - System : false + 3: { + Array : false, + Boolean : false, + Date : false, + decodeURI : false, + decodeURIComponent : false, + encodeURI : false, + encodeURIComponent : false, + Error : false, + "eval" : false, + EvalError : false, + Function : false, + hasOwnProperty : false, + isFinite : false, + isNaN : false, + Math : false, + Number : false, + Object : false, + parseInt : false, + parseFloat : false, + RangeError : false, + ReferenceError : false, + RegExp : false, + String : false, + SyntaxError : false, + TypeError : false, + URIError : false + }, + 5: { + JSON : false + }, + 6: { + Map : false, + Promise : false, + Proxy : false, + Reflect : false, + Set : false, + Symbol : false, + WeakMap : false, + WeakSet : false + } }; // Global variables commonly provided by a web browser environment. @@ -9902,11 +10541,13 @@ exports.browser = { clearTimeout : false, close : false, closed : false, + Comment : false, CustomEvent : false, DOMParser : false, defaultStatus : false, Document : false, document : false, + DocumentFragment : false, Element : false, ElementTimeControl : false, Event : false, @@ -9966,6 +10607,7 @@ exports.browser = { HTMLTableElement : false, HTMLTableRowElement : false, HTMLTableSectionElement: false, + HTMLTemplateElement : false, HTMLTextAreaElement : false, HTMLTitleElement : false, HTMLUListElement : false, @@ -9988,6 +10630,7 @@ exports.browser = { Node : false, NodeFilter : false, NodeList : false, + Notification : false, navigator : false, onbeforeunload : true, onblur : true, @@ -10002,6 +10645,7 @@ exports.browser = { Option : false, parent : false, print : false, + Range : false, requestAnimationFrame : false, removeEventListener : false, resizeBy : false, @@ -10170,6 +10814,7 @@ exports.browser = { SVGViewElement : false, SVGViewSpec : false, SVGZoomAndPan : false, + Text : false, TextDecoder : false, TextEncoder : false, TimeEvent : false, @@ -10495,7 +11140,11 @@ exports.yui = { exports.mocha = { // BDD describe : false, + xdescribe : false, it : false, + xit : false, + context : false, + xcontext : false, before : false, after : false, beforeEach : false,