Repository: eslint/eslint (js)

Issue:
Non reporting errors for redundant space between "typeof" and "(" - related to "space-unary-ops" rule.
**Tell us about your environment**

* **ESLint Version:** v4.16.0
* **Node Version:** v6.10.2
* **npm Version:** v5.6.0

**What parser (default, Babel-ESLint, etc.) are you using?**
Default

**Please show your full configuration:**
<details>
<summary>Configuration</summary>

<!-- Paste your configuration below: -->
```json
{
    "extends": "airbnb-base",
    "rules": {
        "prefer-arrow-callback": "off",
        "func-names": ["error", "never"],
        "space-before-function-paren": ["error", "never"],
        "indent": ["error", 4],
        "no-var": "off",
        "object-shorthand": ["error", "never"],
        "prefer-destructuring": "off",
        "import/no-unresolved": [2, {"commonjs": true}],
        "import/no-dynamic-require": "warn",
        "global-require": "warn",
        "vars-on-top": "off",
        "no-param-reassign": "off",
        "eqeqeq": "off",
        "no-use-before-define": ["error", {"functions": false}],
        "prefer-template": "off",
        "no-shadow": "off",
        "max-len": "off",
        "comma-dangle": ["error", "never"],
        "no-restricted-syntax": ["error", "BinaryExpression[operator='in']"],
        "no-continue": "off",
        "no-plusplus": "off",
        "brace-style": ["error", "stroustrup", {"allowSingleLine": false}],
        "space-unary-ops": [2, {
            "overrides": {
                "typeof": false
            }
        }]
    }
}
```
> Last config is critical
</details>

**What did you do? Please include the actual source code causing the issue, as well as the command that you used to run ESLint.**

<!-- Paste the source code below: -->
```js
var obj = null;
if (typeof (obj) === 'object') {
    console.log("THIS SHOULD REPORT ERROR BETWEEN 'typeof' AND '('. BUT IT DOESN'T");
}
if (typeof(obj) === 'object') {
    console.log('THIS IS OK AS IT DOES NOT REPORT ERROR.');
}
```

<!-- Paste the command you used to run ESLint: -->
```bash
./node_modules/.bin/eslint issue.js
```

**What did you expect to happen?**
First case with `typeof (obj) === 'object'` should report error.

**What actually happened? Please include the actual, raw output from ESLint.**
No error was reported in first case. Actually there is nothing to show from console.


Pull request description:
Update: space-unary-ops uses astUtils.canTokensBeAdjacent (fixes #9907)
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

[ ] Documentation update
[x] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

**Tell us about your environment**

* **ESLint Version:** 4.16.x / master
* **Node Version:** 8.9.3
* **npm Version:** 4.1.1

**What parser (default, Babel-ESLint, etc.) are you using?**

Default

**Please show your full configuration:**

(See demo link below)

**What did you do? Please include the actual source code causing the issue.**

See [demo](https://eslint.org/demo/#eyJ0ZXh0IjoiLyogZXNsaW50IHNwYWNlLXVuYXJ5LW9wczogW2Vycm9yLCB7IHdvcmRzOiBmYWxzZSB9XSAqL1xyXG50eXBlb2YgKGZvby5iYXIpO1xyXG50eXBlb2YgW2Zvb107XHJcblxyXG5kZWxldGUgKGZvby5iYXIpO1xyXG5cclxudm9pZCAoZm9vLmJhcik7XHJcbnZvaWQgW2Zvb107XHJcblxyXG5uZXcgKGZvby5iYXIpO1xyXG5uZXcgW2Zvb11bMF07XHJcblxyXG4oZnVuY3Rpb24gKiAoKSB7IHlpZWxkIChmb28uYmFyKTsgfSk7XHJcbihmdW5jdGlvbiAqICgpIHsgeWllbGQgW2Zvby5iYXJdOyB9KTtcclxuXHJcbihhc3luYyBmdW5jdGlvbiAoKSB7IGF3YWl0IChmb28uYmFyKTsgfSk7XHJcbihhc3luYyBmdW5jdGlvbiAoKSB7IGF3YWl0IFtmb28uYmFyXTsgfSk7Iiwib3B0aW9ucyI6eyJwYXJzZXJPcHRpb25zIjp7ImVjbWFWZXJzaW9uIjo4LCJzb3VyY2VUeXBlIjoibW9kdWxlIiwiZWNtYUZlYXR1cmVzIjp7ImpzeCI6dHJ1ZX19LCJydWxlcyI6eyJjYW1lbGNhc2UiOjJ9LCJlbnYiOnt9fX0=).

**What did you expect to happen?**

Every UnaryExpression, NewExpression, YieldExpression, and AwaitExpression should be flagged as invalid. It is possible to remove the spaces between the unary word operator and the next token.

**What actually happened? Please include the actual, raw output from ESLint.**

No linting errors.

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

**What changes did you make? (Give an overview)**

Changed special-case ObjectExpression check (which seemed to be used as a check for whether the next token was `{`) to instead use `astUtils.canTokensBeAdjacent`, which generalizes the logic and allows the rule to work correctly with `(` and `[` tokens.

**Is there anything you'd like reviewers to focus on?**

Not really.

Patch:
diff --git a/docs/rules/space-unary-ops.md b/docs/rules/space-unary-ops.md
index 8853d9239..d04bd4a45 100644
--- a/docs/rules/space-unary-ops.md
+++ b/docs/rules/space-unary-ops.md
@@ -59,7 +59,7 @@ This rule has three options:
 
 In this case, spacing will be disallowed after a `new` operator and required before/after a `++` operator.
 
-Examples of **incorrect** code for this rule with the `{"words": true, "nonwords": false}` option:
+Examples of **incorrect** code for this rule with the default `{"words": true, "nonwords": false}` option:
 
 ```js
 /*eslint space-unary-ops: "error"*/
@@ -90,6 +90,14 @@ function *foo() {
 }
 ```
 
+```js
+/*eslint space-unary-ops: "error"*/
+
+async function foo() {
+    await(bar);
+}
+```
+
 Examples of **correct** code for this rule with the `{"words": true, "nonwords": false}` option:
 
 ```js
@@ -125,3 +133,11 @@ function *foo() {
     yield (0)
 }
 ```
+
+```js
+/*eslint space-unary-ops: "error"*/
+
+async function foo() {
+    await (bar);
+}
+```
diff --git a/lib/rules/space-unary-ops.js b/lib/rules/space-unary-ops.js
index 4d122836a..601d705e6 100644
--- a/lib/rules/space-unary-ops.js
+++ b/lib/rules/space-unary-ops.js
@@ -66,15 +66,6 @@ module.exports = {
                 node.argument && node.argument.type === "UnaryExpression" && node.argument.operator === "!";
         }
 
-        /**
-         * Check if the node's child argument is an "ObjectExpression"
-         * @param {ASTnode} node AST node
-         * @returns {boolean} Whether or not the argument's type is "ObjectExpression"
-         */
-        function isArgumentObjectExpression(node) {
-            return node.argument && node.argument.type && node.argument.type === "ObjectExpression";
-        }
-
         /**
          * Checks if an override exists for a given operator.
          * @param {string} operator Operator
@@ -125,7 +116,7 @@ module.exports = {
          * @returns {void}
          */
         function verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word) {
-            if (isArgumentObjectExpression(node)) {
+            if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) {
                 if (secondToken.range[0] > firstToken.range[1]) {
                     context.report({
                         node,


Test patch:
diff --git a/tests/lib/rules/space-unary-ops.js b/tests/lib/rules/space-unary-ops.js
index 22efa2088..11b2c96bf 100644
--- a/tests/lib/rules/space-unary-ops.js
+++ b/tests/lib/rules/space-unary-ops.js
@@ -41,6 +41,7 @@ ruleTester.run("space-unary-ops", rule, {
             code: "foo.bar --",
             options: [{ nonwords: true }]
         },
+
         {
             code: "delete foo.bar",
             options: [{ words: true }]
@@ -49,6 +50,14 @@ ruleTester.run("space-unary-ops", rule, {
             code: "delete foo[\"bar\"]",
             options: [{ words: true }]
         },
+        {
+            code: "delete foo.bar",
+            options: [{ words: false }]
+        },
+        {
+            code: "delete(foo.bar)",
+            options: [{ words: false }]
+        },
 
         {
             code: "new Foo",
@@ -79,6 +88,14 @@ ruleTester.run("space-unary-ops", rule, {
             code: "typeof {foo:true}",
             options: [{ words: true }]
         },
+        {
+            code: "typeof (foo)",
+            options: [{ words: true }]
+        },
+        {
+            code: "typeof(foo)",
+            options: [{ words: false }]
+        },
         {
             code: "typeof!foo",
             options: [{ words: false }]
@@ -100,6 +117,14 @@ ruleTester.run("space-unary-ops", rule, {
             code: "void foo",
             options: [{ words: true }]
         },
+        {
+            code: "void foo",
+            options: [{ words: false }]
+        },
+        {
+            code: "void(foo)",
+            options: [{ words: false }]
+        },
 
         {
             code: "-1",
@@ -217,12 +242,12 @@ ruleTester.run("space-unary-ops", rule, {
             options: [{ words: false, overrides: { new: false } }]
         },
         {
-            code: "function *foo () { yield (0) }",
+            code: "function *foo () { yield(0) }",
             options: [{ words: true, overrides: { yield: false } }],
             parserOptions: { ecmaVersion: 6 }
         },
         {
-            code: "function *foo () { yield (0) }",
+            code: "function *foo () { yield(0) }",
             options: [{ words: false, overrides: { yield: false } }],
             parserOptions: { ecmaVersion: 6 }
         }
@@ -247,6 +272,15 @@ ruleTester.run("space-unary-ops", rule, {
                 type: "UnaryExpression"
             }]
         },
+        {
+            code: "delete (foo.bar)",
+            output: "delete(foo.bar)",
+            options: [{ words: false }],
+            errors: [{
+                message: "Unexpected space after unary word operator 'delete'.",
+                type: "UnaryExpression"
+            }]
+        },
         {
             code: "new(Foo)",
             output: "new (Foo)",
@@ -256,6 +290,15 @@ ruleTester.run("space-unary-ops", rule, {
                 type: "NewExpression"
             }]
         },
+        {
+            code: "new (Foo)",
+            output: "new(Foo)",
+            options: [{ words: false }],
+            errors: [{
+                message: "Unexpected space after unary word operator 'new'.",
+                type: "NewExpression"
+            }]
+        },
         {
             code: "new(Foo())",
             output: "new (Foo())",
@@ -265,6 +308,15 @@ ruleTester.run("space-unary-ops", rule, {
                 type: "NewExpression"
             }]
         },
+        {
+            code: "new [foo][0]",
+            output: "new[foo][0]",
+            options: [{ words: false }],
+            errors: [{
+                message: "Unexpected space after unary word operator 'new'.",
+                type: "NewExpression"
+            }]
+        },
 
         {
             code: "typeof(foo)",
@@ -275,6 +327,33 @@ ruleTester.run("space-unary-ops", rule, {
                 type: "UnaryExpression"
             }]
         },
+        {
+            code: "typeof (foo)",
+            output: "typeof(foo)",
+            options: [{ words: false }],
+            errors: [{
+                message: "Unexpected space after unary word operator 'typeof'.",
+                type: "UnaryExpression"
+            }]
+        },
+        {
+            code: "typeof[foo]",
+            output: "typeof [foo]",
+            options: [{ words: true }],
+            errors: [{
+                message: "Unary word operator 'typeof' must be followed by whitespace.",
+                type: "UnaryExpression"
+            }]
+        },
+        {
+            code: "typeof [foo]",
+            output: "typeof[foo]",
+            options: [{ words: false }],
+            errors: [{
+                message: "Unexpected space after unary word operator 'typeof'.",
+                type: "UnaryExpression"
+            }]
+        },
         {
             code: "typeof{foo:true}",
             output: "typeof {foo:true}",
@@ -321,6 +400,15 @@ ruleTester.run("space-unary-ops", rule, {
                 type: "UnaryExpression"
             }]
         },
+        {
+            code: "void[foo];",
+            output: "void [foo];",
+            options: [{ words: true }],
+            errors: [{
+                message: "Unary word operator 'void' must be followed by whitespace.",
+                type: "UnaryExpression"
+            }]
+        },
         {
             code: "void{a:0};",
             output: "void {a:0};",
@@ -330,6 +418,24 @@ ruleTester.run("space-unary-ops", rule, {
                 type: "UnaryExpression"
             }]
         },
+        {
+            code: "void (foo)",
+            output: "void(foo)",
+            options: [{ words: false }],
+            errors: [{
+                message: "Unexpected space after unary word operator 'void'.",
+                type: "UnaryExpression"
+            }]
+        },
+        {
+            code: "void [foo]",
+            output: "void[foo]",
+            options: [{ words: false }],
+            errors: [{
+                message: "Unexpected space after unary word operator 'void'.",
+                type: "UnaryExpression"
+            }]
+        },
 
         {
             code: "! foo",
@@ -488,6 +594,18 @@ ruleTester.run("space-unary-ops", rule, {
                 column: 19
             }]
         },
+        {
+            code: "function *foo() { yield (0) }",
+            output: "function *foo() { yield(0) }",
+            options: [{ words: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                message: "Unexpected space after unary word operator 'yield'.",
+                type: "YieldExpression",
+                line: 1,
+                column: 19
+            }]
+        },
         {
             code: "function *foo() { yield+0 }",
             output: "function *foo() { yield +0 }",