Repository: jshint/jshint
Pull request #3322 (2018-08-13 00:31:00)

Honor `globals` config in JavaScript API
- [[CHORE]] Remove superfluous statement
- [[FIX]] Honor `globals` config in JavaScript API

  Prior to this commit, the `globals` configuration value was not
  considered for invocations of the JavaScript API. Ensure that option is
  consistently interpreted according to the project documentation. Ensure
  that globals defined by the more specific `predef` option of the API
  take precedence over globals defined using this configuration option.

(Owing to a change in indentation, this changeset is best reviewed with git's `-w` flag.)

This should resolve gh-3321

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

Coverage remained the same at 98.755% when pulling **105d5487df26758092a79f70ec8739ebd482d486 on jugglinmike:api-globals-predef** into **80c7fda2c45b682cc83215c58ac1167eadddc6f8 on jshint:master**.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bef924c8..1c2bff7a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+<a name="2.9.7"></a>
+## [2.9.7](https://github.com/jshint/jshint/compare/2.9.6...v2.9.7) (2018-12-07)
+
+This release corrects a packaging issue. It is not expected to modify JSHint's behavior.
+
 <a name="2.9.6"></a>
 ## [2.9.6](https://github.com/jshint/jshint/compare/2.9.5...v2.9.6) (2018-07-30)
 
diff --git a/README.md b/README.md
index 6a9069f2..a92038a7 100644
--- a/README.md
+++ b/README.md
@@ -85,6 +85,7 @@ Engineers from these companies and projects use JSHint:
 * [Apache CouchDB](http://couchdb.apache.org/)
 * [Google](https://www.google.com/)
 * [Codacy](https://www.codacy.com) [ref](https://support.codacy.com/hc/en-us/articles/207995005-Special-Thanks)
+* [Spotify](https://www.spotify.com)
 
 And many more!
 
diff --git a/dist/jshint-rhino.js b/dist/jshint-rhino.js
index baed28d5..fd84824e 100755
--- a/dist/jshint-rhino.js
+++ b/dist/jshint-rhino.js
@@ -1,6 +1,6 @@
 #!/usr/bin/env rhino
 var window = {};
-/*! 2.9.6 */
+/*! 2.9.7 */
 var JSHINT;
 if (typeof window === 'undefined') window = {};
 (function () {
diff --git a/dist/jshint.js b/dist/jshint.js
index 35a9684f..6ce0fdc0 100644
--- a/dist/jshint.js
+++ b/dist/jshint.js
@@ -1,4 +1,4 @@
-/*! 2.9.6 */
+/*! 2.9.7 */
 var JSHINT;
 if (typeof window === 'undefined') window = {};
 (function () {
diff --git a/package.json b/package.json
index 82f3a5d7..2a5b7ec2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "jshint",
-  "version": "2.9.6",
+  "version": "2.9.7",
   "homepage": "http://jshint.com/",
   "description": "Static analysis tool for JavaScript",
   "author": {
diff --git a/src/jshint.js b/src/jshint.js
index f20a9229..1aff3aef 100644
--- a/src/jshint.js
+++ b/src/jshint.js
@@ -1726,7 +1726,6 @@ var JSHINT = (function() {
 
       // create a new block scope
       state.funct["(scope)"].stack();
-      state.funct["(noblockscopedvar)"] = false;
 
       if (state.tokens.next.id !== "}") {
         indent += state.option.indent;
@@ -1798,23 +1797,30 @@ var JSHINT = (function() {
       }
     } else {
 
-      // check to avoid let declaration not within a block
-      // though is fine inside for loop initializer section
-      state.funct["(noblockscopedvar)"] = state.tokens.next.id !== "for";
       state.funct["(scope)"].stack();
 
       if (!stmt || state.option.curly) {
         warning("W116", state.tokens.next, "{", state.tokens.next.value);
       }
 
+      // JSHint observes Annex B of the ECMAScript specification by default,
+      // where function declarations are permitted in the statement positions
+      // of IfStatements.
+      var supportsFnDecl = state.funct["(verb)"] === "if" ||
+        state.tokens.curr.id === "else";
+
       state.tokens.next.inBracelessBlock = true;
       indent += state.option.indent;
       // test indentation only if statement is in new line
       a = [statement()];
       indent -= state.option.indent;
 
+      if (a[0] && a[0].declaration &&
+        !(supportsFnDecl && a[0].id === "function")) {
+        error("E048", a[0], a[0].id[0].toUpperCase() + a[0].id.slice(1));
+      }
+
       state.funct["(scope)"].unstack();
-      delete state.funct["(noblockscopedvar)"];
     }
 
     // Don't clear and let it propagate out if it is "break", "return" or similar in switch case
@@ -3514,8 +3520,7 @@ var JSHINT = (function() {
       advance("(");
       state.funct["(scope)"].stack();
       letblock = true;
-    } else if (state.funct["(noblockscopedvar)"]) {
-      error("E048", state.tokens.curr, isConst ? "Const" : "Let");
+      statement.declaration = false;
     }
 
     statement.first = [];
@@ -3541,7 +3546,7 @@ var JSHINT = (function() {
               warning("W079", t.token, t.id);
             }
           }
-          if (t.id && !state.funct["(noblockscopedvar)"]) {
+          if (t.id) {
             state.funct["(scope)"].addlabel(t.id, {
               type: type,
               token: t.token });
@@ -3600,11 +3605,13 @@ var JSHINT = (function() {
     return blockVariableStatement("const", this, context);
   });
   conststatement.exps = true;
+  conststatement.declaration = true;
 
   var letstatement = stmt("let", function(context) {
     return blockVariableStatement("let", this, context);
   });
   letstatement.exps = true;
+  letstatement.declaration = true;
 
   var varstatement = stmt("var", function(context) {
     var prefix = context && context.prefix;
@@ -3914,7 +3921,7 @@ var JSHINT = (function() {
       error("E039");
     }
     return this;
-  });
+  }).declaration = true;
 
   prefix("function", function() {
     var generator = false;
@@ -5324,8 +5331,6 @@ var JSHINT = (function() {
     combine(predefined, vars.ecmaIdentifiers[3]);
     combine(predefined, vars.reservedVars);
 
-    combine(predefined, g || {});
-
     declared = Object.create(null);
     var exported = Object.create(null); // Variables that live outside the current file
 
@@ -5340,18 +5345,21 @@ var JSHINT = (function() {
     }
 
     if (o) {
-      each(o.predef || null, function(item) {
-        var slice, prop;
-
-        if (item[0] === "-") {
-          slice = item.slice(1);
-          JSHINT.blacklist[slice] = slice;
-          // remove from predefined if there
-          delete predefined[slice];
-        } else {
-          prop = Object.getOwnPropertyDescriptor(o.predef, item);
-          predefined[item] = prop ? prop.value : false;
-        }
+
+      each([o.predef, o.globals], function(dict) {
+        each(dict, function(item) {
+          var slice, prop;
+
+          if (item[0] === "-") {
+            slice = item.slice(1);
+            JSHINT.blacklist[slice] = slice;
+            // remove from predefined if there
+            delete predefined[slice];
+          } else {
+            prop = Object.getOwnPropertyDescriptor(dict, item);
+            predefined[item] = prop ? prop.value : false;
+          }
+        });
       });
 
       each(o.exported || null, function(item) {
diff --git a/src/lex.js b/src/lex.js
index 066a59ec..5956008f 100644
--- a/src/lex.js
+++ b/src/lex.js
@@ -1319,11 +1319,72 @@ Lexer.prototype = {
     var value = char;
     var body = "";
     var flags = [];
+    var groupReferences = [];
     var malformed = false;
     var isCharSet = false;
+    var isCharSetRange = false;
+    var isGroup = false;
+    var isQuantifiable = false;
+    var hasInvalidQuantifier = false;
+    var escapedChars = "";
+    var hasUFlag = function() { return flags.indexOf("u") > -1; };
+    var escapeSequence;
+    var groupCount = 0;
     var terminated, malformedDesc;
 
-    var scanUnexpectedChars = function() {
+    var scanRegexpEscapeSequence = function() {
+      var next, sequence;
+      index += 1;
+      char = this.peek(index);
+
+      if (reg.nonzeroDigit.test(char)) {
+        sequence = char;
+        next = this.peek(index + 1);
+        while (reg.nonzeroDigit.test(next) || next === "0") {
+          index += 1;
+          char = next;
+          sequence += char;
+          body += char;
+          value += char;
+          next = this.peek(index + 1);
+        }
+        groupReferences.push(Number(sequence));
+        return sequence;
+      }
+
+      escapedChars += char;
+
+      if (char === "u" && this.peek(index + 1) === "{") {
+        var x = index + 2;
+        sequence = "u{";
+        next = this.peek(x);
+        while (isHex(next)) {
+          sequence += next;
+          x += 1;
+          next = this.peek(x);
+        }
+
+        if (next !== "}") {
+          this.triggerAsync(
+            "error",
+            {
+              code: "E016",
+              line: this.line,
+              character: this.char,
+              data: [ "Invalid Unicode escape sequence" ]
+            },
+            checks,
+            hasUFlag
+          );
+        } else if (sequence.length > 2) {
+          sequence += "}";
+          body += sequence;
+          value += sequence;
+          index = x + 1;
+          return sequence;
+        }
+      }
+
       // Unexpected control character
       if (char < " ") {
         malformed = true;
@@ -1354,6 +1415,107 @@ Lexer.prototype = {
           function() { return true; }
         );
       }
+
+      index += 1;
+      body += char;
+      value += char;
+
+      return char;
+    }.bind(this);
+
+    var checkQuantifier = function() {
+      var lookahead = index;
+      var lowerBound = "";
+      var upperBound = "";
+      var next;
+
+      next = this.peek(lookahead + 1);
+
+      while (reg.decimalDigit.test(next)) {
+        lookahead += 1;
+        lowerBound += next;
+        next = this.peek(lookahead + 1);
+      }
+
+      if (!lowerBound) {
+        return false;
+      }
+
+      if (next === "}") {
+        return true;
+      }
+
+      if (next !== ",") {
+        return false;
+      }
+
+      lookahead += 1;
+      next = this.peek(lookahead + 1);
+
+      while (reg.decimalDigit.test(next)) {
+        lookahead += 1;
+        upperBound += next;
+        next = this.peek(lookahead + 1);
+      }
+
+      if (next !== "}") {
+        return false;
+      }
+
+      if (upperBound) {
+        return Number(lowerBound) <= Number(upperBound);
+      }
+
+      return true;
+    }.bind(this);
+
+    var translateUFlag = function(body) {
+      // The BMP character to use as a replacement for astral symbols when
+      // translating an ES6 "u"-flagged pattern to an ES5-compatible
+      // approximation.
+      // Note: replacing with '\uFFFF' enables false positives in unlikely
+      // scenarios. For example, `[\u{1044f}-\u{10440}]` is an invalid pattern
+      // that would not be detected by this substitution.
+      var astralSubstitute = "\uFFFF";
+
+      return body
+        // Replace every Unicode escape sequence with the equivalent BMP
+        // character or a constant ASCII code point in the case of astral
+        // symbols. (See the above note on `astralSubstitute` for more
+        // information.)
+        .replace(/\\u\{([0-9a-fA-F]+)\}|\\u([a-fA-F0-9]{4})/g, function($0, $1, $2) {
+          var codePoint = parseInt($1 || $2, 16);
+          var literal;
+
+          if (codePoint > 0x10FFFF) {
+            malformed = true;
+            this.trigger("error", {
+              code: "E016",
+              line: this.line,
+              character: this.char,
+              data: [ char ]
+            });
+
+            return;
+          }
+          literal = String.fromCharCode(codePoint);
+
+          if (reg.regexpSyntaxChars.test(literal)) {
+            return $0;
+          }
+
+          if (codePoint <= 0xFFFF) {
+            return String.fromCharCode(codePoint);
+          }
+          return astralSubstitute;
+        }.bind(this))
+        // Replace each paired surrogate with a single ASCII symbol to avoid
+        // throwing on regular expressions that are only valid in combination
+        // with the "u" flag.
+        .replace(
+          /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
+          astralSubstitute
+        );
     }.bind(this);
 
     // Regular expressions must start with '/'
@@ -1365,11 +1527,15 @@ Lexer.prototype = {
     terminated = false;
 
     // Try to get everything in between slashes. A couple of
-    // cases aside (see scanUnexpectedChars) we don't really
+    // cases aside (see scanRegexpEscapeSequence) we don't really
     // care whether the resulting expression is valid or not.
     // We will check that later using the RegExp object.
 
     while (index < length) {
+      // Because an iteration of this loop may terminate in a number of
+      // distinct locations, `isCharSetRange` is re-set at the onset of
+      // iteration.
+      isCharSetRange &= char === "-";
       char = this.peek(index);
       value += char;
       body += char;
@@ -1379,47 +1545,70 @@ Lexer.prototype = {
           if (this.peek(index - 1) !== "\\" || this.peek(index - 2) === "\\") {
             isCharSet = false;
           }
+        } else if (char === "-") {
+          isCharSetRange = true;
         }
+      }
 
-        if (char === "\\") {
-          index += 1;
-          char = this.peek(index);
-          body += char;
-          value += char;
+      if (char === "\\") {
+        escapeSequence = scanRegexpEscapeSequence();
 
-          scanUnexpectedChars();
+        if (isCharSet && (this.peek(index) === "-" || isCharSetRange) &&
+          reg.regexpCharClasses.test(escapeSequence)) {
+          this.triggerAsync(
+            "error",
+            {
+              code: "E016",
+              line: this.line,
+              character: this.char,
+              data: [ "Character class used in range" ]
+            },
+            checks,
+            hasUFlag
+          );
         }
 
-        index += 1;
         continue;
       }
 
-      if (char === "\\") {
-        index += 1;
-        char = this.peek(index);
-        body += char;
-        value += char;
-
-        scanUnexpectedChars();
-
-        if (char === "/") {
-          index += 1;
-          continue;
-        }
-
-        if (char === "[") {
-          index += 1;
-          continue;
-        }
+      if (char === "{" && !hasInvalidQuantifier) {
+        hasInvalidQuantifier = !checkQuantifier();
       }
 
       if (char === "[") {
         isCharSet = true;
         index += 1;
         continue;
-      }
+      } else if (char === "(") {
+        isGroup = true;
+
+        if (this.peek(index + 1) === "?" &&
+          (this.peek(index + 2) === "=" || this.peek(index + 2) === "!")) {
+          isQuantifiable = true;
+        }
+      } else if (char === ")") {
+        if (isQuantifiable) {
+          isQuantifiable = false;
+
+          if (reg.regexpQuantifiers.test(this.peek(index + 1))) {
+            this.triggerAsync(
+              "error",
+              {
+                code: "E016",
+                line: this.line,
+                character: this.char,
+                data: [ "Quantified quantifiable" ]
+              },
+              checks,
+              hasUFlag
+            );
+          }
+        } else {
+          groupCount += 1;
+        }
 
-      if (char === "/") {
+        isGroup = false;
+      } else if (char === "/") {
         body = body.substr(0, body.length - 1);
         terminated = true;
         index += 1;
@@ -1449,7 +1638,7 @@ Lexer.prototype = {
 
     while (index < length) {
       char = this.peek(index);
-      if (!/[gimy]/.test(char)) {
+      if (!/[gimyu]/.test(char)) {
         break;
       }
       if (char === "y") {
@@ -1466,12 +1655,54 @@ Lexer.prototype = {
             function() { return true; }
           );
         }
-        if (value.indexOf("y") > -1) {
-          malformedDesc = "Duplicate RegExp flag";
+      } else if (char === "u") {
+        if (!state.inES6(true)) {
+          this.triggerAsync(
+            "warning",
+            {
+              code: "W119",
+              line: this.line,
+              character: this.char,
+              data: [ "Unicode RegExp flag", "6" ]
+            },
+            checks,
+            function() { return true; }
+          );
+        }
+
+        var hasInvalidEscape = (function(groupReferences, groupCount, escapedChars, reg) {
+          var hasInvalidGroup = groupReferences.some(function(groupReference) {
+            if (groupReference > groupCount) {
+              return true;
+            }
+          });
+
+          if (hasInvalidGroup) {
+            return true;
+          }
+
+          return !escapedChars.split("").every(function(escapedChar) {
+              return escapedChar === "u" ||
+                escapedChar === "/" ||
+                reg.regexpCharClasses.test(escapedChar) ||
+                reg.regexpSyntaxChars.test(escapedChar);
+            });
+        }(groupReferences, groupCount, escapedChars, reg));
+
+        if (hasInvalidEscape) {
+          malformedDesc = "Invalid escape";
+        } else if (hasInvalidQuantifier) {
+          malformedDesc = "Invalid quantifier";
         }
-      } else {
-        flags.push(char);
+
+        body = translateUFlag(body);
+      }
+
+      if (flags.indexOf(char) > -1) {
+        malformedDesc = "Duplicate RegExp flag";
       }
+      flags.push(char);
+
       value += char;
       index += 1;
     }
@@ -1479,7 +1710,7 @@ Lexer.prototype = {
     // Check regular expression for correctness.
 
     try {
-      new RegExp(body, flags.join(""));
+      new RegExp(body);
     } catch (err) {
       /**
        * Because JSHint relies on the current engine's RegExp parser to
diff --git a/src/reg.js b/src/reg.js
index b830776b..8b4c627c 100644
--- a/src/reg.js
+++ b/src/reg.js
@@ -40,3 +40,13 @@ exports.maxlenException = /^(?:(?:\/\/|\/\*|\*) ?)?[^ ]+$/;
 // Source:
 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
 exports.whitespace = /[ \f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/;
+
+exports.nonzeroDigit = /^[1-9]$/;
+
+exports.decimalDigit = /^[0-9]$/;
+
+exports.regexpSyntaxChars = /[\^$\\.*+?()[\]{}|]/;
+
+exports.regexpQuantifiers = /[*+?{]/;
+
+exports.regexpCharClasses = /[dDsSwW]/;


Test patch:
diff --git a/tests/cli.js b/tests/cli.js
index f3e40703..11624474 100644
--- a/tests/cli.js
+++ b/tests/cli.js
@@ -1290,6 +1290,42 @@ exports.useStdin = {
     test.done();
   },
 
+  testNoFilename: function(test) {
+    var rep = require("../examples/reporter.js");
+    var errors = [];
+    this.sinon.stub(rep, "reporter", function (res) {
+      errors = errors.concat(res);
+    });
+
+    var dir = __dirname + "/../examples/";
+    this.sinon.stub(process, "cwd").returns(dir);
+
+    cli.interpret([
+      "node", "jshint", "--reporter=reporter.js", "-"
+    ]);
+
+    this.stdin.send("void 0;");
+    this.stdin.end();
+
+    test.equal(errors.length, 0, "should not report errors");
+    test.equal(cli.exit.args[0][0], 0, "status code should be 2 when there is a linting error.");
+
+    errors.length = 0;
+    this.stdin.reset();
+
+    cli.interpret([
+      "node", "jshint", "--reporter=reporter.js", "-"
+    ]);
+
+    this.stdin.send("? This is not JavaScript.");
+    this.stdin.end();
+
+    test.ok(errors.length > 0, "should report some number of errors");
+    test.equal(cli.exit.args[1][0], 2, "status code should be 2 when there is a linting error.");
+
+    test.done();
+  },
+
   testFilenameOverridesOption: function (test) {
     test.expect(4);
     var rep = require("../examples/reporter.js");
diff --git a/tests/test262/expectations.txt b/tests/test262/expectations.txt
index 0e15c6c0..03633bd2 100644
--- a/tests/test262/expectations.txt
+++ b/tests/test262/expectations.txt
@@ -249,34 +249,6 @@ test/built-ins/RegExp/S15.10.7_A1_T1.js(default)
 test/built-ins/RegExp/S15.10.7_A1_T1.js(strict mode)
 test/built-ins/RegExp/S15.10.7_A2_T1.js(default)
 test/built-ins/RegExp/S15.10.7_A2_T1.js(strict mode)
-test/built-ins/RegExp/unicode_identity_escape.js(default)
-test/built-ins/RegExp/unicode_identity_escape.js(strict mode)
-test/built-ins/RegExp/prototype/Symbol.search/u-lastindex-advance.js(default)
-test/built-ins/RegExp/prototype/Symbol.search/u-lastindex-advance.js(strict mode)
-test/built-ins/RegExp/prototype/Symbol.match/builtin-infer-unicode.js(default)
-test/built-ins/RegExp/prototype/Symbol.match/builtin-infer-unicode.js(strict mode)
-test/built-ins/RegExp/prototype/Symbol.match/builtin-success-u-return-val-groups.js(default)
-test/built-ins/RegExp/prototype/Symbol.match/builtin-success-u-return-val-groups.js(strict mode)
-test/built-ins/RegExp/prototype/Symbol.match/u-advance-after-empty.js(default)
-test/built-ins/RegExp/prototype/Symbol.match/u-advance-after-empty.js(strict mode)
-test/built-ins/RegExp/prototype/Symbol.replace/u-advance-after-empty.js(default)
-test/built-ins/RegExp/prototype/Symbol.replace/u-advance-after-empty.js(strict mode)
-test/built-ins/RegExp/prototype/flags/u.js(default)
-test/built-ins/RegExp/prototype/flags/u.js(strict mode)
-test/built-ins/RegExp/prototype/Symbol.split/u-lastindex-adv-thru-failure.js(default)
-test/built-ins/RegExp/prototype/Symbol.split/u-lastindex-adv-thru-failure.js(strict mode)
-test/built-ins/RegExp/prototype/Symbol.split/u-lastindex-adv-thru-match.js(default)
-test/built-ins/RegExp/prototype/Symbol.split/u-lastindex-adv-thru-match.js(strict mode)
-test/built-ins/RegExp/prototype/source/value-u.js(default)
-test/built-ins/RegExp/prototype/source/value-u.js(strict mode)
-test/built-ins/RegExp/prototype/unicode/this-val-regexp.js(default)
-test/built-ins/RegExp/prototype/unicode/this-val-regexp.js(strict mode)
-test/built-ins/RegExp/prototype/exec/u-captured-value.js(default)
-test/built-ins/RegExp/prototype/exec/u-captured-value.js(strict mode)
-test/built-ins/RegExp/prototype/exec/u-lastindex-adv.js(default)
-test/built-ins/RegExp/prototype/exec/u-lastindex-adv.js(strict mode)
-test/built-ins/RegExp/prototype/exec/u-lastindex-value.js(default)
-test/built-ins/RegExp/prototype/exec/u-lastindex-value.js(strict mode)
 test/language/asi/S7.9.2_A1_T2.js(default)
 test/language/asi/S7.9_A1.js(default)
 test/language/asi/S7.9_A1.js(strict mode)
@@ -461,27 +433,8 @@ test/language/literals/numeric/binary-invalid-truncated.js(strict mode)
 test/language/literals/numeric/non-octal-decimal-integer-strict.js(strict mode)
 test/language/literals/numeric/octal-invalid-truncated.js(default)
 test/language/literals/numeric/octal-invalid-truncated.js(strict mode)
-test/language/literals/regexp/u-astral.js(default)
-test/language/literals/regexp/u-astral.js(strict mode)
-test/language/literals/regexp/u-case-mapping.js(default)
-test/language/literals/regexp/u-case-mapping.js(strict mode)
-test/language/literals/regexp/u-surrogate-pairs-atom-char-class.js(default)
-test/language/literals/regexp/u-surrogate-pairs-atom-char-class.js(strict mode)
-test/language/literals/regexp/u-surrogate-pairs-atom-dot.js(default)
-test/language/literals/regexp/u-surrogate-pairs-atom-dot.js(strict mode)
-test/language/literals/regexp/u-surrogate-pairs-atom-escape-char-class.js(default)
-test/language/literals/regexp/u-surrogate-pairs-atom-escape-char-class.js(strict mode)
-test/language/literals/regexp/u-surrogate-pairs-atom-escape-decimal.js(default)
-test/language/literals/regexp/u-surrogate-pairs-atom-escape-decimal.js(strict mode)
-test/language/literals/regexp/u-surrogate-pairs.js(default)
-test/language/literals/regexp/u-surrogate-pairs.js(strict mode)
-test/language/literals/regexp/u-unicode-esc.js(default)
-test/language/literals/regexp/u-unicode-esc.js(strict mode)
-test/language/block-scope/syntax/function-declarations/in-statement-position-do-statement-while-expression.js(strict mode)
-test/language/block-scope/syntax/function-declarations/in-statement-position-for-statement.js(strict mode)
 test/language/block-scope/syntax/function-declarations/in-statement-position-if-expression-statement-else-statement.js(strict mode)
 test/language/block-scope/syntax/function-declarations/in-statement-position-if-expression-statement.js(strict mode)
-test/language/block-scope/syntax/function-declarations/in-statement-position-while-expression-statement.js(strict mode)
 test/language/destructuring/binding/syntax/array-elements-with-initializer.js(default)
 test/language/destructuring/binding/syntax/array-elements-with-initializer.js(strict mode)
 test/language/computed-property-names/class/method/constructor-can-be-getter.js(default)
@@ -1094,16 +1047,8 @@ test/language/statements/continue/12.7-1.js(default)
 test/language/statements/continue/12.7-1.js(strict mode)
 test/language/statements/do-while/decl-cls.js(default)
 test/language/statements/do-while/decl-cls.js(strict mode)
-test/language/statements/do-while/decl-fun.js(default)
-test/language/statements/do-while/decl-fun.js(strict mode)
-test/language/statements/do-while/decl-gen.js(default)
-test/language/statements/do-while/decl-gen.js(strict mode)
 test/language/statements/for-in/decl-cls.js(default)
 test/language/statements/for-in/decl-cls.js(strict mode)
-test/language/statements/for-in/decl-fun.js(default)
-test/language/statements/for-in/decl-fun.js(strict mode)
-test/language/statements/for-in/decl-gen.js(default)
-test/language/statements/for-in/decl-gen.js(strict mode)
 test/language/statements/for-in/dstr-array-rest-init.js(default)
 test/language/statements/for-in/dstr-array-rest-init.js(strict mode)
 test/language/statements/for-in/head-const-bound-names-fordecl-tdz.js(default)
@@ -1114,10 +1059,6 @@ test/language/statements/for-in/head-lhs-let.js(default)
 test/language/statements/for-in/head-var-bound-names-let.js(default)
 test/language/statements/for/decl-cls.js(default)
 test/language/statements/for/decl-cls.js(strict mode)
-test/language/statements/for/decl-fun.js(default)
-test/language/statements/for/decl-fun.js(strict mode)
-test/language/statements/for/decl-gen.js(default)
-test/language/statements/for/decl-gen.js(strict mode)
 test/language/statements/for/dstr-const-ary-init-iter-close.js(default)
 test/language/statements/for/dstr-const-ary-init-iter-close.js(strict mode)
 test/language/statements/for/dstr-const-ary-init-iter-get-err.js(default)
@@ -1445,10 +1386,6 @@ test/language/statements/return/S12.9_A1_T9.js(default)
 test/language/statements/return/S12.9_A1_T9.js(strict mode)
 test/language/statements/for-of/decl-cls.js(default)
 test/language/statements/for-of/decl-cls.js(strict mode)
-test/language/statements/for-of/decl-fun.js(default)
-test/language/statements/for-of/decl-fun.js(strict mode)
-test/language/statements/for-of/decl-gen.js(default)
-test/language/statements/for-of/decl-gen.js(strict mode)
 test/language/statements/for-of/dstr-array-elem-init-simple-no-strict.js(default)
 test/language/statements/for-of/dstr-array-elem-init-yield-ident-valid.js(default)
 test/language/statements/for-of/dstr-array-elem-iter-rtrn-close-err.js(default)
@@ -1595,10 +1532,6 @@ test/language/statements/switch/S12.11_A2_T1.js(default)
 test/language/statements/switch/S12.11_A2_T1.js(strict mode)
 test/language/statements/while/decl-cls.js(default)
 test/language/statements/while/decl-cls.js(strict mode)
-test/language/statements/while/decl-fun.js(default)
-test/language/statements/while/decl-fun.js(strict mode)
-test/language/statements/while/decl-gen.js(default)
-test/language/statements/while/decl-gen.js(strict mode)
 test/language/statements/variable/dstr-ary-ptrn-rest-ary-elem.js(default)
 test/language/statements/variable/dstr-ary-ptrn-rest-ary-elem.js(strict mode)
 test/language/statements/variable/dstr-ary-ptrn-rest-ary-elision.js(default)
@@ -1634,8 +1567,6 @@ test/language/statements/try/early-catch-lex.js(strict mode)
 test/language/statements/try/early-catch-var.js(default)
 test/language/statements/try/early-catch-var.js(strict mode)
 test/language/statements/with/decl-cls.js(default)
-test/language/statements/with/decl-fun.js(default)
-test/language/statements/with/decl-gen.js(default)
 test/language/statements/const/block-local-use-before-initialization-in-declaration-statement.js(default)
 test/language/statements/const/block-local-use-before-initialization-in-declaration-statement.js(strict mode)
 test/language/statements/const/block-local-use-before-initialization-in-prior-statement.js(default)
diff --git a/tests/unit/core.js b/tests/unit/core.js
index 3effcb29..b6492c71 100644
--- a/tests/unit/core.js
+++ b/tests/unit/core.js
@@ -38,6 +38,33 @@ exports.testCustomGlobals = function (test) {
     .addError(3, 1, "Read only.")
     .test(code, { es3: true, unused: true, predef: { foo: false }});
 
+  JSHINT("x = null;", { undef: true, globals: { x: true } });
+
+  test.ok(!JSHINT.data().errors);
+
+  JSHINT("x = null;", { undef: true, globals: { x: false } });
+
+  test.equal(JSHINT.data().errors.length, 1);
+
+  JSHINT("parseInt('');", { undef: true, globals: { "-parseInt": true } });
+
+  test.equal(JSHINT.data().errors.length, 1);
+
+  JSHINT('x = null;', { undef: true, globals: { x: false } }, { x: true });
+
+  test.ok(
+    !JSHINT.data().errors,
+    "`predef` parameter takes precedence over `globals` option"
+  );
+
+  JSHINT("x = null;", { undef: true, globals: { x: true } }, { x: false });
+
+  test.equal(
+    JSHINT.data().errors.length,
+    1,
+    "`predef` parameter takes precedence over `globals` option"
+  );
+
   test.done();
 };
 
diff --git a/tests/unit/parser.js b/tests/unit/parser.js
index c5a815e2..2e9ea6fe 100644
--- a/tests/unit/parser.js
+++ b/tests/unit/parser.js
@@ -533,7 +533,9 @@ exports.comments = function (test) {
   test.done();
 };
 
-exports.regexp = function (test) {
+exports.regexp = {};
+
+exports.regexp.basic = function (test) {
   var code = [
     "var a1 = /\\\x1f/;",
     "var a2 = /[\\\x1f]/;",
@@ -643,12 +645,149 @@ exports.regexp = function (test) {
   TestRun(test).test("var a = 1; var b = a-- / 10;", {esnext: true});
   TestRun(test).test("var a = 1; var b = a-- / 10;", {moz: true});
 
+
   TestRun(test, "gh-3308").test("void (function() {} / 0);");
 
+  TestRun(test)
+    .addError(1, 9, "Invalid regular expression.")
+    .test("var a = /.*/ii;");
+
+  test.done();
+};
+
+exports.regexp.uFlag = function (test) {
+  // Flag validity
+  TestRun(test)
+    .addError(1, 9, "'Unicode RegExp flag' is only available in ES6 (use 'esversion: 6').")
+    .test("var a = /.*/u;");
+
+  TestRun(test)
+    .test("var a = /.*/u;", { esversion: 6 });
+
+  // Hexidecimal limits
+  TestRun(test)
+    .addError(3, 5, "Invalid regular expression.")
+    .test([
+      "var a = /\\u{0}/u;",
+      "a = /\\u{10FFFF}/u;",
+      "a = /\\u{110000}/u;"
+    ], { esnext: true });
+
+  TestRun(test, "Guard against regression from escape sequence substitution")
+    .test("void /\\u{3f}/u;", { esversion: 6 });
+
+  // Hexidecimal in range patterns
+  TestRun(test)
+    .addError(3, 5, "Invalid regular expression.")
+    .addError(4, 5, "Invalid regular expression.")
+    .test([
+      "var a = /[\\u{61}-b]/u;",
+      "a = /[\\u{061}-b]/u;",
+      "a = /[\\u{63}-b]/u;",
+      "a = /[\\u{0063}-b]/u;",
+    ], { esnext: true });
+
+  TestRun(test)
+    .test("var x = /[\uD834\uDF06-\uD834\uDF08a-z]/u;", { esversion: 6 });
+
+  TestRun(test)
+    .addError(1, 6, "Invalid regular expression.")
+    .test("void /.{}/u;", { esversion: 6 });
+
+  TestRun(test)
+    .addError(1, 6, "Invalid regular expression.")
+    .test("void /.{,2}/u;", { esversion: 6 });
+
+  TestRun(test)
+    .addError(1, 6, "Invalid regular expression.")
+    .test("void /.{2,1}/u;", { esversion: 6 });
+
+  TestRun(test, "Invalid group reference")
+    .addError(1, 6, "Invalid regular expression.")
+    .test("void /(.)(.)\\3/u;", { esversion: 6 });
+
+  TestRun(test, "Invalid group reference (permitted without flag)")
+    .test("void /(.)(.)\\3/;", { esversion: 6 });
+
+  TestRun(test, "Invalid unicode escape sequence")
+    .addError(1, 6, "Invalid regular expression.")
+    .test("void /\\u{123,}/u;", { esversion: 6 });
+
+  TestRun(test, "Invalid unicode escape sequence (permitted without flag)")
+    .test("void /\\u{123,}/;", { esversion: 6 });
+
+  TestRun(test, "Invalid character escape")
+    .addError(1, 6, "Invalid regular expression.")
+    .test("void /\\m/u;", { esversion: 6 });
+
+  TestRun(test, "Invalid character escape (permitted without flag)")
+    .test("void /\\m/;", { esversion: 6 });
+
+  TestRun(test, "Invalid quantifed group")
+    .addError(1, 6, "Invalid regular expression.")
+    .test("void /(?=.)?/u;", { esversion: 6 });
+
+  TestRun(test, "Invalid quantifed group (permitted without flag)")
+    .test("void /(?=.)?/;", { esversion: 6 });
+
+  TestRun(test, "Invalid quantifier - unclosed")
+    .addError(1, 6, "Invalid regular expression.")
+    .test("void /.{1/u;", { esversion: 6 });
+
+  TestRun(test, "Invalid quantifier - unclosed (permitted without flag)")
+    .test("void /.{1/;", { esversion: 6 });
+
+  TestRun(test, "Invalid quantifier - unclosed with comma")
+    .addError(1, 6, "Invalid regular expression.")
+    .test("void /.{1,/u;", { esversion: 6 });
+
+  TestRun(test, "Invalid quantifier - unclosed with comma (permitted without flag)")
+    .test("void /.{1,/;", { esversion: 6 });
+
+  TestRun(test, "Invalid quantifier - unclosed with upper bound")
+    .addError(1, 6, "Invalid regular expression.")
+    .test("void /.{1,2/u;", { esversion: 6 });
+
+  TestRun(test, "Invalid quantifier - unclosed with upper bound (permitted without flag)")
+    .test("void /.{1,2/;", { esversion: 6 });
+
+  TestRun(test, "Character class in lower bound of range (permitted without flag)")
+    .test('void /[\\s-1]/;', { esversion: 6 });
+
+  TestRun(test, "Character class in lower bound of range")
+    .addError(1, 6, "Invalid regular expression.")
+    .test('void /[\\s-1]/u;', { esversion: 6 });
+
+  TestRun(test, "Character class in upper bound of range (permitted without flag)")
+    .test('void /[1-\\W]/;', { esversion: 6 });
+
+  TestRun(test, "Character class in upper bound of range")
+    .addError(1, 6, "Invalid regular expression.")
+    .test('void /[1-\\W]/u;', { esversion: 6 });
+
+  TestRun(test)
+    .test('void /[\\s0-1\\s2-3\\s]/u;', { esversion: 6 });
+
+  test.done();
+};
+
+exports.regexp.yFlag = function (test) {
+  // Flag validity
+  TestRun(test)
+    .addError(1, 9, "'Sticky RegExp flag' is only available in ES6 (use 'esversion: 6').")
+    .test("var a = /.*/y;");
+
+  TestRun(test)
+    .test("var a = /.*/y;", { esversion: 6 });
+
+  TestRun(test)
+    .addError(1, 9, "Invalid regular expression.")
+    .test("var a = /.*/yiy;", { esversion: 6 });
+
   test.done();
 };
 
-exports.testRegexRegressions = function (test) {
+exports.regexp.regressions = function (test) {
   // GH-536
   TestRun(test).test("str /= 5;", {es3: true}, { str: true });
   TestRun(test).test("str /= 5;", {}, { str: true }); // es5