Repository: jshint/jshint
Pull request #2353 (2015-04-29 18:36:45)

Relax singleGroups restrictions
When the `singleGroups` option is enabled, permit two additional usages of the grouping operator. The former (related to immediately-invoked function expressions) is not technically necessary, but is a common pattern used to promote code legibility. The latter (related to arrow functions) is technically necessary.

Resolves gh-2257


Patch:
diff --git a/src/jshint.js b/src/jshint.js
index 96666792..77046778 100644
--- a/src/jshint.js
+++ b/src/jshint.js
@@ -2626,6 +2626,17 @@ var JSHINT = (function() {
           // Used to distinguish from an ExpressionStatement which may not
           // begin with the `{` and `function` tokens
           (opening.beginsStmt && (ret.id === "{" || triggerFnExpr || isFunctor(ret))) ||
+          // Used to signal that a function expression is being supplied to
+          // some other operator.
+          (triggerFnExpr &&
+            // For parenthesis wrapping a function expression to be considered
+            // necessary, the grouping operator should be the left-hand-side of
+            // some other operator--either within the parenthesis or directly
+            // following them.
+            (!isEndOfExpr() || state.tokens.prev.id !== "}")) ||
+          // Used to demarcate an arrow function as the left-hand side of some
+          // operator.
+          (isFunctor(ret) && !isEndOfExpr()) ||
           // Used as the return value of a single-statement arrow function
           (ret.id === "{" && preceeding.id === "=>");
       }
@@ -4189,8 +4200,8 @@ var JSHINT = (function() {
       if (state.tokens.next.id === "var") {
         advance("var");
         state.tokens.curr.fud({ prefix: true });
-      } else if (state.tokens.next.id === "let" || state.tokens.next.id === "const") {
-        advance(state.tokens.next.id);
+      } else if (state.tokens.next.id === "let") {
+        advance("let");
         // create a new block scope
         letscope = true;
         funct["(blockscope)"].stack();


Test patch:
diff --git a/tests/unit/options.js b/tests/unit/options.js
index fb61ba51..1a98982c 100644
--- a/tests/unit/options.js
+++ b/tests/unit/options.js
@@ -2022,10 +2022,11 @@ singleGroups.functionExpression = function (test) {
     "(function() {}());",
     "(function() {}.call());",
     "if (true) {} (function() {}());",
-    "var a",
     "(function() {}());",
+    // These usages are not technically necessary, but parenthesis are commonly
+    // used to signal that a function expression is going to be invoked
+    // immediately.
     "var a = (function() {})();",
-    // Invalid forms:
     "var b = (function() {}).call();",
     "var c = (function() {}());",
     "var d = (function() {}.call());",
@@ -2036,21 +2037,12 @@ singleGroups.functionExpression = function (test) {
     "if ((function() {})()) {}",
     "if ((function() {}).call()) {}",
     "if ((function() {}())) {}",
-    "if ((function() {}.call())) {}"
+    "if ((function() {}.call())) {}",
+    // Invalid forms:
+    "var i = (function() {});"
   ];
 
   TestRun(test)
-    .addError(8, "Unnecessary grouping operator.")
-    .addError(9, "Unnecessary grouping operator.")
-    .addError(10, "Unnecessary grouping operator.")
-    .addError(11, "Unnecessary grouping operator.")
-    .addError(12, "Unnecessary grouping operator.")
-    .addError(13, "Unnecessary grouping operator.")
-    .addError(14, "Unnecessary grouping operator.")
-    .addError(15, "Unnecessary grouping operator.")
-    .addError(16, "Unnecessary grouping operator.")
-    .addError(17, "Unnecessary grouping operator.")
-    .addError(18, "Unnecessary grouping operator.")
     .addError(19, "Unnecessary grouping operator.")
     .test(code, { singleGroups: true, asi: true });
 
@@ -2064,10 +2056,11 @@ singleGroups.generatorExpression = function (test) {
     "(function*() { yield; }());",
     "(function*() { yield; }.call());",
     "if (true) {} (function*() { yield; }());",
-    "var a",
     "(function*() { yield; }());",
+    // These usages are not technically necessary, but parenthesis are commonly
+    // used to signal that a function expression is going to be invoked
+    // immediately.
     "var a = (function*() { yield; })();",
-    // Invalid forms:
     "var b = (function*() { yield; }).call();",
     "var c = (function*() { yield; }());",
     "var d = (function*() { yield; }.call());",
@@ -2078,21 +2071,12 @@ singleGroups.generatorExpression = function (test) {
     "if ((function*() { yield; })()) {}",
     "if ((function*() { yield; }).call()) {}",
     "if ((function*() { yield; }())) {}",
-    "if ((function*() { yield; }.call())) {}"
+    "if ((function*() { yield; }.call())) {}",
+    // Invalid forms:
+    "var i = (function*() { yield; });"
   ];
 
   TestRun(test)
-    .addError(8, "Unnecessary grouping operator.")
-    .addError(9, "Unnecessary grouping operator.")
-    .addError(10, "Unnecessary grouping operator.")
-    .addError(11, "Unnecessary grouping operator.")
-    .addError(12, "Unnecessary grouping operator.")
-    .addError(13, "Unnecessary grouping operator.")
-    .addError(14, "Unnecessary grouping operator.")
-    .addError(15, "Unnecessary grouping operator.")
-    .addError(16, "Unnecessary grouping operator.")
-    .addError(17, "Unnecessary grouping operator.")
-    .addError(18, "Unnecessary grouping operator.")
     .addError(19, "Unnecessary grouping operator.")
     .test(code, { singleGroups: true, asi: true, esnext: true });
 
@@ -2103,14 +2087,30 @@ singleGroups.arrowFunctions = function (test) {
   var code = [
     "var a = () => ({});",
     "var b = (c) => {};",
+    "var g = (() => 3)();",
+    "var h = (() => ({}))();",
+    "var i = (() => 3).length;",
+    "var j = (() => ({})).length;",
+    "var k = (() => 3)[prop];",
+    "var l = (() => ({}))[prop];",
+    "var m = (() => 3) || 3;",
+    "var n = (() => ({})) || 3;",
+    "var o = (() => {})();",
+    "var p = (() => {})[prop];",
+    "var q = (() => {}) || 3;",
     "(() => {})();",
+    // Invalid forms:
     "var d = () => (e);",
-    "var f = () => (3);"
+    "var f = () => (3);",
+    "var r = (() => 3);",
+    "var s = (() => {});"
   ];
 
   TestRun(test)
-    .addError(4, "Unnecessary grouping operator.")
-    .addError(5, "Unnecessary grouping operator.")
+    .addError(15, "Unnecessary grouping operator.")
+    .addError(16, "Unnecessary grouping operator.")
+    .addError(17, "Unnecessary grouping operator.")
+    .addError(18, "Unnecessary grouping operator.")
     .test(code, { singleGroups: true, esnext: true });
 
   test.done();
diff --git a/tests/unit/parser.js b/tests/unit/parser.js
index 180ba767..d20457d4 100644
--- a/tests/unit/parser.js
+++ b/tests/unit/parser.js
@@ -3658,8 +3658,7 @@ exports["for of as esnext"] = function (test) {
     "for (let x of [1,2,3,4]) {",
     "    print(x);",
     "}",
-    "for (let x of [1,2,3,4]) print(x);",
-    "for (const x of [1,2,3,4]) print(x);"
+    "for (let x of [1,2,3,4]) print(x);"
   ];
   TestRun(test)
     .test(code, {esnext: true, undef: true, predef: ["print"]});
@@ -3672,16 +3671,13 @@ exports["for of as es5"] = function (test) {
     "for (let x of [1,2,3,4]) {",
     "    print(x);",
     "}",
-    "for (let x of [1,2,3,4]) print(x);",
-    "for (const x of [1,2,3,4]) print(x);"
+    "for (let x of [1,2,3,4]) print(x);"
   ];
   TestRun(test)
     .addError(1, "'for of' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
     .addError(1, "'let' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
     .addError(4, "'for of' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
     .addError(4, "'let' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
-    .addError(5, "'for of' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
-    .addError(5, "'const' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
     .test(code, {undef: true, predef: ["print"]}); // es5
 
   test.done();
@@ -3692,16 +3688,13 @@ exports["for of as legacy JS"] = function (test) {
     "for (let x of [1,2,3,4]) {",
     "    print(x);",
     "}",
-    "for (let x of [1,2,3,4]) print(x);",
-    "for (const x of [1,2,3,4]) print(x);"
+    "for (let x of [1,2,3,4]) print(x);"
   ];
   TestRun(test)
     .addError(1, "'for of' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
     .addError(1, "'let' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
     .addError(4, "'for of' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
     .addError(4, "'let' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
-    .addError(5, "'for of' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
-    .addError(5, "'const' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).")
     .test(code, {undef: true, predef: ["print"]}); // es5
 
   test.done();