Repository: jshint/jshint
Pull request #3407 (2019-06-09 16:53:10)

[[CHORE]] Improve test coverage for RegExp parsing

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

Coverage remained the same at 100.0% when pulling **a0d1f65d38e65fa840ce5af91fa9b42a8641b532 on jugglinmike:coverage-regexp** into **0a60c9eb55758891f5b544315563044513913232 on jshint:master**.

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

Coverage remained the same at 100.0% when pulling **a0d1f65d38e65fa840ce5af91fa9b42a8641b532 on jugglinmike:coverage-regexp** into **0a60c9eb55758891f5b544315563044513913232 on jshint:master**.


Patch:
diff --git a/src/lex.js b/src/lex.js
index 897bcabe..98fa1c0f 100644
--- a/src/lex.js
+++ b/src/lex.js
@@ -1352,17 +1352,11 @@ Lexer.prototype = {
         sequence = char;
         next = this.peek(index + 1);
         while (reg.nonzeroDigit.test(next) || next === "0") {
-          /* istanbul ignore next */
           index += 1;
-          /* istanbul ignore next */
           char = next;
-          /* istanbul ignore next */
           sequence += char;
-          /* istanbul ignore next */
           body += char;
-          /* istanbul ignore next */
           value += char;
-          /* istanbul ignore next */
           next = this.peek(index + 1);
         }
         groupReferences.push(Number(sequence));
@@ -1431,6 +1425,18 @@ Lexer.prototype = {
           checks,
           function() { return true; }
         );
+      } else if (char === "0" && reg.decimalDigit.test(this.peek(index + 1))) {
+        this.triggerAsync(
+          "error",
+          {
+            code: "E016",
+            line: this.line,
+            character: this.char,
+            data: [ "Invalid decimal escape sequence" ]
+          },
+          checks,
+          hasUFlag
+        );
       }
 
       index += 1;
@@ -1459,7 +1465,6 @@ Lexer.prototype = {
       }
 
       if (next === "}") {
-        /* istanbul ignore next */
         return true;
       }
 
@@ -1707,6 +1712,8 @@ Lexer.prototype = {
           return !escapedChars.split("").every(function(escapedChar) {
               return escapedChar === "u" ||
                 escapedChar === "/" ||
+                escapedChar === "0" ||
+                reg.regexpControlEscapes.test(escapedChar) ||
                 reg.regexpCharClasses.test(escapedChar) ||
                 reg.regexpSyntaxChars.test(escapedChar);
             });
diff --git a/src/reg.js b/src/reg.js
index bb1804a4..0595c7cd 100644
--- a/src/reg.js
+++ b/src/reg.js
@@ -49,6 +49,8 @@ exports.regexpSyntaxChars = /[\^$\\.*+?()[\]{}|]/;
 
 exports.regexpQuantifiers = /[*+?{]/;
 
+exports.regexpControlEscapes = /[fnrtv]/;
+
 exports.regexpCharClasses = /[dDsSwW]/;
 
 // Identifies the "dot" atom in regular expressions


Test patch:
diff --git a/tests/unit/parser.js b/tests/unit/parser.js
index 98e6f1c6..b3fd37b4 100644
--- a/tests/unit/parser.js
+++ b/tests/unit/parser.js
@@ -700,6 +700,20 @@ exports.regexp.basic = function (test) {
     .addError(1, 9, "Invalid regular expression.")
     .test("var a = /.*/ii;");
 
+  TestRun(test, "Invalid Decimal Escape Sequence tolerated without `u` flag")
+    .test([
+      "void /\\00/;",
+      "void /\\01/;",
+      "void /\\02/;",
+      "void /\\03/;",
+      "void /\\04/;",
+      "void /\\05/;",
+      "void /\\06/;",
+      "void /\\07/;",
+      "void /\\08/;",
+      "void /\\09/;"
+    ]);
+
   test.done();
 };
 
@@ -742,6 +756,16 @@ exports.regexp.uFlag = function (test) {
     .addError(1, 6, "Invalid regular expression.")
     .test("void /.{}/u;", { esversion: 6 });
 
+  TestRun(test)
+    .test([
+      "void /.{0}/;",
+      "void /.{0}/u;",
+      "void /.{9}/;",
+      "void /.{9}/u;",
+      "void /.{23}/;",
+      "void /.{23}/u;"
+    ], { esversion: 6 });
+
   TestRun(test)
     .addError(1, 6, "Invalid regular expression.")
     .test("void /.{,2}/u;", { esversion: 6 });
@@ -754,6 +778,25 @@ exports.regexp.uFlag = function (test) {
     .addError(1, 6, "Invalid regular expression.")
     .test("void /(.)(.)\\3/u;", { esversion: 6 });
 
+  TestRun(test, "Valid group reference - multiple digits")
+    .test([
+      "void /(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\\10/u;",
+      "void /(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\\10a/u;",
+      "void /(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\\11/u;",
+      "void /(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\\11a/u;"
+    ], { esversion: 6 });
+
+  // A negative syntax test is the only way to verify JSHint is interpeting the
+  // adjacent digits as one reference as opposed to a reference followed by a
+  // literal digit.
+  TestRun(test, "Invalid group reference - two digits")
+    .addError(1, 6, "Invalid regular expression.")
+    .test("void /(.)\\10/u;", { esversion: 6 });
+
+  TestRun(test, "Invalid group reference - three digits")
+    .addError(1, 6, "Invalid regular expression.")
+    .test("void /(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\\100/u;", { esversion: 6 });
+
   TestRun(test, "Invalid group reference (permitted without flag)")
     .test("void /(.)(.)\\3/;", { esversion: 6 });
 
@@ -816,6 +859,29 @@ exports.regexp.uFlag = function (test) {
   TestRun(test)
     .test('void /[\\s0-1\\s2-3\\s]/u;', { esversion: 6 });
 
+  TestRun(test, "Null CharacterEscape")
+    .test([
+      "void /\\0/u;",
+      "void /\\0a/u;"
+    ], { esversion: 6 });
+
+  TestRun(test)
+    .addError(1, 6, "Invalid regular expression.")
+    .test("void /\\00/u;", { esversion: 6 });
+
+  TestRun(test)
+    .addError(1, 6, "Invalid regular expression.")
+    .test("void /\\01/u;", { esversion: 6 });
+
+  TestRun(test, "ControlEscape")
+    .test([
+      "void /\\f/u;",
+      "void /\\n/u;",
+      "void /\\r/u;",
+      "void /\\t/u;",
+      "void /\\v/u;"
+    ], { esversion: 6 });
+
   test.done();
 };