Repository: jshint/jshint
Pull request #2214 (2015-02-27 21:59:43)

[[FIX]] Make let variables in the closure shadow predefs
There is a problem when parsing an identifier that would resolve to a
predefined read-only global through the function scope chain.

When checking the block scope for the same identifier only the innermost
block is considered, so while this works:

    /* global foo: false */
    function x() {
      let foo;
      foo = {};
    }

... a W020 read only warning is generated when moving the assignment
into a subscope:

    /* global foo: false */
    function x() {
      let foo;
      {
        foo = {};
      }
    }

By checking the entire block scope chain for the identifier the
assignment generates no warning, as is expected.


Patch:
diff --git a/dist/jshint-rhino.js b/dist/jshint-rhino.js
index 4ca658db..ecc62b48 100755
--- a/dist/jshint-rhino.js
+++ b/dist/jshint-rhino.js
@@ -1,6 +1,6 @@
 #!/usr/bin/env rhino
 var window = {};
-/*! 2.6.1 */
+/*! 2.6.0 */
 var JSHINT;
 if (typeof window === 'undefined') window = {};
 (function () {
@@ -2892,13 +2892,7 @@ var Token = {
   RegExp: 9,
   TemplateHead: 10,
   TemplateMiddle: 11,
-  TemplateTail: 12,
-  NoSubstTemplate: 13
-};
-
-var Context = {
-  Block: 1,
-  Template: 2
+  TemplateTail: 12
 };
 
 // Object that handles postponed lexing verifications that checks the parsed
@@ -2978,8 +2972,9 @@ function Lexer(source) {
   this.from = 1;
   this.input = "";
   this.inComment = false;
-  this.context = [];
-  this.templateStarts = [];
+  this.inTemplate = false;
+  this.templateLine = null;
+  this.templateChar = null;
 
   for (var i = 0; i < state.option.indent; i += 1) {
     state.tab += " ";
@@ -2989,26 +2984,6 @@ function Lexer(source) {
 Lexer.prototype = {
   _lines: [],
 
-  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;
@@ -3102,6 +3077,8 @@ Lexer.prototype = {
     case ")":
     case ";":
     case ",":
+    case "{":
+    case "}":
     case "[":
     case "]":
     case ":":
@@ -3112,24 +3089,6 @@ 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 {
@@ -3826,17 +3785,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":
@@ -3887,89 +3836,76 @@ Lexer.prototype = {
    */
   scanTemplateLiteral: function(checks) {
     var tokenType;
-    var value = "";
+    var value = '';
     var ch;
-    var startLine = this.line;
-    var startChar = this.char;
-    var depth = this.templateStarts.length;
 
-    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;
+    // 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;
       this.skip(1);
-      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.
+    } else if (this.peek() !== '}') {
+      // If we're in a template, and we don't have a '}', lex something else instead.
       return null;
     }
 
     while (this.peek() !== "`") {
       while ((ch = this.peek()) === "") {
-        value += "\n";
         if (!this.nextLine()) {
-          // Unclosed template literal --- point to the starting "`"
-          var startPos = this.templateStarts.pop();
+          // Unclosed template literal --- point to the starting line, or the EOF?
+          tokenType = this.inTemplate ? Token.TemplateHead : Token.TemplateMiddle;
+          this.inTemplate = false;
           this.trigger("error", {
             code: "E052",
-            line: startPos.line,
-            character: startPos.char
+            line: this.templateLine,
+            character: this.templateChar
           });
           return {
             type: tokenType,
             value: value,
-            startLine: startLine,
-            startChar: startChar,
-            isUnclosed: true,
-            depth: depth,
-            context: this.popContext()
+            isUnclosed: true
           };
         }
       }
 
       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,
-          startLine: startLine,
-          startChar: startChar,
-          isUnclosed: false,
-          depth: depth,
-          context: this.currentContext()
+          isUnclosed: false
         };
       } else if (ch === '\\') {
         var escape = this.scanEscapeSequence(checks);
         value += escape.char;
         this.skip(escape.jump);
-      } else if (ch !== '`') {
+      } else if (ch === '`') {
+        break;
+      } else {
         // Otherwise, append the value and continue.
         value += ch;
         this.skip(1);
       }
     }
 
-    // Final value is either NoSubstTemplate or TemplateTail
-    tokenType = tokenType === Token.TemplateHead ? Token.NoSubstTemplate : Token.TemplateTail;
+    // Final value is either TokenTail or NoSubstititionTemplate --- essentially a string
+    tokenType = this.inTemplate ? Token.TemplateTail : Token.StringLiteral;
+    this.inTemplate = false;
     this.skip(1);
-    this.templateStarts.pop();
 
     return {
       type: tokenType,
       value: value,
-      startLine: startLine,
-      startChar: startChar,
       isUnclosed: false,
-      depth: depth,
-      context: this.popContext()
+      quote: "`"
     };
   },
 
@@ -4055,8 +3991,6 @@ Lexer.prototype = {
           return {
             type: Token.StringLiteral,
             value: value,
-            startLine: startLine,
-            startChar: startChar,
             isUnclosed: true,
             quote: quote
           };
@@ -4094,8 +4028,6 @@ Lexer.prototype = {
     return {
       type: Token.StringLiteral,
       value: value,
-      startLine: startLine,
-      startChar: startChar,
       isUnclosed: false,
       quote: quote
     };
@@ -4500,21 +4432,6 @@ 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;
@@ -4554,57 +4471,38 @@ 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; });
 
-        return create("(string)", token.value, null, token);
+        return create("(string)", token.value);
 
       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, null, token);
+        return create("(template)", token.value);
 
       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, null, token);
+        return create("(template middle)", token.value);
 
       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, 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);
+        return create("(template tail)", token.value);
 
       case Token.Identifier:
         this.trigger("Identifier", {
@@ -4690,7 +4588,6 @@ Lexer.prototype = {
 };
 
 exports.Lexer = Lexer;
-exports.Context = Context;
 
 },{"../data/ascii-identifier-data.js":1,"../data/non-ascii-identifier-part-only.js":2,"../data/non-ascii-identifier-start.js":3,"./reg.js":17,"./state.js":18,"events":5,"underscore":12}],14:[function(require,module,exports){
 "use strict";
@@ -4897,9 +4794,7 @@ var warnings = {
   W125: "This line contains non-breaking spaces: http://jshint.com/doc/options/#nonbsp",
   W126: "Unnecessary grouping operator.",
   W127: "Unexpected use of a comma operator.",
-  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."
+  W128: "Empty array elements require elision=true."
 };
 
 var info = {
@@ -5028,11 +4923,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,
 
@@ -5151,11 +5041,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,
 
@@ -5178,11 +5063,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,
 
@@ -5207,11 +5087,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,
 
@@ -5265,10 +5140,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
   },
@@ -5310,11 +5181,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,
 
@@ -5355,14 +5221,6 @@ exports.bool = {
     */
     evil        : true,
 
-    /**
-     * This option supresses 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 prohibits the use of unary increment and decrement
      * operators.  Some people think that `++` and `--` reduces the quality of
@@ -5400,11 +5258,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,
 
@@ -5426,11 +5279,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,
 
@@ -5442,11 +5290,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,
 
@@ -5720,19 +5563,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,
 
@@ -5766,10 +5601,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,
 
@@ -6081,12 +5912,18 @@ 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 !== "'") {
@@ -6180,47 +6017,51 @@ exports.reservedVars = {
 };
 
 exports.ecmaIdentifiers = {
-  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
-  }
+  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
 };
 
 // Global variables commonly provided by a web browser environment.
@@ -6840,11 +6681,7 @@ exports.yui = {
 exports.mocha = {
   // BDD
   describe    : false,
-  xdescribe   : false,
   it          : false,
-  xit         : false,
-  context     : false,
-  xcontext    : false,
   before      : false,
   after       : false,
   beforeEach  : false,
@@ -7081,12 +6918,8 @@ var JSHINT = (function() {
 
     processenforceall();
 
-    if (!state.option.es3) {
-      combine(predefined, vars.ecmaIdentifiers[5]);
-    }
-
     if (state.option.esnext) {
-      combine(predefined, vars.ecmaIdentifiers[6]);
+      combine(predefined, vars.newEcmaIdentifiers);
     }
 
     if (state.option.couch) {
@@ -7221,9 +7054,7 @@ var JSHINT = (function() {
   }
 
   function isundef(scope, code, token, a) {
-    if (!state.ignored[code] && state.option.undef !== false) {
-      JSHINT.undefs.push([scope, code, token, a]);
-    }
+    return JSHINT.undefs.push([scope, code, token, a]);
   }
 
   function removeIgnoredMessages() {
@@ -7716,7 +7547,7 @@ var JSHINT = (function() {
       return true;
     }
     if (isInfix(next) === isInfix(curr) || (curr.id === "yield" && state.option.inMoz(true))) {
-      return curr.line !== startLine(next);
+      return curr.line !== next.line;
     }
     return false;
   }
@@ -7761,9 +7592,13 @@ var JSHINT = (function() {
     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 !== startLine(state.tokens.curr) &&
+      state.tokens.prev.line < state.tokens.curr.line &&
       _.contains(["]", ")"], state.tokens.prev.id) &&
       _.contains(["[", "("], state.tokens.curr.id);
 
@@ -7786,9 +7621,7 @@ var JSHINT = (function() {
         error("E030", state.tokens.curr, state.tokens.curr.id);
       }
 
-      // TODO: use pratt mechanics rather than special casing template tokens
-      while ((rbp < state.tokens.next.lbp || state.tokens.next.type === "(template)") &&
-              !isEndOfExpr()) {
+      while (rbp < state.tokens.next.lbp && !isEndOfExpr()) {
         isArray = state.tokens.curr.value === "Array";
         isObject = state.tokens.curr.value === "Object";
 
@@ -7839,27 +7672,23 @@ 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 !== startLine(right)) {
+    if (!state.option.laxbreak && left.line !== right.line) {
       warning("W014", right, right.value);
     }
   }
 
   function nolinebreak(t) {
     t = t || state.tokens.curr;
-    if (t.line !== startLine(state.tokens.next)) {
+    if (t.line !== state.tokens.next.line) {
       warning("E022", t, t.value);
     }
   }
 
   function nobreakcomma(left, right) {
-    if (left.line !== startLine(right)) {
+    if (left.line !== right.line) {
       if (!state.option.laxcomma) {
         if (comma.first) {
           warning("I001");
@@ -7873,6 +7702,10 @@ 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(",");
@@ -8060,7 +7893,7 @@ var JSHINT = (function() {
       nobreaknonadjacent(state.tokens.prev, state.tokens.curr);
 
       this.left = left;
-      this.right = doFunction({ type: "arrow", loneArg: left });
+      this.right = doFunction(undefined, undefined, false, { loneArg: left });
       return this;
     };
     return x;
@@ -8130,27 +7963,6 @@ 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",
@@ -8307,8 +8119,9 @@ 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) {
+  function optionalidentifier(fnparam, prop, preserve, exported) {
     if (!state.tokens.next.identifier) {
       return;
     }
@@ -8320,6 +8133,10 @@ 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;
     }
@@ -8341,8 +8158,9 @@ 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
-  function identifier(fnparam, prop) {
-    var i = optionalidentifier(fnparam, prop, false);
+  // `exported` means that the identifier token should be exported.
+  function identifier(fnparam, prop, exported) {
+    var i = optionalidentifier(fnparam, prop, false, exported);
     if (i) {
       return i;
     }
@@ -8396,14 +8214,12 @@ 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 !== "}" ||
-          startLine(state.tokens.next) !== state.tokens.curr.line) {
+          state.tokens.next.line !== state.tokens.curr.line) {
           warningAt("W033", state.tokens.curr.line, state.tokens.curr.character);
         }
       }
@@ -8892,37 +8708,20 @@ var JSHINT = (function() {
     }
   };
 
-  var baseTemplateSyntax = {
+  state.syntax["(template)"] = {
+    type: "(template)",
     lbp: 0,
     identifier: false,
-    template: true,
+    fud: doTemplateLiteral
   };
-  state.syntax["(template)"] = _.extend({
-    type: "(template)",
-    nud: doTemplateLiteral,
-    led: doTemplateLiteral,
-    noSubst: false
-  }, baseTemplateSyntax);
-
-  state.syntax["(template middle)"] = _.extend({
-    type: "(template middle)",
-    middle: true,
-    noSubst: false
-  }, baseTemplateSyntax);
-
-  state.syntax["(template tail)"] = _.extend({
-    type: "(template tail)",
-    tail: true,
-    noSubst: false
-  }, baseTemplateSyntax);
-
-  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("(template middle)", function() {
+    return this;
+  });
+
+  type("(template tail)", function() {
+    return this;
+  });
 
   type("(regexp)", function() {
     return this;
@@ -8985,16 +8784,11 @@ var JSHINT = (function() {
   infix(",", function(left, that) {
     var expr;
     that.exprs = [left];
-
-    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);
@@ -9093,10 +8887,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;
@@ -9105,7 +8896,8 @@ var JSHINT = (function() {
       }
       return left;
     }
-
+    that.left = left;
+    that.right = right;
     return that;
   }, 130);
   prefix("+", "num");
@@ -9148,11 +8940,7 @@ var JSHINT = (function() {
   state.syntax["--"].exps = true;
   prefix("delete", function() {
     var p = expression(10);
-    if (!p) {
-      return this;
-    }
-
-    if (p.id !== "." && p.id !== "[") {
+    if (!p || (p.id !== "." && p.id !== "[")) {
       warning("W051");
     }
     this.first = p;
@@ -9167,7 +8955,7 @@ var JSHINT = (function() {
 
   prefix("~", function() {
     if (state.option.bitwise) {
-      warning("W016", this, "~");
+      warning("W052", this, "~");
     }
     this.arity = "unary";
     expression(150);
@@ -9178,42 +8966,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);
@@ -9324,9 +9077,7 @@ var JSHINT = (function() {
     }
 
     if (!state.option.evil && (m === "eval" || m === "execScript")) {
-      if (isGlobalEval(left, state, funct)) {
-        warning("W061");
-      }
+      warning("W061");
     }
 
     return that;
@@ -9434,7 +9185,7 @@ var JSHINT = (function() {
     // current token marks the beginning of a "fat arrow" function and parsing
     // should proceed accordingly.
     if (pn.value === "=>") {
-      return doFunction({ type: "arrow", parsedOpening: true });
+      return doFunction(null, null, null, { parsedParen: true });
     }
 
     var exprs = [];
@@ -9446,11 +9197,6 @@ var JSHINT = (function() {
         if (state.tokens.next.id !== ",") {
           break;
         }
-
-        if (state.option.nocomma) {
-          warning("W127");
-        }
-
         comma();
       }
     }
@@ -9503,7 +9249,7 @@ var JSHINT = (function() {
       }
 
       if (!isNecessary) {
-        warning("W126", opening);
+        warning("W126");
       }
 
       ret.paren = true;
@@ -9518,9 +9264,7 @@ var JSHINT = (function() {
     var e = expression(10), s;
     if (e && e.type === "(string)") {
       if (!state.option.evil && (e.value === "eval" || e.value === "execScript")) {
-        if (isGlobalEval(left, state, funct)) {
-          warning("W061");
-        }
+        warning("W061", that);
       }
 
       countMember(e.value);
@@ -9605,7 +9349,7 @@ var JSHINT = (function() {
     } else if (blocktype.isDestAssign && !state.option.inESNext()) {
       warning("W104", state.tokens.curr, "destructuring assignment");
     }
-    var b = state.tokens.curr.line !== startLine(state.tokens.next);
+    var b = state.tokens.curr.line !== state.tokens.next.line;
     this.first = [];
     if (b) {
       indent += state.option.indent;
@@ -9699,22 +9443,14 @@ var JSHINT = (function() {
     return id;
   }
 
-  /**
-   * @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) {
+  function functionparams(fatarrow) {
     var next;
     var params = [];
     var ident;
     var tokens = [];
     var t;
     var pastDefault = false;
-    var loneArg = options && options.loneArg;
+    var loneArg = fatarrow && fatarrow.loneArg;
 
     if (loneArg && loneArg.identifier === true) {
       addlabel(loneArg.value, { type: "unused", token: loneArg });
@@ -9723,7 +9459,7 @@ var JSHINT = (function() {
 
     next = state.tokens.next;
 
-    if (!options || !options.parsedOpening) {
+    if (!fatarrow || !fatarrow.parsedParen) {
       advance("(");
     }
 
@@ -9839,81 +9575,47 @@ var JSHINT = (function() {
     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();
-        }
+  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?
       }
     }
-
     return {
       id: "(template)",
-      type: "(template)",
-      tag: left
+      type: "(template)"
     };
-
-    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} [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.
+   * @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.
    */
-  function doFunction(options) {
-    var f, name, isStatement, classExprBinding, isGenerator, isArrow;
+  function doFunction(name, statement, generator, fatarrow) {
+    var f;
     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)": isStatement,
+      "(statement)": statement,
       "(context)":   funct,
-      "(generator)": isGenerator
+      "(generator)": generator ? true : null
     });
 
     f = funct;
@@ -9925,26 +9627,22 @@ var JSHINT = (function() {
       addlabel(name, { type: "function" });
     }
 
-    if (classExprBinding) {
-      addlabel(classExprBinding, { type: "function" });
-    }
-
-    funct["(params)"] = functionparams(options);
+    funct["(params)"] = functionparams(fatarrow);
     funct["(metrics)"].verifyMaxParametersPerFunction(funct["(params)"]);
 
-    if (isArrow) {
+    if (fatarrow) {
       if (!state.option.esnext) {
         warning("W119", state.tokens.curr, "arrow function syntax (=>)");
       }
 
-      if (!options.loneArg) {
+      if (!fatarrow.loneArg) {
         advance("=>");
       }
     }
 
-    block(false, true, true, isArrow);
+    block(false, true, true, !!fatarrow);
 
-    if (!state.option.noyield && isGenerator &&
+    if (!state.option.noyield && generator &&
         funct["(generator)"] !== "yielded") {
       warning("W124", state.tokens.curr);
     }
@@ -10061,7 +9759,7 @@ var JSHINT = (function() {
       var b, f, i, p, t, g, nextVal;
       var props = {}; // All properties, including accessors
 
-      b = state.tokens.curr.line !== startLine(state.tokens.next);
+      b = state.tokens.curr.line !== state.tokens.next.line;
       if (b) {
         indent += state.option.indent;
         if (state.tokens.next.from === indent + state.option.indent) {
@@ -10108,6 +9806,7 @@ 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");
@@ -10142,7 +9841,7 @@ var JSHINT = (function() {
               if (!state.option.inESNext()) {
                 warning("W104", state.tokens.curr, "concise methods");
               }
-              doFunction({ type: g ? "generator" : null });
+              doFunction(null, undefined, g);
             } else {
               advance(":");
               expression(10);
@@ -10341,15 +10040,8 @@ var JSHINT = (function() {
           if (state.option.inESNext() && funct[t.id] === "const") {
             warning("E011", null, t.id);
           }
-          if (funct["(global)"]) {
-            if (predefined[t.id] === false) {
-              warning("W079", t.token, t.id);
-            } else if (!state.option.futurehostile) {
-              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 (funct["(global)"] && predefined[t.id] === false) {
+            warning("W079", t.token, t.id);
           }
           if (t.id) {
             addlabel(t.id, { type: "unused", token: t.token });
@@ -10476,20 +10168,18 @@ var JSHINT = (function() {
     return classdef.call(this, true);
   });
 
-  function classdef(isStatement) {
-
+  function classdef(stmt) {
     /*jshint validthis:true */
     if (!state.option.inESNext()) {
       warning("W104", state.tokens.curr, "class");
     }
-    if (isStatement) {
+    if (stmt) {
       // 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();
     }
@@ -10578,7 +10268,7 @@ var JSHINT = (function() {
           advance();
         }
         if (state.tokens.next.value !== "(") {
-          doFunction({ isStatement: true });
+          doFunction(undefined, c, false, null);
         }
       }
 
@@ -10606,11 +10296,7 @@ var JSHINT = (function() {
 
       propertyName(name);
 
-      doFunction({
-        isStatement: true,
-        type: isGenerator ? "generator" : null,
-        classExprBinding: c.namedExpr ? c.name : null
-      });
+      doFunction(null, c, isGenerator, null);
     }
 
     checkProperties(props);
@@ -10641,11 +10327,7 @@ var JSHINT = (function() {
     }
     addlabel(i, { type: "unction", token: state.tokens.curr });
 
-    doFunction({
-      name: i,
-      isStatement: true,
-      type: generator ? "generator" : null
-    });
+    doFunction(i, { statement: true }, generator);
     if (state.tokens.next.id === "(" && state.tokens.next.line === state.tokens.curr.line) {
       error("E039");
     }
@@ -10664,7 +10346,7 @@ var JSHINT = (function() {
     }
 
     var i = optionalidentifier();
-    var fn = doFunction({ name: i, type: generator ? "generator" : null });
+    var fn = doFunction(i, undefined, generator);
 
     function isVariable(name) { return name[0] !== "("; }
     function isLocal(name) { return fn[name] === "var"; }
@@ -11025,9 +10707,8 @@ 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();
       }
@@ -11138,7 +10819,7 @@ var JSHINT = (function() {
       nolinebreak(this);
 
     if (state.tokens.next.id !== ";" && !state.tokens.next.reach) {
-      if (state.tokens.curr.line === startLine(state.tokens.next)) {
+      if (state.tokens.curr.line === state.tokens.next.line) {
         if (funct[v] !== "label") {
           warning("W090", state.tokens.next, v);
         } else if (scope[v] !== funct) {
@@ -11165,7 +10846,7 @@ var JSHINT = (function() {
       nolinebreak(this);
 
     if (state.tokens.next.id !== ";" && !state.tokens.next.reach) {
-      if (state.tokens.curr.line === startLine(state.tokens.next)) {
+      if (state.tokens.curr.line === state.tokens.next.line) {
         if (funct[v] !== "label") {
           warning("W090", state.tokens.next, v);
         } else if (scope[v] !== funct) {
@@ -11185,7 +10866,7 @@ var JSHINT = (function() {
 
 
   stmt("return", function() {
-    if (this.line === startLine(state.tokens.next)) {
+    if (this.line === state.tokens.next.line) {
       if (state.tokens.next.id !== ";" && !state.tokens.next.reach) {
         this.first = expression(0);
 
@@ -11228,7 +10909,7 @@ var JSHINT = (function() {
       advance("*");
     }
 
-    if (this.line === startLine(state.tokens.next) || !state.option.inMoz(true)) {
+    if (this.line === state.tokens.next.line || !state.option.inMoz(true)) {
       if (delegatingYield ||
           (state.tokens.next.id !== ";" && !state.tokens.next.reach && state.tokens.next.nud)) {
 
@@ -11276,22 +10957,7 @@ var JSHINT = (function() {
       // ImportClause :: ImportedDefaultBinding
       this.name = identifier();
       addlabel(this.name, { type: "unused", token: state.tokens.curr });
-      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 === "*") {
+    } else if (state.tokens.next.id === "*") {
       // ImportClause :: NameSpaceImport
       advance("*");
       advance("as");
@@ -11300,7 +10966,6 @@ var JSHINT = (function() {
         addlabel(this.name, { type: "unused", token: state.tokens.curr });
       }
     } else {
-      // ImportClause :: NamedImports
       advance("{");
       for (;;) {
         if (state.tokens.next.value === "}") {
@@ -11340,9 +11005,6 @@ var JSHINT = (function() {
 
   stmt("export", function() {
     var ok = true;
-    var token;
-    var identifier;
-
     if (!state.option.inESNext()) {
       warning("W119", state.tokens.curr, "export");
       ok = false;
@@ -11354,7 +11016,6 @@ var JSHINT = (function() {
     }
 
     if (state.tokens.next.value === "*") {
-      // ExportDeclaration :: export * FromClause
       advance("*");
       advance("from");
       advance("(string)");
@@ -11362,50 +11023,23 @@ 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;
       }
-
-      token = peek();
-
-      expression(10);
-
-      if (state.tokens.next.id === "class") {
-        identifier = token.name;
-      } else {
-        identifier = token.value;
-      }
-
-      addlabel(identifier, {
-        type: "function", token: token
-      });
+      this.exportee = expression(10);
 
       return this;
     }
 
     if (state.tokens.next.value === "{") {
-      // ExportDeclaration :: export ExportClause
       advance("{");
-      var exportedTokens = [];
       for (;;) {
-        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();
+        var id;
+        exported[id = identifier(false, false, ok)] = ok;
+        if (ok) {
+          funct["(blockscope)"].setExported(id);
         }
 
         if (state.tokens.next.value === ",") {
@@ -11418,49 +11052,31 @@ 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);
     } 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);
     } 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);
     } 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;
@@ -11542,7 +11158,7 @@ var JSHINT = (function() {
         ret.isBlock = true;
         ret.notJson = true;
       }
-    } while (bracketStack > 0 && pn.id !== "(end)");
+    } while (bracketStack > 0 && pn.id !== "(end)" && i < 15);
     return ret;
   };
 
@@ -11757,7 +11373,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, "key", state.tokens.next.value);
+            warning("W075", state.tokens.next, state.tokens.next.value);
           } else if ((state.tokens.next.value === "__proto__" &&
             !state.option.proto) || (state.tokens.next.value === "__iterator__" &&
             !state.option.iterator)) {
@@ -11930,7 +11546,7 @@ var JSHINT = (function() {
     }
 
     predefined = Object.create(null);
-    combine(predefined, vars.ecmaIdentifiers[3]);
+    combine(predefined, vars.ecmaIdentifiers);
     combine(predefined, vars.reservedVars);
 
     combine(predefined, g || {});
diff --git a/dist/jshint.js b/dist/jshint.js
index 40743165..538f9a70 100644
--- a/dist/jshint.js
+++ b/dist/jshint.js
@@ -1,4 +1,4 @@
-/*! 2.6.1 */
+/*! 2.6.0 */
 var JSHINT;
 if (typeof window === 'undefined') window = {};
 (function () {
@@ -2890,13 +2890,7 @@ var Token = {
   RegExp: 9,
   TemplateHead: 10,
   TemplateMiddle: 11,
-  TemplateTail: 12,
-  NoSubstTemplate: 13
-};
-
-var Context = {
-  Block: 1,
-  Template: 2
+  TemplateTail: 12
 };
 
 // Object that handles postponed lexing verifications that checks the parsed
@@ -2976,8 +2970,9 @@ function Lexer(source) {
   this.from = 1;
   this.input = "";
   this.inComment = false;
-  this.context = [];
-  this.templateStarts = [];
+  this.inTemplate = false;
+  this.templateLine = null;
+  this.templateChar = null;
 
   for (var i = 0; i < state.option.indent; i += 1) {
     state.tab += " ";
@@ -2987,26 +2982,6 @@ function Lexer(source) {
 Lexer.prototype = {
   _lines: [],
 
-  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;
@@ -3100,6 +3075,8 @@ Lexer.prototype = {
     case ")":
     case ";":
     case ",":
+    case "{":
+    case "}":
     case "[":
     case "]":
     case ":":
@@ -3110,24 +3087,6 @@ 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 {
@@ -3824,17 +3783,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":
@@ -3885,89 +3834,76 @@ Lexer.prototype = {
    */
   scanTemplateLiteral: function(checks) {
     var tokenType;
-    var value = "";
+    var value = '';
     var ch;
-    var startLine = this.line;
-    var startChar = this.char;
-    var depth = this.templateStarts.length;
 
-    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;
+    // 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;
       this.skip(1);
-      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.
+    } else if (this.peek() !== '}') {
+      // If we're in a template, and we don't have a '}', lex something else instead.
       return null;
     }
 
     while (this.peek() !== "`") {
       while ((ch = this.peek()) === "") {
-        value += "\n";
         if (!this.nextLine()) {
-          // Unclosed template literal --- point to the starting "`"
-          var startPos = this.templateStarts.pop();
+          // Unclosed template literal --- point to the starting line, or the EOF?
+          tokenType = this.inTemplate ? Token.TemplateHead : Token.TemplateMiddle;
+          this.inTemplate = false;
           this.trigger("error", {
             code: "E052",
-            line: startPos.line,
-            character: startPos.char
+            line: this.templateLine,
+            character: this.templateChar
           });
           return {
             type: tokenType,
             value: value,
-            startLine: startLine,
-            startChar: startChar,
-            isUnclosed: true,
-            depth: depth,
-            context: this.popContext()
+            isUnclosed: true
           };
         }
       }
 
       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,
-          startLine: startLine,
-          startChar: startChar,
-          isUnclosed: false,
-          depth: depth,
-          context: this.currentContext()
+          isUnclosed: false
         };
       } else if (ch === '\\') {
         var escape = this.scanEscapeSequence(checks);
         value += escape.char;
         this.skip(escape.jump);
-      } else if (ch !== '`') {
+      } else if (ch === '`') {
+        break;
+      } else {
         // Otherwise, append the value and continue.
         value += ch;
         this.skip(1);
       }
     }
 
-    // Final value is either NoSubstTemplate or TemplateTail
-    tokenType = tokenType === Token.TemplateHead ? Token.NoSubstTemplate : Token.TemplateTail;
+    // Final value is either TokenTail or NoSubstititionTemplate --- essentially a string
+    tokenType = this.inTemplate ? Token.TemplateTail : Token.StringLiteral;
+    this.inTemplate = false;
     this.skip(1);
-    this.templateStarts.pop();
 
     return {
       type: tokenType,
       value: value,
-      startLine: startLine,
-      startChar: startChar,
       isUnclosed: false,
-      depth: depth,
-      context: this.popContext()
+      quote: "`"
     };
   },
 
@@ -4053,8 +3989,6 @@ Lexer.prototype = {
           return {
             type: Token.StringLiteral,
             value: value,
-            startLine: startLine,
-            startChar: startChar,
             isUnclosed: true,
             quote: quote
           };
@@ -4092,8 +4026,6 @@ Lexer.prototype = {
     return {
       type: Token.StringLiteral,
       value: value,
-      startLine: startLine,
-      startChar: startChar,
       isUnclosed: false,
       quote: quote
     };
@@ -4498,21 +4430,6 @@ 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;
@@ -4552,57 +4469,38 @@ 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; });
 
-        return create("(string)", token.value, null, token);
+        return create("(string)", token.value);
 
       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, null, token);
+        return create("(template)", token.value);
 
       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, null, token);
+        return create("(template middle)", token.value);
 
       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, 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);
+        return create("(template tail)", token.value);
 
       case Token.Identifier:
         this.trigger("Identifier", {
@@ -4688,7 +4586,6 @@ Lexer.prototype = {
 };
 
 exports.Lexer = Lexer;
-exports.Context = Context;
 
 },{"../data/ascii-identifier-data.js":1,"../data/non-ascii-identifier-part-only.js":2,"../data/non-ascii-identifier-start.js":3,"./reg.js":17,"./state.js":18,"events":5,"underscore":12}],14:[function(require,module,exports){
 "use strict";
@@ -4895,9 +4792,7 @@ var warnings = {
   W125: "This line contains non-breaking spaces: http://jshint.com/doc/options/#nonbsp",
   W126: "Unnecessary grouping operator.",
   W127: "Unexpected use of a comma operator.",
-  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."
+  W128: "Empty array elements require elision=true."
 };
 
 var info = {
@@ -5026,11 +4921,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,
 
@@ -5149,11 +5039,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,
 
@@ -5176,11 +5061,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,
 
@@ -5205,11 +5085,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,
 
@@ -5263,10 +5138,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
   },
@@ -5308,11 +5179,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,
 
@@ -5353,14 +5219,6 @@ exports.bool = {
     */
     evil        : true,
 
-    /**
-     * This option supresses 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 prohibits the use of unary increment and decrement
      * operators.  Some people think that `++` and `--` reduces the quality of
@@ -5398,11 +5256,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,
 
@@ -5424,11 +5277,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,
 
@@ -5440,11 +5288,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,
 
@@ -5718,19 +5561,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,
 
@@ -5764,10 +5599,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,
 
@@ -6079,12 +5910,18 @@ 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 !== "'") {
@@ -6178,47 +6015,51 @@ exports.reservedVars = {
 };
 
 exports.ecmaIdentifiers = {
-  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
-  }
+  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
 };
 
 // Global variables commonly provided by a web browser environment.
@@ -6838,11 +6679,7 @@ exports.yui = {
 exports.mocha = {
   // BDD
   describe    : false,
-  xdescribe   : false,
   it          : false,
-  xit         : false,
-  context     : false,
-  xcontext    : false,
   before      : false,
   after       : false,
   beforeEach  : false,
@@ -7079,12 +6916,8 @@ var JSHINT = (function() {
 
     processenforceall();
 
-    if (!state.option.es3) {
-      combine(predefined, vars.ecmaIdentifiers[5]);
-    }
-
     if (state.option.esnext) {
-      combine(predefined, vars.ecmaIdentifiers[6]);
+      combine(predefined, vars.newEcmaIdentifiers);
     }
 
     if (state.option.couch) {
@@ -7219,9 +7052,7 @@ var JSHINT = (function() {
   }
 
   function isundef(scope, code, token, a) {
-    if (!state.ignored[code] && state.option.undef !== false) {
-      JSHINT.undefs.push([scope, code, token, a]);
-    }
+    return JSHINT.undefs.push([scope, code, token, a]);
   }
 
   function removeIgnoredMessages() {
@@ -7714,7 +7545,7 @@ var JSHINT = (function() {
       return true;
     }
     if (isInfix(next) === isInfix(curr) || (curr.id === "yield" && state.option.inMoz(true))) {
-      return curr.line !== startLine(next);
+      return curr.line !== next.line;
     }
     return false;
   }
@@ -7759,9 +7590,13 @@ var JSHINT = (function() {
     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 !== startLine(state.tokens.curr) &&
+      state.tokens.prev.line < state.tokens.curr.line &&
       _.contains(["]", ")"], state.tokens.prev.id) &&
       _.contains(["[", "("], state.tokens.curr.id);
 
@@ -7784,9 +7619,7 @@ var JSHINT = (function() {
         error("E030", state.tokens.curr, state.tokens.curr.id);
       }
 
-      // TODO: use pratt mechanics rather than special casing template tokens
-      while ((rbp < state.tokens.next.lbp || state.tokens.next.type === "(template)") &&
-              !isEndOfExpr()) {
+      while (rbp < state.tokens.next.lbp && !isEndOfExpr()) {
         isArray = state.tokens.curr.value === "Array";
         isObject = state.tokens.curr.value === "Object";
 
@@ -7837,27 +7670,23 @@ 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 !== startLine(right)) {
+    if (!state.option.laxbreak && left.line !== right.line) {
       warning("W014", right, right.value);
     }
   }
 
   function nolinebreak(t) {
     t = t || state.tokens.curr;
-    if (t.line !== startLine(state.tokens.next)) {
+    if (t.line !== state.tokens.next.line) {
       warning("E022", t, t.value);
     }
   }
 
   function nobreakcomma(left, right) {
-    if (left.line !== startLine(right)) {
+    if (left.line !== right.line) {
       if (!state.option.laxcomma) {
         if (comma.first) {
           warning("I001");
@@ -7871,6 +7700,10 @@ 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(",");
@@ -8058,7 +7891,7 @@ var JSHINT = (function() {
       nobreaknonadjacent(state.tokens.prev, state.tokens.curr);
 
       this.left = left;
-      this.right = doFunction({ type: "arrow", loneArg: left });
+      this.right = doFunction(undefined, undefined, false, { loneArg: left });
       return this;
     };
     return x;
@@ -8128,27 +7961,6 @@ 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",
@@ -8305,8 +8117,9 @@ 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) {
+  function optionalidentifier(fnparam, prop, preserve, exported) {
     if (!state.tokens.next.identifier) {
       return;
     }
@@ -8318,6 +8131,10 @@ 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;
     }
@@ -8339,8 +8156,9 @@ 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
-  function identifier(fnparam, prop) {
-    var i = optionalidentifier(fnparam, prop, false);
+  // `exported` means that the identifier token should be exported.
+  function identifier(fnparam, prop, exported) {
+    var i = optionalidentifier(fnparam, prop, false, exported);
     if (i) {
       return i;
     }
@@ -8394,14 +8212,12 @@ 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 !== "}" ||
-          startLine(state.tokens.next) !== state.tokens.curr.line) {
+          state.tokens.next.line !== state.tokens.curr.line) {
           warningAt("W033", state.tokens.curr.line, state.tokens.curr.character);
         }
       }
@@ -8890,37 +8706,20 @@ var JSHINT = (function() {
     }
   };
 
-  var baseTemplateSyntax = {
+  state.syntax["(template)"] = {
+    type: "(template)",
     lbp: 0,
     identifier: false,
-    template: true,
+    fud: doTemplateLiteral
   };
-  state.syntax["(template)"] = _.extend({
-    type: "(template)",
-    nud: doTemplateLiteral,
-    led: doTemplateLiteral,
-    noSubst: false
-  }, baseTemplateSyntax);
-
-  state.syntax["(template middle)"] = _.extend({
-    type: "(template middle)",
-    middle: true,
-    noSubst: false
-  }, baseTemplateSyntax);
-
-  state.syntax["(template tail)"] = _.extend({
-    type: "(template tail)",
-    tail: true,
-    noSubst: false
-  }, baseTemplateSyntax);
-
-  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("(template middle)", function() {
+    return this;
+  });
+
+  type("(template tail)", function() {
+    return this;
+  });
 
   type("(regexp)", function() {
     return this;
@@ -8983,16 +8782,11 @@ var JSHINT = (function() {
   infix(",", function(left, that) {
     var expr;
     that.exprs = [left];
-
-    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);
@@ -9091,10 +8885,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;
@@ -9103,7 +8894,8 @@ var JSHINT = (function() {
       }
       return left;
     }
-
+    that.left = left;
+    that.right = right;
     return that;
   }, 130);
   prefix("+", "num");
@@ -9146,11 +8938,7 @@ var JSHINT = (function() {
   state.syntax["--"].exps = true;
   prefix("delete", function() {
     var p = expression(10);
-    if (!p) {
-      return this;
-    }
-
-    if (p.id !== "." && p.id !== "[") {
+    if (!p || (p.id !== "." && p.id !== "[")) {
       warning("W051");
     }
     this.first = p;
@@ -9165,7 +8953,7 @@ var JSHINT = (function() {
 
   prefix("~", function() {
     if (state.option.bitwise) {
-      warning("W016", this, "~");
+      warning("W052", this, "~");
     }
     this.arity = "unary";
     expression(150);
@@ -9176,42 +8964,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);
@@ -9322,9 +9075,7 @@ var JSHINT = (function() {
     }
 
     if (!state.option.evil && (m === "eval" || m === "execScript")) {
-      if (isGlobalEval(left, state, funct)) {
-        warning("W061");
-      }
+      warning("W061");
     }
 
     return that;
@@ -9432,7 +9183,7 @@ var JSHINT = (function() {
     // current token marks the beginning of a "fat arrow" function and parsing
     // should proceed accordingly.
     if (pn.value === "=>") {
-      return doFunction({ type: "arrow", parsedOpening: true });
+      return doFunction(null, null, null, { parsedParen: true });
     }
 
     var exprs = [];
@@ -9444,11 +9195,6 @@ var JSHINT = (function() {
         if (state.tokens.next.id !== ",") {
           break;
         }
-
-        if (state.option.nocomma) {
-          warning("W127");
-        }
-
         comma();
       }
     }
@@ -9501,7 +9247,7 @@ var JSHINT = (function() {
       }
 
       if (!isNecessary) {
-        warning("W126", opening);
+        warning("W126");
       }
 
       ret.paren = true;
@@ -9516,9 +9262,7 @@ var JSHINT = (function() {
     var e = expression(10), s;
     if (e && e.type === "(string)") {
       if (!state.option.evil && (e.value === "eval" || e.value === "execScript")) {
-        if (isGlobalEval(left, state, funct)) {
-          warning("W061");
-        }
+        warning("W061", that);
       }
 
       countMember(e.value);
@@ -9603,7 +9347,7 @@ var JSHINT = (function() {
     } else if (blocktype.isDestAssign && !state.option.inESNext()) {
       warning("W104", state.tokens.curr, "destructuring assignment");
     }
-    var b = state.tokens.curr.line !== startLine(state.tokens.next);
+    var b = state.tokens.curr.line !== state.tokens.next.line;
     this.first = [];
     if (b) {
       indent += state.option.indent;
@@ -9697,22 +9441,14 @@ var JSHINT = (function() {
     return id;
   }
 
-  /**
-   * @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) {
+  function functionparams(fatarrow) {
     var next;
     var params = [];
     var ident;
     var tokens = [];
     var t;
     var pastDefault = false;
-    var loneArg = options && options.loneArg;
+    var loneArg = fatarrow && fatarrow.loneArg;
 
     if (loneArg && loneArg.identifier === true) {
       addlabel(loneArg.value, { type: "unused", token: loneArg });
@@ -9721,7 +9457,7 @@ var JSHINT = (function() {
 
     next = state.tokens.next;
 
-    if (!options || !options.parsedOpening) {
+    if (!fatarrow || !fatarrow.parsedParen) {
       advance("(");
     }
 
@@ -9837,81 +9573,47 @@ var JSHINT = (function() {
     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();
-        }
+  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?
       }
     }
-
     return {
       id: "(template)",
-      type: "(template)",
-      tag: left
+      type: "(template)"
     };
-
-    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} [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.
+   * @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.
    */
-  function doFunction(options) {
-    var f, name, isStatement, classExprBinding, isGenerator, isArrow;
+  function doFunction(name, statement, generator, fatarrow) {
+    var f;
     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)": isStatement,
+      "(statement)": statement,
       "(context)":   funct,
-      "(generator)": isGenerator
+      "(generator)": generator ? true : null
     });
 
     f = funct;
@@ -9923,26 +9625,22 @@ var JSHINT = (function() {
       addlabel(name, { type: "function" });
     }
 
-    if (classExprBinding) {
-      addlabel(classExprBinding, { type: "function" });
-    }
-
-    funct["(params)"] = functionparams(options);
+    funct["(params)"] = functionparams(fatarrow);
     funct["(metrics)"].verifyMaxParametersPerFunction(funct["(params)"]);
 
-    if (isArrow) {
+    if (fatarrow) {
       if (!state.option.esnext) {
         warning("W119", state.tokens.curr, "arrow function syntax (=>)");
       }
 
-      if (!options.loneArg) {
+      if (!fatarrow.loneArg) {
         advance("=>");
       }
     }
 
-    block(false, true, true, isArrow);
+    block(false, true, true, !!fatarrow);
 
-    if (!state.option.noyield && isGenerator &&
+    if (!state.option.noyield && generator &&
         funct["(generator)"] !== "yielded") {
       warning("W124", state.tokens.curr);
     }
@@ -10059,7 +9757,7 @@ var JSHINT = (function() {
       var b, f, i, p, t, g, nextVal;
       var props = {}; // All properties, including accessors
 
-      b = state.tokens.curr.line !== startLine(state.tokens.next);
+      b = state.tokens.curr.line !== state.tokens.next.line;
       if (b) {
         indent += state.option.indent;
         if (state.tokens.next.from === indent + state.option.indent) {
@@ -10106,6 +9804,7 @@ 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");
@@ -10140,7 +9839,7 @@ var JSHINT = (function() {
               if (!state.option.inESNext()) {
                 warning("W104", state.tokens.curr, "concise methods");
               }
-              doFunction({ type: g ? "generator" : null });
+              doFunction(null, undefined, g);
             } else {
               advance(":");
               expression(10);
@@ -10339,15 +10038,8 @@ var JSHINT = (function() {
           if (state.option.inESNext() && funct[t.id] === "const") {
             warning("E011", null, t.id);
           }
-          if (funct["(global)"]) {
-            if (predefined[t.id] === false) {
-              warning("W079", t.token, t.id);
-            } else if (!state.option.futurehostile) {
-              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 (funct["(global)"] && predefined[t.id] === false) {
+            warning("W079", t.token, t.id);
           }
           if (t.id) {
             addlabel(t.id, { type: "unused", token: t.token });
@@ -10474,20 +10166,18 @@ var JSHINT = (function() {
     return classdef.call(this, true);
   });
 
-  function classdef(isStatement) {
-
+  function classdef(stmt) {
     /*jshint validthis:true */
     if (!state.option.inESNext()) {
       warning("W104", state.tokens.curr, "class");
     }
-    if (isStatement) {
+    if (stmt) {
       // 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();
     }
@@ -10576,7 +10266,7 @@ var JSHINT = (function() {
           advance();
         }
         if (state.tokens.next.value !== "(") {
-          doFunction({ isStatement: true });
+          doFunction(undefined, c, false, null);
         }
       }
 
@@ -10604,11 +10294,7 @@ var JSHINT = (function() {
 
       propertyName(name);
 
-      doFunction({
-        isStatement: true,
-        type: isGenerator ? "generator" : null,
-        classExprBinding: c.namedExpr ? c.name : null
-      });
+      doFunction(null, c, isGenerator, null);
     }
 
     checkProperties(props);
@@ -10639,11 +10325,7 @@ var JSHINT = (function() {
     }
     addlabel(i, { type: "unction", token: state.tokens.curr });
 
-    doFunction({
-      name: i,
-      isStatement: true,
-      type: generator ? "generator" : null
-    });
+    doFunction(i, { statement: true }, generator);
     if (state.tokens.next.id === "(" && state.tokens.next.line === state.tokens.curr.line) {
       error("E039");
     }
@@ -10662,7 +10344,7 @@ var JSHINT = (function() {
     }
 
     var i = optionalidentifier();
-    var fn = doFunction({ name: i, type: generator ? "generator" : null });
+    var fn = doFunction(i, undefined, generator);
 
     function isVariable(name) { return name[0] !== "("; }
     function isLocal(name) { return fn[name] === "var"; }
@@ -11023,9 +10705,8 @@ 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();
       }
@@ -11136,7 +10817,7 @@ var JSHINT = (function() {
       nolinebreak(this);
 
     if (state.tokens.next.id !== ";" && !state.tokens.next.reach) {
-      if (state.tokens.curr.line === startLine(state.tokens.next)) {
+      if (state.tokens.curr.line === state.tokens.next.line) {
         if (funct[v] !== "label") {
           warning("W090", state.tokens.next, v);
         } else if (scope[v] !== funct) {
@@ -11163,7 +10844,7 @@ var JSHINT = (function() {
       nolinebreak(this);
 
     if (state.tokens.next.id !== ";" && !state.tokens.next.reach) {
-      if (state.tokens.curr.line === startLine(state.tokens.next)) {
+      if (state.tokens.curr.line === state.tokens.next.line) {
         if (funct[v] !== "label") {
           warning("W090", state.tokens.next, v);
         } else if (scope[v] !== funct) {
@@ -11183,7 +10864,7 @@ var JSHINT = (function() {
 
 
   stmt("return", function() {
-    if (this.line === startLine(state.tokens.next)) {
+    if (this.line === state.tokens.next.line) {
       if (state.tokens.next.id !== ";" && !state.tokens.next.reach) {
         this.first = expression(0);
 
@@ -11226,7 +10907,7 @@ var JSHINT = (function() {
       advance("*");
     }
 
-    if (this.line === startLine(state.tokens.next) || !state.option.inMoz(true)) {
+    if (this.line === state.tokens.next.line || !state.option.inMoz(true)) {
       if (delegatingYield ||
           (state.tokens.next.id !== ";" && !state.tokens.next.reach && state.tokens.next.nud)) {
 
@@ -11274,22 +10955,7 @@ var JSHINT = (function() {
       // ImportClause :: ImportedDefaultBinding
       this.name = identifier();
       addlabel(this.name, { type: "unused", token: state.tokens.curr });
-      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 === "*") {
+    } else if (state.tokens.next.id === "*") {
       // ImportClause :: NameSpaceImport
       advance("*");
       advance("as");
@@ -11298,7 +10964,6 @@ var JSHINT = (function() {
         addlabel(this.name, { type: "unused", token: state.tokens.curr });
       }
     } else {
-      // ImportClause :: NamedImports
       advance("{");
       for (;;) {
         if (state.tokens.next.value === "}") {
@@ -11338,9 +11003,6 @@ var JSHINT = (function() {
 
   stmt("export", function() {
     var ok = true;
-    var token;
-    var identifier;
-
     if (!state.option.inESNext()) {
       warning("W119", state.tokens.curr, "export");
       ok = false;
@@ -11352,7 +11014,6 @@ var JSHINT = (function() {
     }
 
     if (state.tokens.next.value === "*") {
-      // ExportDeclaration :: export * FromClause
       advance("*");
       advance("from");
       advance("(string)");
@@ -11360,50 +11021,23 @@ 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;
       }
-
-      token = peek();
-
-      expression(10);
-
-      if (state.tokens.next.id === "class") {
-        identifier = token.name;
-      } else {
-        identifier = token.value;
-      }
-
-      addlabel(identifier, {
-        type: "function", token: token
-      });
+      this.exportee = expression(10);
 
       return this;
     }
 
     if (state.tokens.next.value === "{") {
-      // ExportDeclaration :: export ExportClause
       advance("{");
-      var exportedTokens = [];
       for (;;) {
-        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();
+        var id;
+        exported[id = identifier(false, false, ok)] = ok;
+        if (ok) {
+          funct["(blockscope)"].setExported(id);
         }
 
         if (state.tokens.next.value === ",") {
@@ -11416,49 +11050,31 @@ 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);
     } 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);
     } 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);
     } 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;
@@ -11540,7 +11156,7 @@ var JSHINT = (function() {
         ret.isBlock = true;
         ret.notJson = true;
       }
-    } while (bracketStack > 0 && pn.id !== "(end)");
+    } while (bracketStack > 0 && pn.id !== "(end)" && i < 15);
     return ret;
   };
 
@@ -11755,7 +11371,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, "key", state.tokens.next.value);
+            warning("W075", state.tokens.next, state.tokens.next.value);
           } else if ((state.tokens.next.value === "__proto__" &&
             !state.option.proto) || (state.tokens.next.value === "__iterator__" &&
             !state.option.iterator)) {
@@ -11928,7 +11544,7 @@ var JSHINT = (function() {
     }
 
     predefined = Object.create(null);
-    combine(predefined, vars.ecmaIdentifiers[3]);
+    combine(predefined, vars.ecmaIdentifiers);
     combine(predefined, vars.reservedVars);
 
     combine(predefined, g || {});
diff --git a/package.json b/package.json
index 9cc08dfa..9886572a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name":     "jshint",
-  "version":  "2.6.1",
+  "version":  "2.6.0",
   "homepage": "http://jshint.com/",
   "description": "Static analysis tool for JavaScript",
 
diff --git a/src/jshint.js b/src/jshint.js
index 989f565e..fa32c8c5 100644
--- a/src/jshint.js
+++ b/src/jshint.js
@@ -877,6 +877,10 @@ var JSHINT = (function() {
     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 !== startLine(state.tokens.curr) &&
@@ -902,9 +906,7 @@ var JSHINT = (function() {
         error("E030", state.tokens.curr, state.tokens.curr.id);
       }
 
-      // TODO: use pratt mechanics rather than special casing template tokens
-      while ((rbp < state.tokens.next.lbp || state.tokens.next.type === "(template)") &&
-              !isEndOfExpr()) {
+      while (rbp < state.tokens.next.lbp && !isEndOfExpr()) {
         isArray = state.tokens.curr.value === "Array";
         isObject = state.tokens.curr.value === "Object";
 
@@ -1512,8 +1514,6 @@ 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.
@@ -1897,10 +1897,12 @@ var JSHINT = (function() {
         return this;
       }
 
+      block = funct["(blockscope)"].getlabel(v);
+
       if (typeof s === "function") {
         // Protection against accidental inheritance.
         s = undefined;
-      } else if (!funct["(blockscope)"].current.has(v) && typeof s === "boolean") {
+      } else if (!block && typeof s === "boolean") {
         f = funct;
         funct = functions[0];
         addlabel(v, { type: "var" });
@@ -1908,8 +1910,6 @@ var JSHINT = (function() {
         funct = f;
       }
 
-      block = funct["(blockscope)"].getlabel(v);
-
       // The name is in scope and defined in the current function.
       if (funct === s || block) {
         // Change 'unused' to 'var', and reject labels.
@@ -2008,37 +2008,24 @@ var JSHINT = (function() {
     }
   };
 
-  var baseTemplateSyntax = {
+  state.syntax["(template)"] = {
+    type: "(template)",
     lbp: 0,
     identifier: false,
-    template: true,
+    nud: doTemplateLiteral
   };
-  state.syntax["(template)"] = _.extend({
-    type: "(template)",
-    nud: doTemplateLiteral,
-    led: doTemplateLiteral,
-    noSubst: false
-  }, baseTemplateSyntax);
-
-  state.syntax["(template middle)"] = _.extend({
-    type: "(template middle)",
-    middle: true,
-    noSubst: false
-  }, baseTemplateSyntax);
-
-  state.syntax["(template tail)"] = _.extend({
-    type: "(template tail)",
-    tail: true,
-    noSubst: false
-  }, baseTemplateSyntax);
-
-  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("(template middle)", function() {
+    return this;
+  });
+
+  type("(template tail)", function() {
+    return this;
+  });
+
+  type("(no subst template)", function() {
+    return this;
+  });
 
   type("(regexp)", function() {
     return this;
@@ -2955,38 +2942,20 @@ var JSHINT = (function() {
     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();
-        }
+  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?
       }
     }
-
     return {
       id: "(template)",
-      type: "(template)",
-      tag: left
+      type: "(template)"
     };
-
-    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;
-    }
   }
 
   /**
@@ -3592,13 +3561,12 @@ var JSHINT = (function() {
     return classdef.call(this, true);
   });
 
-  function classdef(isStatement) {
-
+  function classdef(stmt) {
     /*jshint validthis:true */
     if (!state.option.inESNext()) {
       warning("W104", state.tokens.curr, "class");
     }
-    if (isStatement) {
+    if (stmt) {
       // BindingIdentifier
       this.name = identifier();
       addlabel(this.name, { type: "unused", token: state.tokens.curr });
@@ -4141,9 +4109,8 @@ 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();
       }
@@ -4456,9 +4423,6 @@ var JSHINT = (function() {
 
   stmt("export", function() {
     var ok = true;
-    var token;
-    var identifier;
-
     if (!state.option.inESNext()) {
       warning("W119", state.tokens.curr, "export");
       ok = false;
@@ -4485,20 +4449,7 @@ var JSHINT = (function() {
       if (state.tokens.next.id === "function" || state.tokens.next.id === "class") {
         this.block = true;
       }
-
-      token = peek();
-
-      expression(10);
-
-      if (state.tokens.next.id === "class") {
-        identifier = token.name;
-      } else {
-        identifier = token.value;
-      }
-
-      addlabel(identifier, {
-        type: "function", token: token
-      });
+      this.exportee = expression(10);
 
       return this;
     }
@@ -4658,7 +4609,7 @@ var JSHINT = (function() {
         ret.isBlock = true;
         ret.notJson = true;
       }
-    } while (bracketStack > 0 && pn.id !== "(end)");
+    } while (bracketStack > 0 && pn.id !== "(end)" && i < 15);
     return ret;
   };
 
diff --git a/src/lex.js b/src/lex.js
index ff5554aa..d3e3a358 100644
--- a/src/lex.js
+++ b/src/lex.js
@@ -129,23 +129,7 @@ Lexer.prototype = {
   _lines: [],
 
   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];
+    return this.context.length > 0 && this.context[this.context.length - 1] === ctxType;
   },
 
   getLines: function() {
@@ -253,7 +237,7 @@ Lexer.prototype = {
 
     // A block/object opener
     case "{":
-      this.pushContext(Context.Block);
+      this.context.push(Context.Block);
       return {
         type: Token.Punctuator,
         value: ch1
@@ -262,7 +246,7 @@ Lexer.prototype = {
     // A block/object closer
     case "}":
       if (this.inContext(Context.Block)) {
-        this.popContext();
+        this.context.pop();
       }
       return {
         type: Token.Punctuator,
@@ -1030,7 +1014,6 @@ Lexer.prototype = {
     var ch;
     var startLine = this.line;
     var startChar = this.char;
-    var depth = this.templateStarts.length;
 
     if (!state.option.esnext) {
       // Only lex template strings in ESNext mode.
@@ -1039,9 +1022,8 @@ Lexer.prototype = {
       // 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);
-      this.pushContext(Context.Template);
+      this.context.push(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;
@@ -1055,6 +1037,7 @@ Lexer.prototype = {
         value += "\n";
         if (!this.nextLine()) {
           // Unclosed template literal --- point to the starting "`"
+          this.context.pop();
           var startPos = this.templateStarts.pop();
           this.trigger("error", {
             code: "E052",
@@ -1066,9 +1049,7 @@ Lexer.prototype = {
             value: value,
             startLine: startLine,
             startChar: startChar,
-            isUnclosed: true,
-            depth: depth,
-            context: this.popContext()
+            isUnclosed: true
           };
         }
       }
@@ -1081,9 +1062,7 @@ Lexer.prototype = {
           value: value,
           startLine: startLine,
           startChar: startChar,
-          isUnclosed: false,
-          depth: depth,
-          context: this.currentContext()
+          isUnclosed: false
         };
       } else if (ch === '\\') {
         var escape = this.scanEscapeSequence(checks);
@@ -1099,6 +1078,7 @@ Lexer.prototype = {
     // Final value is either NoSubstTemplate or TemplateTail
     tokenType = tokenType === Token.TemplateHead ? Token.NoSubstTemplate : Token.TemplateTail;
     this.skip(1);
+    this.context.pop();
     this.templateStarts.pop();
 
     return {
@@ -1106,9 +1086,7 @@ Lexer.prototype = {
       value: value,
       startLine: startLine,
       startChar: startChar,
-      isUnclosed: false,
-      depth: depth,
-      context: this.popContext()
+      isUnclosed: false
     };
   },
 
@@ -1642,18 +1620,6 @@ Lexer.prototype = {
       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;
@@ -1829,4 +1795,3 @@ Lexer.prototype = {
 };
 
 exports.Lexer = Lexer;
-exports.Context = Context;


Test patch:
diff --git a/tests/unit/core.js b/tests/unit/core.js
index cad3dfda..83b8fc2b 100644
--- a/tests/unit/core.js
+++ b/tests/unit/core.js
@@ -734,29 +734,6 @@ exports.testES6ModulesNamedExportsAffectUnused = function (test) {
   test.done();
 };
 
-
-exports["class declaration export (default)"] = function (test) {
-  var source = fs.readFileSync(__dirname + "/fixtures/class-declaration.js", "utf8");
-
-  TestRun(test).test(source, {
-    esnext: true,
-    undef: true
-  });
-
-  test.done();
-};
-
-exports["function declaration export (default)"] = function (test) {
-  var source = fs.readFileSync(__dirname + "/fixtures/function-declaration.js", "utf8");
-
-  TestRun(test).test(source, {
-    esnext: true,
-    undef: true
-  });
-
-  test.done();
-};
-
 exports.testES6ModulesNamedExportsAffectUndef = function (test) {
   // The identifier "foo" is expected to have been defined in the scope
   // of this file in order to be exported.
@@ -870,15 +847,8 @@ exports.testES6TemplateLiterals = function (test) {
   TestRun(test)
     .addError(14, "Octal literals are not allowed in strict mode.")
     .addError(21, "Unclosed template literal.")
-    .test(src, { esnext: true });
-  test.done();
-};
-
-exports.testES6TaggedTemplateLiterals = function (test) {
-  var src = fs.readFileSync(__dirname + "/fixtures/es6-template-literal-tagged.js", "utf8");
-  TestRun(test)
-    .addError(16, "Octal literals are not allowed in strict mode.")
-    .addError(23, "Unclosed template literal.")
+    .addError(22, "Expected an identifier and instead saw '(end)'.")
+    .addError(22, "Missing semicolon.")
     .test(src, { esnext: true });
   test.done();
 };
@@ -894,19 +864,6 @@ exports.testES6TemplateLiteralsUnused = function (test) {
   test.done();
 };
 
-exports.testES6TaggedTemplateLiteralsUnused = function (test) {
-  var src = [
-    "function tag() {}",
-    "var a = 'hello';",
-    "alert(tag`${a} world`);"
-  ];
-  TestRun(test)
-    .test(src, { esnext: true, unused: true });
-
-  test.done();
-};
-
-
 exports.testES6TemplateLiteralsUndef = function (test) {
   var src = [
     "/* global alert */",
@@ -919,21 +876,6 @@ exports.testES6TemplateLiteralsUndef = function (test) {
   test.done();
 };
 
-
-exports.testES6TaggedTemplateLiteralsUndef = function (test) {
-  var src = [
-    "/* global alert */",
-    "alert(tag`${a} world`);"
-  ];
-  TestRun(test)
-    .addError(2, "'tag' is not defined.")
-    .addError(2, "'a' is not defined.")
-    .test(src, { esnext: true, undef: true });
-
-  test.done();
-};
-
-
 exports.testES6TemplateLiteralMultiline = function (test) {
   var src = [
     'let multiline = `',
@@ -1019,35 +961,7 @@ exports.testES6TemplateLiteralMultilineReturnValue = function (test) {
   test.done();
 };
 
-
-exports.testES6TaggedTemplateLiteralMultilineReturnValue = function (test) {
-  var src = [
-    'function tag() {}',
-    'function sayHello(to) {',
-    '  return tag`Hello, ',
-    '    ${to}!`;',
-    '}',
-    'print(sayHello("George"));'
-  ];
-
-  TestRun(test).test(src, { esnext: true });
-
-  var src = [
-    'function tag() {}',
-    'function* sayHello(to) {',
-    '  yield tag`Hello, ',
-    '    ${to}!`;',
-    '}',
-    'print(sayHello("George"));'
-  ];
-
-  TestRun(test).test(src, { esnext: true });
-
-  test.done();
-};
-
-
-exports.testMultilineReturnValueStringLiteral = function (test) {
+exports.tesMultilineReturnValueStringLiteral = function (test) {
   var src = [
     'function sayHello(to) {',
     '  return "Hello, \\',
@@ -1256,14 +1170,3 @@ exports.testPermitEvalAsKey = function (test) {
   test.done();
 
 };
-
-// gh-2194 jshint confusing arrays at beginning of file with JSON
-exports.beginningArraysAreNotJSON = function (test) {
-  var src = fs.readFileSync(__dirname + "/fixtures/gh-2194.js", "utf8");
-
-  TestRun(test)
-  .test(src);
-
-  test.done();
-
-};
diff --git a/tests/unit/fixtures/class-declaration.js b/tests/unit/fixtures/class-declaration.js
deleted file mode 100644
index 5a1f678c..00000000
--- a/tests/unit/fixtures/class-declaration.js
+++ /dev/null
@@ -1,15 +0,0 @@
-class Section {
-  constructor() {
-
-  }
-}
-
-export default class Header extends Section {
-  someMethod() {
-    // ...
-  }
-}
-
-Object.defineProperty(Header, "CONST", {
-  value: 1
-});
diff --git a/tests/unit/fixtures/es6-template-literal-tagged.js b/tests/unit/fixtures/es6-template-literal-tagged.js
deleted file mode 100644
index d0cc007d..00000000
--- a/tests/unit/fixtures/es6-template-literal-tagged.js
+++ /dev/null
@@ -1,23 +0,0 @@
-function tag() {}
-
-var one = 1, two = 2;
-
-var string = tag`  ${one} + ${two}
-= ${one + two}`;
-
-var literal = tag`because I
-can`;
-
-var escaped = tag`one = \`${one}\``;
-
-function octal_strictmode() {
-  "use strict";
-
-  var test = tag`\033\t`;
-}
-
-var nested = tag`Look and ${ tag`Nested ${ tag`whoaaa` } template` } listen`;
-
-var innerobj = tag`Template with ${ {obj: "literal"} } inside`;
-
-var unterminated = tag`${one}
diff --git a/tests/unit/fixtures/function-declaration.js b/tests/unit/fixtures/function-declaration.js
deleted file mode 100644
index 3d0125b2..00000000
--- a/tests/unit/fixtures/function-declaration.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export default function Header() {}
-
-Object.defineProperty(Header, "CONST", {
-  value: 1
-});
diff --git a/tests/unit/fixtures/gh-2194.js b/tests/unit/fixtures/gh-2194.js
deleted file mode 100644
index cbd7e269..00000000
--- a/tests/unit/fixtures/gh-2194.js
+++ /dev/null
@@ -1,3 +0,0 @@
-[ 'w','a','f','f','l','e','s','a','r','e','a','w','e','s','o','m','e' ].map(function(letter) {
-  return letter;
-});
diff --git a/tests/unit/parser.js b/tests/unit/parser.js
index d6d524d2..81259939 100644
--- a/tests/unit/parser.js
+++ b/tests/unit/parser.js
@@ -2605,6 +2605,34 @@ exports["make sure var variables can shadow let variables"] = function (test) {
   test.done();
 };
 
+exports["make sure let variables in the closure of functions shadow predefined globals"] = function (test) {
+  var code = [
+    "function x() {",
+    "  let foo;",
+    "  function y() {",
+    "    foo = {};",
+    "  }",
+    "}"
+  ];
+
+  TestRun(test).test(code, { esnext: true, predef: { foo: false } });
+  test.done();
+};
+
+exports["make sure let variables in the closure of blocks shadow predefined globals"] = function (test) {
+  var code = [
+    "function x() {",
+    "  let foo;",
+    "  {",
+    "    foo = {};",
+    "  }",
+    "}"
+  ];
+
+  TestRun(test).test(code, { esnext: true, predef: { foo: false } });
+  test.done();
+};
+
 exports["test destructuring function as moz"] = function (test) {
   // Example from https://developer.mozilla.org/en-US/docs/JavaScript/New_in_JavaScript/1.7
   var code = [