Repository: jshint/jshint
Pull request #3563 (2021-08-19 17:43:42)

[[FIX]] Tolerate unterminated nullish coalescing
This resolves gh-3560


Patch:
diff --git a/src/jshint.js b/src/jshint.js
index f409c061..3be7ad4e 100644
--- a/src/jshint.js
+++ b/src/jshint.js
@@ -423,9 +423,10 @@ var JSHINT = (function() {
 
     removeIgnoredMessages();
 
-    if (JSHINT.errors.length >= state.option.maxerr)
+    var errors = JSHINT.errors.filter(function(e) { return /E\d{3}/.test(e.code); });
+    if (errors.length >= state.option.maxerr) {
       quit("E043", t);
-
+    }
     return w;
   }
 
@@ -2450,7 +2451,9 @@ var JSHINT = (function() {
     that.left = left;
     var right = that.right = expression(context, 39);
 
-    if (!right.paren && (right.id === "||" || right.id === "&&")) {
+    if (!right) {
+      error("E024", state.tokens.next, state.tokens.next.id);
+    } else if (!right.paren && (right.id === "||" || right.id === "&&")) {
       error("E024", that.right, that.right.id);
     }
 
diff --git a/src/lex.js b/src/lex.js
index 8113baba..7e5b5cdf 100644
--- a/src/lex.js
+++ b/src/lex.js
@@ -1114,9 +1114,6 @@ Lexer.prototype = {
     case "\\":
       char = "\\\\";
       break;
-    case "\"":
-      char = "\\\"";
-      break;
     case "/":
       break;
     case "":
diff --git a/src/options.js b/src/options.js
index 482575ea..8e622296 100644
--- a/src/options.js
+++ b/src/options.js
@@ -820,7 +820,7 @@ exports.val = {
   indent       : false,
 
   /**
-   * This options allows you to set the maximum amount of warnings JSHint will
+   * This options allows you to set the maximum amount of errors JSHint will
    * produce before giving up. Default is 50.
    */
   maxerr       : false,
diff --git a/src/vars.js b/src/vars.js
index c9a63ecd..173e8e07 100644
--- a/src/vars.js
+++ b/src/vars.js
@@ -518,7 +518,8 @@ exports.node = {
   setInterval   : true,
   clearInterval : true,
   setImmediate  : true, // v0.9.1+
-  clearImmediate: true  // v0.9.1+
+  clearImmediate: true, // v0.9.1+
+  URL           : true  // v6.13.0+
 };
 
 exports.browserify = {


Test patch:
diff --git a/tests/unit/core.js b/tests/unit/core.js
index 0d989788..80ae889b 100644
--- a/tests/unit/core.js
+++ b/tests/unit/core.js
@@ -421,9 +421,17 @@ exports.argsInCatchReused = function (test) {
   test.done();
 };
 
-exports.testRawOnError = function (test) {
+exports.testRawOnWarning = function (test) {
   JSHINT(';', { maxerr: 1 });
   test.equal(JSHINT.errors[0].raw, 'Unnecessary semicolon.');
+  test.equal(JSHINT.errors[1], null);
+
+  test.done();
+};
+
+exports.testRawOnError = function (test) {
+  JSHINT('@', { maxerr: 1 });
+  test.equal(JSHINT.errors[0].raw, 'Unexpected \'{a}\'.');
   test.equal(JSHINT.errors[1].raw, 'Too many errors.');
   test.equal(JSHINT.errors[2], null);
 
@@ -472,12 +480,6 @@ exports.insideEval = function (test) {
 
     .test(src, { es3: true, evil: false });
 
-  // Regression test for bug GH-714.
-  JSHINT(src, { evil: false, maxerr: 1 });
-  var err = JSHINT.data().errors[1];
-  test.equal(err.raw, "Too many errors.");
-  test.equal(err.scope, "(main)");
-
   test.done();
 };
 
@@ -2310,6 +2312,17 @@ exports.duplicateProto = function (test) {
     //.addError(3, 23232323, "Duplicate key '__proto__'.")
     .test(src, { proto: true });
 
+  src = [
+    "void {",
+    "  '\"': null,",
+    "  \"\\\"\": null",
+    "};"
+  ];
+
+  TestRun(test, "Duplicate keys (backslash)")
+    .addError(3, 7, "Duplicate key '\"'.")
+    .test(src);
+
   src = [
     "void {",
     "  __proto__: null,",
diff --git a/tests/unit/parser.js b/tests/unit/parser.js
index 96928580..fb4507f9 100644
--- a/tests/unit/parser.js
+++ b/tests/unit/parser.js
@@ -9142,7 +9142,14 @@ exports.restOperatorWithoutIdentifier = function (test) {
     .addError(8, 24, "Expected an identifier and instead saw ')'.")
     .addError(8, 26, "Expected ',' and instead saw '=>'.")
     .addError(8, 30, "Expected ',' and instead saw ';'.")
-    .addError(8, 30, "Too many errors. (44% scanned).")
+    .addError(9, 1, "Expected an identifier and instead saw 'var' (a reserved word).")
+    .addError(9, 5, "Expected ',' and instead saw 'arrow3'.")
+    .addError(9, 12, "Expected an identifier and instead saw '='.")
+    .addError(9, 14, "Expected ',' and instead saw '('.")
+    .addError(9, 19, "Expected an identifier and instead saw ']'.")
+    .addError(9, 20, "Expected ',' and instead saw ')'.")
+    .addError(9, 22, "Expected an identifier and instead saw '=>'.")
+    .addError(9, 22, "Too many errors. (50% scanned).")
     .test(code, { esnext: true });
 
   test.done();
@@ -10617,3 +10624,26 @@ exports.loneNew = function (test) {
 
   test.done();
 };
+
+// gh-3560: "Logical nullish assignment (??=) throwing error"
+exports.loneNullishCoalescing = function (test) {
+  TestRun(test, "as reported")
+    .addError(2, 8, "Expected an identifier and instead saw '='.")
+    .addError(2, 10, "Unexpected '(number)'.")
+    .addError(2, 8, "Expected an assignment or function call and instead saw an expression.")
+    .addError(2, 9, "Missing semicolon.")
+    .addError(2, 10, "Expected an assignment or function call and instead saw an expression.")
+    .test([
+      "let a = [1,2];",
+      "a[0] ??= 0;"
+    ], {esversion: 11});
+
+  TestRun(test, "simplified")
+    .addError(1, 4, "Expected an identifier and instead saw ';'.")
+    .addError(1, 4, "Unexpected '(end)'.")
+    .addError(1, 4, "Expected an assignment or function call and instead saw an expression.")
+    .addError(1, 5, "Missing semicolon.")
+    .test("0??;", {esversion: 11});
+
+  test.done();
+};