Repository: jlongster/prettier
Pull request #707 (2017-02-14 22:49:32)

[RFC] Preserve empty lines between array elements
This has come up many times in the past and while going through the fb codebase, there are a few instances where we group array elements logically using empty lines and it's a shame that they are gone.

garth: in cerebraljs (http://www.cerebraljs.com/) arrays and object literals are used to compose a tree of actions, which read like high level recipes of what happens in the app.

Being able to place the each item on it's own line is key to readability.

I'm sure that there are many use cases where an option to "preserve-new-lines" (even on a per file bases) would help to maintain readability.
vjeux: @garth could you open an issue with real cerebral code where it outputs things that you are not happy with? This way we have concrete examples to look at in order to find a way to print it the best way :)
jlongster: Did you mean to use `hardline` instead of `softline`, where it would maintain existing line breaks *if* it stayed expanded? Or did you intentionally want to keep arrays expanded?

I feared this was going to come up as arrays are inconsistent with objects in this regard. I personally wish we wouldn't do this, and I feel the same about objects.

But it's only my personal preference and I don't have a strong technical reason for it. It makes it so that if you add something, and it makes it expand, and then you remove it, it doesn't collapse again. I've definitely run into this already with objects where I had to manually collapse them.

I'm fine adapting to the community thought and doing what's needed. It's not a huge deal.
vjeux: Oops, I just wanted to preserve new lines in between, not force it to be expanded if there was a single newline. I copy and pasted the object code.
vjeux: Just replaced `hardline` with `softline` and added a test to make sure that if the array was not expanded, those empty lines vanish.

Patch:
diff --git a/README.md b/README.md
index d408e9786..c4ab30824 100644
--- a/README.md
+++ b/README.md
@@ -124,6 +124,34 @@ prettier --write 'src/**/*.js' 'bin/*.js'
 
 In the future we will have better support for formatting whole projects.
 
+#### Pre-commit hook for changed files
+
+[🚫💩 lint-staged](https://github.com/okonet/lint-staged) can re-format your files that are marked as "staged" via `git add`  before you commit.
+
+Install it along with [pre-commit](https://github.com/observing/pre-commit) (or [husky](https://github.com/typicode/husky)):
+
+```bash
+npm install --save-dev lint-staged pre-commit
+```
+
+and add this config to your `package.json`:
+
+```json
+{
+  "scripts": {
+    "lint:staged": "lint-staged"
+  },
+  "lint-staged": {
+    "*.js": [
+      "prettier --write",
+      "git add"
+    ]
+  },
+  "pre-commit": "lint:staged"
+}
+```
+
+
 ### API
 
 The API is a single function exported as `format`. The options
@@ -200,9 +228,13 @@ Can also be installed using `ext install prettier-vscode`
 
 [Check repository for configuration and shortcuts](https://github.com/esbenp/prettier-vscode)
 
+### Visual Studio
+
+Install the [JavaScript Prettier extension](https://github.com/madskristensen/JavaScriptPrettier)
+
 ### Sublime Text
 
-Sublime Text support is available through Package Control and 
+Sublime Text support is available through Package Control and
 the [JsPrettier](https://packagecontrol.io/packages/JsPrettier) plug-in.
 
 ### JetBrains
diff --git a/src/printer.js b/src/printer.js
index 0acad6736..ce45e1818 100644
--- a/src/printer.js
+++ b/src/printer.js
@@ -376,6 +376,10 @@ function genericPrintNoParens(path, options, print) {
       return join(" ", parts);
     case "ImportSpecifier":
       if (n.imported) {
+        if (n.importKind) {
+          parts.push(path.call(print, "importKind"), " ");
+        }
+
         parts.push(path.call(print, "imported"));
 
         if (n.local && n.local.name !== n.imported.name) {
@@ -751,16 +755,29 @@ function genericPrintNoParens(path, options, print) {
         const needsForcedTrailingComma = canHaveTrailingComma &&
           lastElem === null;
 
+        var printedElements = [];
+        let separatorParts = [];
+        path.each(
+          function(childPath) {
+            printedElements.push(concat(separatorParts));
+            printedElements.push(group(print(childPath)));
+
+            separatorParts = [",", line];
+            if (childPath.getValue() &&
+                util.isNextLineEmpty(options.originalText, childPath.getValue())) {
+              separatorParts.push(softline);
+            }
+          },
+          "elements"
+        );
+
         parts.push(
           group(
             concat([
               "[",
               indent(
                 options.tabWidth,
-                concat([
-                  softline,
-                  join(concat([",", line]), path.map(print, "elements"))
-                ])
+                concat([softline, concat(printedElements)])
               ),
               needsForcedTrailingComma ? "," : "",
               ifBreak(


Test patch:
diff --git a/tests/arrays/__snapshots__/jsfmt.spec.js.snap b/tests/arrays/__snapshots__/jsfmt.spec.js.snap
index 911335320..2e38900ec 100644
--- a/tests/arrays/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/arrays/__snapshots__/jsfmt.spec.js.snap
@@ -10,3 +10,21 @@ exports[`test last.js 1`] = `
 [, , 1, 1];
 "
 `;
+
+exports[`test preserve_empty_lines.js 1`] = `
+"a = [
+
+  1,
+  2,
+
+  3,
+
+
+  4,
+
+
+]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+a = [1, 2, 3, 4];
+"
+`;
diff --git a/tests/arrays/preserve_empty_lines.js b/tests/arrays/preserve_empty_lines.js
new file mode 100644
index 000000000..0cd853eef
--- /dev/null
+++ b/tests/arrays/preserve_empty_lines.js
@@ -0,0 +1,12 @@
+a = [
+
+  1,
+  2,
+
+  3,
+
+
+  4,
+
+
+]
diff --git a/tests/flow/arith/__snapshots__/jsfmt.spec.js.snap b/tests/flow/arith/__snapshots__/jsfmt.spec.js.snap
index f80e6347d..6f4d1a212 100644
--- a/tests/flow/arith/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/arith/__snapshots__/jsfmt.spec.js.snap
@@ -162,6 +162,7 @@ let tests = [
     x + {}; // error
     ({}) + x; // error
   },
+
   // when one side is a string or number and the other is invalid, we
   // assume you are expecting a string or number (respectively), rather than
   // erroring twice saying number !~> string and obj !~> string.
@@ -171,6 +172,7 @@ let tests = [
     (\"1\" + {}: string); // error: object !~> string
     ({} + \"1\": string); // error: object !~> string
   },
+
   function(x: any, y: number, z: string) {
     (x + y: string); // ok
     (y + x: string); // ok
diff --git a/tests/flow/binary/__snapshots__/jsfmt.spec.js.snap b/tests/flow/binary/__snapshots__/jsfmt.spec.js.snap
index 597e4753f..d04fa0799 100644
--- a/tests/flow/binary/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/binary/__snapshots__/jsfmt.spec.js.snap
@@ -64,17 +64,20 @@ let tests = [
     0 in {};
     0 in { \"0\": null };
   },
+
   // arrays on RHS
   function() {
     \"foo\" in [];
     0 in [];
     \"length\" in [];
   },
+
   // primitive classes on RHS
   function() {
     \"foo\" in new String(\"bar\");
     \"foo\" in new Number(123);
   },
+
   // primitives on RHS
   function() {
     \"foo\" in 123; // error
@@ -82,6 +85,7 @@ let tests = [
     \"foo\" in void 0; // error
     \"foo\" in null; // error
   },
+
   // bogus stuff on LHS
   function() {
     null in {}; // error
@@ -90,6 +94,7 @@ let tests = [
     [] in {}; // error
     false in []; // error
   },
+
   // in predicates
   function() {
     if (\"foo\" in 123) {
@@ -99,6 +104,7 @@ let tests = [
     if (!(\"foo\" in {})) {
     }
   },
+
   // annotations on RHS
   function(x: Object, y: mixed) {
     \"foo\" in x; // ok
diff --git a/tests/flow/core_tests/__snapshots__/jsfmt.spec.js.snap b/tests/flow/core_tests/__snapshots__/jsfmt.spec.js.snap
index 0ac76b71c..cb73472ad 100644
--- a/tests/flow/core_tests/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/core_tests/__snapshots__/jsfmt.spec.js.snap
@@ -58,6 +58,7 @@ let tests = [
     new Boolean(undefined);
     new Boolean(\"\");
   },
+
   // toString
   function() {
     true.toString();
@@ -65,10 +66,12 @@ let tests = [
     x.toString();
     new Boolean(true).toString();
   },
+
   // valueOf
   function() {
     (new Boolean(0).valueOf(): boolean);
   },
+
   // casting
   function() {
     Boolean();
@@ -137,11 +140,13 @@ let tests = [
     let b: Map<string, number> = new Map([[\"foo\", 123]]);
     let c: Map<string, number> = new Map(generator());
   },
+
   // bad constructors
   function() {
     let x = new Map([\"foo\", 123]); // error
     let y: Map<number, string> = new Map([[\"foo\", 123]]); // error
   },
+
   // get()
   function(x: Map<string, number>) {
     (x.get(\"foo\"): boolean); // error, string | void
@@ -194,6 +199,7 @@ let tests = [
     new RegExp(/foo/, \"i\"); // invalid in ES5, valid in ES6
     new RegExp(/foo/g, \"i\"); // invalid in ES5, valid in ES6
   },
+
   // called as a function (equivalent to the constructor per ES6 21.2.3)
   function() {
     RegExp(\"foo\");
@@ -203,6 +209,7 @@ let tests = [
     RegExp(/foo/, \"i\"); // invalid in ES5, valid in ES6
     RegExp(/foo/g, \"i\"); // invalid in ES5, valid in ES6
   },
+
   // invalid flags
   function() {
     RegExp(\"foo\", \"z\"); // error
diff --git a/tests/flow/dom/__snapshots__/jsfmt.spec.js.snap b/tests/flow/dom/__snapshots__/jsfmt.spec.js.snap
index ffd01a6b5..2ed4d1546 100644
--- a/tests/flow/dom/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/dom/__snapshots__/jsfmt.spec.js.snap
@@ -20,6 +20,7 @@ let tests = [
   function(ctx: CanvasRenderingContext2D) {
     ctx.fillRect(0, 0, 200, 100);
   },
+
   // moveTo
   function(ctx: CanvasRenderingContext2D) {
     ctx.moveTo(\"0\", \"1\"); // error: should be numbers
@@ -293,12 +294,14 @@ let tests = [
     (target.attachEvent(\"foo\", listener): void); // invalid, may be undefined
     (target.attachEvent && target.attachEvent(\"foo\", listener): void); // valid
   },
+
   // detachEvent
   function() {
     let target = new EventTarget();
     (target.detachEvent(\"foo\", listener): void); // invalid, may be undefined
     (target.detachEvent && target.detachEvent(\"foo\", listener): void); // valid
   },
+
   function() {
     window.onmessage = (event: MessageEvent) => {
       (event.target: window);
diff --git a/tests/flow/facebookisms/__snapshots__/jsfmt.spec.js.snap b/tests/flow/facebookisms/__snapshots__/jsfmt.spec.js.snap
index 0481d54ca..0f4a557da 100644
--- a/tests/flow/facebookisms/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/facebookisms/__snapshots__/jsfmt.spec.js.snap
@@ -55,6 +55,7 @@ let tests = [
   function() {
     copyProperties(); // error, unknown global
   },
+
   // annotation
   function(copyProperties: Object$Assign) {
     let result = {};
@@ -65,6 +66,7 @@ let tests = [
       baz: boolean
     });
   },
+
   // module from lib
   function() {
     const copyProperties = require(\"copyProperties\");
@@ -72,11 +74,13 @@ let tests = [
     let y = { bar: 123 };
     (copyProperties({}, x, y): { foo: string, bar: number });
   },
+
   // too few args
   function(copyProperties: Object$Assign) {
     copyProperties();
     (copyProperties({ foo: \"a\" }): { foo: number }); // err, num !~> string
   },
+
   // passed as a function
   function(copyProperties: Object$Assign) {
     function x(cb: Function) {}
@@ -109,6 +113,7 @@ let tests = [
     let x: ?string = null;
     invariant(x, \"truthy only\"); // error, forgot to require invariant
   },
+
   function(invariant: Function) {
     let x: ?string = null;
     invariant(x);
@@ -186,6 +191,7 @@ let tests = [
   function() {
     mergeInto(); // error, unknown global
   },
+
   // annotation
   function(mergeInto: $Facebookism$MergeInto) {
     let result = {};
@@ -193,16 +199,19 @@ let tests = [
     (mergeInto(result, { foo: \"a\" }, { bar: 123 }): void);
     (result: { foo: string, bar: number, baz: boolean });
   },
+
   // module from lib
   function() {
     const mergeInto = require(\"mergeInto\");
     let result: { foo?: string, bar?: number, baz: boolean } = { baz: false };
     (mergeInto(result, { foo: \"a\" }, { bar: 123 }): void);
   },
+
   // too few args
   function(mergeInto: $Facebookism$MergeInto) {
     mergeInto();
   },
+
   // passed as a function
   function(mergeInto: $Facebookism$MergeInto) {
     function x(cb: Function) {}
diff --git a/tests/flow/function/__snapshots__/jsfmt.spec.js.snap b/tests/flow/function/__snapshots__/jsfmt.spec.js.snap
index 02ff22094..39a2e88aa 100644
--- a/tests/flow/function/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/function/__snapshots__/jsfmt.spec.js.snap
@@ -307,6 +307,7 @@ let tests = [
     (y.name: void); // error, it\'s a string
     (z.name: void); // error, it\'s a string
   },
+
   function(y: () => void, z: Function) {
     function x() {}
     x.length = \"foo\"; // error, it\'s a number
diff --git a/tests/flow/import_type/__snapshots__/jsfmt.spec.js.snap b/tests/flow/import_type/__snapshots__/jsfmt.spec.js.snap
index 0beb91ee7..059cd220d 100644
--- a/tests/flow/import_type/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/import_type/__snapshots__/jsfmt.spec.js.snap
@@ -345,6 +345,21 @@ function foo() {
 "
 `;
 
+exports[`test import_type_specifier.js 1`] = `
+"/**
+ * @flow
+ */
+
+import { Foo, type Baz } from \"../module\";
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+/**
+ * @flow
+ */
+
+import { Foo, type Baz } from \"../module\";
+"
+`;
+
 exports[`test issue-359.js 1`] = `
 "/* @flow */
 
diff --git a/tests/flow/import_type/import_type_specifier.js b/tests/flow/import_type/import_type_specifier.js
new file mode 100644
index 000000000..4e5d1bfae
--- /dev/null
+++ b/tests/flow/import_type/import_type_specifier.js
@@ -0,0 +1,5 @@
+/**
+ * @flow
+ */
+
+import { Foo, type Baz } from "../module";
diff --git a/tests/flow/node_tests/crypto/__snapshots__/jsfmt.spec.js.snap b/tests/flow/node_tests/crypto/__snapshots__/jsfmt.spec.js.snap
index 715431380..5b8f998db 100644
--- a/tests/flow/node_tests/crypto/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/node_tests/crypto/__snapshots__/jsfmt.spec.js.snap
@@ -56,6 +56,7 @@ let tests = [
     hmac.write(123); // 2 errors: not a string or a Buffer
     hmac.end();
   },
+
   // Hmac supports update and digest functions too
   function(buf: Buffer) {
     const hmac = crypto.createHmac(\"sha256\", \"a secret\");
diff --git a/tests/flow/refi/__snapshots__/jsfmt.spec.js.snap b/tests/flow/refi/__snapshots__/jsfmt.spec.js.snap
index a2845a6a1..bab6e1cc3 100644
--- a/tests/flow/refi/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/refi/__snapshots__/jsfmt.spec.js.snap
@@ -73,27 +73,32 @@ var tests = [
   function() {
     var y: string = x; // not ok
   },
+
   function() {
     if (x != null) {
       var y: string = x; // ok
     }
   },
+
   function() {
     if (x == null) {
     } else {
       var y: string = x; // ok
     }
   },
+
   function() {
     if (x == null) return;
     var y: string = x; // ok
   },
+
   function() {
     if (!(x != null)) {
     } else {
       var y: string = x; // ok
     }
   },
+
   /* TODO we actually allow this currently; fix
   // requires further remedial work in Env
   function() {
@@ -108,15 +113,18 @@ var tests = [
     }
     var y: string = x; // not ok
   },
+
   function() {
     if (x != null) {
     } else {
       var y: string = x; // not ok
     }
   },
+
   function() {
     var y: string = x != null ? x : \"\"; // ok
   },
+
   function() {
     var y: string = x || \"\"; // ok
   }
@@ -368,12 +376,14 @@ var tests = [
     var x: { p: ?string } = { p: \"xxx\" };
     var y: string = x.p; // not ok
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p != null) {
       var y: string = x.p; // ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p == null) {
@@ -381,11 +391,13 @@ var tests = [
       var y: string = x.p; // ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p == null) return;
     var y: string = x.p; // ok
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (!(x.p != null)) {
@@ -393,6 +405,7 @@ var tests = [
       var y: string = x.p; // ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p != null) {
@@ -400,6 +413,7 @@ var tests = [
       var y: string = x.p; // not ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p != null) {
@@ -407,12 +421,14 @@ var tests = [
       var y: string = x.p; // not ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p != null) {
     }
     var y: string = x.p; // not ok
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p != null) {
@@ -420,14 +436,17 @@ var tests = [
       var y: string = x.p; // not ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     var y: string = x.p != null ? x.p : \"\"; // ok
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     var y: string = x.p || \"\"; // ok
   },
+
   function() {
     var x: { p: string | string[] } = { p: [\"xxx\"] };
     if (Array.isArray(x.p)) {
@@ -436,6 +455,7 @@ var tests = [
       var z: string = x.p; // ok
     }
   },
+
   function() {
     var x: { y: ?string } = { y: null };
     if (!x.y) {
@@ -443,6 +463,7 @@ var tests = [
     }
     (x.y: string);
   },
+
   function() {
     var x: { y: ?string } = { y: null };
     if (x.y) {
@@ -451,6 +472,7 @@ var tests = [
     }
     (x.y: string);
   },
+
   function() {
     var x: { y: ?string } = { y: null };
     if (!x.y) {
@@ -458,6 +480,7 @@ var tests = [
     }
     (x.y: string); // error, this got widened to a number
   },
+
   function() {
     var x: { y: ?string } = { y: null };
     if (x.y) {
@@ -467,6 +490,7 @@ var tests = [
     }
     (x.y: string);
   },
+
   function() {
     var x: { y: string | number | boolean } = { y: false };
     if (typeof x.y == \"number\") {
@@ -474,6 +498,7 @@ var tests = [
     }
     (x.y: string); // error, could also be boolean
   },
+
   function() {
     var x: { y: string | number | boolean } = { y: false };
     if (typeof x.y == \"number\") {
@@ -483,6 +508,7 @@ var tests = [
     }
     (x.y: boolean); // error, string
   },
+
   function() {
     var x: { y: ?string } = { y: null };
     if (!x.y) {
@@ -493,6 +519,7 @@ var tests = [
     }
     (x.y: string); // error
   },
+
   function() {
     var x: { y: string | number | boolean } = { y: false };
     if (typeof x.y == \"number\") {
@@ -505,6 +532,7 @@ var tests = [
     // now x.y is only boolean
     (x.y: string); // error
   },
+
   function() {
     var x: { y: string | number | boolean } = { y: false };
     if (typeof x.y == \"number\") {
@@ -517,6 +545,7 @@ var tests = [
     // now x.y is number | boolean
     (x.y: string); // error
   },
+
   function() {
     var x: { y: ?string } = { y: null };
     var z: string = \"foo\";
@@ -527,16 +556,19 @@ var tests = [
     }
     (x.y: string);
   },
+
   function(x: string) {
     if (x === \"a\") {
     }
     (x: \"b\"); // error (but only once, string !~> \'b\'; \'a\' is irrelevant)
   },
+
   function(x: mixed) {
     if (typeof x.bar === \"string\") {
     } // error, so \`x.bar\` refinement is empty
     (x: string & number);
   },
+
   // --- nested conditionals ---
   // after a branch, the current scope may have changed. this causes the
   // subsequent assignment to refine the new scope. these tests make sure that
@@ -553,6 +585,7 @@ var tests = [
     }
     (x.foo: string);
   },
+
   function() {
     let x: { foo: ?string } = { foo: null };
     if (!x.foo) {
@@ -562,6 +595,7 @@ var tests = [
     }
     (x.foo: string);
   },
+
   function() {
     let x: { foo: ?string } = { foo: null };
     if (!x.foo) {
@@ -571,6 +605,7 @@ var tests = [
     }
     (x.foo: string);
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p != null) {
@@ -750,12 +785,14 @@ var paths = [
     var x: ?string = \"xxx\";
     var y: string = x; // not ok
   },
+
   function() {
     var x: ?string = \"xxx\";
     if (x != null) {
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: ?string = \"xxx\";
     if (x == null) {
@@ -763,11 +800,13 @@ var paths = [
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: ?string = \"xxx\";
     if (x == null) return;
     var y: string = x; // ok
   },
+
   function() {
     var x: ?string = \"xxx\";
     if (!(x != null)) {
@@ -775,6 +814,7 @@ var paths = [
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: ?string = \"xxx\";
     if (x != null) {
@@ -782,12 +822,14 @@ var paths = [
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: ?string = \"xxx\";
     if (x != null) {
     }
     var y: string = x; // not ok
   },
+
   function() {
     var x: ?string = \"xxx\";
     if (x != null) {
@@ -795,14 +837,17 @@ var paths = [
       var y: string = x; // not ok
     }
   },
+
   function() {
     var x: ?string = \"xxx\";
     var y: string = x != null ? x : \"\"; // ok
   },
+
   function() {
     var x: ?string = \"xxx\";
     var y: string = x || \"\"; // ok
   },
+
   function() {
     var x: string | string[] = [\"xxx\"];
     if (Array.isArray(x)) {
@@ -811,6 +856,7 @@ var paths = [
       var z: string = x; // ok
     }
   },
+
   function() {
     var x: ?string = null;
     if (!x) {
@@ -986,24 +1032,28 @@ var null_tests = [
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: ?string = \"xxx\";
     if (null != x) {
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p != null) {
       var y: string = x.p; // ok
     }
   },
+
   function() {
     var x: { p: { q: ?string } } = { p: { q: \"xxx\" } };
     if (x.p.q != null) {
       var y: string = x.p.q; // ok
     }
   },
+
   // expr == null
   function() {
     var x: ?string = \"xxx\";
@@ -1012,6 +1062,7 @@ var null_tests = [
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p == null) {
@@ -1019,6 +1070,7 @@ var null_tests = [
       var y: string = x.p; // ok
     }
   },
+
   function() {
     var x: { p: { q: ?string } } = { p: { q: \"xxx\" } };
     if (x.p.q == null) {
@@ -1026,6 +1078,7 @@ var null_tests = [
       var y: string = x.p.q; // ok
     }
   },
+
   // expr !== null
   function() {
     var x: ?string = \"xxx\";
@@ -1033,18 +1086,21 @@ var null_tests = [
       var y: string | void = x; // ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p !== null) {
       var y: string | void = x.p; // ok
     }
   },
+
   function() {
     var x: { p: { q: ?string } } = { p: { q: \"xxx\" } };
     if (x.p.q !== null) {
       var y: string | void = x.p.q; // ok
     }
   },
+
   // expr === null
   function() {
     var x: ?string = \"xxx\";
@@ -1053,6 +1109,7 @@ var null_tests = [
       var y: string | void = x; // ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p === null) {
@@ -1060,6 +1117,7 @@ var null_tests = [
       var y: string | void = x.p; // ok
     }
   },
+
   function() {
     var x: { p: { q: ?string } } = { p: { q: \"xxx\" } };
     if (x.p.q === null) {
@@ -1388,24 +1446,28 @@ var null_tests = [
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: ?string = \"xxx\";
     if (\"string\" == typeof x) {
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (typeof x.p == \"string\") {
       var y: string = x.p; // ok
     }
   },
+
   function() {
     var x: { p: { q: ?string } } = { p: { q: \"xxx\" } };
     if (typeof x.p.q == \"string\") {
       var y: string = x.p.q; // ok
     }
   },
+
   // typeof expr != typename
   function() {
     var x: ?string = \"xxx\";
@@ -1414,6 +1476,7 @@ var null_tests = [
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (typeof x.p != \"string\") {
@@ -1421,6 +1484,7 @@ var null_tests = [
       var y: string = x.p; // ok
     }
   },
+
   function() {
     var x: { p: { q: ?string } } = { p: { q: \"xxx\" } };
     if (typeof x.p.q != \"string\") {
@@ -1428,6 +1492,7 @@ var null_tests = [
       var y: string = x.p.q; // ok
     }
   },
+
   // typeof expr === typename
   function() {
     var x: ?string = \"xxx\";
@@ -1435,18 +1500,21 @@ var null_tests = [
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (typeof x.p === \"string\") {
       var y: string = x.p; // ok
     }
   },
+
   function() {
     var x: { p: { q: ?string } } = { p: { q: \"xxx\" } };
     if (typeof x.p.q === \"string\") {
       var y: string = x.p.q; // ok
     }
   },
+
   // typeof expr !== typename
   function() {
     var x: ?string = \"xxx\";
@@ -1455,6 +1523,7 @@ var null_tests = [
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (typeof x.p !== \"string\") {
@@ -1462,6 +1531,7 @@ var null_tests = [
       var y: string = x.p; // ok
     }
   },
+
   function() {
     var x: { p: { q: ?string } } = { p: { q: \"xxx\" } };
     if (typeof x.p.q !== \"string\") {
@@ -1584,24 +1654,28 @@ var undef_tests = [
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: ?string = \"xxx\";
     if (undefined !== x && x !== null) {
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p !== undefined && x.p !== null) {
       var y: string = x.p; // ok
     }
   },
+
   function() {
     var x: { p: { q: ?string } } = { p: { q: \"xxx\" } };
     if (x.p.q !== undefined && x.p.q !== null) {
       var y: string = x.p.q; // ok
     }
   },
+
   // expr === undefined
   function() {
     var x: ?string = \"xxx\";
@@ -1610,6 +1684,7 @@ var undef_tests = [
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p === undefined || x.p === null) {
@@ -1617,6 +1692,7 @@ var undef_tests = [
       var y: string = x.p; // ok
     }
   },
+
   function() {
     var x: { p: { q: ?string } } = { p: { q: \"xxx\" } };
     if (x.p.q === undefined || x.p.q === null) {
@@ -1729,24 +1805,28 @@ var void_tests = [
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: ?string = \"xxx\";
     if (void 0 !== x && x !== null) {
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p !== void 0 && x.p !== null) {
       var y: string | void = x.p; // ok
     }
   },
+
   function() {
     var x: { p: { q: ?string } } = { p: { q: \"xxx\" } };
     if (x.p.q !== void 0 && x.p.q !== null) {
       var y: string = x.p.q; // ok
     }
   },
+
   // expr === void(...)
   function() {
     var x: ?string = \"xxx\";
@@ -1755,6 +1835,7 @@ var void_tests = [
       var y: string = x; // ok
     }
   },
+
   function() {
     var x: { p: ?string } = { p: \"xxx\" };
     if (x.p === void 0 || x.p === null) {
@@ -1762,6 +1843,7 @@ var void_tests = [
       var y: string = x.p; // ok
     }
   },
+
   function() {
     var x: { p: { q: ?string } } = { p: { q: \"xxx\" } };
     if (x.p.q === void 0 || x.p.q === null) {
diff --git a/tests/flow/refinements/__snapshots__/jsfmt.spec.js.snap b/tests/flow/refinements/__snapshots__/jsfmt.spec.js.snap
index 9fffbbf19..c1a2cef2a 100644
--- a/tests/flow/refinements/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/refinements/__snapshots__/jsfmt.spec.js.snap
@@ -182,6 +182,7 @@ let tests = [
     }
     return x.result; // error
   },
+
   function(x: { done: true, result: string } | { done: false }) {
     if (true === x.done) {
       return x.result;
@@ -309,12 +310,14 @@ let tests = [
     }
     return x.result; // error
   },
+
   function(x: { done: true, result: string } | { foo: string }) {
     if (x.done) {
       return x.result; // error, consider { foo: \"herp\", done: \"derp\" }
     }
     return x.result; // error
   },
+
   function() {
     type T = { foo: Object, bar: string } | { baz: string, quux: string };
 
@@ -386,16 +389,19 @@ let tests = [
     if (x === y) {
     } // no error, to match \`let z = (x === y)\` which is allowed
   },
+
   function(x: string) {
     if (x == undefined) {
     } // ok
     if (x == void 0) {
     } // ok
   },
+
   function(x: string) {
     if (x == null) {
     } // ok
   },
+
   function(x: { y: \"foo\" } | { y: \"bar\" }) {
     if (x.y == 123) {
     } // error
@@ -1340,36 +1346,42 @@ let tests = [
     }
     (x: 0); // error
   },
+
   function(x: number) {
     if (x !== 0) {
       (x: 0); // error
     }
     (x: void); // error
   },
+
   function(x: 1): 0 {
     if (x === 0) {
       return x; // unreachable, no error
     }
     return 0;
   },
+
   function(x: 0): number {
     if (x === 1) {
       return x;
     }
     return x;
   },
+
   function(x: 0) {
     if (x !== 1) {
       (x: 0);
     }
     (x: 0);
   },
+
   function(x: 0): number {
     if (x === 0) {
       return x;
     }
     return x;
   },
+
   function(x: 0 | 1) {
     if (x === 0) {
       (x: 0);
@@ -1380,12 +1392,14 @@ let tests = [
       (x: void); // error
     }
   },
+
   function(x: { foo: number }): 0 {
     if (x.foo === 0) {
       return x.foo;
     }
     return x.foo; // error
   },
+
   function(x: { kind: 0, foo: number } | { kind: 1, bar: number }): number {
     if (x.kind === 0) {
       return x.foo;
@@ -1393,22 +1407,26 @@ let tests = [
       return x.bar;
     }
   },
+
   function(num: number, obj: { foo: number }) {
     if (num === obj.bar) {
       // ok, typos allowed in conditionals
     }
   },
+
   function(num: number, obj: { [key: string]: number }) {
     if (num === obj.bar) {
       // ok
     }
   },
+
   function(n: number): Mode {
     if (n !== 0 && n !== 1 && n !== 2) {
       throw new Error(\"Wrong number passed\");
     }
     return n;
   },
+
   function(s: number): ?Mode {
     if (s === 0) {
       return s;
@@ -1416,6 +1434,7 @@ let tests = [
       return s; // error
     }
   },
+
   function(mode: Mode) {
     switch (mode) {
       case 0:
@@ -1428,6 +1447,7 @@ let tests = [
         break;
     }
   },
+
   function(x: number): 0 {
     if (x) {
       return x; // error
@@ -1920,36 +1940,42 @@ let tests = [
     }
     (x: \"foo\"); // error
   },
+
   function(x: string) {
     if (x !== \"foo\") {
       (x: \"foo\"); // error
     }
     (x: void); // error
   },
+
   function(x: \"bar\"): \"foo\" {
     if (x === \"foo\") {
       return x; // unreachable, no error
     }
     return \"foo\";
   },
+
   function(x: \"foo\"): string {
     if (x === \"bar\") {
       return x;
     }
     return x;
   },
+
   function(x: \"foo\") {
     if (x !== \"bar\") {
       (x: \"foo\");
     }
     (x: \"foo\");
   },
+
   function(x: \"foo\"): string {
     if (x === \"foo\") {
       return x;
     }
     return x;
   },
+
   function(x: \"foo\" | \"bar\") {
     if (x === \"foo\") {
       (x: \"foo\");
@@ -1960,12 +1986,14 @@ let tests = [
       (x: void); // error
     }
   },
+
   function(x: { foo: string }): \"foo\" {
     if (x.foo === \"foo\") {
       return x.foo;
     }
     return x.foo; // error
   },
+
   function(
     x: { kind: \"foo\", foo: string } | { kind: \"bar\", bar: string }
   ): string {
@@ -1975,16 +2003,19 @@ let tests = [
       return x.bar;
     }
   },
+
   function(str: string, obj: { foo: string }) {
     if (str === obj.bar) {
       // ok, typos allowed in conditionals
     }
   },
+
   function(str: string, obj: { [key: string]: string }) {
     if (str === obj.bar) {
       // ok
     }
   },
+
   function(str: string): Mode {
     var ch = str[0];
     if (ch !== \"a\" && ch !== \"b\" && ch !== \"c\") {
@@ -1992,6 +2023,7 @@ let tests = [
     }
     return ch;
   },
+
   function(s: string): ?Mode {
     if (s === \"a\") {
       return s;
@@ -1999,6 +2031,7 @@ let tests = [
       return s; // error
     }
   },
+
   function(mode: Mode) {
     switch (mode) {
       case \"a\":
@@ -2011,6 +2044,7 @@ let tests = [
         break;
     }
   },
+
   function(x: string): \"\" {
     if (x) {
       return x; // error
@@ -2018,6 +2052,7 @@ let tests = [
       return x; // no error, inferred to be \"\"
     }
   },
+
   // Simple template literals are ok
   function(x: string): \"foo\" {
     if (x === \`foo\`) {
@@ -2532,6 +2567,7 @@ let tests = [
       (x.kindTypo: string); // typos can\'t be used, though
     }
   },
+
   // nested objects
   function test8(x: { foo: { bar: 1 } }) {
     if (x.foo.bar === 1) {
@@ -2539,6 +2575,7 @@ let tests = [
     if (x.fooTypo.bar === 1) {
     } // error, fooTypo doesn\'t exist
   },
+
   // invalid RHS
   function(x: A) {
     if (x.kind === null.toString()) {
@@ -2546,6 +2583,7 @@ let tests = [
     if (({ kind: 1 }).kind === null.toString()) {
     } // error, method on null
   },
+
   // non-objects on LHS
   function(
     x: Array<string>,
@@ -2601,6 +2639,7 @@ let tests = [
       (t.calll: empty); // ok, props on functions are \`any\` :/
     }
   },
+
   // sentinel props become the RHS
   function(x: { str: string, num: number, bool: boolean }) {
     if (x.str === \"str\") {
@@ -2623,6 +2662,7 @@ let tests = [
       (x.badBool: empty); // error: true !~> empty
     }
   },
+
   // type mismatch
   function(x: { foo: 123, y: string } | { foo: \"foo\", z: string }) {
     if (x.foo === 123) {
@@ -2640,6 +2680,7 @@ let tests = [
       x.z; // error
     }
   },
+
   // type mismatch, but one is not a literal
   function(x: { foo: number, y: string } | { foo: \"foo\", z: string }) {
     if (x.foo === 123) {
@@ -2658,6 +2699,7 @@ let tests = [
       x.z; // error
     }
   },
+
   // type mismatch, neither is a literal
   function(x: { foo: number, y: string } | { foo: string, z: string }) {
     if (x.foo === 123) {
@@ -2676,6 +2718,7 @@ let tests = [
       x.z; // error: x.foo could be a number
     }
   },
+
   // type mismatch, neither is a literal, test is not a literal either
   function(
     x: { foo: number, y: string } | { foo: string, z: string },
diff --git a/tests/flow/taint/__snapshots__/jsfmt.spec.js.snap b/tests/flow/taint/__snapshots__/jsfmt.spec.js.snap
index 5a9b44db6..7d339fc95 100644
--- a/tests/flow/taint/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/taint/__snapshots__/jsfmt.spec.js.snap
@@ -101,11 +101,13 @@ let tests = [
     obj.foo = x; // error, taint ~> any
     obj[y] = x; // error, taint ~> any
   },
+
   // getting a property
   function() {
     let obj: Object = { foo: \"foo\" };
     (obj.foo: $Tainted<string>); // ok
   },
+
   // calling a method
   function(x: $Tainted<string>) {
     let obj: Object = {};
@@ -237,6 +239,7 @@ let tests = [
   function(x: any, y: $Tainted<string>) {
     x(y); // error, taint ~> any
   },
+
   // calling \`any\` returns \`any\`
   function(x: any, y: $Tainted<string>) {
     let z = x();
diff --git a/tests/flow/typecast/__snapshots__/jsfmt.spec.js.snap b/tests/flow/typecast/__snapshots__/jsfmt.spec.js.snap
index fa443d21b..90f414af5 100644
--- a/tests/flow/typecast/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/typecast/__snapshots__/jsfmt.spec.js.snap
@@ -37,11 +37,13 @@ var tests = [
     var x: number = (\"hey\": any);
     var y = ((\"hey\": any): number);
   },
+
   () => {
     // typecasts in sequences
     // (parens always required around typecasts)
     var s: string = ((0: number), (\"hey\": string));
   },
+
   () => {
     // TODO pending array element inference issues
     // control case:
diff --git a/tests/flow/unary/__snapshots__/jsfmt.spec.js.snap b/tests/flow/unary/__snapshots__/jsfmt.spec.js.snap
index 85d11fd50..aef906d5b 100644
--- a/tests/flow/unary/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/unary/__snapshots__/jsfmt.spec.js.snap
@@ -90,25 +90,31 @@ let tests = [
     ++y;
     --y;
   },
+
   function(y: string) {
     y++; // error, we don\'t allow coercion here
     (y: number); // ok, y is a number now
     y++; // error, but you still can\'t write a number to a string
   },
+
   function(y: string) {
     y--; // error, we don\'t allow coercion here
   },
+
   function(y: string) {
     ++y; // error, we don\'t allow coercion here
   },
+
   function(y: string) {
     --y; // error, we don\'t allow coercion here
   },
+
   function() {
     const y = 123;
     y++; // error, can\'t update const
     y--; // error, can\'t update const
   },
+
   function(y: any) {
     y++; // ok
   }
diff --git a/tests/flow/undefined/__snapshots__/jsfmt.spec.js.snap b/tests/flow/undefined/__snapshots__/jsfmt.spec.js.snap
index bf6c59a03..a340f78ef 100644
--- a/tests/flow/undefined/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/undefined/__snapshots__/jsfmt.spec.js.snap
@@ -98,6 +98,7 @@ let tests = [
     const bar = [undefined, \"bar\"];
     (bar[x]: boolean); // error, string or void
   },
+
   function(x: number) {
     var undefined = \"foo\";
     (undefined: string); // ok
diff --git a/tests/strings/__snapshots__/jsfmt.spec.js.snap b/tests/strings/__snapshots__/jsfmt.spec.js.snap
index b75363d4e..838149c9e 100644
--- a/tests/strings/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/strings/__snapshots__/jsfmt.spec.js.snap
@@ -28,17 +28,23 @@ exports[`test strings.js 1`] = `
 [
   \"abc\",
   \"abc\",
+
   \"\'\",
+
   \'\"\',
   \'\"\',
   \'\\\\\"\',
+
   \"\'\",
   \"\'\",
   \"\\\\\'\",
+
   \"\'\\\"\",
   \"\'\\\"\",
+
   \"\\\\\",
   \"\\\\\",
+
   \"\\0\",
   \"🐶\",
   \'\\uD801\\uDC28\'