Repository: jshint/jshint
Pull request #2185 (2015-02-22 18:27:57)

Class expr identifier
Rather than add yet another optional positional argument to `doFunction`, I bit the bullet and refactored the function to accept a single "options" argument. This is something we've discussed in the past, so I don't think it's too controversial.

I was hoping to find some way to unify the `name` option and the (proposed) `classExprBinding` option since they both create an entry in the function's scope. These attempts resulted in a strange, error prone API that split apart the function's name (as [given to `functor`](https://github.com/jshint/jshint/blob/4e59553f7e2e09ba6c15643fdcb5b4416301148f/src/jshint.js#L2902) and [used in result reporting](https://github.com/jshint/jshint/pull/1971)) from the variable inserted into the function scope. If there's a better way to combine these similar-looking options, I'm all ears!

This should resolve gh-2182. 

coveralls: 
[![Coverage Status](https://coveralls.io/builds/1974203/badge)](https://coveralls.io/builds/1974203)

Coverage increased (+0.04%) to 94.86% when pulling **4ef3bdd3d4db998dac383cc696d0c742b1eecbd4 on jugglinmike:class-expr-identifier** into **4e59553f7e2e09ba6c15643fdcb5b4416301148f on jshint:master**.


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b48203e2..b2178404 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -7,7 +7,6 @@ to a snippet, graffiti outside of Anton's apartment, etc.
 However, before sending a patch, please make sure that the following applies:
 
 * Your commit message follows the [Commit Message Guidelines](#commit-message-guidelines).
-* You have signed the [Contributor's License Agreement](https://docs.google.com/forms/d/1BkEC9ds73aQLF73_kTX-9nhKCBJ8U5aFFUb-_Trt8Bc/viewform).
 * Your patch doesn't have useless merge commits.
 * Your coding style is similar to ours (see below).
 * Your patch is 100% tested. We don't accept any test regressions.
diff --git a/README.md b/README.md
index 9f2e9024..827e7eea 100644
--- a/README.md
+++ b/README.md
@@ -44,12 +44,6 @@ that cover your issue. And remember the wisdom: pull request > bug report > twee
 JSHint is distributed under the MIT License. One file and one file only
 (src/stable/jshint.js) is distributed under the slightly modified MIT License.
 
-## The JSHint Team
-
-JSHint is maintained by [Rick Waldron](https://github.com/rwaldron/), [Caitlin
-Potter](https://github.com/caitp/), [Mike
-Sherov](https://github.com/mikesherov/), and [Mike
-Pennisi](https://github.com/jugglinmike/).
 
 ## Thank you!
 
diff --git a/src/jshint.js b/src/jshint.js
index 43d29d42..ca666c29 100644
--- a/src/jshint.js
+++ b/src/jshint.js
@@ -1172,7 +1172,7 @@ var JSHINT = (function() {
       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;
@@ -2169,10 +2169,7 @@ var JSHINT = (function() {
   infix("in", "in", 120);
   infix("instanceof", "instanceof", 120);
   infix("+", function(left, that) {
-    var right;
-    that.left = left;
-    that.right = right = expression(130);
-
+    var right = expression(130);
     if (left && right && left.id === "(string)" && right.id === "(string)") {
       left.value += right.value;
       left.character = right.character;
@@ -2181,7 +2178,8 @@ var JSHINT = (function() {
       }
       return left;
     }
-
+    that.left = left;
+    that.right = right;
     return that;
   }, 130);
   prefix("+", "num");
@@ -2239,7 +2237,7 @@ var JSHINT = (function() {
 
   prefix("~", function() {
     if (state.option.bitwise) {
-      warning("W016", this, "~");
+      warning("W052", this, "~");
     }
     this.arity = "unary";
     expression(150);
@@ -2250,42 +2248,7 @@ var JSHINT = (function() {
     if (!state.option.esnext) {
       warning("W119", this, "spread/rest operator");
     }
-
-    // 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, ["[", "("])) {
-
+    if (!state.tokens.next.identifier) {
       error("E030", state.tokens.next, state.tokens.next.value);
     }
     expression(150);
@@ -2504,7 +2467,7 @@ 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 = [];
@@ -2573,7 +2536,7 @@ var JSHINT = (function() {
       }
 
       if (!isNecessary) {
-        warning("W126", opening);
+        warning("W126");
       }
 
       ret.paren = true;
@@ -2767,14 +2730,22 @@ 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 loneArg = options && options.loneArg;
 
     if (loneArg && loneArg.identifier === true) {
       addlabel(loneArg.value, { type: "unused", token: loneArg });
@@ -2783,7 +2754,7 @@ var JSHINT = (function() {
 
     next = state.tokens.next;
 
-    if (!fatarrow || !fatarrow.parsedParen) {
+    if (!options || !options.parsedOpening) {
       advance("(");
     }
 
@@ -2916,30 +2887,46 @@ var JSHINT = (function() {
   }
 
   /**
-   * @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.isStatement] Whether the function has been
+   *                                        declared as part of another
+   *                                        statement
+   * @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, isStatement, classExprBinding, isGenerator, isArrow;
     var oldOption = state.option;
     var oldIgnored = state.ignored;
     var oldScope  = scope;
 
+    if (options) {
+      name = options.name;
+      isStatement = options.isStatement;
+      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);
 
     funct = functor(name || state.nameStack.infer(), state.tokens.next, scope, {
-      "(statement)": statement,
+      "(statement)": isStatement,
       "(context)":   funct,
-      "(generator)": generator ? true : null
+      "(generator)": isGenerator
     });
 
     f = funct;
@@ -2951,22 +2938,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);
     }
@@ -3130,7 +3121,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");
@@ -3165,7 +3155,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);
@@ -3504,6 +3494,7 @@ var JSHINT = (function() {
     } 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();
     }
@@ -3592,7 +3583,7 @@ var JSHINT = (function() {
           advance();
         }
         if (state.tokens.next.value !== "(") {
-          doFunction(undefined, c, false, null);
+          doFunction({ isStatement: true });
         }
       }
 
@@ -3620,7 +3611,11 @@ var JSHINT = (function() {
 
       propertyName(name);
 
-      doFunction(null, c, isGenerator, null);
+      doFunction({
+        isStatement: true,
+        type: isGenerator ? "generator" : null,
+        classExprBinding: c.namedExpr ? c.name : null
+      });
     }
 
     checkProperties(props);
@@ -3651,7 +3646,11 @@ var JSHINT = (function() {
     }
     addlabel(i, { type: "unction", token: state.tokens.curr });
 
-    doFunction(i, { statement: true }, generator);
+    doFunction({
+      name: i,
+      isStatement: true,
+      type: generator ? "generator" : null
+    });
     if (state.tokens.next.id === "(" && state.tokens.next.line === state.tokens.curr.line) {
       error("E039");
     }
@@ -3670,7 +3669,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"; }
diff --git a/src/lex.js b/src/lex.js
index d3e3a358..62269418 100644
--- a/src/lex.js
+++ b/src/lex.js
@@ -949,17 +949,7 @@ Lexer.prototype = {
       function() { return n >= 0 && n <= 7 && state.directive["use strict"]; });
       break;
     case "u":
-      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);
+      char = String.fromCharCode(parseInt(this.input.substr(1, 4), 16));
       jump = 5;
       break;
     case "v":
diff --git a/src/options.js b/src/options.js
index 3a5cccf2..1ec94db6 100644
--- a/src/options.js
+++ b/src/options.js
@@ -25,11 +25,6 @@ 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,
 
@@ -148,11 +143,6 @@ 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,
 
@@ -175,11 +165,6 @@ 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,
 
@@ -204,11 +189,6 @@ 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,
 
@@ -262,10 +242,6 @@ exports.bool = {
     /**
      * 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
   },
@@ -307,11 +283,6 @@ 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,
 
@@ -389,11 +360,6 @@ 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,
 
@@ -415,11 +381,6 @@ 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,
 
@@ -431,11 +392,6 @@ 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,
 
@@ -709,19 +665,11 @@ 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,
 
@@ -755,10 +703,6 @@ 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,
 
diff --git a/src/vars.js b/src/vars.js
index 91ac6565..cad50327 100644
--- a/src/vars.js
+++ b/src/vars.js
@@ -674,11 +674,7 @@ exports.yui = {
 exports.mocha = {
   // BDD
   describe    : false,
-  xdescribe   : false,
   it          : false,
-  xit         : false,
-  context     : false,
-  xcontext    : false,
   before      : false,
   after       : false,
   beforeEach  : false,


Test patch:
diff --git a/tests/unit/fixtures/latedef-inline.js b/tests/unit/fixtures/latedef-inline.js
deleted file mode 100644
index ecd3eb75..00000000
--- a/tests/unit/fixtures/latedef-inline.js
+++ /dev/null
@@ -1,31 +0,0 @@
-(function () {
-    /* jshint latedef:true */
-    foo();
-    function foo() {}
-    a = 1;
-    var a;
-}());
-
-(function () {
-    /* jshint latedef:false */
-    foo();
-    function foo() {}
-    a = 1;
-    var a;
-}());
-
-(function () {
-    /* jshint latedef:nofunc */
-    foo();
-    function foo() {}
-    a = 1;
-    var a;
-}());
-
-(function () {
-    /* jshint latedef:badoption */
-    foo();
-    function foo() {}
-    a = 1;
-    var a;
-}());
diff --git a/tests/unit/fixtures/shadow-inline.js b/tests/unit/fixtures/shadow-inline.js
deleted file mode 100644
index 9326a464..00000000
--- a/tests/unit/fixtures/shadow-inline.js
+++ /dev/null
@@ -1,49 +0,0 @@
-(function () {
-    /* jshint shadow:outer */
-
-    var a = 1;
-
-    function foo(a) {
-        var a = 2;
-    }
-}());
-
-(function () {
-    /* jshint shadow:inner */
-
-    var a = 1;
-
-    function foo(a) {
-        var a = 2;
-    }
-}());
-
-(function () {
-    /* jshint shadow:false */
-
-    var a = 1;
-
-    function foo(a) {
-        var a = 2;
-    }
-}());
-
-(function () {
-    /* jshint shadow:true */
-
-    var a = 1;
-
-    function foo(a) {
-        var a = 2;
-    }
-}());
-
-(function () {
-    /* jshint shadow:puppet */
-
-    var a = 1;
-
-    function foo(a) {
-        var a = 2;
-    }
-}());
diff --git a/tests/unit/options.js b/tests/unit/options.js
index 4260762e..fc8569af 100644
--- a/tests/unit/options.js
+++ b/tests/unit/options.js
@@ -68,23 +68,7 @@ exports.scopeshadow = function (test) {
     .test(src, { es3: true, shadow: "outer" });
 
   test.done();
-};
-
-exports.shadowInline = function (test) {
-  var src = fs.readFileSync(__dirname + "/fixtures/shadow-inline.js", "utf8");
-
-  TestRun(test)
-    .addError(6, "'a' is already defined in outer scope.")
-    .addError(7, "'a' is already defined.")
-    .addError(7, "'a' is already defined in outer scope.")
-    .addError(17, "'a' is already defined.")
-    .addError(27, "'a' is already defined.")
-    .addError(42, "Bad option value.")
-    .addError(47, "'a' is already defined.")
-    .test(src);
-
-  test.done();
-};
+}
 
 /**
  * Option `latedef` allows you to prohibit the use of variable before their
@@ -133,19 +117,6 @@ exports.latedef = function (test) {
   test.done();
 };
 
-exports.latedefInline = function (test) {
-  var src  = fs.readFileSync(__dirname + '/fixtures/latedef-inline.js', 'utf8');
-
-  TestRun(test)
-    .addError(4, "'foo' was used before it was defined.")
-    .addError(6, "'a' was used before it was defined.")
-    .addError(22, "'a' was used before it was defined.")
-    .addError(26, "Bad option value.")
-    .test(src);
-
-  test.done();
-};
-
 exports.notypeof = function (test) {
   var src = fs.readFileSync(__dirname + '/fixtures/typeofcomp.js', 'utf8');
 
@@ -714,14 +685,6 @@ exports['unused overrides'] = function (test) {
     .addError(3, "'i' is defined but never used.")
     .test(code, {es3: true, unused: "strict"});
 
-  code = ['/*jshint unused:badoption */', 'function foo(a, b) {', 'var i = 3;', '}', 'foo();'];
-  TestRun(test)
-    .addError(1, "Bad option value.")
-    .addError(2, "'b' is defined but never used.")
-    .addError(2, "'a' is defined but never used.")
-    .addError(3, "'i' is defined but never used.")
-    .test(code, {es3: true, unused: "strict"});
-
   test.done();
 };
 
@@ -903,44 +866,27 @@ exports.supernew = function (test) {
 
 /** Option `bitwise` disallows the use of bitwise operators. */
 exports.bitwise = function (test) {
-  var unOps = [ "~" ];
-  var binOps = [ "&",  "|",  "^",  "<<",  ">>",  ">>>" ];
-  var modOps = [ "&=", "|=", "^=", "<<=", ">>=", ">>>=" ];
-
-  var i, op;
-
-  for (i = 0; i < unOps.length; i += 1) {
-    op = unOps[i];
-
-    TestRun(test)
-      .test("var b = " + op + "a;", {es3: true});
-
-    TestRun(test)
-      .addError(1, "Unexpected use of '" + op + "'.")
-      .test("var b = " + op + "a;", {es3: true, bitwise: true});
-  }
-
-  for (i = 0; i < binOps.length; i += 1) {
-    op = binOps[i];
-
-    TestRun(test)
-      .test("var c = a " + op + " b;", {es3: true});
+  var ops = [ '&', '|', '^', '<<', '>>', '>>>' ];
+  var moreTests = [
+    'var c = ~a;',
+    'c &= 2;'
+  ];
 
-    TestRun(test)
-      .addError(1, "Unexpected use of '" + op + "'.")
-      .test("var c = a " + op + " b;", {es3: true, bitwise: true});
+  // By default allow bitwise operators
+  for (var i = 0, op; op = ops[i]; i += 1) {
+    TestRun(test).test('var c = a ' + op + ' b;', {es3: true});
   }
+  TestRun(test).test(moreTests, {es3: true});
 
-  for (i = 0; i < modOps.length; i += 1) {
-    op = modOps[i];
-
-    TestRun(test)
-      .test("b " + op + " a;", {es3: true});
-
+  for (i = 0, op = null; op = ops[i]; i += 1) {
     TestRun(test)
       .addError(1, "Unexpected use of '" + op + "'.")
-      .test("b " + op + " a;", {es3: true, bitwise: true});
+      .test('var c = a ' + op + ' b;', { es3: true, bitwise: true });
   }
+  TestRun(test)
+    .addError(1, "Unexpected '~'.")
+    .addError(2, "Unexpected use of '&='.")
+    .test(moreTests, { es3: true, bitwise: true });
 
   test.done();
 };
@@ -1880,8 +1826,6 @@ singleGroups.bindingPower.singleExpr = function (test) {
     "var i = 2 * (3 - 4 - 5) * 6;",
     "var j = (a = 1) + 2;",
     "var j = (a += 1) / 2;",
-    "var k = 'foo' + ('bar' ? 'baz' : 'qux');",
-    "var l = 1 + (0 || 3);",
     // Invalid forms:
     "var j = 2 * ((3 - 4) - 5) * 6;",
     "var k = 2 * (3 - (4 - 5)) * 6;",
@@ -1905,6 +1849,8 @@ singleGroups.bindingPower.singleExpr = function (test) {
   ];
 
   TestRun(test)
+    .addError(12, "Unnecessary grouping operator.")
+    .addError(13, "Unnecessary grouping operator.")
     .addError(14, "Unnecessary grouping operator.")
     .addError(15, "Unnecessary grouping operator.")
     .addError(16, "Unnecessary grouping operator.")
@@ -1922,8 +1868,6 @@ singleGroups.bindingPower.singleExpr = function (test) {
     .addError(28, "Unnecessary grouping operator.")
     .addError(29, "Unnecessary grouping operator.")
     .addError(30, "Unnecessary grouping operator.")
-    .addError(31, "Unnecessary grouping operator.")
-    .addError(32, "Unnecessary grouping operator.")
     .test(code, { singleGroups: true });
 
   test.done();
@@ -2122,21 +2066,6 @@ singleGroups.newLine = function(test) {
   test.done();
 };
 
-singleGroups.lineNumber = function (test) {
-  var code = [
-    "var x = (",
-    "  1",
-    ")",
-    ";"
-  ];
-
-  TestRun(test)
-    .addError(1, "Unnecessary grouping operator.")
-    .test(code, { singleGroups: true });
-
-  test.done();
-};
-
 exports.elision = function (test) {
   var code = [
     "var a = [1,,2];",
@@ -2173,13 +2102,3 @@ exports.elision = function (test) {
 
   test.done();
 };
-
-exports.badInlineOptionValue = function (test) {
-  var src = [ "/* jshint bitwise:batcrazy */" ];
-
-  TestRun(test)
-    .addError(1, "Bad option value.")
-    .test(src);
-
-  test.done();
-};
diff --git a/tests/unit/parser.js b/tests/unit/parser.js
index 4f4807b8..ffa9e33c 100644
--- a/tests/unit/parser.js
+++ b/tests/unit/parser.js
@@ -534,11 +534,6 @@ exports.testRegexRegressions = function (test) {
   TestRun(test).test("var exp = /function(.*){/gi;", {esnext: true});
   TestRun(test).test("var exp = /function(.*){/gi;", {moz: true});
 
-  TestRun(test).test("var exp = /\\[\\]/;", {es3: true});
-  TestRun(test).test("var exp = /\\[\\]/;", {}); // es5
-  TestRun(test).test("var exp = /\\[\\]/;", {esnext: true});
-  TestRun(test).test("var exp = /\\[\\]/;", {moz: true});
-
   test.done();
 };
 
@@ -547,7 +542,6 @@ exports.strings = function (test) {
     "var a = '\u0012\\r';",
     "var b = \'\\g\';",
     "var c = '\\u0022\\u0070\\u005C';",
-    "var d = '\\\\';",
     "var e = '\\x6b..\\x6e';",
     "var f = '\\b\\f\\n\\/';",
     "var g = 'ax",
@@ -556,23 +550,8 @@ exports.strings = function (test) {
   var run = TestRun(test)
     .addError(1, "Control character in string: <non-printable>.", {character: 10})
     .addError(1, "This character may get silently deleted by one or more browsers.")
-    .addError(7, "Unclosed string.")
-    .addError(7, "Missing semicolon.");
-  run.test(code, {es3: true});
-  run.test(code, {}); // es5
-  run.test(code, {esnext: true});
-  run.test(code, {moz: true});
-
-  test.done();
-};
-
-exports.badStrings = function (test) {
-  var code = [
-    "var a = '\\uNOTHEX';"
-  ];
-
-  var run = TestRun(test)
-    .addError(1, "Unexpected 'uNOTH'.");
+    .addError(6, "Unclosed string.")
+    .addError(6, "Missing semicolon.");
   run.test(code, {es3: true});
   run.test(code, {}); // es5
   run.test(code, {esnext: true});
@@ -793,36 +772,6 @@ exports.testIdentifiers = function (test) {
   test.done();
 };
 
-exports.badIdentifiers = function (test) {
-  var badUnicode = [
-    "var \\uNOTHEX;"
-  ];
-
-  var run = TestRun(test)
-    .addError(1, "Unexpected '\\'.")
-    .addError(1, "Expected an identifier and instead saw ''.")
-    .addError(1, "Missing semicolon.");
-  run.test(badUnicode, {es3: true});
-  run.test(badUnicode, {}); // es5
-  run.test(badUnicode, {esnext: true});
-  run.test(badUnicode, {moz: true});
-
-  var invalidUnicodeIdent = [
-    "var \\u0000;"
-  ];
-
-  var run = TestRun(test)
-    .addError(1, "Unexpected '\\'.")
-    .addError(1, "Expected an identifier and instead saw ''.")
-    .addError(1, "Missing semicolon.");
-  run.test(invalidUnicodeIdent, {es3: true});
-  run.test(invalidUnicodeIdent, {}); // es5
-  run.test(invalidUnicodeIdent, {esnext: true});
-  run.test(invalidUnicodeIdent, {moz: true});
-
-  test.done();
-};
-
 exports["regression for GH-878"] = function (test) {
   var src = fs.readFileSync(__dirname + "/fixtures/gh878.js", "utf8");
 
@@ -3981,81 +3930,48 @@ exports["object ComputedPropertyName"] = function (test) {
 
 exports["spread & rest operator support"] = function (test) {
   var code = [
-    // 1
-    // Spread Identifier
+    // spread operator
+    "function foo(a, b, c) {",
+    "  console.log(a, b, c); ",
+    "}",
+    "var args = [ 0, 1, 2 ];",
     "foo(...args);",
 
-    // 2
-    // Spread Array Literal
-    "foo(...[]);",
-
-    // 3, 4
-    // Spread String Literal
-    "foo(...'');",
-    'foo(..."");',
-
-    // 5
-    // Spread Group
-    "foo(...([]));",
-
-    // 6, 7, 8
-    // Spread Operator
+    // spread operator
     "let initial = [ 1, 2, 3, 4, 5 ];",
     "let extended = [ ...initial, 6, 7, 8, 9 ];",
-    "let nest = [ ...[], 6, 7, 8, 9 ];",
-
-    // 9
-    // Rest Operator
-    "function foo(...args) {}",
 
-    // 10
-    // Rest Operator (Fat Arrow Params)
-    "let bar = (...args) => args;",
+    // rest operator
+    "(function foo(i, j, ...args) {",
+    "  return args;",
+    "}());",
 
-    // 11
-    "foo(...[].entries());",
-
-    // 12
-    "foo(...(new Map()).set('a', 1).values());"
+    // rest operator on a fat arrow function
+    "let bar = (...args) => args;"
   ];
 
-  TestRun(test)
-    .test(code, {esnext: true});
+  var run = TestRun(test);
+  run.test(code, {esnext: true});
 
-  TestRun(test)
-    .addError(1, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .addError(2, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .addError(3, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .addError(4, "'spread/rest operator' is only available in ES6 (use esnext option).")
+  run = TestRun(test)
+    .addError(11, "'arrow function syntax (=>)' is only available in ES6 (use esnext option).")
     .addError(5, "'spread/rest operator' is only available in ES6 (use esnext option).")
     .addError(7, "'spread/rest operator' is only available in ES6 (use esnext option).")
     .addError(8, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .addError(9, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .addError(10, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .addError(10, "'arrow function syntax (=>)' is only available in ES6 (use esnext option).")
-    .addError(11, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .addError(12, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .test(code, {moz: true});
+    .addError(11, "'spread/rest operator' is only available in ES6 (use esnext option).");
 
-  TestRun(test)
-    .addError(1, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .addError(2, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .addError(3, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .addError(4, "'spread/rest operator' is only available in ES6 (use esnext option).")
+  run.test(code, {moz: true});
+
+  run = TestRun(test)
     .addError(5, "'spread/rest operator' is only available in ES6 (use esnext option).")
     .addError(6, "'let' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
     .addError(7, "'let' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
-
     .addError(7, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .addError(8, "'let' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
     .addError(8, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .addError(9, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .addError(10, "'let' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
-    .addError(10, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .addError(10, "'arrow function syntax (=>)' is only available in ES6 (use esnext option).")
+    .addError(11, "'let' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
     .addError(11, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .addError(12, "'spread/rest operator' is only available in ES6 (use esnext option).")
-    .test(code);
+    .addError(11, "'arrow function syntax (=>)' is only available in ES6 (use esnext option).");
+  run.test(code);
 
   test.done();
 };
@@ -4399,6 +4315,30 @@ exports["class method this"] = function (test) {
   test.done();
 };
 
+exports.classExpression = function (test) {
+  var code = [
+    "void class MyClass {",
+    "  constructor() { MyClass = null; }",
+    "  method() { MyClass = null; }",
+    "  static method() { MyClass = null; }",
+    "  get accessor() { MyClass = null; }",
+    "  set accessor() { MyClass = null; }",
+    "};",
+    "void MyClass;"
+  ];
+
+  TestRun(test)
+    .addError(2, "'MyClass' is a function.")
+    .addError(3, "'MyClass' is a function.")
+    .addError(4, "'MyClass' is a function.")
+    .addError(5, "'MyClass' is a function.")
+    .addError(6, "'MyClass' is a function.")
+    .addError(8, "'MyClass' is not defined.")
+    .test(code, { esnext: true, undef: true });
+
+  test.done();
+};
+
 exports["test for GH-1018"] = function (test) {
   var code = [
     "if (a = 42) {}",