Repository: jshint/jshint (js)
Issue: Jasmine global doesn't recognize `spyOnProperty`
Jasmine 2.6 added the ability to spy on properties via the new global, `spyOnProperty`. But JSHint's `jasmine` setting doesn't recognize that as a valid global.
Pull request description: [[FIX]] recognize Jasmine global `spyOnProperty`
Added `spyOnProperty` (introduced in Jasmine 2.6) to the list of known globals.

Fixes #3183

Patch:
diff --git a/src/jshint.js b/src/jshint.js
index 3a50af0b..4a969806 100644
--- a/src/jshint.js
+++ b/src/jshint.js
@@ -75,11 +75,6 @@ var JSHINT = (function() {
 
     declared, // Globals that were declared using /*global ... */ syntax.
 
-    functionicity = [
-      "closure", "exception", "global", "label",
-      "outer", "unused", "var"
-    ],
-
     functions, // All of the functions
 
     inblock,
@@ -2848,7 +2843,7 @@ var JSHINT = (function() {
       // a possible code smell.
       if (pastDefault) {
         if (state.tokens.next.id !== "=") {
-          error("W138", state.tokens.current);
+          error("W138", state.tokens.curr);
         }
       }
       if (state.tokens.next.id === "=") {
@@ -2969,21 +2964,21 @@ var JSHINT = (function() {
 
   /**
    * @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.name] The identifier belonging to the function (if
+   *                                any)
+   * @param {token} [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.
+   * @param {string} [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(options) {
     var f, token, name, statement, classExprBinding, isGenerator, isArrow, ignoreLoopFunc;
@@ -3554,9 +3549,7 @@ var JSHINT = (function() {
         if (!prefix && value && value.type === "undefined") {
           warning("W080", id, id.value);
         }
-        if (lone) {
-          tokens[0].first = value;
-        } else {
+        if (!lone) {
           destructuringPatternMatch(names, value);
         }
       }
@@ -3666,9 +3659,7 @@ var JSHINT = (function() {
         if (value && !prefix && !state.funct["(loopage)"] && value.type === "undefined") {
           warning("W080", id, id.value);
         }
-        if (lone) {
-          tokens[0].first = value;
-        } else {
+        if (!lone) {
           destructuringPatternMatch(names, value);
         }
       }
@@ -3729,7 +3720,7 @@ var JSHINT = (function() {
     state.inClassBody = true;
     advance("{");
     // ClassBody(opt)
-    c.body = classbody(c);
+    classbody(c);
     advance("}");
     state.inClassBody = wasInClassBody;
   }
@@ -3742,7 +3733,7 @@ var JSHINT = (function() {
     var props = Object.create(null);
     var staticProps = Object.create(null);
     var computed;
-    for (var i = 0; state.tokens.next.id !== "}"; ++i) {
+    while (state.tokens.next.id !== "}") {
       name = state.tokens.next;
       isStatic = false;
       isGenerator = false;
@@ -5043,7 +5034,7 @@ var JSHINT = (function() {
         unstack: function() {
           _current.variables.filter(function(v) {
             if (v.unused)
-              warning("W098", v.token, v.raw_text || v.value);
+              warning("W098", v.token, v.token.raw_text || v.value);
             if (v.undef)
               state.funct["(scope)"].block.use(v.value, v.token);
           });
@@ -5482,7 +5473,7 @@ var JSHINT = (function() {
       options: state.option
     };
 
-    var fu, f, i, j, n, globals;
+    var fu, f, i, n, globals;
 
     if (itself.errors.length) {
       data.errors = itself.errors;
@@ -5510,16 +5501,6 @@ var JSHINT = (function() {
       f = functions[i];
       fu = {};
 
-      for (j = 0; j < functionicity.length; j += 1) {
-        fu[functionicity[j]] = [];
-      }
-
-      for (j = 0; j < functionicity.length; j += 1) {
-        if (fu[functionicity[j]].length === 0) {
-          delete fu[functionicity[j]];
-        }
-      }
-
       fu.name = f["(name)"];
       fu.param = f["(params)"];
       fu.line = f["(line)"];
diff --git a/src/lex.js b/src/lex.js
index a3085a29..ade8c102 100644
--- a/src/lex.js
+++ b/src/lex.js
@@ -1895,7 +1895,7 @@ Lexer.prototype = {
           from: this.from,
           value: token.value,
           base: token.base,
-          isMalformed: token.malformed
+          isMalformed: token.isMalformed
         });
 
         return create("(number)", token.value);
diff --git a/src/vars.js b/src/vars.js
index 0cdd2be6..c948d762 100644
--- a/src/vars.js
+++ b/src/vars.js
@@ -738,5 +738,7 @@ exports.jasmine = {
   fail        : false,
   fdescribe   : false,
   fit         : false,
-  pending     : false
+  pending     : false,
+  // Jasmine 2.6
+  spyOnProperty: false
 };


Test patch:
diff --git a/tests/unit/core.js b/tests/unit/core.js
index f8a1f7a1..4db9883d 100644
--- a/tests/unit/core.js
+++ b/tests/unit/core.js
@@ -1512,7 +1512,7 @@ exports.testDefaultArguments = function (test) {
     .addError(14, 39, "'bar' is not defined.")
     .addError(14, 32, "'num3' was used before it was declared, which is illegal for 'param' variables.")
     .addError(15, 32, "'num4' was used before it was declared, which is illegal for 'param' variables.")
-    .addError(18, 45, "Regular parameters should not come after default parameters.")
+    .addError(18, 41, "Regular parameters should not come after default parameters.")
     .addError(27, 10, "'c' is not defined.")
     .addError(33, 4, "'d' was used before it was defined.")
     .addError(36, 16, "'e' was used before it was declared, which is illegal for 'param' variables.")
@@ -1521,7 +1521,7 @@ exports.testDefaultArguments = function (test) {
   TestRun(test)
     .addError(14, 32, "'num3' was used before it was declared, which is illegal for 'param' variables.")
     .addError(15, 32, "'num4' was used before it was declared, which is illegal for 'param' variables.")
-    .addError(18, 45, "Regular parameters should not come after default parameters.")
+    .addError(18, 41, "Regular parameters should not come after default parameters.")
     .addError(36, 16, "'e' was used before it was declared, which is illegal for 'param' variables.")
     .test(src, { moz: true });
 
@@ -1536,7 +1536,7 @@ exports.testDefaultArguments = function (test) {
     .addError(15, 37, "'default parameters' is only available in ES6 (use 'esversion: 6').")
     .addError(15, 32, "'num4' was used before it was declared, which is illegal for 'param' variables.")
     .addError(18, 37, "'default parameters' is only available in ES6 (use 'esversion: 6').")
-    .addError(18, 45, "Regular parameters should not come after default parameters.")
+    .addError(18, 41, "Regular parameters should not come after default parameters.")
     .addError(26, 18, "'default parameters' is only available in ES6 (use 'esversion: 6').")
     .addError(31, 18, "'default parameters' is only available in ES6 (use 'esversion: 6').")
     .addError(33, 6, "'default parameters' is only available in ES6 (use 'esversion: 6').")
diff --git a/tests/unit/envs.js b/tests/unit/envs.js
index ea339985..eb6851d0 100644
--- a/tests/unit/envs.js
+++ b/tests/unit/envs.js
@@ -538,6 +538,7 @@ exports.jasmine = function (test) {
     "setFixtures",
     "loadFixtures",
     "spyOn",
+    "spyOnProperty",
     "expect",
     "runs",
     "waitsFor",
diff --git a/tests/unit/parser.js b/tests/unit/parser.js
index 01afbff9..86379fb0 100644
--- a/tests/unit/parser.js
+++ b/tests/unit/parser.js
@@ -3690,19 +3690,23 @@ exports["array comprehension unused and undefined"] = function (test) {
   var code = [
     "var range = [1, 2];",
     "var a = [for (i of range) if (i % 2 === 0) i];",
-    "var b = [for (j of range) doesnotexist];"
+    "var b = [for (j of range) doesnotexist];",
+    "var c = [for (\\u0024 of range) 1];"
   ];
   TestRun(test)
     .addError(2, 5, "'a' is defined but never used.")
     .addError(3, 15, "'j' is defined but never used.")
     .addError(3, 27, "'doesnotexist' is not defined.")
     .addError(3, 5, "'b' is defined but never used.")
+    .addError(4, 15, "'\\u0024' is defined but never used.")
+    .addError(4, 5, "'c' is defined but never used.")
     .test(code, { esnext: true, unused: true, undef: true });
 
   var unused = JSHINT.data().unused;
   test.deepEqual([
     { name: 'a', line: 2, character: 5 },
-    { name: 'b', line: 3, character: 5 }
+    { name: 'b', line: 3, character: 5 },
+    { name: 'c', line: 4, character: 5 }
     //{ name: 'j', line: 3, character: 15 } // see gh-2440
   ], unused);