Repository: jshint/jshint
Pull request #3120 (2017-04-16 00:18:32)

Correct a few bugs in escape sequence parsing
@rwaldron 5 tests down, 1,411 to go :P.

If this looks good to you, please rebase (if necessary) and merge using git's fast-forward strategy.

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

Coverage increased (+0.0005%) to 97.753% when pulling **457e51014b829acdc4672873352f6f57327e21e1 on jugglinmike:escapes** into **376fa624d7eacc8f7b85c721bcd48a07ecf2643b on jshint:master**.

rwaldron: Excellent :)

Patch:
diff --git a/src/lex.js b/src/lex.js
index 74b20592..573092b5 100644
--- a/src/lex.js
+++ b/src/lex.js
@@ -40,6 +40,14 @@ var Context = {
   Template: 2
 };
 
+function isHex(str) {
+  return /^[0-9a-fA-F]+$/.test(str);
+}
+
+function isHexDigit(str) {
+  return str.length === 1 && isHex(str);
+}
+
 // Object that handles postponed lexing verifications that checks the parsed
 // environment state.
 
@@ -585,10 +593,6 @@ Lexer.prototype = {
       return isNonAsciiIdentifierStart(code) || nonAsciiIdentifierPartTable.indexOf(code) > -1;
     }
 
-    function isHexDigit(str) {
-      return (/^[0-9a-fA-F]$/).test(str);
-    }
-
     var readUnicodeEscapeSequence = function() {
       /*jshint validthis:true */
       index += 1;
@@ -597,18 +601,16 @@ Lexer.prototype = {
         return null;
       }
 
-      var ch1 = this.peek(index + 1);
-      var ch2 = this.peek(index + 2);
-      var ch3 = this.peek(index + 3);
-      var ch4 = this.peek(index + 4);
+      var sequence = this.peek(index + 1) + this.peek(index + 2) +
+        this.peek(index + 3) + this.peek(index + 4);
       var code;
 
-      if (isHexDigit(ch1) && isHexDigit(ch2) && isHexDigit(ch3) && isHexDigit(ch4)) {
-        code = parseInt(ch1 + ch2 + ch3 + ch4, 16);
+      if (isHex(sequence)) {
+        code = parseInt(sequence, 16);
 
         if (asciiIdentifierPartTable[code] || isNonAsciiIdentifierPart(code)) {
           index += 5;
-          return "\\u" + ch1 + ch2 + ch3 + ch4;
+          return "\\u" + sequence;
         }
 
         return null;
@@ -742,10 +744,6 @@ Lexer.prototype = {
       return (/^[01]$/).test(str);
     }
 
-    function isHexDigit(str) {
-      return (/^[0-9a-fA-F]$/).test(str);
-    }
-
     function isIdentifierStart(ch) {
       return (ch === "$") || (ch === "_") || (ch === "\\") ||
         (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z");
@@ -984,17 +982,32 @@ Lexer.prototype = {
       }, checks,
       function() { return n >= 0 && n <= 7 && state.isStrict(); });
       break;
+    case "1":
+    case "2":
+    case "3":
+    case "4":
+    case "5":
+    case "6":
+    case "7":
+      char = "\\" + char;
+      this.triggerAsync("warning", {
+        code: "W115",
+        line: this.line,
+        character: this.char
+      }, checks,
+      function() { return state.isStrict(); });
+      break;
     case "u":
-      var hexCode = this.input.substr(1, 4);
-      var code = parseInt(hexCode, 16);
-      if (isNaN(code)) {
+      var sequence = this.input.substr(1, 4);
+      var code = parseInt(sequence, 16);
+      if (!isHex(sequence)) {
         // This condition unequivocally describes a syntax error.
         // TODO: Re-factor as an "error" (not a "warning").
         this.trigger("warning", {
           code: "W052",
           line: this.line,
           character: this.char,
-          data: [ "u" + hexCode ]
+          data: [ "u" + sequence ]
         });
       }
       char = String.fromCharCode(code);


Test patch:
diff --git a/tests/test262/expectations.txt b/tests/test262/expectations.txt
index 573597fa..b445dc7b 100644
--- a/tests/test262/expectations.txt
+++ b/tests/test262/expectations.txt
@@ -441,10 +441,6 @@ language/eval-code/indirect/var-env-var-init-global-exstng.js
 language/eval-code/indirect/var-env-var-non-strict.js
 language/eval-code/indirect/var-env-var-init-global-new.js
 language/eval-code/indirect/var-env-var-strict.js
-language/literals/string/S7.8.4_A4.3_T1.js
-language/literals/string/S7.8.4_A4.3_T2.js
-language/literals/string/S7.8.4_A7.1_T4.js
-language/literals/string/legacy-octal-escape-sequence-strict.js
 language/literals/numeric/S7.8.3_A6.1_T1.js
 language/literals/numeric/S7.8.3_A6.1_T2.js
 language/literals/numeric/binary-invalid-truncated.js
@@ -624,7 +620,6 @@ language/expressions/yield/rhs-omitted.js
 language/expressions/yield/rhs-primitive.js
 language/expressions/yield/rhs-template-middle.js
 language/expressions/template-literal/invalid-hexidecimal-character-escape-sequence-truncated-2.js
-language/expressions/template-literal/invalid-unicode-escape-sequence-4.js
 language/expressions/template-literal/tv-utf16-escape-sequence.js
 language/expressions/function/arguments-with-arguments-fn.js
 language/expressions/function/arguments-with-arguments-lex.js
diff --git a/tests/unit/parser.js b/tests/unit/parser.js
index 14b96275..e8e78735 100644
--- a/tests/unit/parser.js
+++ b/tests/unit/parser.js
@@ -709,11 +709,20 @@ exports.strings = function (test) {
 
 exports.badStrings = function (test) {
   var code = [
-    "var a = '\\uNOTHEX';"
+    "var a = '\\uNOTHEX';",
+    "void '\\u0000';",
+    "void '\\ug000';",
+    "void '\\u0g00';",
+    "void '\\u00g0';",
+    "void '\\u000g';"
   ];
 
   var run = TestRun(test)
-    .addError(1, "Unexpected 'uNOTH'.");
+    .addError(1, "Unexpected 'uNOTH'.")
+    .addError(3, "Unexpected 'ug000'.")
+    .addError(4, "Unexpected 'u0g00'.")
+    .addError(5, "Unexpected 'u00g0'.")
+    .addError(6, "Unexpected 'u000g'.");
   run.test(code, {es3: true});
   run.test(code, {}); // es5
   run.test(code, {esnext: true});
@@ -7960,3 +7969,43 @@ exports.forInExpr = function (test) {
 
   test.done();
 };
+
+exports.octalEscape = function (test) {
+  TestRun(test)
+    .addError(3, "Octal literals are not allowed in strict mode.")
+    .addError(4, "Octal literals are not allowed in strict mode.")
+    .addError(5, "Octal literals are not allowed in strict mode.")
+    .addError(6, "Octal literals are not allowed in strict mode.")
+    .addError(7, "Octal literals are not allowed in strict mode.")
+    .addError(8, "Octal literals are not allowed in strict mode.")
+    .addError(9, "Octal literals are not allowed in strict mode.")
+    .test([
+      "'use strict';",
+      "void '\\0';",
+      "void '\\1';",
+      "void '\\2';",
+      "void '\\3';",
+      "void '\\4';",
+      "void '\\5';",
+      "void '\\6';",
+      "void '\\7';",
+      "void '\\8';",
+      "void '\\9';"
+    ], { strict: "global" });
+
+  TestRun(test)
+    .test([
+      "void '\\0';",
+      "void '\\1';",
+      "void '\\2';",
+      "void '\\3';",
+      "void '\\4';",
+      "void '\\5';",
+      "void '\\6';",
+      "void '\\7';",
+      "void '\\8';",
+      "void '\\9';"
+    ]);
+
+  test.done();
+};