Repository: eslint/eslint
Pull request #10545 (2018-06-30 02:11:16)

Fix: Account for comments in implicit-arrow-linebreak

**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:

`Implicit-arrow-linebreak` does not take comments into consideration when the option for the rule is `beside`.

When the rule evaluates the expression:
```
(foo) =>
   //comment
   bar;
```

The outcome is:
```
(foo) => bar;
```

The proposed fix is:
```
// comment
(foo) => bar;
```

**What changes did you make? (Give an overview)**
- Add test cases including comments in `tests/lib/rules/implicit-arrow-linebreak`
- Add helper for formatting comment text split by line breaks
- Implement filtering condition, and fix for when the above bug occurs

**Is there anything you'd like reviewers to focus on?**
- This is my first PR for this project -- any feedback would be heavily appreciated.
- Do the test cases suffice? Any missing logic?
- What should be the `outcome` with the test case of
```
(foo) =>
  // hi
     bar =>
       // there
         braz;
```
? This one was a doozy.
@sharmilajesupaul 


jsf-clabot: [![CLA assistant check](https://cla.js.foundation/pull/badge/not_signed)](https://cla.js.foundation/eslint/eslint?pullRequest=10545) <br/>Thank you for your submission, we really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla.js.foundation/eslint/eslint?pullRequest=10545) before we can accept your contribution.<br/><hr/>**dios** seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please [add the email address used for this commit to your account](https://help.github.com/articles/why-are-my-commits-linked-to-the-wrong-user/#commits-are-not-linked-to-any-user).
ilyavolodin: Hmm... It seems like this fix is improving current behavior, but I'm not 100% sure this is the best way. In the last example you showed, the only reasonable way to fix it based on your approach would be to move all of the comments above the line:
```js
// hi
// there
(foo) =>bar => braz;
```

However, chances are, if there's a chaining like that, comments describe what each step of the chain is doing, and if you move them above, it would be unclear. I think best way to fix that would be to change line comments into inline comments.

```js
(foo) => /* hi */ bar => /* there */ braz;
```

But that might not work and might possibly change the meaning of the code. So I'm not sure what would be the best way to do that.
not-an-aardvark: I think the best solution would be to just not do an autofix if a line comment is found in that location. We have a similar behavior with `brace-style`, where we don't move the opening bracket when the code looks like this:

```js
if (foo) // comment
{
}
```
ljharb: I'd expect:
```js
(foo) =>
  // hi
     bar =>
       // there
         braz;
```

to become:
```js
(foo) => (
  // hi
     bar => (
       // there
         braz
      )
);
```
peanutenthusiast: @ljharb @not-an-aardvark @ilyavolodin Thanks for all of your input. It seems like prepending the arrow functions with parentheses clarifies the intent of the comments.

I will go ahead and implement this change.
peanutenthusiast: @sharmilajesupaul Thanks for the review and comments! Still working on the above test case as well as when:
```
async (foo) => 
    // comment
    bar
```
sharmilajesupaul: @peanutenthusiast instead of trying to find the parent and placing the comment before it. What if you got the line number from the node range and placed the comment on the line before that with a prepended `\n`?

Also, does this account for cases where you are in an object, or a method chain, or array? 

eg: 

```js
const foo = {
  id: 'bar'
  prop: (foo1) =>
    // comment
    'returning this string', 
}

[ foo => 
  // comment
  'bar'
]

"foo".split('').map((char) => 
  // comment
  `-${char}-`
))
```

can you add test cases for those? ^
peanutenthusiast: @sharmilajesupaul will need to account for the aforementioned test cases as well. thanks! 
peanutenthusiast: @sharmilajesupaul Would the expected output for the method chain test case be
```
// comment
"foo".split('').map((char) => `-${char}-`
)
```
or (probably nicer on the eyes)
```
// comment
"foo".split('').map((char) => `-${char}-`)
```
?
sharmilajesupaul: @peanutenthusiast The paren linebreak is handled by [`function-paren-newline`](https://eslint.org/docs/rules/function-paren-newline) so I don't think you need to worry about includin that in your autofix. 

There might be cases where you do want the paren on it's own line:
```js
"foo".split('').map(
  (char) => 
    `-${char}-`,
)
```
I think that might be out of the scope of this PR. 

ljharb: this seems like it needs a fresh rebase
peanutenthusiast: @ljharb agreed and working on it.
peanutenthusiast: Any updates with this PR? If any sharp eyes notice test cases that still need accounting for, more than happy to take care of them.
sharmilajesupaul: hey @peanutenthusiast! is this ready for another review?
peanutenthusiast: Yes!

On Tue, Dec 4, 2018 at 5:13 PM Sharmila Jesupaul <notifications@github.com>
wrote:

> hey @peanutenthusiast <https://github.com/peanutenthusiast>! is this
> ready for another review?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/eslint/eslint/pull/10545#issuecomment-444320455>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/Aa-ISTkZKKdXwI4v_Ay3_rbsHsKqe_e7ks5u1x2vgaJpZM4U9zaM>
> .
>

nzakas: @peanutenthusiast Sorry we lost track of this!

@platinumazure have your changes been addressed?

@ilyavolodin @not-an-aardvark thoughts on moving this forward?
not-an-aardvark: Should this be marked as a bug rather than an enhancement? Fixers generally shouldn't be deleting comments.
platinumazure: This PR is labeled as "enhancement" but the commit summary says "Fix". Is this a bug fix or an enhancement?
not-an-aardvark: I'm going to re-label it as a bug -- I think the original labelling was a mistake.

Patch:
diff --git a/docs/developer-guide/working-with-plugins.md b/docs/developer-guide/working-with-plugins.md
index 4d0ab55d5..83f107512 100644
--- a/docs/developer-guide/working-with-plugins.md
+++ b/docs/developer-guide/working-with-plugins.md
@@ -52,31 +52,33 @@ Plugin environments can define the following objects:
 You can also create plugins that would tell ESLint how to process files other than JavaScript. In order to create a processor, the object that is exported from your module has to conform to the following interface:
 
 ```js
-processors: {
+module.exports = {
+    processors: {
 
-    // assign to the file extension you want (.js, .jsx, .html, etc.)
-    ".ext": {
-        // takes text of the file and filename
-        preprocess: function(text, filename) {
-            // here, you can strip out any non-JS content
-            // and split into multiple strings to lint
+        // assign to the file extension you want (.js, .jsx, .html, etc.)
+        ".ext": {
+            // takes text of the file and filename
+            preprocess: function(text, filename) {
+                // here, you can strip out any non-JS content
+                // and split into multiple strings to lint
 
-            return [string];  // return an array of strings to lint
-        },
+                return [string];  // return an array of strings to lint
+            },
 
-        // takes a Message[][] and filename
-        postprocess: function(messages, filename) {
-            // `messages` argument contains two-dimensional array of Message objects
-            // where each top-level array item contains array of lint messages related
-            // to the text that was returned in array from preprocess() method
+            // takes a Message[][] and filename
+            postprocess: function(messages, filename) {
+                // `messages` argument contains two-dimensional array of Message objects
+                // where each top-level array item contains array of lint messages related
+                // to the text that was returned in array from preprocess() method
 
-            // you need to return a one-dimensional array of the messages you want to keep
-            return [Message];
-        },
+                // you need to return a one-dimensional array of the messages you want to keep
+                return messages[0];
+            },
 
-        supportsAutofix: true // (optional, defaults to false)
+            supportsAutofix: true // (optional, defaults to false)
+        }
     }
-}
+};
 ```
 
 The `preprocess` method takes the file contents and filename as arguments, and returns an array of strings to lint. The strings will be linted separately but still be registered to the filename. It's up to the plugin to decide if it needs to return just one part, or multiple pieces. For example in the case of processing `.html` files, you might want to return just one item in the array by combining all scripts, but for `.md` file where each JavaScript block might be independent, you can return multiple items.
@@ -117,22 +119,45 @@ To support multiple extensions, add each one to the `processors` element and poi
 
 ### Configs in Plugins
 
-You can bundle configurations inside a plugin. This can be useful when you want to provide not just code style, but also some custom rules to support it. You can specify configurations under `configs` key. Please note that when exposing configurations, you have to name each one, and there is no default. So your users will have to specify the name of the configuration they want to use.
+You can bundle configurations inside a plugin by specifying them under the `configs` key. This can be useful when you want to provide not just code style, but also some custom rules to support it. Multiple configurations are supported per plugin. Note that it is not possible to specify a default configuration for a given plugin and that users must specify in their configuration file when they want to use one.
 
 ```js
-configs: {
-    myConfig: {
-        env: ["browser"],
-        rules: {
-            semi: 2,
-            "myPlugin/my-rule": 2,
-            "eslint-plugin-myPlugin/another-rule": 2
+// eslint-plugin-myPlugin
+
+module.exports = {
+    configs: {
+        myConfig: {
+            plugins: ["myPlugin"],
+            env: ["browser"],
+            rules: {
+                semi: "error",
+                "myPlugin/my-rule": "error",
+                "eslint-plugin-myPlugin/another-rule": "error"
+            }
+        },
+        myOtherConfig: {
+            plugins: ["myPlugin"],
+            env: ["node"],
+            rules: {
+                "myPlugin/my-rule": "off",
+                "eslint-plugin-myPlugin/another-rule": "off"
+                "eslint-plugin-myPlugin/yet-another-rule": "error"
+            }
         }
     }
+};
+```
+
+If the example plugin above were called `eslint-plugin-myPlugin`, the `myConfig` and `myOtherConfig` configurations would then be usable by extending off of `"plugin:myPlugin/myConfig"` and `"plugin:myPlugin/myOtherConfig"`, respectively.
+
+```json
+{
+    "extends": ["plugin:myPlugin/myConfig"]
 }
+
 ```
 
-**Note:** Please note that configuration will not automatically attach your rules and you have to specify your plugin name and any rules you want to enable that are part of the plugin. Any plugin rules must be prefixed with the short or long plugin name. See [Configuring Plugins](../user-guide/configuring.md#configuring-plugins)
+**Note:** Please note that configuration will not enable any of the plugin's rules by default, and instead should be treated as a standalone config. This means that you must specify your plugin name in the `plugins` array as well as any rules you want to enable that are part of the plugin. Any plugin rules must be prefixed with the short or long plugin name. See [Configuring Plugins](../user-guide/configuring.md#configuring-plugins) for more information.
 
 ### Peer Dependency
 
diff --git a/docs/rules/no-unused-expressions.md b/docs/rules/no-unused-expressions.md
index 480915ee2..8a9748d3d 100644
--- a/docs/rules/no-unused-expressions.md
+++ b/docs/rules/no-unused-expressions.md
@@ -2,7 +2,7 @@
 
 An unused expression which has no effect on the state of the program indicates a logic error.
 
-For example, `n + 1;` is not a syntax error, but it might be a typing mistake where a programmer meant an assignment statement `n += 1;` instead.
+For example, `n + 1;` is not a syntax error, but it might be a typing mistake where a programmer meant an assignment statement `n += 1;` instead. Sometimes, such unused expressions may be eliminated by some build tools in production environment, which possibly breaks application logic.
 
 ## Rule Details
 
diff --git a/docs/rules/no-unused-vars.md b/docs/rules/no-unused-vars.md
index 996dea181..2b3f093cc 100644
--- a/docs/rules/no-unused-vars.md
+++ b/docs/rules/no-unused-vars.md
@@ -4,16 +4,16 @@ Variables that are declared and not used anywhere in the code are most likely an
 
 ## Rule Details
 
-This rule is aimed at eliminating unused variables, functions, and parameters of functions.
+This rule is aimed at eliminating unused variables, functions, and function parameters.
 
-A variable is considered to be used if any of the following are true:
+A variable `foo` is considered to be used if any of the following are true:
 
-* It represents a function that is called (`doSomething()`)
-* It is read (`var y = x`)
-* It is passed into a function as an argument (`doSomething(x)`)
+* It is called (`foo()`) or constructed (`new foo()`)
+* It is read (`var bar = foo`)
+* It is passed into a function as an argument (`doSomething(foo)`)
 * It is read inside of a function that is passed to another function (`doSomething(function() { foo(); })`)
 
-A variable is *not* considered to be used if it is only ever assigned to (`var x = 5`) or declared.
+A variable is *not* considered to be used if it is only ever declared (`var foo = 5`) or assigned to (`foo = 7`).
 
 Examples of **incorrect** code for this rule:
 
diff --git a/docs/rules/padded-blocks.md b/docs/rules/padded-blocks.md
index 3393d090e..5c7908cff 100644
--- a/docs/rules/padded-blocks.md
+++ b/docs/rules/padded-blocks.md
@@ -51,11 +51,6 @@ if (a)
     b();
 }
 
-if (a) {
-
-    b();
-}
-
 if (a) {
     b();
 
diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js
index 6fb8760f1..a8341c84b 100644
--- a/lib/rules/camelcase.js
+++ b/lib/rules/camelcase.js
@@ -100,14 +100,20 @@ module.exports = {
          * @private
          */
         function isInsideObjectPattern(node) {
-            let { parent } = node;
+            let current = node;
 
-            while (parent) {
-                if (parent.type === "ObjectPattern") {
+            while (current) {
+                const parent = current.parent;
+
+                if (parent && parent.type === "Property" && parent.computed && parent.key === current) {
+                    return false;
+                }
+
+                if (current.type === "ObjectPattern") {
                     return true;
                 }
 
-                parent = parent.parent;
+                current = parent;
             }
 
             return false;
@@ -169,12 +175,15 @@ module.exports = {
 
                     if (node.parent.parent && node.parent.parent.type === "ObjectPattern") {
                         if (node.parent.shorthand && node.parent.value.left && nameIsUnderscored) {
-
                             report(node);
                         }
 
                         const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name;
 
+                        if (isUnderscored(name) && node.parent.computed) {
+                            report(node);
+                        }
+
                         // prevent checking righthand side of destructured object
                         if (node.parent.key === node && node.parent.value !== node) {
                             return;
diff --git a/lib/rules/implicit-arrow-linebreak.js b/lib/rules/implicit-arrow-linebreak.js
index a883b2264..fb7d603a3 100644
--- a/lib/rules/implicit-arrow-linebreak.js
+++ b/lib/rules/implicit-arrow-linebreak.js
@@ -4,6 +4,12 @@
  */
 "use strict";
 
+const {
+    isArrowToken,
+    isParenthesised,
+    isOpeningParenToken
+} = require("../util/ast-utils");
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -41,6 +47,142 @@ module.exports = {
             return context.options[0] || "beside";
         }
 
+        /**
+         * Formats the comments depending on whether it's a line or block comment.
+         * @param {Comment[]} comments The array of comments between the arrow and body
+         * @param {Integer} column The column number of the first token
+         * @returns {string} A string of comment text joined by line breaks
+         */
+        function formatComments(comments, column) {
+            const whiteSpaces = " ".repeat(column);
+
+            return `${comments.map(comment => {
+
+                if (comment.type === "Line") {
+                    return `//${comment.value}`;
+                }
+
+                return `/*${comment.value}*/`;
+            }).join(`\n${whiteSpaces}`)}\n${whiteSpaces}`;
+        }
+
+        /**
+         * Finds the first token to prepend comments to depending on the parent type
+         * @param {Node} node The validated node
+         * @returns {Token|Node} The node to prepend comments to
+         */
+        function findFirstToken(node) {
+            switch (node.parent.type) {
+                case "VariableDeclarator":
+
+                    // If the parent is first or only declarator, return the declaration, else, declarator
+                    return sourceCode.getFirstToken(
+                        node.parent.parent.declarations.length === 1 ||
+                        node.parent.parent.declarations[0].id.name === node.parent.id.name
+                            ? node.parent.parent : node.parent
+                    );
+                case "CallExpression":
+                case "Property":
+
+                    // find the object key
+                    return sourceCode.getFirstToken(node.parent);
+                default:
+                    return node;
+            }
+        }
+
+        /**
+         * Helper function for adding parentheses fixes for nodes containing nested arrow functions
+         * @param {Fixer} fixer Fixer
+         * @param {Token} arrow - The arrow token
+         * @param {ASTNode} arrowBody - The arrow function body
+         * @returns {Function[]} autofixer -- wraps function bodies with parentheses
+         */
+        function addParentheses(fixer, arrow, arrowBody) {
+            const parenthesesFixes = [];
+            let closingParentheses = "";
+
+            let followingBody = arrowBody;
+            let currentArrow = arrow;
+
+            while (currentArrow) {
+                if (!isParenthesised(sourceCode, followingBody)) {
+                    parenthesesFixes.push(
+                        fixer.insertTextAfter(currentArrow, " (")
+                    );
+
+                    const paramsToken = sourceCode.getTokenBefore(currentArrow, token =>
+                        isOpeningParenToken(token) || token.type === "Identifier");
+
+                    const whiteSpaces = " ".repeat(paramsToken.loc.start.column);
+
+                    closingParentheses = `\n${whiteSpaces})${closingParentheses}`;
+                }
+
+                currentArrow = sourceCode.getTokenAfter(currentArrow, isArrowToken);
+
+                if (currentArrow) {
+                    followingBody = sourceCode.getTokenAfter(currentArrow, token => !isOpeningParenToken(token));
+                }
+            }
+
+            return [...parenthesesFixes,
+                fixer.insertTextAfter(arrowBody, closingParentheses)
+            ];
+        }
+
+        /**
+         * Autofixes the function body to collapse onto the same line as the arrow.
+         * If comments exist, prepends the comments before the arrow function.
+         * If the function body contains arrow functions, appends the function bodies with parentheses.
+         * @param {Token} arrowToken The arrow token.
+         * @param {ASTNode} arrowBody the function body
+         * @param {ASTNode} node The evaluated node
+         * @returns {Function} autofixer -- validates the node to adhere to besides
+         */
+        function autoFixBesides(arrowToken, arrowBody, node) {
+            return fixer => {
+                const placeBesides = fixer.replaceTextRange([arrowToken.range[1], arrowBody.range[0]], " ");
+
+                const comments = sourceCode.getCommentsInside(node).filter(comment =>
+                    comment.loc.start.line < arrowBody.loc.start.line);
+
+                if (comments.length) {
+
+                    // If the grandparent is not a variable declarator
+                    if (
+                        arrowBody.parent &&
+                        arrowBody.parent.parent &&
+                        arrowBody.parent.parent.type !== "VariableDeclarator"
+                    ) {
+
+                        // If any arrow functions follow, return the necessary parens fixes.
+                        if (sourceCode.getTokenAfter(arrowToken, isArrowToken) && arrowBody.parent.parent.type !== "VariableDeclarator") {
+                            return addParentheses(fixer, arrowToken, arrowBody);
+                        }
+
+                        // If any arrow functions precede, the necessary fixes have already been returned, so return null.
+                        if (sourceCode.getTokenBefore(arrowToken, isArrowToken) && arrowBody.parent.parent.type !== "VariableDeclarator") {
+                            return null;
+                        }
+                    }
+
+                    const firstToken = findFirstToken(node);
+
+                    const commentText = formatComments(comments, firstToken.loc.start.column);
+
+                    const commentBeforeExpression = fixer.insertTextBeforeRange(
+                        firstToken.range,
+                        commentText
+                    );
+
+                    return [placeBesides, commentBeforeExpression];
+                }
+
+                return placeBesides;
+            };
+        }
+
         /**
          * Validates the location of an arrow function body
          * @param {ASTNode} node The arrow function body
@@ -75,7 +217,7 @@ module.exports = {
                 context.report({
                     node: fixerTarget,
                     message: "Expected no linebreak before this expression.",
-                    fix: fixer => fixer.replaceTextRange([tokenBefore.range[1], fixerTarget.range[0]], " ")
+                    fix: autoFixBesides(tokenBefore, fixerTarget, node)
                 });
             }
         }
diff --git a/lib/rules/no-irregular-whitespace.js b/lib/rules/no-irregular-whitespace.js
index 7920ebdc5..90d1a79a1 100644
--- a/lib/rules/no-irregular-whitespace.js
+++ b/lib/rules/no-irregular-whitespace.js
@@ -30,7 +30,7 @@ module.exports = {
         type: "problem",
 
         docs: {
-            description: "disallow irregular whitespace outside of strings and comments",
+            description: "disallow irregular whitespace",
             category: "Possible Errors",
             recommended: true,
             url: "https://eslint.org/docs/rules/no-irregular-whitespace"


Test patch:
diff --git a/tests/lib/rules/camelcase.js b/tests/lib/rules/camelcase.js
index d1ce3b3fd..eb80b4ef4 100644
--- a/tests/lib/rules/camelcase.js
+++ b/tests/lib/rules/camelcase.js
@@ -106,6 +106,11 @@ ruleTester.run("camelcase", rule, {
             options: [{ ignoreDestructuring: true }],
             parserOptions: { ecmaVersion: 6 }
         },
+        {
+            code: "var { [{category_id} = query]: categoryId } = query;",
+            options: [{ ignoreDestructuring: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
         {
             code: "var { category_id: category } = query;",
             parserOptions: { ecmaVersion: 6 }
@@ -345,6 +350,29 @@ ruleTester.run("camelcase", rule, {
                 }
             ]
         },
+        {
+            code: "var { [category_id]: categoryId } = query;",
+            options: [{ ignoreDestructuring: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "category_id" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "var { [category_id]: categoryId } = query;",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "category_id" },
+                    type: "Identifier"
+                }
+            ]
+        },
         {
             code: "var { category_id: categoryId, ...other_props } = query;",
             options: [{ ignoreDestructuring: true }],
diff --git a/tests/lib/rules/implicit-arrow-linebreak.js b/tests/lib/rules/implicit-arrow-linebreak.js
index 7212e083f..f07182c1b 100644
--- a/tests/lib/rules/implicit-arrow-linebreak.js
+++ b/tests/lib/rules/implicit-arrow-linebreak.js
@@ -37,9 +37,76 @@ ruleTester.run("implicit-arrow-linebreak", rule, {
         `(foo) => (
             bar
         )`,
+        "(foo) => bar();",
+        `
+        //comment
+        foo => bar;
+        `,
+        `
+        foo => (
+            // comment
+            bar => (
+                // another comment
+                baz
+            )       
+        )
+        `,
+        `
+        foo => (
+            // comment
+            bar => baz
+        )
+        `,
+        `
+        /* text */
+        () => bar; 
+        `,
+        `
+        /* foo */
+        const bar = () => baz;
+        `,
+        `
+        (foo) => (
+                //comment
+                    bar
+                )
+        `,
+        `
+          [ // comment
+            foo => 'bar'
+          ]
+        `,
+        `
+         /*
+         One two three four
+         Five six seven nine.
+         */
+         (foo) => bar
+        `,
+        `
+        const foo = {
+          id: 'bar',
+          // comment
+          prop: (foo1) => 'returning this string', 
+        }
+        `,
+        `
+        // comment
+         "foo".split('').map((char) => char
+         )
+        `,
         {
-            code: "(foo) => bar();",
-            options: ["beside"]
+            code: `
+            async foo => () => bar;
+            `,
+            parserOptions: { ecmaVersion: 8 }
+        },
+        {
+            code: `
+            // comment
+            async foo => 'string'        
+            `,
+            parserOptions: { ecmaVersion: 8 }
         },
 
         // 'below' option
@@ -132,6 +199,371 @@ ruleTester.run("implicit-arrow-linebreak", rule, {
                     )
             `,
             errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+                (foo) =>
+                  // test comment
+                  bar
+            `,
+            output: `
+                // test comment
+                (foo) => bar
+            `,
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+                const foo = () =>
+                // comment
+                []
+            `,
+            output: `
+                // comment
+                const foo = () => []
+            `,
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+                (foo) =>
+                    (
+                    //comment
+                        bar
+                    )
+            `,
+            output: `
+                (foo) => (
+                    //comment
+                        bar
+                    )
+            `,
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+                (foo) =>
+                    (
+                        bar
+                    //comment
+                    )
+            
+            `,
+            output: `
+                (foo) => (
+                        bar
+                    //comment
+                    )
+            
+            `,
+            errors: [UNEXPECTED_LINEBREAK]
+
+        }, {
+            code: `
+            (foo) =>
+             // comment
+             // another comment
+                bar`,
+            output: `
+            // comment
+            // another comment
+            (foo) => bar`,
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+            (foo) =>
+            // comment
+            (
+            // another comment
+            bar
+            )`,
+            output: `
+            // comment
+            (foo) => (
+            // another comment
+            bar
+            )`,
+            errors: [UNEXPECTED_LINEBREAK]
+        },
+        {
+            code: "() => // comment \n bar",
+            output: "// comment \n() => bar",
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: "(foo) => //comment \n bar",
+            output: "//comment \n(foo) => bar",
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+                (foo) =>
+                  /* test comment */
+                  bar
+            `,
+            output: `
+                /* test comment */
+                (foo) => bar
+            `,
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+                (foo) =>
+                  // hi
+                     bar =>
+                       // there
+                         baz;
+            `,
+            output: `
+                (foo) => (
+                  // hi
+                     bar => (
+                       // there
+                         baz
+                     )
+                 );
+            `,
+            errors: [UNEXPECTED_LINEBREAK, UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+                (foo) =>
+                  // hi
+                     bar => (
+                       // there
+                         baz
+                     )
+            `,
+            output: `
+                (foo) => (
+                  // hi
+                     bar => (
+                       // there
+                         baz
+                     )
+                 )
+            `,
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+            const foo = {
+              id: 'bar',
+              prop: (foo1) =>
+                // comment
+                'returning this string', 
+            }
+            `,
+            output: `
+            const foo = {
+              id: 'bar',
+              // comment
+              prop: (foo1) => 'returning this string', 
+            }
+            `,
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+            [ foo => 
+              // comment
+              'bar'
+            ]
+            `,
+            output: `
+            [ // comment
+              foo => 'bar'
+            ]
+            `,
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+             "foo".split('').map((char) =>
+                // comment
+                char
+             )
+            `,
+            output: `
+             // comment
+             "foo".split('').map((char) => char
+             )
+            `,
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+                new Promise((resolve, reject) =>
+                    // comment
+                    resolve()
+                )
+            `,
+            output: `
+                new Promise(// comment
+                            (resolve, reject) => resolve()
+                )
+            `,
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+                () =>
+                /*
+                succinct
+                explanation
+                of code
+                */
+                bar
+            `,
+            output: `
+                /*
+                succinct
+                explanation
+                of code
+                */
+                () => bar
+            `,
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+                stepOne =>
+                    /*
+                    here is
+                    what is
+                    happening
+                    */
+                    stepTwo =>
+                        // then this happens
+                        stepThree`,
+            output: `
+                stepOne => (
+                    /*
+                    here is
+                    what is
+                    happening
+                    */
+                    stepTwo => (
+                        // then this happens
+                        stepThree
+                    )
+                )`,
+            errors: [UNEXPECTED_LINEBREAK, UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+            () =>
+                /*
+                multi
+                line
+                */
+                bar =>
+                    /*
+                    many
+                    lines
+                    */
+                    baz
+            `,
+            output: `
+            () => (
+                /*
+                multi
+                line
+                */
+                bar => (
+                    /*
+                    many
+                    lines
+                    */
+                    baz
+                )
+            )
+            `,
+            errors: [UNEXPECTED_LINEBREAK, UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+               foo('', boo =>
+                  // comment
+                  bar
+               )
+            `,
+            output: `
+               // comment
+               foo('', boo => bar
+               )
+            `,
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+            async foo =>
+                // comment
+                'string'
+            `,
+            output: `
+            // comment
+            async foo => 'string'
+            `,
+            parserOptions: { ecmaVersion: 8 },
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+            async foo =>
+                // comment
+                // another
+                bar;
+            `,
+            output: `
+            // comment
+            // another
+            async foo => bar;
+            `,
+            parserOptions: { ecmaVersion: 8 },
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+            async (foo) =>
+                // comment
+                'string'
+            `,
+            output: `
+            // comment
+            async (foo) => 'string'
+            `,
+            parserOptions: { ecmaVersion: 8 },
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+              const foo = 1,
+                  bar = 2,
+                  baz = () => // comment
+                    qux
+            `,
+            output: `
+              const foo = 1,
+                  bar = 2,
+                  // comment
+                  baz = () => qux
+            `,
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+              const foo = () =>
+                  //comment
+                  qux,
+                  bar = 2,
+                  baz = 3
+            `,
+            output: `
+              //comment
+              const foo = () => qux,
+                  bar = 2,
+                  baz = 3
+            `,
+            errors: [UNEXPECTED_LINEBREAK]
+        }, {
+            code: `
+            const foo = () =>
+                //two
+                1,
+                boo = () => 
+                //comment
+                2,
+                bop = "what"
+            `,
+            output: `
+            //two
+            const foo = () => 1,
+                //comment
+                boo = () => 2,
+                bop = "what"
+            `,
+            errors: [UNEXPECTED_LINEBREAK, UNEXPECTED_LINEBREAK]
         },
 
         // 'below' option