Repository: eslint/eslint (js)

Issue:
object-curly-newline --fix  erroneously removes comment
ESLint Version: v3.19.0
Node Version: v7.9.0

```js
module.exports = {
  'env': {
    'browser': true,
    'es6': true
  },
  'extends': 'eslint:recommended',
  'rules': {
    'object-curly-newline': ['error', {multiline: true}],
    'no-unused-vars': 'off',
  }
```

**Before**
```js
var a = {
  // Nothing here yet, but comment should not be removed.
};
```

**After invoking ESLint with the --fix flag the file is modified as follows**
```js
var a = {};
```

**What did you expect to happen?**
The comment should have been preserved.

**What actually happened?**
The comment was removed.

Pull request description:
Fix: Avoid fixing objects containing comments (fixes #8484)
<!--
    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.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (http://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)**
Avoid fixing objects containing comments proposed by @not-an-aardvark in PR #8487  





Patch:
diff --git a/bin/eslint.js b/bin/eslint.js
index 2b5d4e7fe..7c05ad3b6 100755
--- a/bin/eslint.js
+++ b/bin/eslint.js
@@ -44,11 +44,10 @@ process.once("uncaughtException", err => {
     if (typeof err.messageTemplate === "string" && err.messageTemplate.length > 0) {
         const template = lodash.template(fs.readFileSync(path.resolve(__dirname, `../messages/${err.messageTemplate}.txt`), "utf-8"));
 
-        console.log("\nOops! Something went wrong! :(");
-        console.log(`\n${template(err.messageData || {})}`);
+        console.error("\nOops! Something went wrong! :(");
+        console.error(`\n${template(err.messageData || {})}`);
     } else {
-        console.log(err.message);
-        console.log(err.stack);
+        console.error(err.stack);
     }
 
     process.exitCode = 1;
diff --git a/docs/developer-guide/shareable-configs.md b/docs/developer-guide/shareable-configs.md
index 23d6a3fef..58f36e904 100644
--- a/docs/developer-guide/shareable-configs.md
+++ b/docs/developer-guide/shareable-configs.md
@@ -33,7 +33,7 @@ Once your shareable config is ready, you can [publish to npm](https://docs.npmjs
 You should declare your dependency on eslint in `package.json` using the [peerDependencies](https://docs.npmjs.com/files/package.json#peerdependencies) field. The recommended way to declare a dependency for future proof compatibility is with the ">=" range syntax, using the lowest required eslint version. For example:
 
 ```
-peerDependencies: {
+"peerDependencies": {
     "eslint": ">= 3"
 }
 ```
diff --git a/lib/rules/object-curly-newline.js b/lib/rules/object-curly-newline.js
index b78cb9cfc..42f277873 100644
--- a/lib/rules/object-curly-newline.js
+++ b/lib/rules/object-curly-newline.js
@@ -143,6 +143,8 @@ module.exports = {
                     first.loc.start.line !== last.loc.end.line
                 )
             );
+            const hasCommentsFirstToken = astUtils.isCommentToken(first);
+            const hasCommentsLastToken = astUtils.isCommentToken(last);
 
             /*
              * Use tokens or comments to check multiline or not.
@@ -162,6 +164,10 @@ module.exports = {
                         node,
                         loc: openBrace.loc.start,
                         fix(fixer) {
+                            if (hasCommentsFirstToken) {
+                                return null;
+                            }
+
                             return fixer.insertTextAfter(openBrace, "\n");
                         }
                     });
@@ -172,6 +178,10 @@ module.exports = {
                         node,
                         loc: closeBrace.loc.start,
                         fix(fixer) {
+                            if (hasCommentsLastToken) {
+                                return null;
+                            }
+
                             return fixer.insertTextBefore(closeBrace, "\n");
                         }
                     });
@@ -190,6 +200,10 @@ module.exports = {
                         node,
                         loc: openBrace.loc.start,
                         fix(fixer) {
+                            if (hasCommentsFirstToken) {
+                                return null;
+                            }
+
                             return fixer.removeRange([
                                 openBrace.range[1],
                                 first.range[0]
@@ -206,6 +220,10 @@ module.exports = {
                         node,
                         loc: closeBrace.loc.start,
                         fix(fixer) {
+                            if (hasCommentsLastToken) {
+                                return null;
+                            }
+
                             return fixer.removeRange([
                                 last.range[1],
                                 closeBrace.range[0]


Test patch:
diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js
index 7c5cd6552..0774500f0 100644
--- a/tests/bin/eslint.js
+++ b/tests/bin/eslint.js
@@ -34,13 +34,16 @@ function assertExitCode(exitingProcess, expectedExitCode) {
 /**
 * Returns a Promise for the stdout of a process.
 * @param {ChildProcess} runningProcess The child process
-* @returns {Promise<string>} A Promise that fulfills with all of the stdout output produced by the process when it exits.
+* @returns {Promise<{stdout: string, stderr: string}>} A Promise that fulfills with all of the
+* stdout and stderr output produced by the process when it exits.
 */
-function getStdout(runningProcess) {
+function getOutput(runningProcess) {
     let stdout = "";
+    let stderr = "";
 
     runningProcess.stdout.on("data", data => (stdout += data));
-    return awaitExit(runningProcess).then(() => stdout);
+    runningProcess.stderr.on("data", data => (stderr += data));
+    return awaitExit(runningProcess).then(() => ({ stdout, stderr }));
 }
 
 describe("bin/eslint.js", () => {
@@ -88,8 +91,8 @@ describe("bin/eslint.js", () => {
             const child = runESLint(["--stdin"], { cwd: "/" }); // Assumes the root directory has no .eslintrc file
 
             const exitCodePromise = assertExitCode(child, 1);
-            const stdoutPromise = getStdout(child).then(stdout => {
-                assert.match(stdout, /ESLint couldn't find a configuration file/);
+            const stdoutPromise = getOutput(child).then(output => {
+                assert.match(output.stderr, /ESLint couldn't find a configuration file/);
             });
 
             child.stdin.write("var foo = bar\n");
@@ -131,7 +134,7 @@ describe("bin/eslint.js", () => {
         it("has exit code 0, fixes errors in a file, and does not report or fix warnings if --quiet and --fix are used", () => {
             const child = runESLint(["--fix", "--quiet", "--no-eslintrc", "--no-ignore", tempFilePath]);
             const exitCodeAssertion = assertExitCode(child, 0);
-            const stdoutAssertion = getStdout(child).then(stdout => assert.strictEqual(stdout, ""));
+            const stdoutAssertion = getOutput(child).then(output => assert.strictEqual(output.stdout, ""));
             const outputFileAssertion = awaitExit(child).then(() => {
                 assert.strictEqual(fs.readFileSync(tempFilePath).toString(), expectedFixedTextQuiet);
             });
@@ -259,6 +262,24 @@ describe("bin/eslint.js", () => {
         });
     });
 
+    describe("handling crashes", () => {
+        it("prints the error message exactly once to stderr in the event of a crash", () => {
+            const child = runESLint(["--rule=no-restricted-syntax:[error, 'Invalid Selector [[[']", "Makefile.js"]);
+            const exitCodeAssertion = assertExitCode(child, 1);
+            const outputAssertion = getOutput(child).then(output => {
+                const expectedSubstring = "Syntax error in selector";
+
+                assert.strictEqual(output.stdout, "");
+                assert.include(output.stderr, expectedSubstring);
+
+                // The message should appear exactly once in stderr
+                assert.strictEqual(output.stderr.indexOf(expectedSubstring), output.stderr.lastIndexOf(expectedSubstring));
+            });
+
+            return Promise.all([exitCodeAssertion, outputAssertion]);
+        });
+    });
+
     afterEach(() => {
 
         // Clean up all the processes after every test.
diff --git a/tests/lib/rules/object-curly-newline.js b/tests/lib/rules/object-curly-newline.js
index 05376e0d4..ce8f87165 100644
--- a/tests/lib/rules/object-curly-newline.js
+++ b/tests/lib/rules/object-curly-newline.js
@@ -584,6 +584,31 @@ ruleTester.run("object-curly-newline", rule, {
                 { line: 2, column: 1, message: "Unexpected line break before this closing brace." }
             ]
         },
+        {
+            code: [
+                "var a = {",
+                " /* comment */ ",
+                "};"
+            ].join("\n"),
+            output: null,
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Unexpected line break after this opening brace." },
+                { line: 3, column: 1, message: "Unexpected line break before this closing brace." }
+            ]
+        },
+        {
+            code: [
+                "var a = { // comment",
+                "};"
+            ].join("\n"),
+            output: null,
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Unexpected line break after this opening brace." },
+                { line: 2, column: 1, message: "Unexpected line break before this closing brace." }
+            ]
+        },
         {
             code: [
                 "var b = {",
@@ -599,6 +624,22 @@ ruleTester.run("object-curly-newline", rule, {
                 { line: 3, column: 1, message: "Unexpected line break before this closing brace." }
             ]
         },
+        {
+            code: [
+                "var b = {",
+                "   a: 1 // comment",
+                "};"
+            ].join("\n"),
+            output: [
+                "var b = {a: 1 // comment",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Unexpected line break after this opening brace." },
+                { line: 3, column: 1, message: "Unexpected line break before this closing brace." }
+            ]
+        },
         {
             code: [
                 "var c = {",
@@ -614,6 +655,22 @@ ruleTester.run("object-curly-newline", rule, {
                 { line: 3, column: 1, message: "Unexpected line break before this closing brace." }
             ]
         },
+        {
+            code: [
+                "var c = {",
+                "    a: 1, b: 2 // comment",
+                "};"
+            ].join("\n"),
+            output: [
+                "var c = {a: 1, b: 2 // comment",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Unexpected line break after this opening brace." },
+                { line: 3, column: 1, message: "Unexpected line break before this closing brace." }
+            ]
+        },
         {
             code: [
                 "var d = {a: 1,",
@@ -631,6 +688,23 @@ ruleTester.run("object-curly-newline", rule, {
                 { line: 2, column: 9, message: "Expected a line break before this closing brace." }
             ]
         },
+        {
+            code: [
+                "var d = {a: 1, // comment",
+                "    b: 2};"
+            ].join("\n"),
+            output: [
+                "var d = {",
+                "a: 1, // comment",
+                "    b: 2",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Expected a line break after this opening brace." },
+                { line: 2, column: 9, message: "Expected a line break before this closing brace." }
+            ]
+        },
         {
             code: [
                 "var e = {a: function foo() {",
@@ -650,6 +724,71 @@ ruleTester.run("object-curly-newline", rule, {
                 { line: 3, column: 2, message: "Expected a line break before this closing brace." }
             ]
         },
+        {
+            code: [
+                "var e = {a: function foo() { // comment",
+                "    dosomething();",
+                "}};"
+            ].join("\n"),
+            output: [
+                "var e = {",
+                "a: function foo() { // comment",
+                "    dosomething();",
+                "}",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Expected a line break after this opening brace." },
+                { line: 3, column: 2, message: "Expected a line break before this closing brace." }
+            ]
+        },
+        {
+            code: [
+                "var e = {a: 1, /* comment */",
+                "    b: 2, // another comment",
+                "};"
+            ].join("\n"),
+            output: [
+                "var e = {",
+                "a: 1, /* comment */",
+                "    b: 2, // another comment",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Expected a line break after this opening brace." }
+            ]
+        },
+        {
+            code: [
+                "var f = { /* comment */ a:",
+                "2",
+                "};"
+            ].join("\n"),
+            output: null,
+            options: [{ multiline: true }],
+            errors: [
+                { line: 1, column: 9, message: "Expected a line break after this opening brace." }
+            ]
+        },
+        {
+            code: [
+                "var f = {",
+                "/* comment */",
+                "a: 1};"
+            ].join("\n"),
+            output: [
+                "var f = {",
+                "/* comment */",
+                "a: 1",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true }],
+            errors: [
+                { line: 3, column: 5, message: "Expected a line break before this closing brace." }
+            ]
+        },
 
         // "minProperties" ----------------------------------------------------------
         {