Repository: jshint/jshint (JavaScript)

Issue:
Optional chaining reports unorthodox function invocation error in specific case
Using the latest JSHint 2.13.0 that has optional chaining support (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining), I see that a specific line wrongly reports an "unorthodox function invocation" error:

```
/* jshint esversion:11 */
var someChainingObj = {aNum:10,
                       aFunc: function() { return 500; },
                       anArr: ['val1', 'val2'] };

alert(someChainingObj?.aFunc?.()); // Reports unorthodox function invocation error
alert(someChainingObj.aFunc?.()); // Works fine

alert(someChainingObj?.anArr?.[1]); // Works fine
alert(someChainingObj.anArr?.[1]); // Works fine

alert(someChainingObj?.aNum?.isPrototypeOf); // Works fine
alert(someChainingObj.aNum?.isPrototypeOf); // Works fine

```

The error is only for the above mentioned line for "aFunc". It does not happen for arrays and for objects. I don't see anything wrong in the line that reports the above error.


Discussion:
Thanks for the report! The issue is the CallExpression, so we can futher reduce the problem like this:

```js
/* jshint expr: true, esversion: 11 */

Math?.min();
```

Working on a fix, now.
Fix under review here: gh-3552
[We released the fix in JSHint version 2.13.1.](https://jshint.com/blog/2021-08-10/release-2-13-1/)



Patch:
diff --git a/src/jshint.js b/src/jshint.js
index 3ffbee968c..e60a65b056 100644
--- a/src/jshint.js
+++ b/src/jshint.js
@@ -3081,6 +3081,21 @@ var JSHINT = (function() {
     return that;
   }, 160, true);
 
+
+  /**
+   * Determine if a CallExpression's "base" is a type of expression commonly
+   * used in this position.
+   *
+   * @param {token} token - token describing the "base" of the CallExpression
+   * @returns {boolean}
+   */
+  function isTypicalCallExpression(token) {
+    return token.identifier || token.id === "." || token.id === "[" ||
+      token.id === "=>" || token.id === "(" || token.id === "&&" ||
+      token.id === "||" || token.id === "?" || token.id === "async" ||
+      token.id === "?." || (state.inES6() && token["(name)"]);
+  }
+
   infix("(", function(context, left, that) {
     if (state.option.immed && left && !left.immed && left.id === "function") {
       warning("W062");
@@ -3171,9 +3186,7 @@ var JSHINT = (function() {
           addEvalCode(left, p[0]);
         }
       }
-      if (!left.identifier && left.id !== "." && left.id !== "[" && left.id !== "=>" &&
-          left.id !== "(" && left.id !== "&&" && left.id !== "||" && left.id !== "?" &&
-          left.id !== "async" && !(state.inES6() && left["(name)"])) {
+      if (!isTypicalCallExpression(left)) {
         warning("W067", that);
       }
     }


Test patch:
diff --git a/tests/unit/parser.js b/tests/unit/parser.js
index 1c31fc5630..c94e0f9060 100644
--- a/tests/unit/parser.js
+++ b/tests/unit/parser.js
@@ -10590,5 +10590,11 @@ exports.optionalChaining = function (test) {
       { esversion: 11 }
     );
 
+  TestRun(test, "CallExpression")
+    .test(
+      "true?.false();",
+      { esversion: 11 }
+    );
+
   test.done();
 };