Repository: jlongster/prettier
Pull request #234 (2017-01-16 05:15:10)

[WIP] [JSX] Add newlines between elements, preserving spaces
Supersedes https://github.com/jlongster/prettier/pull/219 and intended to supersede https://github.com/jlongster/prettier/pull/220

Still hammering a few tricky trailing-whitespace situations out, but may be ready for 👀 nonetheless

jlongster: I will look through the PR now and give feedback, but first I tried this out. It definitely works better. Thanks a lot!

Here are a few cases to talk about (I may have missed decisions about these earlier, but I don't think so).

```js
var x = <div>
    hello <strong>hi</strong> <foo>sdkflsdfjk</foo>
 </div>;
```

The above is turned into:

```js
var x = (
  <div>
    hello <strong>hi</strong>
    <foo>sdkflsdfjk</foo>
  </div>
);
```

This example never had to break -- should `foo` be moved down to the next line? It seems arbitrary that `strong` wasn't but `foo` was. What is the exact rule for moving things down?

Well, the above the case illustrates my question. I thought I had other cases but they're all pretty much the same as above.
jlongster: I wonder if we should create a document that described various rules we use for breaking things. I wouldn't want this to be very long, but some short succinct descriptions about how we deal with popular patterns might give a good overview and be a reference we can point to when people open new issues. @rattrayalex If you want to, can you start such a document in `docs`? Don't worry if you don't have time / don't want to. I can start it soon myself. Seeing the JSX algorithm described in words would be nice though.
jlongster: Actually, don't worry about the document. We're changing things pretty quickly right now and it'll get out-of-date fast so it's not worth it. It would be good at least to explain in #202 any edge cases you find and flesh it out a little so we have it as a reference.
jlongster: I'm having a hard time understanding this code, mainly because I haven't used JSX very much. It seems tricky, so we need to have comments documenting why each case is handled the way it is. It would also help a lot with the review (I think that's what I was looking for when I was talking about docs above). Can you add thorough comments in the JSX printing code? I think I'd have an easier time understanding why these special cases exist, as would future maintainers.
rattrayalex: Haha yeah some comments would indeed be helpful 😄 I'll do that.

I think there may be something "wrong" though... 

What's happening is that there's only one `group` per element, including its opening element, its children, and its closing element. That group is a `multilineGroup`, so if anything is split, *all* children are split into their own lines, if there are `line`s on which to split. So currently, most element-splitting logic here is done by inserting `softline`s selectively. 

Mostly to specifically support the sort of use-case you highlighted, those `softline`s are only inserted directly between node elements, not between node elements and text. (They're also inserted after a space-only text element, which I believe only occurs between node elements).

It would probably be preferable to add `softline`s between every kind of element, node and text, and have better grouping logic. Everything I've tried to that effect has come out pretty ugly, but my group-foo may be too basic at this point. 

The challenge is that this:
```jsx
<div
  long="attrs"
><child /></div>
```
should always split the child out with an indent, which means a single `multilineGroup` around the whole element (what we have now). But if you wrap the children code in its own (multiline)group, that breaks. 

Any ideas?
jlongster: > It would probably be preferable to add softlines between every kind of element, node and text, and have better grouping logic. Everything I've tried to that effect has come out pretty ugly, but my group-foo may be too basic at this point.

Aah I see. Can this be summarized as: we should always try to break the children of a JSX element first before breaking the attributes?
jlongster: > What's happening is that there's only one group per element, including its opening element, its children, and its closing element.

Also, that does seem a little weird and we probably need to change it so it adds a group per JSX element so deeper elements can be broken without forcing all parents to break.
rattrayalex: Ahhh yes, I was starting to come to the same conclusion as I typed out that explanation. The "when you're not doing outer-to-inner, you might need conditionalGroup" sounds like a nice heuristic. I've used it before, but also abused it, as you may recall 😉

I'll give that a try!
jlongster: Cool! Yeah, it's a "use only in really special cases" type thing but this is a special case. Just make sure you share documents as much as possible across the groups, i.e.

```js
const childrenPrinted = path.call(print, "children");
return conditionalGroup([
  concat([" ", childrenPrinted, " "]),
  concat([hardline, childrenPrinted, hardline]),
])
```

instead of 

```js
return conditionalGroup([
  concat([" ", path.call(print, "children"), " "]),
  concat([hardline, path.call(print, "children"), hardline]),
])
```

Because the latter we print the expression twice and duplicate the children document, which is really bad both for CPU and memory. 
jlongster: @rattrayalex This is getting into parts of the API that we probably need to polish some more, but one more thing. You can forcefully break a document wrapping it in a group and passing an option: `group(doc, { shouldBreak: true })`. This will force everything in `doc` to break. (If it's not clear: a document is any piece of the "intermediate representation", i.e. anything returned from `print`).

This is useful with `conditionalGroup` because you can call `print` on an arbitrary expression but forcefully break it. That's how you force a child expression to break before the parent.
rattrayalex: That's very helpful as well! Cool I'll get cracking on that.
jlongster: If you need any real-time help, feel free to jump on gitter! You're doing some of the most complicated work so I'm happy to work through it with you. You can also check out `printArgumentsList` which is another place we use this technique (which allows the last arg of a function call to expand across lines before breaking all params, which is the same break-child-first problem).
rattrayalex: Very close, sharing progress. Found a weird edge case.
yamafaktory: @rattrayalex & @jlongster really interesting discussion and progress, looking forward to seeing how it will go 👍 
rattrayalex: Okay, having slept on this a bit, I think we should step back and ask what formatting we *want* for JSX. 

Most of my uncertainty revolves around siblings on the same line, and as a special case, mixed text and node siblings. Eg; 

(1) 
```jsx
<div>
  <Icon type="foo" /><Icon type="bar" />
</div>
```
(2)
```jsx
<div>
  <Icon type="foo" /><Icon type="bar" />
  <Icon type="foo" /><Icon type="bar" />
  <Icon type="foo" /><Icon type="bar" />
</div>
```
(3)
```jsx
<div>
  <Icon type="foo" /> This is a paragraph with <strong>text</strong>.
  <div with="long attrs, let's pretend" />
</div>
```
(4)
```jsx
<div>
  A bunch of text here...
  <br /><hr /><br />
  And a bunch more <strong>text</strong> here...
</div>
```

In all the cases above, everything within the div is a sibling. So a typical group approach would break onto its own line. Example (4) is particularly, telling, which would be broken out into:

```jsx
<div>
  A bunch of text here...
  <br />
  <hr />
  <br />
  And a bunch more{' '}
  <strong>text</strong>
  {' '}here...
</div>
```

That seems clearly wrong in the sentence-with-strong examples. But what about the examples without text, like (2)? My bunch is that in (2), all Icon's should be on their own line, and that if the same happened to (1), that would be acceptable – but is it?

If (2) should remain printed as-written, then we probably want to wrap each actual line in its own `group` (or maybe `multilineGroup`). That sounds somewhat tricky and hacky but probably doable.

If (2) should break but (3) should not, then we probably want to treat text with jsx siblings as belonging to an invisible `<span>` parent, which also sounds tricky (wrap text-and-jsx-siblings in a group, probably only per-line). 

Thoughts? Should we ask **gaearon** for input on this?
rattrayalex: Okay, I've gone and implemented the above algorithm (wrap each line in a `group`). This might sound like it defeats the purpose of `prettier`, but I think it makes sense in the context of JSX because you can essentially have multiple "statements" per line, which is impossible in JS (without semicolons). 

It resulted in generally simpler code, though there still is a lot going on. 

There's one lingering `{' '}` related bug, but that should be fixable in an isolated way. 

Otherwise, I think this is ready for a careful review. 
rattrayalex: @vjeux not sure you were tagged on this, would love your 👀 if you have the time and inclination!
vjeux: I don't know what you did, but it's actually working like magic on the Nuclide codebase. Most of the differences I see are actually better with your algorithm!

Two things that are probably best addressed in follow ups.

- [ ] We shouldn't output parenthesis inside of function calls.

![image](https://cloud.githubusercontent.com/assets/197597/22049560/f6df54f4-dce8-11e6-971b-88c9523afded.png)

- [ ] There's a bunch of cases where inside of a JSXExpression, the output is very whitespace and linebreaks heavy. We should be more aggressive at condensing it

![image](https://cloud.githubusercontent.com/assets/197597/22049586/18655bdc-dce9-11e6-9350-69f62d6c42a8.png)



rattrayalex: Awesome to hear!!

Could you link to the source of those examples so I can add them as tests?
They may both have a simple quick fix that I'd happily roll into this PR.

On Tue, Jan 17, 2017, 19:22 Christopher Chedeau <notifications@github.com>
wrote:

> I don't know what you did, but it's actually working like magic on the
> Nuclide codebase. Most of the differences I see are actually better with
> your algorithm!
>
> Two things that are probably best addressed in follow ups.
>
>    - We shouldn't output parenthesis inside of function calls.
>
> [image: image]
> <https://cloud.githubusercontent.com/assets/197597/22049560/f6df54f4-dce8-11e6-971b-88c9523afded.png>
>
>    - There's a bunch of cases where inside of a JSXExpression, the output
>    is very whitespace and linebreaks heavy. We should be more aggressive at
>    condensing it
>
> [image: image]
> <https://cloud.githubusercontent.com/assets/197597/22049586/18655bdc-dce9-11e6-9350-69f62d6c42a8.png>
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/jlongster/prettier/pull/234#issuecomment-273372361>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAq_LlB1HWNOOZxKQGr2BLMv6IYNgFfTks5rTYVjgaJpZM4LkL6t>
> .
>

rattrayalex: K, I added non-paren cases for `CallExpression` and `ConditionalExpression`, with tests. Ternaries still need some fixing, but that should be in a follow-up.
vjeux: Oh sorry I missed your question. Glad you figured it out!

Can't wait to get this landed <3
existentialism: Nice work @rattrayalex!

Patch:
diff --git a/.travis.yml b/.travis.yml
index 90ca8ce6e..113524a7c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,3 +6,4 @@ node_js:
 cache:
   directories:
   - node_modules
+script: npm test -- --runInBand
diff --git a/bin/prettier.js b/bin/prettier.js
index 11b81588e..3321b030d 100755
--- a/bin/prettier.js
+++ b/bin/prettier.js
@@ -13,11 +13,17 @@ const argv = minimist(process.argv.slice(2), {
     "flow-parser",
     "bracket-spacing",
     "single-quote",
-    "trailing-comma"
+    "trailing-comma",
+    "version"
   ],
   default: { "bracket-spacing": true }
 });
 
+if (argv["version"]) {
+  console.log(jscodefmt.version);
+  process.exit(0);
+}
+
 const filenames = argv["_"];
 const write = argv["write"];
 const stdin = argv["stdin"];
diff --git a/index.js b/index.js
index d74d07ed1..194f2a936 100644
--- a/index.js
+++ b/index.js
@@ -3,6 +3,7 @@ const babylon = require("babylon");
 const Printer = require("./src/printer").Printer;
 const flowParser = require("flow-parser");
 const comments = require("./src/comments");
+const version = require('./package.json').version;
 
 var babylonOptions = {
   sourceType: "module",
@@ -74,5 +75,6 @@ function formatWithShebang(text, opts) {
 module.exports = {
   format: function(text, opts) {
     return formatWithShebang(text, opts);
-  }
+  },
+  version: version
 };
diff --git a/src/pp.js b/src/pp.js
index 4a85a4460..4b61b8ac3 100644
--- a/src/pp.js
+++ b/src/pp.js
@@ -352,19 +352,17 @@ function print(w, doc) {
               }
 
             case MODE_BREAK:
-              if (out.length > 0) {
-                const lastString = out[out.length - 1];
-
-                if (lastString.match(/^\s*\n\s*$/)) {
-                  out[out.length - 1] = "\n";
-                }
-              }
-
               if (doc.literal) {
                 out.push("\n");
 
                 pos = 0;
               } else {
+                if (out.length > 0) {
+                  // Trim whitespace at the end of line
+                  out[out.length - 1] = out[out.length - 1]
+                    .replace(/[^\S\n]*$/, '');
+                }
+
                 out.push("\n" + " ".repeat(ind));
 
                 pos = ind;
diff --git a/src/printer.js b/src/printer.js
index 52f9ed72b..7735f1091 100644
--- a/src/printer.js
+++ b/src/printer.js
@@ -1068,8 +1068,10 @@ function genericPrintNoParens(path, options, print) {
           "}"
         ])
       );
-    case "JSXElement":
-      return printJSXElement(path, options, print);
+    case "JSXElement": {
+      const elem = printJSXElement(path, options, print);
+      return maybeWrapJSXElementInParens(path, elem, options);
+    }
     case "JSXOpeningElement":
       return group(
         concat([
@@ -1839,7 +1841,6 @@ function typeIsFunction(type) {
 function printExportDeclaration(path, options, print) {
   var decl = path.getValue();
   var parts = [ "export " ];
-  var shouldPrintSpaces = options.bracketSpacing;
 
   namedTypes.Declaration.assert(decl);
 
@@ -2048,71 +2049,198 @@ function printMemberChain(node, options, print) {
   }
 }
 
+function isEmptyJSXElement(node) {
+  if (node.children.length === 0) return true;
+  if (node.children.length > 1) return false;
+
+  // if there is one child but it's just a newline, treat as empty
+  const value = node.children[0].value;
+  if (!/\S/.test(value) && /\n/.test(value)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+// JSX Children are strange, mostly for two reasons:
+// 1. JSX reads newlines into string values, instead of skipping them like JS
+// 2. up to one whitespace between elements within a line is significant,
+//    but not between lines.
+//
+// So for one thing, '\n' needs to be parsed out of string literals
+// and turned into hardlines (with string boundaries otherwise using softline)
+//
+// For another, leading, trailing, and lone whitespace all need to
+// turn themselves into the rather ugly `{' '}` when breaking.
+function printJSXChildren(path, options, print) {
+  const n = path.getValue();
+  const children = [];
+  const jsxWhitespace = options.singleQuote
+    ? ifBreak("{' '}", " ")
+    : ifBreak("{\" \"}", " ");
+
+  // using `map` instead of `each` because it provides `i`
+  path.map(function(childPath, i) {
+    const child = childPath.getValue();
+    const isLiteral = namedTypes.Literal.check(child);
+
+    if (isLiteral && typeof child.value === "string") {
+      if (/\S/.test(child.value)) {
+        const beginBreak = child.value.match(/^\s*\n/);
+        const endBreak = child.value.match(/\n\s*$/);
+        const beginSpace = child.value.match(/^\s+/);
+        const endSpace = child.value.match(/\s+$/);
+
+        if (beginBreak) {
+          children.push(hardline);
+        } else if (beginSpace) {
+          children.push(jsxWhitespace);
+        }
+
+        children.push(child.value.replace(/^\s+|\s+$/g, ""));
+
+        if (endBreak) {
+          children.push(hardline);
+        } else {
+          if (endSpace) children.push(jsxWhitespace);
+          children.push(softline);
+        }
+      } else if (/\n/.test(child.value)) {
+        // TODO: add another hardline if >1 newline appeared. (also above)
+        children.push(hardline);
+      } else if (/\s/.test(child.value)) {
+        // whitespace-only without newlines,
+        // eg; a single space separating two elements
+        children.push(jsxWhitespace);
+        children.push(softline);
+      }
+    } else {
+      children.push(print(childPath));
+
+      // add a line unless it's followed by a JSX newline
+      let next = n.children[i + 1];
+      if (!(next && /^\s*\n/.test(next.value))) {
+        children.push(softline);
+      }
+    }
+  }, "children");
+
+  return children;
+}
+
+// JSX expands children from the inside-out, instead of the outside-in.
+// This is both to break children before attributes,
+// and to ensure that when children break, their parents do as well.
+//
+// Any element that is written without any newlines and fits on a single line
+// is left that way.
+// Not only that, any user-written-line containing multiple JSX siblings
+// should also be kept on one line if possible,
+// so each user-written-line is wrapped in its own group.
+//
+// Elements that contain newlines or don't fit on a single line (recursively)
+// are fully-split, using hardline and shouldBreak: true.
+//
+// To support that case properly, all leading and trailing spaces
+// are stripped from the list of children, and replaced with a single hardline.
 function printJSXElement(path, options, print) {
   const n = path.getValue();
-  var openingLines = path.call(print, "openingElement");
 
-  let elem;
+  // turn <div></div> into <div />
+  if (isEmptyJSXElement(n)) {
+    n.openingElement.selfClosing = true;
+    delete n.closingElement;
+  }
+
+  // if no children, just print the opening element
+  const openingLines = path.call(print, "openingElement");
   if (n.openingElement.selfClosing) {
     assert.ok(!n.closingElement);
+    return openingLines;
+  }
 
-    elem = openingLines;
-  } else {
-    var children = [];
+  const children = printJSXChildren(path, options, print);
+  let hasAnyHardLine = false;
+
+  // trim trailing whitespace, recording if there was a hardline
+  while (children.length && util.getLast(children).type === "line") {
+    if (hasHardLine(util.getLast(children))) hasAnyHardLine = true;
+    children.pop();
+  }
 
-    path.map(
-      function(childPath) {
-        var child = childPath.getValue();
+  // trim leading whitespace, recording if there was a hardline
+  while (children.length && children[0].type === "line") {
+    if (hasHardLine(children[0])) hasAnyHardLine = true;
+    children.shift();
+  }
 
-        if (
-          namedTypes.Literal.check(child) && typeof child.value === "string"
-        ) {
-          if (/\S/.test(child.value)) {
-            const beginBreak = child.value.match(/^\s*\n/);
-            const endBreak = child.value.match(/\n\s*$/);
-
-            children.push(
-              beginBreak ? hardline : "",
-              child.value.replace(/^\s+|\s+$/g, endBreak ? "" : " "),
-              endBreak ? hardline : ""
-            );
-          } else if (/\n/.test(child.value)) {
-            children.push(hardline);
-          }
-        } else {
-          children.push(print(childPath));
-        }
-      },
-      "children"
-    );
+  // group by line, recording if there was a hardline.
+  let childrenGroupedByLine;
+  if (children.length === 1) {
+    if (!hasAnyHardLine && hasHardLine(children[0])) hasAnyHardLine = true;
 
-    var mostChildren = children.slice(0, -1);
-    var closingLines = path.call(print, "closingElement");
+    // no need for groups, and a lone jsx whitespace doesn't work otherwise
+    childrenGroupedByLine = [ hardline, children[0] ];
+  } else {
+    //  prefill leading newline, and initialize the first line's group
+    childrenGroupedByLine = [ hardline, group(concat([])) ];
+    children.forEach((child, i) => {
+      let prev = children[i - 1];
+      if (prev && hasHardLine(prev)) {
+        hasAnyHardLine = true;
+
+        // on a new line, so create a new group and put this element in it
+        const newLineGroup = group(concat([ child ]));
+        childrenGroupedByLine.push(newLineGroup);
+      } else {
+        // not on a newline, so add this element to the current group
+        const currentLineGroup = util.getLast(childrenGroupedByLine);
+        currentLineGroup.contents.parts.push(child);
+      }
 
-    const firstChild = children[(0)];
-    const lastChild = util.getLast(children);
-    const beginBreak = firstChild && firstChild.type === "line";
-    const endBreak = lastChild && lastChild.type === "line";
+      // ensure we record hardline of last element
+      if (!hasAnyHardLine && i === children.length - 1) {
+        if (hasHardLine(child)) hasAnyHardLine = true;
+      }
+    });
+  }
 
-    elem = multilineGroup(
-      concat([
-        openingLines,
-        indent(
-          options.tabWidth,
-          concat([ beginBreak ? "" : softline ].concat(mostChildren))
-        ),
-        lastChild || "",
-        endBreak ? "" : softline,
-        closingLines
-      ])
-    );
+  const closingLines = path.call(print, "closingElement");
+
+  const multiLineElem = group(concat([
+    openingLines,
+    indent(options.tabWidth, group(
+      concat(childrenGroupedByLine),
+      { shouldBreak: true }
+    )),
+    hardline,
+    closingLines,
+  ]));
+
+  let elem;
+  if (hasAnyHardLine) {
+    elem = multiLineElem;
+  } else {
+    elem = conditionalGroup([
+      group(concat([openingLines, concat(children), closingLines])),
+      multiLineElem,
+    ]);
   }
 
+  return elem;
+}
+
+function maybeWrapJSXElementInParens(path, elem, options) {
   const parent = path.getParentNode();
-  if (
-    !parent || parent.type === "JSXElement" ||
-      parent.type === "ExpressionStatement"
-  ) {
+  if (!parent) return elem;
+
+  const NO_WRAP_PARENTS = {
+    "JSXElement": true,
+    "ExpressionStatement": true,
+    "CallExpression": true,
+    "ConditionalExpression": true,
+  };
+  if (NO_WRAP_PARENTS[parent.type]) {
     return elem;
   }
 


Test patch:
diff --git a/tests/disjoint-union-perf/__snapshots__/jsfmt.spec.js.snap b/tests/disjoint-union-perf/__snapshots__/jsfmt.spec.js.snap
index 70682e9d2..b7e4583e3 100644
--- a/tests/disjoint-union-perf/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/disjoint-union-perf/__snapshots__/jsfmt.spec.js.snap
@@ -84,7 +84,7 @@ export type TypedNode =
  * @flow
  */
 
-export type InferredType = 
+export type InferredType =
   | \"unknown\"
   | \"gender\"
   | \"enum\"
@@ -151,7 +151,7 @@ export type TypedFunctionInvocationNode = {
   typed: true
 };
 
-export type TypedNode = 
+export type TypedNode =
   | TypedBinaryOpNode
   | TypedUnaryMinusNode
   | TypedNumberNode
@@ -969,7 +969,7 @@ export type Program = {
   body: Statement[]
 };
 
-export type BinaryOperator = 
+export type BinaryOperator =
   | \"==\"
   | \"!=\"
   | \"===\"
@@ -993,7 +993,7 @@ export type BinaryOperator =
   | \"instanceof\"
   | \"..\";
 
-export type UnaryOperator = 
+export type UnaryOperator =
   | \"-\"
   | \"+\"
   | \"!\"
@@ -1002,7 +1002,7 @@ export type UnaryOperator =
   | \"void\"
   | \"delete\";
 
-export type AssignmentOperator = 
+export type AssignmentOperator =
   | \"=\"
   | \"+=\"
   | \"-=\"
@@ -1020,7 +1020,7 @@ export type UpdateOperator = \"++\" | \"--\";
 
 export type LogicalOperator = \"&&\" | \"||\";
 
-export type Node = 
+export type Node =
   | EmptyStatement
   | BlockStatement
   | ExpressionStatement
@@ -1054,7 +1054,7 @@ export type Node =
   | FunctionDeclaration
   | VariableDeclarator;
 
-export type Statement = 
+export type Statement =
   | BlockStatement
   | EmptyStatement
   | ExpressionStatement
@@ -1198,7 +1198,7 @@ export type CatchClause = {
   body: BlockStatement
 };
 
-export type Expression = 
+export type Expression =
   | Identifier
   | ThisExpression
   | Literal
diff --git a/tests/dom/__snapshots__/jsfmt.spec.js.snap b/tests/dom/__snapshots__/jsfmt.spec.js.snap
index 7bb8e6158..c0c36f5ac 100644
--- a/tests/dom/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/dom/__snapshots__/jsfmt.spec.js.snap
@@ -692,7 +692,7 @@ let tests = [
   function() {
     const i: NodeIterator<*, *> = document.createNodeIterator(document.body);
     const filter: NodeFilter = i.filter;
-    const response: 
+    const response:
       | typeof NodeFilter.FILTER_ACCEPT
       | typeof NodeFilter.FILTER_REJECT
       | typeof NodeFilter.FILTER_SKIP = filter.acceptNode(document.body);
@@ -700,7 +700,7 @@ let tests = [
   function() {
     const w: TreeWalker<*, *> = document.createTreeWalker(document.body);
     const filter: NodeFilter = w.filter;
-    const response: 
+    const response:
       | typeof NodeFilter.FILTER_ACCEPT
       | typeof NodeFilter.FILTER_REJECT
       | typeof NodeFilter.FILTER_SKIP = filter.acceptNode(document.body);
diff --git a/tests/intersection/__snapshots__/jsfmt.spec.js.snap b/tests/intersection/__snapshots__/jsfmt.spec.js.snap
index 7e3e25e57..efee67571 100644
--- a/tests/intersection/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/intersection/__snapshots__/jsfmt.spec.js.snap
@@ -77,7 +77,7 @@ function hasObjectMode_ok(options: DuplexStreamOptions): boolean {
  * @flow
  */
 
-type DuplexStreamOptions = 
+type DuplexStreamOptions =
   & ReadableStreamOptions
   & WritableStreamOptions
   & {
diff --git a/tests/prettier/__snapshots__/jsfmt.spec.js.snap b/tests/prettier/__snapshots__/jsfmt.spec.js.snap
index 497e84ad6..a7d0701c6 100644
--- a/tests/prettier/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/prettier/__snapshots__/jsfmt.spec.js.snap
@@ -80,13 +80,120 @@ const comp5 = <div>Keep it on one line.</div>;
 `;
 
 exports[`test jsx-significant-space.js 1`] = `
-"<span>
-    foo <span>bar</span>
-</span>
+"after =
+  <span>
+      foo <span>bar</span>
+  </span>
+
+before =
+  <span>
+    <span>bar</span> foo
+  </span>
+
+before_break1 =
+  <span>
+    <span barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar /> foo
+  </span>
+
+before_break2 =
+  <span>
+    <span barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar /> foo
+  </span>
+
+after_break =
+  <span>
+    foo <span barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar />
+  </span>
+
+within =
+  <span>
+    foo <span> bar </span>
+  </span>
+
+break_components =
+  <div>
+    <Foo />
+    <Bar>
+      <p>foo<span>bar bar bar</span></p><h1><span><em>yep</em></span></h1>
+    </Bar>
+    <h2>nope</h2>
+  </div>
+
+var x = <div>
+    hello <strong>hi</strong> <foo>sdkflsdfjk</foo>
+ </div>;
+
+nest_plz =
+  <div>
+    <div>
+    <div></div>
+    </div>
+  </div>
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-<span>
-   foo <span>bar</span>
-</span>;
+after = (
+  <span>
+    foo <span>bar</span>
+  </span>
+);
+
+before = (
+  <span>
+    <span>bar</span> foo
+  </span>
+);
+
+before_break1 = (
+  <span>
+    <span barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar />
+    {\" \"}foo
+  </span>
+);
+
+before_break2 = (
+  <span>
+    <span
+      barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar
+    />
+    {\" \"}foo
+  </span>
+);
+
+after_break = (
+  <span>
+    foo{\" \"}
+    <span barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar />
+  </span>
+);
+
+within = (
+  <span>
+    foo <span> bar </span>
+  </span>
+);
+
+break_components = (
+  <div>
+    <Foo />
+    <Bar>
+      <p>foo<span>bar bar bar</span></p><h1><span><em>yep</em></span></h1>
+    </Bar>
+    <h2>nope</h2>
+  </div>
+);
+
+var x = (
+  <div>
+    hello <strong>hi</strong> <foo>sdkflsdfjk</foo>
+  </div>
+);
+
+nest_plz = (
+  <div>
+    <div>
+      <div />
+    </div>
+  </div>
+);
 "
 `;
 
@@ -99,6 +206,20 @@ long_open =
     hello
   </BaseForm>
 
+long_open_long_children =
+  <BaseForm url=\"/auth/google\" method=\"GET\" colour=\"blue\" size=\"large\" submitLabel=\"Sign in with Google\">
+    <BaseForm url=\"/auth/google\" method=\"GET\" colour=\"blue\" size=\"large\" submitLabel=\"Sign in with Google\">
+      Hello world
+    </BaseForm>
+    <div><div><div><div><div><div>hey hiya how are ya</div></div></div></div></div></div>
+    <div><div><div><div attr=\"long\" attr2=\"also long\" attr3=\"gonna break\"></div></div></div></div>
+    <div><div><div>
+      <div attr=\"long\" attr2=\"also long\" attr3=\"gonna break\" attr4=\"hello please break me\" />
+    </div></div></div>
+    <BaseForm url=\"/auth/google\" method=\"GET\" colour=\"blue\" size=\"large\" submitLabel=\"Sign in with Google\"><BaseForm url=\"/auth/google\" method=\"GET\" colour=\"blue\" size=\"large\" submitLabel=\"Sign in with Google\"></BaseForm>d</BaseForm>
+    <BaseForm url=\"/auth/google\" method=\"GET\" colour=\"blue\" size=\"large\" submitLabel=\"Sign in with Google\"><BaseForm url=\"/auth/google\" method=\"GET\" colour=\"blue\" size=\"large\" submitLabel=\"Sign in with Google\"></BaseForm></BaseForm>
+  </BaseForm>
+
 short_closed =
   <BaseForm url=\"/auth/google\" method=\"GET\"/>
 
@@ -106,6 +227,16 @@ short_open =
   <BaseForm url=\"/auth/google\" method=\"GET\">
     hello
   </BaseForm>
+
+make_self_closing =
+  <div>
+    <BaseForm url=\"/auth/google\" method=\"GET\" colour=\"blue\" size=\"large\" submitLabel=\"Sign in with Google\">
+    </BaseForm>
+    <BaseForm url=\"/auth/google\" method=\"GET\" colour=\"blue\" size=\"large\" submitLabel=\"Sign in with Google\"></BaseForm>
+  </div>
+
+leave_opening =
+  <BaseForm url=\"/auth/google\" method=\"GET\" colour=\"blue\" size=\"large\" submitLabel=\"Sign in with Google\"> </BaseForm>
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 long_closed = (
   <BaseForm
@@ -129,6 +260,77 @@ long_open = (
   </BaseForm>
 );
 
+long_open_long_children = (
+  <BaseForm
+    url=\"/auth/google\"
+    method=\"GET\"
+    colour=\"blue\"
+    size=\"large\"
+    submitLabel=\"Sign in with Google\"
+  >
+    <BaseForm
+      url=\"/auth/google\"
+      method=\"GET\"
+      colour=\"blue\"
+      size=\"large\"
+      submitLabel=\"Sign in with Google\"
+    >
+      Hello world
+    </BaseForm>
+    <div>
+      <div><div><div><div><div>hey hiya how are ya</div></div></div></div></div>
+    </div>
+    <div>
+      <div>
+        <div><div attr=\"long\" attr2=\"also long\" attr3=\"gonna break\" /></div>
+      </div>
+    </div>
+    <div>
+      <div>
+        <div>
+          <div
+            attr=\"long\"
+            attr2=\"also long\"
+            attr3=\"gonna break\"
+            attr4=\"hello please break me\"
+          />
+        </div>
+      </div>
+    </div>
+    <BaseForm
+      url=\"/auth/google\"
+      method=\"GET\"
+      colour=\"blue\"
+      size=\"large\"
+      submitLabel=\"Sign in with Google\"
+    >
+      <BaseForm
+        url=\"/auth/google\"
+        method=\"GET\"
+        colour=\"blue\"
+        size=\"large\"
+        submitLabel=\"Sign in with Google\"
+      />
+      d
+    </BaseForm>
+    <BaseForm
+      url=\"/auth/google\"
+      method=\"GET\"
+      colour=\"blue\"
+      size=\"large\"
+      submitLabel=\"Sign in with Google\"
+    >
+      <BaseForm
+        url=\"/auth/google\"
+        method=\"GET\"
+        colour=\"blue\"
+        size=\"large\"
+        submitLabel=\"Sign in with Google\"
+      />
+    </BaseForm>
+  </BaseForm>
+);
+
 short_closed = <BaseForm url=\"/auth/google\" method=\"GET\" />;
 
 short_open = (
@@ -136,6 +338,37 @@ short_open = (
     hello
   </BaseForm>
 );
+
+make_self_closing = (
+  <div>
+    <BaseForm
+      url=\"/auth/google\"
+      method=\"GET\"
+      colour=\"blue\"
+      size=\"large\"
+      submitLabel=\"Sign in with Google\"
+    />
+    <BaseForm
+      url=\"/auth/google\"
+      method=\"GET\"
+      colour=\"blue\"
+      size=\"large\"
+      submitLabel=\"Sign in with Google\"
+    />
+  </div>
+);
+
+leave_opening = (
+  <BaseForm
+    url=\"/auth/google\"
+    method=\"GET\"
+    colour=\"blue\"
+    size=\"large\"
+    submitLabel=\"Sign in with Google\"
+  >
+    {\" \"}
+  </BaseForm>
+);
 "
 `;
 
@@ -171,11 +404,48 @@ const render7 = () =>
     <div /> <div />
   </div>
 
+const render7A = () => (
+  <div>
+    <div /><div /><div />
+  </div>
+)
+
+const render7B = () => (
+  <div>
+    <span> <span/> Dont break plz</span>
+    <span><span/>Dont break plz</span>
+    <span>Dont break plz<span/></span>
+  </div>
+)
+
 const render8 = (props) => <div>{props.text}</div>
 const render9 = (props) => <div>{props.looooooooooooooooooooooooooooooong_text}</div>
 const render10 = (props) => <div>{props.even_looooooooooooooooooooooooooooooooooooooooooonger_contents}</div>
 
 const notJSX = (aaaaaaaaaaaaaaaaa, bbbbbbbbbbb) => this.someLongCallWithParams(aaaaaa, bbbbbbb).anotherLongCallWithParams(cccccccccccc, dddddddddddddddddddddd)
+
+React.render(
+  <BaseForm url=\"/auth/google\" method=\"GET\" colour=\"blue\" size=\"large\" submitLabel=\"Sign in with Google\" />
+  , document.querySelector(\'#react-root\')
+)
+
+
+const renderTernary = (props) =>
+  <BaseForm url=\"/auth/google\" method=\"GET\" colour=\"blue\" size=\"large\" submitLabel=\"Sign in with Google\">
+    {props.showTheThing ?
+      <BaseForm url=\"/auth/google\" method=\"GET\" colour=\"blue\" size=\"large\" submitLabel=\"Sign in with Google\">Hello world</BaseForm>
+      : \"hello \" + \"howdy! \"}
+    {props.showTheThing ?
+      <BaseForm url=\"/auth/google\" method=\"GET\" colour=\"blue\" size=\"large\" submitLabel=\"Sign in with Google\">Hello world</BaseForm>
+      :
+      null
+    }
+    {props.showTheThing ? null :
+      <BaseForm url=\"/auth/google\" method=\"GET\" colour=\"blue\" size=\"large\" submitLabel=\"Sign in with Google\">Hello world</BaseForm>
+    }
+    {props.showTheOtherThing ? <div>I am here</div> : <div attr=\"blah\" />}
+    {props.showTheOtherThing ? <div>I am here!!</div> : null}
+  </BaseForm>
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 const render1 = ({ styles }) => (
   <div style={styles} key=\"something\">
@@ -232,15 +502,31 @@ const render6 = ({ styles }) => (
         attr4
       >
         ddd d dd d d dddd dddd <strong>hello</strong>
-      </div><strong>hello</strong>
+      </div>
+      {\" \"}
+      <strong>hello</strong>
     </div>
   </div>
 );
 
 const render7 = () => (
   <div>
-    <span /><span>Dont break each elem onto its own line.</span><span />
-    <div /><div />
+    <span /><span>Dont break each elem onto its own line.</span> <span />
+    <div /> <div />
+  </div>
+);
+
+const render7A = () => (
+  <div>
+    <div /><div /><div />
+  </div>
+);
+
+const render7B = () => (
+  <div>
+    <span> <span /> Dont break plz</span>
+    <span><span />Dont break plz</span>
+    <span>Dont break plz<span /></span>
   </div>
 );
 
@@ -258,6 +544,69 @@ const notJSX = (aaaaaaaaaaaaaaaaa, bbbbbbbbbbb) =>
   this
     .someLongCallWithParams(aaaaaa, bbbbbbb)
     .anotherLongCallWithParams(cccccccccccc, dddddddddddddddddddddd);
+
+React.render(
+  <BaseForm
+    url=\"/auth/google\"
+    method=\"GET\"
+    colour=\"blue\"
+    size=\"large\"
+    submitLabel=\"Sign in with Google\"
+  />,
+  document.querySelector(\"#react-root\")
+);
+
+const renderTernary = props => (
+  <BaseForm
+    url=\"/auth/google\"
+    method=\"GET\"
+    colour=\"blue\"
+    size=\"large\"
+    submitLabel=\"Sign in with Google\"
+  >
+    {
+      props.showTheThing
+        ? <BaseForm
+          url=\"/auth/google\"
+          method=\"GET\"
+          colour=\"blue\"
+          size=\"large\"
+          submitLabel=\"Sign in with Google\"
+        >
+          Hello world
+        </BaseForm>
+        : \"hello \" + \"howdy! \"
+    }
+    {
+      props.showTheThing
+        ? <BaseForm
+          url=\"/auth/google\"
+          method=\"GET\"
+          colour=\"blue\"
+          size=\"large\"
+          submitLabel=\"Sign in with Google\"
+        >
+          Hello world
+        </BaseForm>
+        : null
+    }
+    {
+      props.showTheThing
+        ? null
+        : <BaseForm
+          url=\"/auth/google\"
+          method=\"GET\"
+          colour=\"blue\"
+          size=\"large\"
+          submitLabel=\"Sign in with Google\"
+        >
+          Hello world
+        </BaseForm>
+    }
+    {props.showTheOtherThing ? <div>I am here</div> : <div attr=\"blah\" />}
+    {props.showTheOtherThing ? <div>I am here!!</div> : null}
+  </BaseForm>
+);
 "
 `;
 
diff --git a/tests/prettier/jsx-significant-space.js b/tests/prettier/jsx-significant-space.js
index 7b80d6ce7..c39264395 100644
--- a/tests/prettier/jsx-significant-space.js
+++ b/tests/prettier/jsx-significant-space.js
@@ -1,3 +1,49 @@
-<span>
-    foo <span>bar</span>
-</span>
+after =
+  <span>
+      foo <span>bar</span>
+  </span>
+
+before =
+  <span>
+    <span>bar</span> foo
+  </span>
+
+before_break1 =
+  <span>
+    <span barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar /> foo
+  </span>
+
+before_break2 =
+  <span>
+    <span barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar /> foo
+  </span>
+
+after_break =
+  <span>
+    foo <span barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar />
+  </span>
+
+within =
+  <span>
+    foo <span> bar </span>
+  </span>
+
+break_components =
+  <div>
+    <Foo />
+    <Bar>
+      <p>foo<span>bar bar bar</span></p><h1><span><em>yep</em></span></h1>
+    </Bar>
+    <h2>nope</h2>
+  </div>
+
+var x = <div>
+    hello <strong>hi</strong> <foo>sdkflsdfjk</foo>
+ </div>;
+
+nest_plz =
+  <div>
+    <div>
+    <div></div>
+    </div>
+  </div>
diff --git a/tests/prettier/jsx-split-attrs.js b/tests/prettier/jsx-split-attrs.js
index 8d725ccf5..489720cef 100644
--- a/tests/prettier/jsx-split-attrs.js
+++ b/tests/prettier/jsx-split-attrs.js
@@ -6,6 +6,20 @@ long_open =
     hello
   </BaseForm>
 
+long_open_long_children =
+  <BaseForm url="/auth/google" method="GET" colour="blue" size="large" submitLabel="Sign in with Google">
+    <BaseForm url="/auth/google" method="GET" colour="blue" size="large" submitLabel="Sign in with Google">
+      Hello world
+    </BaseForm>
+    <div><div><div><div><div><div>hey hiya how are ya</div></div></div></div></div></div>
+    <div><div><div><div attr="long" attr2="also long" attr3="gonna break"></div></div></div></div>
+    <div><div><div>
+      <div attr="long" attr2="also long" attr3="gonna break" attr4="hello please break me" />
+    </div></div></div>
+    <BaseForm url="/auth/google" method="GET" colour="blue" size="large" submitLabel="Sign in with Google"><BaseForm url="/auth/google" method="GET" colour="blue" size="large" submitLabel="Sign in with Google"></BaseForm>d</BaseForm>
+    <BaseForm url="/auth/google" method="GET" colour="blue" size="large" submitLabel="Sign in with Google"><BaseForm url="/auth/google" method="GET" colour="blue" size="large" submitLabel="Sign in with Google"></BaseForm></BaseForm>
+  </BaseForm>
+
 short_closed =
   <BaseForm url="/auth/google" method="GET"/>
 
@@ -13,3 +27,13 @@ short_open =
   <BaseForm url="/auth/google" method="GET">
     hello
   </BaseForm>
+
+make_self_closing =
+  <div>
+    <BaseForm url="/auth/google" method="GET" colour="blue" size="large" submitLabel="Sign in with Google">
+    </BaseForm>
+    <BaseForm url="/auth/google" method="GET" colour="blue" size="large" submitLabel="Sign in with Google"></BaseForm>
+  </div>
+
+leave_opening =
+  <BaseForm url="/auth/google" method="GET" colour="blue" size="large" submitLabel="Sign in with Google"> </BaseForm>
diff --git a/tests/prettier/jsx-stateless-arrow-fn.js b/tests/prettier/jsx-stateless-arrow-fn.js
index d137f191c..20bde80bd 100644
--- a/tests/prettier/jsx-stateless-arrow-fn.js
+++ b/tests/prettier/jsx-stateless-arrow-fn.js
@@ -29,8 +29,45 @@ const render7 = () =>
     <div /> <div />
   </div>
 
+const render7A = () => (
+  <div>
+    <div /><div /><div />
+  </div>
+)
+
+const render7B = () => (
+  <div>
+    <span> <span/> Dont break plz</span>
+    <span><span/>Dont break plz</span>
+    <span>Dont break plz<span/></span>
+  </div>
+)
+
 const render8 = (props) => <div>{props.text}</div>
 const render9 = (props) => <div>{props.looooooooooooooooooooooooooooooong_text}</div>
 const render10 = (props) => <div>{props.even_looooooooooooooooooooooooooooooooooooooooooonger_contents}</div>
 
 const notJSX = (aaaaaaaaaaaaaaaaa, bbbbbbbbbbb) => this.someLongCallWithParams(aaaaaa, bbbbbbb).anotherLongCallWithParams(cccccccccccc, dddddddddddddddddddddd)
+
+React.render(
+  <BaseForm url="/auth/google" method="GET" colour="blue" size="large" submitLabel="Sign in with Google" />
+  , document.querySelector('#react-root')
+)
+
+
+const renderTernary = (props) =>
+  <BaseForm url="/auth/google" method="GET" colour="blue" size="large" submitLabel="Sign in with Google">
+    {props.showTheThing ?
+      <BaseForm url="/auth/google" method="GET" colour="blue" size="large" submitLabel="Sign in with Google">Hello world</BaseForm>
+      : "hello " + "howdy! "}
+    {props.showTheThing ?
+      <BaseForm url="/auth/google" method="GET" colour="blue" size="large" submitLabel="Sign in with Google">Hello world</BaseForm>
+      :
+      null
+    }
+    {props.showTheThing ? null :
+      <BaseForm url="/auth/google" method="GET" colour="blue" size="large" submitLabel="Sign in with Google">Hello world</BaseForm>
+    }
+    {props.showTheOtherThing ? <div>I am here</div> : <div attr="blah" />}
+    {props.showTheOtherThing ? <div>I am here!!</div> : null}
+  </BaseForm>
diff --git a/tests/trailing_whitespace/__snapshots__/jsfmt.spec.js.snap b/tests/trailing_whitespace/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000..967746b7b
--- /dev/null
+++ b/tests/trailing_whitespace/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,26 @@
+exports[`test trailing.js 1`] = `
+"export type Result<T, V> = | { kind: \"not-test-editor1\" } | { kind: \"not-test-editor2\" };
+
+// Note: there are trailing whitespace in this file
+\`
+   
+   
+\` + \`
+    
+    
+\`;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+export type Result<T, V> =
+  | { kind: \"not-test-editor1\" }
+  | { kind: \"not-test-editor2\" };
+
+// Note: there are trailing whitespace in this file
+\`
+   
+   
+\` + \`
+    
+    
+\`;
+"
+`;
diff --git a/tests/trailing_whitespace/jsfmt.spec.js b/tests/trailing_whitespace/jsfmt.spec.js
new file mode 100644
index 000000000..989047bcc
--- /dev/null
+++ b/tests/trailing_whitespace/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname);
diff --git a/tests/trailing_whitespace/trailing.js b/tests/trailing_whitespace/trailing.js
new file mode 100644
index 000000000..2d23f824b
--- /dev/null
+++ b/tests/trailing_whitespace/trailing.js
@@ -0,0 +1,10 @@
+export type Result<T, V> = | { kind: "not-test-editor1" } | { kind: "not-test-editor2" };
+
+// Note: there are trailing whitespace in this file
+`
+   
+   
+` + `
+    
+    
+`;
diff --git a/tests/type-printer/__snapshots__/jsfmt.spec.js.snap b/tests/type-printer/__snapshots__/jsfmt.spec.js.snap
index c5871d48f..bbc448a99 100644
--- a/tests/type-printer/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/type-printer/__snapshots__/jsfmt.spec.js.snap
@@ -5096,7 +5096,7 @@ export type JSXSpreadAttribute = {
  * Flow types for the Babylon AST.
  */
 // Abstract types. Something must extend these.
-export type Comment = 
+export type Comment =
   | {
     type: \"CommentLine\",
     _CommentLine: void,
@@ -5120,7 +5120,7 @@ export type Comment =
     start: number
   };
 
-export type Declaration = 
+export type Declaration =
   | {
     type: \"ClassBody\",
     _ClassBody: void,
@@ -5231,7 +5231,7 @@ export type Declaration =
     trailingComments: ?Array<Comment>
   };
 
-export type Expression = 
+export type Expression =
   | {
     type: \"ArrayExpression\",
     _ArrayExpression: void,
@@ -5682,7 +5682,7 @@ export type Expression =
     trailingComments: ?Array<Comment>
   };
 
-export type Function = 
+export type Function =
   | {
     type: \"ArrowFunctionExpression\",
     _ArrowFunctionExpression: void,
@@ -5753,7 +5753,7 @@ export type Function =
     typeParameters: ?TypeParameterDeclaration
   };
 
-export type Node = 
+export type Node =
   | {
     type: \"ArrayExpression\",
     _ArrayExpression: void,
@@ -7605,7 +7605,7 @@ export type Node =
     trailingComments: ?Array<Comment>
   };
 
-export type Pattern = 
+export type Pattern =
   | {
     type: \"ArrayPattern\",
     _ArrayPattern: void,
@@ -7682,7 +7682,7 @@ export type Pattern =
     trailingComments: ?Array<Comment>
   };
 
-export type Statement = 
+export type Statement =
   | {
     type: \"BlockStatement\",
     _BlockStatement: void,
@@ -8033,7 +8033,7 @@ export type Statement =
     trailingComments: ?Array<Comment>
   };
 
-export type Type = 
+export type Type =
   | {
     type: \"AnyTypeAnnotation\",
     _AnyTypeAnnotation: void,
@@ -8357,7 +8357,7 @@ export type ArrowFunctionExpression = {
   typeParameters: ?TypeParameterDeclaration
 };
 
-type AssignmentOperator = 
+type AssignmentOperator =
   | \"=\"
   | \"+=\"
   | \"-=\"
@@ -8420,7 +8420,7 @@ export type AwaitExpression = {
   trailingComments: ?Array<Comment>
 };
 
-type BinaryOperator = 
+type BinaryOperator =
   | \"==\"
   | \"!=\"
   | \"===\"
diff --git a/tests/typeapp_perf/__snapshots__/jsfmt.spec.js.snap b/tests/typeapp_perf/__snapshots__/jsfmt.spec.js.snap
index 061fdb9a2..1feb00422 100644
--- a/tests/typeapp_perf/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/typeapp_perf/__snapshots__/jsfmt.spec.js.snap
@@ -25,7 +25,7 @@ declare var b: B<any>;
 
 class A {}
 
-type B<T> = 
+type B<T> =
   & A
   & {
     +a: () => B<T>,
@@ -72,7 +72,7 @@ declare var b: B<any>;
 
 class A {}
 
-type B<T> = 
+type B<T> =
   & A
   & {
     +a: (x: B<T>) => void,
diff --git a/tests/union-intersection/__snapshots__/jsfmt.spec.js.snap b/tests/union-intersection/__snapshots__/jsfmt.spec.js.snap
index 700487d9b..e79fb2538 100644
--- a/tests/union-intersection/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/union-intersection/__snapshots__/jsfmt.spec.js.snap
@@ -4004,7 +4004,7 @@ type TAction =
 function foo(x: TAction): TAction { return x; }
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // perf test for big disjoint union with 1000 cases
-type TAction = 
+type TAction =
   | { type: \"a1\", a1: number }
   | { type: \"a2\", a1: number }
   | { type: \"a3\", a1: number }
@@ -5087,7 +5087,7 @@ function f4(x: T4): T4 {
   return x;
 }
 
-type T5 = 
+type T5 =
   | \"1\"
   | \"2\"
   | \"3\"
@@ -5102,13 +5102,13 @@ type T5 =
   | \"12\"
   | \"13\";
 
-type T6 = 
+type T6 =
   | \"a-long-string\"
   | \"another-long-string\"
   | \"yet-another-long-string\"
   | \"one-more-for-good-measure\";
 
-type T7 = 
+type T7 =
   | { eventName: \"these\", a: number }
   | { eventName: \"will\", b: number }
   | { eventName: \"not\", c: number }
@@ -5117,7 +5117,7 @@ type T7 =
   | { eventName: \"one\", f: number }
   | { eventName: \"line\", g: number };
 
-type Comment = 
+type Comment =
   | {
     type: \"CommentLine\",
     _CommentLine: void,
diff --git a/tests/union/__snapshots__/jsfmt.spec.js.snap b/tests/union/__snapshots__/jsfmt.spec.js.snap
index 17476359f..ec346e8cf 100644
--- a/tests/union/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/union/__snapshots__/jsfmt.spec.js.snap
@@ -5517,7 +5517,7 @@ class SecurityCheckupTypedLogger {
   }
 }
 
-type ErrorCode = 
+type ErrorCode =
   | 0
   | 1
   | 2
diff --git a/tests/union_new/__snapshots__/jsfmt.spec.js.snap b/tests/union_new/__snapshots__/jsfmt.spec.js.snap
index 88cd6def4..4be583197 100644
--- a/tests/union_new/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/union_new/__snapshots__/jsfmt.spec.js.snap
@@ -1329,7 +1329,7 @@ exports[`test test13.js 1`] = `
 
 /* ensure there are no unintended side effects when trying branches */
 
-({ type: \"B\", id: \"hi\" }: 
+({ type: \"B\", id: \"hi\" }:
   | { type: \"A\", id: ?string }
   | { type: \"B\", id: string });
 "