Repository: eslint/eslint (js)

Issue:
Proposal: enhance `no-restricted-syntax` to support custom error messages
**What version are you using?**
v3.18.0

**What did you do?**
```js
/* eslint no-restricted-syntax: ["error", "CallExpression[callee.name='setTimeout'][arguments.length!=2]"] */
setTimeout(() => {}, 100, 'foo');
```

**What happened?**
I got the following error message:
```bash
  2:1  error  Using 'CallExpression[callee.name='setTimeout'][arguments.length!=2]' is not allowed  no-restricted-syntax
```

**What did you expect to happen?**
I would like to be able to provide custom error messages for the syntax patterns I prohibit using `no-restricted-syntax`. For the example above:

```js
/*
 * eslint no-restricted-syntax: [
 *     "error",
 *     {
 *         "pattern": "CallExpression[callee.name='setTimeout'][arguments.length!=2]",
 *         "message": "invoking setTimeout with more than 2 arguments is not allowed"
 *     }
 * ]
 */
setTimeout(() => {}, 100, 'foo');
```

Would provide the following error message:
```bash
  2:1  error  invoking setTimeout with more than 2 arguments is not allowed  no-restricted-syntax
```

**Rationale**
With the release of the AST selectors feature, the `no-restricted-syntax` rule just got super powerful, which is awesome.
However, when a complex selector is used to prohibit a certain syntax pattern, the error message can be quite cryptic for devs that are not familiar with the AST selectors.

This proposal aims to allow the customization of the error messages for each of the syntax patterns configured for `no-restricted-syntax`.

This proposal would be backwards compatible, since the rule logic can check if the passed-in option is a string (old format) or an object (new format). For example:
```js
"no-restricted-syntax": [
    "error",
    "WithStatement",
    "FunctionDeclaration[params.length>3]",
    {
        "pattern": "CallExpression[callee.name='setTimeout'][arguments.length!=2]",
        "message": "invoking setTimeout with more than 2 arguments is not allowed"
    }
]    
```

Pull request description:
Update: add new schema to no-restricted-syntax (fixes #8298)
**What is the purpose of this pull request? (put an "X" next to item)**

[ ] Documentation update
[ ] 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))
[x] 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:

**What changes did you make? (Give an overview)**
This PR updates the `no-restricted-syntax` rule to support custom messages, like discussed in #8298.
This is a non-breaking enhancement to the rule.

**Is there anything you'd like reviewers to focus on?**
Any suggestions to make the docs clearer are welcome!



Patch:
diff --git a/docs/rules/no-restricted-syntax.md b/docs/rules/no-restricted-syntax.md
index ffe763c8b..8efbd780e 100644
--- a/docs/rules/no-restricted-syntax.md
+++ b/docs/rules/no-restricted-syntax.md
@@ -12,7 +12,7 @@ This rule disallows specified (that is, user-defined) syntax.
 
 ## Options
 
-This rule takes a list of strings:
+This rule takes a list of strings, where each string is an AST selector:
 
 ```json
 {
@@ -22,6 +22,30 @@ This rule takes a list of strings:
 }
 ```
 
+Alternatively, the rule also accepts objects, where the selector and an optional custom message are specified:
+
+```json
+{
+    "rules": {
+        "no-restricted-syntax": [
+            "error",
+            {
+                "selector": "FunctionExpression",
+                "message": "Function expressions are not allowed."
+            },
+            {
+                "selector": "CallExpression[callee.name='setTimeout'][arguments.length!=2]",
+                "message": "setTimeout must always be invoked with two arguments."
+            }
+        ]
+    }
+}
+```
+
+If a custom message is specified with the `message` property, ESLint will use that message when reporting occurrences of the syntax specified in the `selector` property.
+
+The string and object formats can be freely mixed in the configuration as needed.
+
 Examples of **incorrect** code for this rule with the `"FunctionExpression", "WithStatement", BinaryExpression[operator='in']` options:
 
 ```js
diff --git a/lib/rules/no-native-reassign.js b/lib/rules/no-native-reassign.js
index f721fc278..d3dfefbaf 100644
--- a/lib/rules/no-native-reassign.js
+++ b/lib/rules/no-native-reassign.js
@@ -15,7 +15,7 @@ module.exports = {
         docs: {
             description: "disallow assignments to native objects or read-only global variables",
             category: "Best Practices",
-            recommended: true,
+            recommended: false,
             replacedBy: ["no-global-assign"]
         },
 
diff --git a/lib/rules/no-negated-in-lhs.js b/lib/rules/no-negated-in-lhs.js
index 143518060..495cbc160 100644
--- a/lib/rules/no-negated-in-lhs.js
+++ b/lib/rules/no-negated-in-lhs.js
@@ -15,7 +15,7 @@ module.exports = {
         docs: {
             description: "disallow negating the left operand in `in` expressions",
             category: "Possible Errors",
-            recommended: true,
+            recommended: false,
             replacedBy: ["no-unsafe-negation"]
         },
         deprecated: true,
diff --git a/lib/rules/no-restricted-syntax.js b/lib/rules/no-restricted-syntax.js
index d87e589c7..1798065ec 100644
--- a/lib/rules/no-restricted-syntax.js
+++ b/lib/rules/no-restricted-syntax.js
@@ -18,18 +18,45 @@ module.exports = {
 
         schema: {
             type: "array",
-            items: [{ type: "string" }],
+            items: [{
+                oneOf: [
+                    {
+                        type: "string"
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            selector: { type: "string" },
+                            message: { type: "string" }
+                        },
+                        required: ["selector"],
+                        additionalProperties: false
+                    }
+                ]
+            }],
             uniqueItems: true,
             minItems: 0
         }
     },
 
     create(context) {
-        return context.options.reduce((result, selector) => Object.assign(result, {
-            [selector](node) {
-                context.report({ node, message: "Using '{{selector}}' is not allowed.", data: { selector } });
-            }
-        }), {});
+        return context.options.reduce((result, selectorOrObject) => {
+            const isStringFormat = (typeof selectorOrObject === "string");
+            const hasCustomMessage = !isStringFormat && Boolean(selectorOrObject.message);
+
+            const selector = isStringFormat ? selectorOrObject : selectorOrObject.selector;
+            const message = hasCustomMessage ? selectorOrObject.message : "Using '{{selector}}' is not allowed.";
+
+            return Object.assign(result, {
+                [selector](node) {
+                    context.report({
+                        node,
+                        message,
+                        data: hasCustomMessage ? {} : { selector }
+                    });
+                }
+            });
+        }, {});
 
     }
 };
diff --git a/lib/rules/no-unsafe-negation.js b/lib/rules/no-unsafe-negation.js
index 4e2378d4d..761dc0338 100644
--- a/lib/rules/no-unsafe-negation.js
+++ b/lib/rules/no-unsafe-negation.js
@@ -44,7 +44,7 @@ module.exports = {
         docs: {
             description: "disallow negating the left operand of relational operators",
             category: "Possible Errors",
-            recommended: false
+            recommended: true
         },
         schema: [],
         fixable: "code"
diff --git a/lib/rules/operator-assignment.js b/lib/rules/operator-assignment.js
index 1c40eed15..99cca356f 100644
--- a/lib/rules/operator-assignment.js
+++ b/lib/rules/operator-assignment.js
@@ -4,6 +4,12 @@
  */
 "use strict";
 
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("../ast-utils");
+
 //------------------------------------------------------------------------------
 // Helpers
 //------------------------------------------------------------------------------
@@ -171,9 +177,20 @@ module.exports = {
                         if (canBeFixed(node.left)) {
                             const operatorToken = getOperatorToken(node);
                             const leftText = sourceCode.getText().slice(node.range[0], operatorToken.range[0]);
-                            const rightText = sourceCode.getText().slice(operatorToken.range[1], node.range[1]);
+                            const newOperator = node.operator.slice(0, -1);
+                            let rightText;
+
+                            // If this change would modify precedence (e.g. `foo *= bar + 1` => `foo = foo * (bar + 1)`), parenthesize the right side.
+                            if (
+                                astUtils.getPrecedence(node.right) <= astUtils.getPrecedence({ type: "BinaryExpression", operator: newOperator }) &&
+                                !astUtils.isParenthesised(sourceCode, node.right)
+                            ) {
+                                rightText = `${sourceCode.text.slice(operatorToken.range[1], node.right.range[0])}(${sourceCode.getText(node.right)})`;
+                            } else {
+                                rightText = sourceCode.text.slice(operatorToken.range[1], node.range[1]);
+                            }
 
-                            return fixer.replaceText(node, `${leftText}= ${leftText}${node.operator.slice(0, -1)}${rightText}`);
+                            return fixer.replaceText(node, `${leftText}= ${leftText}${newOperator}${rightText}`);
                         }
                         return null;
                     }
diff --git a/lib/rules/space-before-function-paren.js b/lib/rules/space-before-function-paren.js
index c36c5a953..53aac71ed 100644
--- a/lib/rules/space-before-function-paren.js
+++ b/lib/rules/space-before-function-paren.js
@@ -102,34 +102,43 @@ module.exports = {
             return "ignore";
         }
 
-        return {
-            ":function"(node) {
-                const functionConfig = getConfigForFunction(node);
+        /**
+         * Checks the parens of a function node
+         * @param {ASTNode} node A function node
+         * @returns {void}
+         */
+        function checkFunction(node) {
+            const functionConfig = getConfigForFunction(node);
 
-                if (functionConfig === "ignore") {
-                    return;
-                }
+            if (functionConfig === "ignore") {
+                return;
+            }
 
-                const rightToken = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken);
-                const leftToken = sourceCode.getTokenBefore(rightToken);
-                const hasSpacing = sourceCode.isSpaceBetweenTokens(leftToken, rightToken);
-
-                if (hasSpacing && functionConfig === "never") {
-                    context.report({
-                        node,
-                        loc: leftToken.loc.end,
-                        message: "Unexpected space before function parentheses.",
-                        fix: fixer => fixer.removeRange([leftToken.range[1], rightToken.range[0]])
-                    });
-                } else if (!hasSpacing && functionConfig === "always") {
-                    context.report({
-                        node,
-                        loc: leftToken.loc.end,
-                        message: "Missing space before function parentheses.",
-                        fix: fixer => fixer.insertTextAfter(leftToken, " ")
-                    });
-                }
+            const rightToken = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken);
+            const leftToken = sourceCode.getTokenBefore(rightToken);
+            const hasSpacing = sourceCode.isSpaceBetweenTokens(leftToken, rightToken);
+
+            if (hasSpacing && functionConfig === "never") {
+                context.report({
+                    node,
+                    loc: leftToken.loc.end,
+                    message: "Unexpected space before function parentheses.",
+                    fix: fixer => fixer.removeRange([leftToken.range[1], rightToken.range[0]])
+                });
+            } else if (!hasSpacing && functionConfig === "always") {
+                context.report({
+                    node,
+                    loc: leftToken.loc.end,
+                    message: "Missing space before function parentheses.",
+                    fix: fixer => fixer.insertTextAfter(leftToken, " ")
+                });
             }
+        }
+
+        return {
+            ArrowFunctionExpression: checkFunction,
+            FunctionDeclaration: checkFunction,
+            FunctionExpression: checkFunction
         };
     }
 };


Test patch:
diff --git a/tests/fixtures/parsers/babel-eslint7/function-type-annotation.js b/tests/fixtures/parsers/babel-eslint7/function-type-annotation.js
new file mode 100644
index 000000000..a663b4e1e
--- /dev/null
+++ b/tests/fixtures/parsers/babel-eslint7/function-type-annotation.js
@@ -0,0 +1,525 @@
+"use strict";
+
+/**
+ * Parser: babel-eslint v7.2.1
+ * Source code:
+ * type TransformFunction = (el: ASTElement, code: string) => string;
+ */
+
+exports.parse = () => ({
+    type: "Program",
+    start: 0,
+    end: 66,
+    loc: {
+        start: {
+            line: 1,
+            column: 0
+        },
+        end: {
+            line: 1,
+            column: 66
+        }
+    },
+    comments: [],
+    tokens: [
+        {
+            type: "Identifier",
+            value: "type",
+            start: 0,
+            end: 4,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 0
+                },
+                end: {
+                    line: 1,
+                    column: 4
+                }
+            },
+            range: [
+                0,
+                4
+            ]
+        },
+        {
+            type: "Identifier",
+            value: "TransformFunction",
+            start: 5,
+            end: 22,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 5
+                },
+                end: {
+                    line: 1,
+                    column: 22
+                }
+            },
+            range: [
+                5,
+                22
+            ]
+        },
+        {
+            type: "Punctuator",
+            value: "=",
+            start: 23,
+            end: 24,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 23
+                },
+                end: {
+                    line: 1,
+                    column: 24
+                }
+            },
+            range: [
+                23,
+                24
+            ]
+        },
+        {
+            type: "Punctuator",
+            value: "(",
+            start: 25,
+            end: 26,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 25
+                },
+                end: {
+                    line: 1,
+                    column: 26
+                }
+            },
+            range: [
+                25,
+                26
+            ]
+        },
+        {
+            type: "Identifier",
+            value: "el",
+            start: 26,
+            end: 28,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 26
+                },
+                end: {
+                    line: 1,
+                    column: 28
+                }
+            },
+            range: [
+                26,
+                28
+            ]
+        },
+        {
+            type: "Punctuator",
+            value: ":",
+            start: 28,
+            end: 29,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 28
+                },
+                end: {
+                    line: 1,
+                    column: 29
+                }
+            },
+            range: [
+                28,
+                29
+            ]
+        },
+        {
+            type: "Identifier",
+            value: "ASTElement",
+            start: 30,
+            end: 40,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 30
+                },
+                end: {
+                    line: 1,
+                    column: 40
+                }
+            },
+            range: [
+                30,
+                40
+            ]
+        },
+        {
+            type: "Punctuator",
+            value: ",",
+            start: 40,
+            end: 41,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 40
+                },
+                end: {
+                    line: 1,
+                    column: 41
+                }
+            },
+            range: [
+                40,
+                41
+            ]
+        },
+        {
+            type: "Identifier",
+            value: "code",
+            start: 42,
+            end: 46,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 42
+                },
+                end: {
+                    line: 1,
+                    column: 46
+                }
+            },
+            range: [
+                42,
+                46
+            ]
+        },
+        {
+            type: "Punctuator",
+            value: ":",
+            start: 46,
+            end: 47,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 46
+                },
+                end: {
+                    line: 1,
+                    column: 47
+                }
+            },
+            range: [
+                46,
+                47
+            ]
+        },
+        {
+            type: "Identifier",
+            value: "string",
+            start: 48,
+            end: 54,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 48
+                },
+                end: {
+                    line: 1,
+                    column: 54
+                }
+            },
+            range: [
+                48,
+                54
+            ]
+        },
+        {
+            type: "Punctuator",
+            value: ")",
+            start: 54,
+            end: 55,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 54
+                },
+                end: {
+                    line: 1,
+                    column: 55
+                }
+            },
+            range: [
+                54,
+                55
+            ]
+        },
+        {
+            type: "Punctuator",
+            value: "=>",
+            start: 56,
+            end: 58,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 56
+                },
+                end: {
+                    line: 1,
+                    column: 58
+                }
+            },
+            range: [
+                56,
+                58
+            ]
+        },
+        {
+            type: "Identifier",
+            value: "string",
+            start: 59,
+            end: 65,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 59
+                },
+                end: {
+                    line: 1,
+                    column: 65
+                }
+            },
+            range: [
+                59,
+                65
+            ]
+        },
+        {
+            type: "Punctuator",
+            value: ";",
+            start: 65,
+            end: 66,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 65
+                },
+                end: {
+                    line: 1,
+                    column: 66
+                }
+            },
+            range: [
+                65,
+                66
+            ]
+        }
+    ],
+    range: [
+        0,
+        66
+    ],
+    sourceType: "module",
+    body: [
+        {
+            type: "TypeAlias",
+            start: 0,
+            end: 66,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 0
+                },
+                end: {
+                    line: 1,
+                    column: 66
+                }
+            },
+            id: {
+                type: "Identifier",
+                start: 5,
+                end: 22,
+                loc: {
+                    start: {
+                        line: 1,
+                        column: 5
+                    },
+                    end: {
+                        line: 1,
+                        column: 22
+                    },
+                    identifierName: "TransformFunction"
+                },
+                name: "TransformFunction",
+                range: [
+                    5,
+                    22
+                ],
+                _babelType: "Identifier"
+            },
+            typeParameters: null,
+            right: {
+                type: "FunctionTypeAnnotation",
+                start: 25,
+                end: 65,
+                loc: {
+                    start: {
+                        line: 1,
+                        column: 25
+                    },
+                    end: {
+                        line: 1,
+                        column: 65
+                    }
+                },
+                params: [
+                    {
+                        type: "FunctionTypeParam",
+                        start: 26,
+                        end: 40,
+                        loc: {
+                            start: {
+                                line: 1,
+                                column: 26
+                            },
+                            end: {
+                                line: 1,
+                                column: 40
+                            }
+                        },
+                        optional: false,
+                        typeAnnotation: {
+                            type: "GenericTypeAnnotation",
+                            start: 30,
+                            end: 40,
+                            loc: {
+                                start: {
+                                    line: 1,
+                                    column: 30
+                                },
+                                end: {
+                                    line: 1,
+                                    column: 40
+                                }
+                            },
+                            typeParameters: null,
+                            id: {
+                                type: "Identifier",
+                                start: 30,
+                                end: 40,
+                                loc: {
+                                    start: {
+                                        line: 1,
+                                        column: 30
+                                    },
+                                    end: {
+                                        line: 1,
+                                        column: 40
+                                    },
+                                    identifierName: "ASTElement"
+                                },
+                                name: "ASTElement",
+                                range: [
+                                    30,
+                                    40
+                                ],
+                                _babelType: "Identifier"
+                            },
+                            range: [
+                                30,
+                                40
+                            ],
+                            _babelType: "GenericTypeAnnotation"
+                        },
+                        range: [
+                            26,
+                            40
+                        ],
+                        _babelType: "FunctionTypeParam"
+                    },
+                    {
+                        type: "FunctionTypeParam",
+                        start: 42,
+                        end: 54,
+                        loc: {
+                            start: {
+                                line: 1,
+                                column: 42
+                            },
+                            end: {
+                                line: 1,
+                                column: 54
+                            }
+                        },
+                        optional: false,
+                        typeAnnotation: {
+                            type: "StringTypeAnnotation",
+                            start: 48,
+                            end: 54,
+                            loc: {
+                                start: {
+                                    line: 1,
+                                    column: 48
+                                },
+                                end: {
+                                    line: 1,
+                                    column: 54
+                                }
+                            },
+                            range: [
+                                48,
+                                54
+                            ],
+                            _babelType: "StringTypeAnnotation"
+                        },
+                        range: [
+                            42,
+                            54
+                        ],
+                        _babelType: "FunctionTypeParam"
+                    }
+                ],
+                rest: null,
+                returnType: {
+                    type: "StringTypeAnnotation",
+                    start: 59,
+                    end: 65,
+                    loc: {
+                        start: {
+                            line: 1,
+                            column: 59
+                        },
+                        end: {
+                            line: 1,
+                            column: 65
+                        }
+                    },
+                    range: [
+                        59,
+                        65
+                    ],
+                    _babelType: "StringTypeAnnotation"
+                },
+                typeParameters: null,
+                range: [
+                    25,
+                    65
+                ],
+                _babelType: "FunctionTypeAnnotation"
+            },
+            range: [
+                0,
+                66
+            ],
+            _babelType: "TypeAlias"
+        }
+    ]
+});
diff --git a/tests/lib/rules/no-restricted-syntax.js b/tests/lib/rules/no-restricted-syntax.js
index 7547e302b..3a653a978 100644
--- a/tests/lib/rules/no-restricted-syntax.js
+++ b/tests/lib/rules/no-restricted-syntax.js
@@ -19,6 +19,8 @@ const ruleTester = new RuleTester();
 
 ruleTester.run("no-restricted-syntax", rule, {
     valid: [
+
+        // string format
         { code: "doSomething();" },
         { code: "var foo = 42;", options: ["ConditionalExpression"] },
         { code: "foo += 42;", options: ["VariableDeclaration", "FunctionExpression"] },
@@ -26,9 +28,19 @@ ruleTester.run("no-restricted-syntax", rule, {
         { code: "() => 5", options: ["ArrowFunctionExpression > BlockStatement"], parserOptions: { ecmaVersion: 6 } },
         { code: "({ foo: 1, bar: 2 })", options: ["Property > Literal.key"] },
         { code: "A: for (;;) break;", options: ["BreakStatement[label]"] },
-        { code: "function foo(bar, baz) {}", options: ["FunctionDeclaration[params.length>2]"] }
+        { code: "function foo(bar, baz) {}", options: ["FunctionDeclaration[params.length>2]"] },
+
+        //  object format
+        { code: "var foo = 42;", options: [{ selector: "ConditionalExpression" }] },
+        { code: "({ foo: 1, bar: 2 })", options: [{ selector: "Property > Literal.key" }] },
+        {
+            code: "({ foo: 1, bar: 2 })",
+            options: [{ selector: "FunctionDeclaration[params.length>2]", message: "custom error message." }]
+        }
     ],
     invalid: [
+
+        // string format
         {
             code: "var foo = 41;",
             options: ["VariableDeclaration"],
@@ -82,6 +94,30 @@ ruleTester.run("no-restricted-syntax", rule, {
             code: "function foo(bar, baz, qux) {}",
             options: ["FunctionDeclaration[params.length>2]"],
             errors: [{ message: "Using 'FunctionDeclaration[params.length>2]' is not allowed.", type: "FunctionDeclaration" }]
+        },
+
+        // object format
+        {
+            code: "var foo = 41;",
+            options: [{ selector: "VariableDeclaration" }],
+            errors: [{ message: "Using 'VariableDeclaration' is not allowed.", type: "VariableDeclaration" }]
+        },
+        {
+            code: "function foo(bar, baz, qux) {}",
+            options: [{ selector: "FunctionDeclaration[params.length>2]" }],
+            errors: [{ message: "Using 'FunctionDeclaration[params.length>2]' is not allowed.", type: "FunctionDeclaration" }]
+        },
+        {
+            code: "function foo(bar, baz, qux) {}",
+            options: [{ selector: "FunctionDeclaration[params.length>2]", message: "custom error message." }],
+            errors: [{ message: "custom error message.", type: "FunctionDeclaration" }]
+        },
+
+        // with object format, the custom message may contain the string '{{selector}}'
+        {
+            code: "function foo(bar, baz, qux) {}",
+            options: [{ selector: "FunctionDeclaration[params.length>2]", message: "custom message with {{selector}}" }],
+            errors: [{ message: "custom message with {{selector}}", type: "FunctionDeclaration" }]
         }
     ]
 });
diff --git a/tests/lib/rules/operator-assignment.js b/tests/lib/rules/operator-assignment.js
index 5e5e65564..fb1ff889e 100644
--- a/tests/lib/rules/operator-assignment.js
+++ b/tests/lib/rules/operator-assignment.js
@@ -81,7 +81,8 @@ ruleTester.run("operator-assignment", rule, {
         "x = x === y",
         "x = x !== y",
         "x = x && y",
-        "x = x || y"
+        "x = x || y",
+        "x = x * y + z"
     ],
 
     invalid: [{
@@ -214,6 +215,31 @@ ruleTester.run("operator-assignment", rule, {
         output: "foo = foo ** bar",
         options: ["never"],
         errors: UNEXPECTED_OPERATOR_ASSIGNMENT
+    }, {
+        code: "foo *= bar + 1",
+        output: "foo = foo * (bar + 1)",
+        options: ["never"],
+        errors: UNEXPECTED_OPERATOR_ASSIGNMENT
+    }, {
+        code: "foo -= bar - baz",
+        output: "foo = foo - (bar - baz)",
+        options: ["never"],
+        errors: UNEXPECTED_OPERATOR_ASSIGNMENT
+    }, {
+        code: "foo += bar + baz",
+        output: "foo = foo + (bar + baz)", // addition is not associative in JS, e.g. (1 + 2) + '3' !== 1 + (2 + '3')
+        options: ["never"],
+        errors: UNEXPECTED_OPERATOR_ASSIGNMENT
+    }, {
+        code: "foo += bar = 1",
+        output: "foo = foo + (bar = 1)",
+        options: ["never"],
+        errors: UNEXPECTED_OPERATOR_ASSIGNMENT
+    }, {
+        code: "foo *= (bar + 1)",
+        output: "foo = foo * (bar + 1)",
+        options: ["never"],
+        errors: UNEXPECTED_OPERATOR_ASSIGNMENT
     }]
 
 });
diff --git a/tests/lib/rules/space-before-function-paren.js b/tests/lib/rules/space-before-function-paren.js
index 26180be5f..5e37dfd4e 100644
--- a/tests/lib/rules/space-before-function-paren.js
+++ b/tests/lib/rules/space-before-function-paren.js
@@ -8,8 +8,9 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const rule = require("../../../lib/rules/space-before-function-paren"),
-    RuleTester = require("../../../lib/testers/rule-tester");
+const rule = require("../../../lib/rules/space-before-function-paren");
+const RuleTester = require("../../../lib/testers/rule-tester");
+const baseParser = require("../../fixtures/fixture-parser");
 
 //------------------------------------------------------------------------------
 // Tests
@@ -95,6 +96,10 @@ ruleTester.run("space-before-function-paren", rule, {
         { code: "var bar = function foo () {}",
             options: [{ named: "ignore", anonymous: "always" }]
         },
+        {
+            code: "type TransformFunction = (el: ASTElement, code: string) => string;",
+            parser: baseParser("babel-eslint7", "function-type-annotation")
+        },
 
         // Async arrow functions
         { code: "() => 1", parserOptions: { ecmaVersion: 6 } },