Repository: jlongster/prettier (js)

Issue:
Trailing comma added after comment in call expression
This should be in the same general area of #530:

```js
foo({}
  // Hi
);
```

```js
foo(
  {}
  // Hi,
);
```



Pull request description:
Fix trailing commas with a trailing comment
The reason why trailing comments on a line work with commas is because of a special `lineSuffix` document that delays printing the content until a \n is printed. But `lineSuffix` only worked for pure string. By making it work with arbitrary documents we can fix trailing commas with trailing comments.

Fixes #538

Patch:
diff --git a/editors/jetbrains/README.md b/editors/jetbrains/README.md
index f75eddde7..1a7ae61f4 100644
--- a/editors/jetbrains/README.md
+++ b/editors/jetbrains/README.md
@@ -14,6 +14,6 @@ If on the other hand you have `prettier` installed locally, replace the **Progra
 
 ### Configure Keymap
 
-Now when you setup **External Tool** I guess you what to add hotkey for it. Go to *File | Settings | Keymap* for Windows and Linux *WebStorm | Preferences | Keymap* and type external tool name in search box.
+Now when you setup **External Tool** I guess you want to add hotkey for it. Go to *File | Settings | Keymap* for Windows and Linux *WebStorm | Preferences | Keymap* and type external tool name in search box.
 
 See [this documentation](https://www.jetbrains.com/help/webstorm/configuring-keyboard-shortcuts.html) about configuring keyboard shortcuts.
diff --git a/src/comments.js b/src/comments.js
index d97dae762..55d42a936 100644
--- a/src/comments.js
+++ b/src/comments.js
@@ -157,9 +157,11 @@ function attach(comments, ast, text) {
         addDanglingComment(ast, comment);
       }
     } else if (util.hasNewline(text, locEnd(comment))) {
-      // There is content before this comment on the same line, but
-      // none after it, so prefer a trailing comment of the previous node.
-      if (precedingNode) {
+      if (handleConditionalExpressionComments(enclosingNode, followingNode, comment)) {
+        // We're good
+      } else if (precedingNode) {
+        // There is content before this comment on the same line, but
+        // none after it, so prefer a trailing comment of the previous node.
         addTrailingComment(precedingNode, comment);
       } else if (followingNode) {
         addLeadingComment(followingNode, comment);
@@ -170,12 +172,14 @@ function attach(comments, ast, text) {
         addDanglingComment(ast, comment);
       }
     } else {
-      // Otherwise, text exists both before and after the comment on
-      // the same line. If there is both a preceding and following
-      // node, use a tie-breaking algorithm to determine if it should
-      // be attached to the next or previous node. In the last case,
-      // simply attach the right node;
-      if (precedingNode && followingNode) {
+      if (handleIfStatementComments(enclosingNode, followingNode, comment)) {
+        // We're good
+      } else if (precedingNode && followingNode) {
+        // Otherwise, text exists both before and after the comment on
+        // the same line. If there is both a preceding and following
+        // node, use a tie-breaking algorithm to determine if it should
+        // be attached to the next or previous node. In the last case,
+        // simply attach the right node;
         const tieCount = tiesToBreak.length;
         if (tieCount > 0) {
           var lastTie = tiesToBreak[tieCount - 1];
@@ -367,6 +371,14 @@ function handleMemberExpressionComment(enclosingNode, followingNode, comment) {
   return false;
 }
 
+function handleConditionalExpressionComments(enclosingNode, followingNode, comment) {
+  if (enclosingNode && enclosingNode.type === 'ConditionalExpression' && followingNode) {
+    addLeadingComment(followingNode, comment);
+    return true;
+  }
+  return false;
+}
+
 function printComment(commentPath) {
   const comment = commentPath.getValue();
 
@@ -427,7 +439,7 @@ function printTrailingComment(commentPath, print, options, parentNode) {
       comment
     );
 
-    return concat([hardline, isLineBeforeEmpty ? hardline : "", contents]);
+    return lineSuffix(concat([hardline, isLineBeforeEmpty ? hardline : "", contents]));
   } else if (isBlock) {
     // Trailing block comments never need a newline
     return concat([" ", contents]);
diff --git a/src/doc-builders.js b/src/doc-builders.js
index d25cd06a1..9402f9da0 100644
--- a/src/doc-builders.js
+++ b/src/doc-builders.js
@@ -63,11 +63,7 @@ function ifBreak(breakContents, flatContents) {
 }
 
 function lineSuffix(contents) {
-  if (typeof contents !== "string") {
-    throw new Error(
-      "lineSuffix only takes a string, but given: " + JSON.stringify(contents)
-    );
-  }
+  assertDoc(contents);
   return { type: "line-suffix", contents };
 }
 
diff --git a/src/doc-debug.js b/src/doc-debug.js
index efcf9d20d..e1fc15671 100644
--- a/src/doc-debug.js
+++ b/src/doc-debug.js
@@ -92,6 +92,12 @@ function printDoc(doc) {
       printDoc(doc.contents) +
       ")";
   }
+
+  if (doc.type === "line-suffix") {
+    return "lineSuffix(" + printDoc(doc.contents) + ")";
+  }
+
+  throw new Error('Unknown doc type ' + doc.type);
 }
 
 module.exports = {
diff --git a/src/doc-printer.js b/src/doc-printer.js
index 1e1bf2764..e41ba0941 100644
--- a/src/doc-printer.js
+++ b/src/doc-printer.js
@@ -87,7 +87,7 @@ function printDocToString(doc, width, newLine) {
   let cmds = [[0, MODE_BREAK, doc]];
   let out = [];
   let shouldRemeasure = false;
-  let lineSuffix = "";
+  let lineSuffix = [];
 
   while (cmds.length !== 0) {
     const x = cmds.pop();
@@ -190,7 +190,7 @@ function printDocToString(doc, width, newLine) {
 
           break;
         case "line-suffix":
-          lineSuffix += doc.contents;
+          lineSuffix.push([ind, mode, doc.contents]);
           break;
         case "line":
           switch (mode) {
@@ -215,8 +215,15 @@ function printDocToString(doc, width, newLine) {
               }
 
             case MODE_BREAK:
+              if (lineSuffix.length) {
+                cmds.push([ind, mode, doc]);
+                [].push.apply(cmds, lineSuffix.reverse());
+                lineSuffix = [];
+                break;
+              }
+
               if (doc.literal) {
-                out.push(lineSuffix + newLine);
+                out.push(newLine);
                 pos = 0;
               } else {
                 if (out.length > 0) {
@@ -227,11 +234,9 @@ function printDocToString(doc, width, newLine) {
                   );
                 }
 
-                out.push(lineSuffix + newLine + " ".repeat(ind));
+                out.push(newLine + " ".repeat(ind));
                 pos = ind;
               }
-
-              lineSuffix = "";
               break;
           }
           break;
diff --git a/src/printer.js b/src/printer.js
index e78ec9718..81ccc2ed5 100644
--- a/src/printer.js
+++ b/src/printer.js
@@ -684,13 +684,13 @@ function genericPrintNoParens(path, options, print) {
           parts.push(concat([": ", printedValue]));
         } else {
           parts.push(
-            concat([
+            group(concat([
               ":",
               ifBreak(" (", " "),
               indent(options.tabWidth, concat([softline, printedValue])),
               softline,
               ifBreak(")")
-            ])
+            ]))
           );
         }
       }
@@ -712,7 +712,14 @@ function genericPrintNoParens(path, options, print) {
     case "ArrayPattern":
       if (n.elements.length === 0) {
         parts.push(
-          concat(["[", comments.printDanglingComments(path, options), "]"])
+          group(
+            concat([
+              "[",
+              comments.printDanglingComments(path, options),
+              softline,
+              "]"
+            ])
+          )
         );
       } else {
         const lastElem = util.getLast(n.elements);
@@ -2224,22 +2231,23 @@ function printMemberChain(path, options, print) {
   // node is just an identifier with the name starting with a capital
   // letter or just a sequence of _$. The rationale is that they are
   // likely to be factories.
-  if (
+  const shouldMerge =
     groups[0].length === 1 &&
-      groups[0][0].node.type === "Identifier" &&
-      groups[0][0].node.name.match(/(^[A-Z])|^[_$]+$/) &&
-      groups.length >= 2
-  ) {
-    // Push all the values of groups[0] at the beginning of groups[1]
-    [].unshift.apply(groups[1], groups[0]);
-    // Remove groups[0]
-    groups.splice(0, 1);
-  }
+    groups[0][0].node.type === "Identifier" &&
+    groups[0][0].node.name.match(/(^[A-Z])|^[_$]+$/) &&
+    groups.length >= 2;
 
   function printGroup(printedGroup) {
     return concat(printedGroup.map(tuple => tuple.printed));
   }
 
+  function printIndentedGroup(groups, lineType) {
+    return indent(
+      options.tabWidth,
+      group(concat([lineType, join(lineType, groups.map(printGroup))]))
+    );
+  }
+
   const printedGroups = groups.map(printGroup);
 
   const oneLine = concat(printedGroups);
@@ -2252,10 +2260,8 @@ function printMemberChain(path, options, print) {
 
   const expanded = concat([
     printGroup(groups[0]),
-    indent(
-      options.tabWidth,
-      group(concat([hardline, join(hardline, groups.slice(1).map(printGroup))]))
-    )
+    shouldMerge ? printIndentedGroup(groups.slice(1, 2), softline) : "",
+    printIndentedGroup(groups.slice(shouldMerge ? 2 : 1), hardline)
   ]);
 
   // If any group but the last one has a hard line, we want to force expand


Test patch:
diff --git a/tests/comments/__snapshots__/jsfmt.spec.js.snap b/tests/comments/__snapshots__/jsfmt.spec.js.snap
index 063b75f2d..b6c036234 100644
--- a/tests/comments/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/comments/__snapshots__/jsfmt.spec.js.snap
@@ -74,6 +74,32 @@ declare class Foo extends Qux<string> {/* dangling */}
 "
 `;
 
+exports[`test dangling_array.js 1`] = `
+"expect(() => {}).toTriggerReadyStateChanges([
+  // Nothing.
+]);
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+expect(() => {}).toTriggerReadyStateChanges(
+  [
+    // Nothing.
+  ]
+);
+"
+`;
+
+exports[`test dangling_array.js 2`] = `
+"expect(() => {}).toTriggerReadyStateChanges([
+  // Nothing.
+]);
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+expect(() => {}).toTriggerReadyStateChanges(
+  [
+    // Nothing.
+  ]
+);
+"
+`;
+
 exports[`test first-line.js 1`] = `
 "a // comment
 b
diff --git a/tests/comments/dangling_array.js b/tests/comments/dangling_array.js
new file mode 100644
index 000000000..599897d57
--- /dev/null
+++ b/tests/comments/dangling_array.js
@@ -0,0 +1,3 @@
+expect(() => {}).toTriggerReadyStateChanges([
+  // Nothing.
+]);
diff --git a/tests/conditional/__snapshots__/jsfmt.spec.js.snap b/tests/conditional/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000..688b10956
--- /dev/null
+++ b/tests/conditional/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,71 @@
+exports[`test comments.js 1`] = `
+"var inspect = 4 === util.inspect.length
+  ? // node <= 0.8.x
+    (function(v, colors) {
+      return util.inspect(v, void 0, void 0, colors);
+    })
+  : // node > 0.8.x
+    (function(v, colors) {
+      return util.inspect(v, { colors: colors });
+    });
+
+var inspect = 4 === util.inspect.length
+  ? // node <= 0.8.x
+    (function(v, colors) {
+      return util.inspect(v, void 0, void 0, colors);
+    })
+  : // node > 0.8.x
+    (function(v, colors) {
+      return util.inspect(v, { colors: colors });
+    });
+
+const extractTextPluginOptions = shouldUseRelativeAssetPaths
+  // Making sure that the publicPath goes back to to build folder.
+  ? { publicPath: Array(cssFilename.split(\'/\').length).join(\'../\') } :
+  {};
+
+const extractTextPluginOptions = shouldUseRelativeAssetPaths
+  ? // Making sure that the publicPath goes back to to build folder.
+    { publicPath: Array(cssFilename.split(\"/\").length).join(\"../\") }
+  : {};
+
+const extractTextPluginOptions = shouldUseRelativeAssetPaths // Making sure that the publicPath goes back to to build folder.
+  ? { publicPath: Array(cssFilename.split(\"/\").length).join(\"../\") }
+  : {};
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+var inspect = 4 === util.inspect.length
+  ? // node <= 0.8.x
+    (function(v, colors) {
+      return util.inspect(v, void 0, void 0, colors);
+    })
+  : // node > 0.8.x
+    (function(v, colors) {
+      return util.inspect(v, { colors: colors });
+    });
+
+var inspect = 4 === util.inspect.length
+  ? // node <= 0.8.x
+    (function(v, colors) {
+      return util.inspect(v, void 0, void 0, colors);
+    })
+  : // node > 0.8.x
+    (function(v, colors) {
+      return util.inspect(v, { colors: colors });
+    });
+
+const extractTextPluginOptions = shouldUseRelativeAssetPaths
+  ? // Making sure that the publicPath goes back to to build folder.
+    { publicPath: Array(cssFilename.split(\"/\").length).join(\"../\") }
+  : {};
+
+const extractTextPluginOptions = shouldUseRelativeAssetPaths
+  ? // Making sure that the publicPath goes back to to build folder.
+    { publicPath: Array(cssFilename.split(\"/\").length).join(\"../\") }
+  : {};
+
+const extractTextPluginOptions = shouldUseRelativeAssetPaths
+  ? // Making sure that the publicPath goes back to to build folder.
+    { publicPath: Array(cssFilename.split(\"/\").length).join(\"../\") }
+  : {};
+"
+`;
diff --git a/tests/conditional/comments.js b/tests/conditional/comments.js
new file mode 100644
index 000000000..78e9a024f
--- /dev/null
+++ b/tests/conditional/comments.js
@@ -0,0 +1,33 @@
+var inspect = 4 === util.inspect.length
+  ? // node <= 0.8.x
+    (function(v, colors) {
+      return util.inspect(v, void 0, void 0, colors);
+    })
+  : // node > 0.8.x
+    (function(v, colors) {
+      return util.inspect(v, { colors: colors });
+    });
+
+var inspect = 4 === util.inspect.length
+  ? // node <= 0.8.x
+    (function(v, colors) {
+      return util.inspect(v, void 0, void 0, colors);
+    })
+  : // node > 0.8.x
+    (function(v, colors) {
+      return util.inspect(v, { colors: colors });
+    });
+
+const extractTextPluginOptions = shouldUseRelativeAssetPaths
+  // Making sure that the publicPath goes back to to build folder.
+  ? { publicPath: Array(cssFilename.split('/').length).join('../') } :
+  {};
+
+const extractTextPluginOptions = shouldUseRelativeAssetPaths
+  ? // Making sure that the publicPath goes back to to build folder.
+    { publicPath: Array(cssFilename.split("/").length).join("../") }
+  : {};
+
+const extractTextPluginOptions = shouldUseRelativeAssetPaths // Making sure that the publicPath goes back to to build folder.
+  ? { publicPath: Array(cssFilename.split("/").length).join("../") }
+  : {};
diff --git a/tests/conditional/jsfmt.spec.js b/tests/conditional/jsfmt.spec.js
new file mode 100644
index 000000000..989047bcc
--- /dev/null
+++ b/tests/conditional/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname);
diff --git a/tests/if_comments/__snapshots__/jsfmt.spec.js.snap b/tests/if_comments/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000..569510378
--- /dev/null
+++ b/tests/if_comments/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,73 @@
+exports[`test if_comments.js 1`] = `
+"async function f() {
+  if (untrackedChoice === 0) /* Cancel */ {
+    return null;
+  } else if (untrackedChoice === 1) /* Add */ {
+    await repository.addAll(Array.from(untrackedChanges.keys()));
+    shouldAmend = true;
+  } else if (untrackedChoice === 2) /* Allow Untracked */ {
+    allowUntracked = true;
+  }
+}
+
+async function f() {
+  if (untrackedChoice === 0)
+    /* Cancel */ {
+      return null;
+    }
+  else if (untrackedChoice === 1)
+    /* Add */ {
+      await repository.addAll(Array.from(untrackedChanges.keys()));
+      shouldAmend = true;
+    }
+  else if (untrackedChoice === 2)
+    /* Allow Untracked */ {
+      allowUntracked = true;
+    }
+}
+
+async function f() {
+  if (untrackedChoice === 0) {
+    /* Cancel */ return null;
+  } else if (untrackedChoice === 1) {
+    /* Add */ await repository.addAll(Array.from(untrackedChanges.keys()));
+    shouldAmend = true;
+  } else if (untrackedChoice === 2) {
+    /* Allow Untracked */ allowUntracked = true;
+  }
+}
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+async function f() {
+  if (untrackedChoice === 0) {
+    /* Cancel */ return null;
+  } else if (untrackedChoice === 1) {
+    /* Add */ await repository.addAll(Array.from(untrackedChanges.keys()));
+    shouldAmend = true;
+  } else if (untrackedChoice === 2) {
+    /* Allow Untracked */ allowUntracked = true;
+  }
+}
+
+async function f() {
+  if (untrackedChoice === 0) {
+    /* Cancel */ return null;
+  } else if (untrackedChoice === 1) {
+    /* Add */ await repository.addAll(Array.from(untrackedChanges.keys()));
+    shouldAmend = true;
+  } else if (untrackedChoice === 2) {
+    /* Allow Untracked */ allowUntracked = true;
+  }
+}
+
+async function f() {
+  if (untrackedChoice === 0) {
+    /* Cancel */ return null;
+  } else if (untrackedChoice === 1) {
+    /* Add */ await repository.addAll(Array.from(untrackedChanges.keys()));
+    shouldAmend = true;
+  } else if (untrackedChoice === 2) {
+    /* Allow Untracked */ allowUntracked = true;
+  }
+}
+"
+`;
diff --git a/tests/if_comments/if_comments.js b/tests/if_comments/if_comments.js
new file mode 100644
index 000000000..2b3552999
--- /dev/null
+++ b/tests/if_comments/if_comments.js
@@ -0,0 +1,37 @@
+async function f() {
+  if (untrackedChoice === 0) /* Cancel */ {
+    return null;
+  } else if (untrackedChoice === 1) /* Add */ {
+    await repository.addAll(Array.from(untrackedChanges.keys()));
+    shouldAmend = true;
+  } else if (untrackedChoice === 2) /* Allow Untracked */ {
+    allowUntracked = true;
+  }
+}
+
+async function f() {
+  if (untrackedChoice === 0)
+    /* Cancel */ {
+      return null;
+    }
+  else if (untrackedChoice === 1)
+    /* Add */ {
+      await repository.addAll(Array.from(untrackedChanges.keys()));
+      shouldAmend = true;
+    }
+  else if (untrackedChoice === 2)
+    /* Allow Untracked */ {
+      allowUntracked = true;
+    }
+}
+
+async function f() {
+  if (untrackedChoice === 0) {
+    /* Cancel */ return null;
+  } else if (untrackedChoice === 1) {
+    /* Add */ await repository.addAll(Array.from(untrackedChanges.keys()));
+    shouldAmend = true;
+  } else if (untrackedChoice === 2) {
+    /* Allow Untracked */ allowUntracked = true;
+  }
+}
diff --git a/tests/if_comments/jsfmt.spec.js b/tests/if_comments/jsfmt.spec.js
new file mode 100644
index 000000000..989047bcc
--- /dev/null
+++ b/tests/if_comments/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname);
diff --git a/tests/method-chain/__snapshots__/jsfmt.spec.js.snap b/tests/method-chain/__snapshots__/jsfmt.spec.js.snap
index 86427b531..daa999035 100644
--- a/tests/method-chain/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/method-chain/__snapshots__/jsfmt.spec.js.snap
@@ -115,6 +115,41 @@ it(\"should group messages with same created time\", () => {
 "
 `;
 
+exports[`test first_long.js 1`] = `
+"export default function theFunction(action$, store) {
+  return action$.ofType(THE_ACTION).switchMap(action => Observable
+    .webSocket({
+      url: THE_URL,
+      more: stuff(),
+      evenMore: stuff({
+        value1: true,
+        value2: false,
+        value3: false
+      })
+    })
+    .filter(data => theFilter(data))
+    .map(({ theType, ...data }) => theMap(theType, data))
+    .retryWhen(errors => errors));
+}
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+export default function theFunction(action$, store) {
+  return action$.ofType(THE_ACTION).switchMap(action => Observable
+    .webSocket({
+      url: THE_URL,
+      more: stuff(),
+      evenMore: stuff({
+        value1: true,
+        value2: false,
+        value3: false
+      })
+    })
+    .filter(data => theFilter(data))
+    .map(({ theType, ...data }) => theMap(theType, data))
+    .retryWhen(errors => errors));
+}
+"
+`;
+
 exports[`test multiple-members.js 1`] = `
 "if (testConfig.ENABLE_ONLINE_TESTS === \"true\") {
   describe(\"POST /users/me/pet\", function() {
diff --git a/tests/method-chain/first_long.js b/tests/method-chain/first_long.js
new file mode 100644
index 000000000..ced9a2035
--- /dev/null
+++ b/tests/method-chain/first_long.js
@@ -0,0 +1,15 @@
+export default function theFunction(action$, store) {
+  return action$.ofType(THE_ACTION).switchMap(action => Observable
+    .webSocket({
+      url: THE_URL,
+      more: stuff(),
+      evenMore: stuff({
+        value1: true,
+        value2: false,
+        value3: false
+      })
+    })
+    .filter(data => theFilter(data))
+    .map(({ theType, ...data }) => theMap(theType, data))
+    .retryWhen(errors => errors));
+}
diff --git a/tests/object-prop-break-in/__snapshots__/jsfmt.spec.js.snap b/tests/object-prop-break-in/__snapshots__/jsfmt.spec.js.snap
index 1bc4417f6..cb925d852 100644
--- a/tests/object-prop-break-in/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/object-prop-break-in/__snapshots__/jsfmt.spec.js.snap
@@ -1,3 +1,20 @@
+exports[`test comment.js 1`] = `
+"function foo() {
+  return {
+    // this comment causes the problem
+    bar: baz() + 1
+  };
+}
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+function foo() {
+  return {
+    // this comment causes the problem
+    bar: baz() + 1
+  };
+}
+"
+`;
+
 exports[`test test.js 1`] = `
 "const a = classnames({
   \"some-prop\": this.state.longLongLongLongLongLongLongLongLongTooLongProp
diff --git a/tests/object-prop-break-in/comment.js b/tests/object-prop-break-in/comment.js
new file mode 100644
index 000000000..470a85819
--- /dev/null
+++ b/tests/object-prop-break-in/comment.js
@@ -0,0 +1,6 @@
+function foo() {
+  return {
+    // this comment causes the problem
+    bar: baz() + 1
+  };
+}
diff --git a/tests/trailing_comma/__snapshots__/jsfmt.spec.js.snap b/tests/trailing_comma/__snapshots__/jsfmt.spec.js.snap
index 2096a8ffd..5f2e3336a 100644
--- a/tests/trailing_comma/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/trailing_comma/__snapshots__/jsfmt.spec.js.snap
@@ -153,3 +153,153 @@ const aLong = {
 };
 "
 `;
+
+exports[`test trailing_whitespace.js 1`] = `
+"let example = [
+  \'FOO\',
+  \'BAR\',
+  // Comment
+];
+
+foo({},
+  // Comment
+);
+
+o = {
+  state,
+  // Comment
+};
+
+o = {
+  state,
+
+  // Comment
+};
+
+function supersupersupersuperLongF(
+  supersupersupersuperLongA,
+  supersupersupersuperLongB
+  // Comment
+) {
+  a
+}
+function supersupersupersuperLongF(
+  supersupersupersuperLongA,
+  supersupersupersuperLongB,
+  // Comment
+) {
+  a
+}
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+let example = [
+  \"FOO\",
+  \"BAR\"
+  // Comment
+];
+
+foo(
+  {}
+  // Comment
+);
+
+o = {
+  state
+  // Comment
+};
+
+o = {
+  state
+  // Comment
+};
+
+function supersupersupersuperLongF(
+  supersupersupersuperLongA,
+  supersupersupersuperLongB
+) // Comment
+{
+  a;
+}
+function supersupersupersuperLongF(
+  supersupersupersuperLongA,
+  supersupersupersuperLongB
+) // Comment
+{
+  a;
+}
+"
+`;
+
+exports[`test trailing_whitespace.js 2`] = `
+"let example = [
+  \'FOO\',
+  \'BAR\',
+  // Comment
+];
+
+foo({},
+  // Comment
+);
+
+o = {
+  state,
+  // Comment
+};
+
+o = {
+  state,
+
+  // Comment
+};
+
+function supersupersupersuperLongF(
+  supersupersupersuperLongA,
+  supersupersupersuperLongB
+  // Comment
+) {
+  a
+}
+function supersupersupersuperLongF(
+  supersupersupersuperLongA,
+  supersupersupersuperLongB,
+  // Comment
+) {
+  a
+}
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+let example = [
+  \"FOO\",
+  \"BAR\",
+  // Comment
+];
+
+foo(
+  {},
+  // Comment
+);
+
+o = {
+  state,
+  // Comment
+};
+
+o = {
+  state,
+  // Comment
+};
+
+function supersupersupersuperLongF(
+  supersupersupersuperLongA,
+  supersupersupersuperLongB,
+) // Comment
+{
+  a;
+}
+function supersupersupersuperLongF(
+  supersupersupersuperLongA,
+  supersupersupersuperLongB,
+) // Comment
+{
+  a;
+}
+"
+`;
diff --git a/tests/trailing_comma/trailing_whitespace.js b/tests/trailing_comma/trailing_whitespace.js
new file mode 100644
index 000000000..797568031
--- /dev/null
+++ b/tests/trailing_comma/trailing_whitespace.js
@@ -0,0 +1,35 @@
+let example = [
+  'FOO',
+  'BAR',
+  // Comment
+];
+
+foo({},
+  // Comment
+);
+
+o = {
+  state,
+  // Comment
+};
+
+o = {
+  state,
+
+  // Comment
+};
+
+function supersupersupersuperLongF(
+  supersupersupersuperLongA,
+  supersupersupersuperLongB
+  // Comment
+) {
+  a
+}
+function supersupersupersuperLongF(
+  supersupersupersuperLongA,
+  supersupersupersuperLongB,
+  // Comment
+) {
+  a
+}