Repository: prettier/prettier (JavaScript)

Issue:
Remove `flow` support from `parser: babel`
I was trying to improve parse performance on large files, and found it will cost almost half time on some file by removing plugins.

Something interesting come up, we have `flow` and `babel-flow` parser, but we also enabled `flow` syntax in `babel` plugins https://github.com/prettier/prettier/blob/master/src/language-js/parser-babel.js#L101

Shouldn't we encourage people use `flow` or `babel-flow`? People use `typescript` and `babel-ts` for `.ts`, not `babel`.

Can we remove it?

---

Refs: 

Motivation for `babel-flow`  https://github.com/prettier/prettier/issues/5460#issuecomment-441298551 and https://github.com/prettier/prettier/issues/5460#issuecomment-441360123

PR introduce `babel-flow` https://github.com/prettier/prettier/pull/5685


Hints:
> found it will cost almost half time on some file by removing plugins.

Could you show some benchmark results?

> Can we remove it?

I guess we can if we make Prettier switch to `babel-flow` automatically when the `@flow` pragma is found.
> Could you show some benchmark results?

Will do.

> I guess we can if we make Prettier switch to babel-flow automatically when the @flow pragma is found.

My intention is not support flow at all, but check `@flow` is a good idea, so we don't break anything.
Run against `src/**/*`

<details>
<summary>Test script</summary>
<pre><code>
const fs = require('fs')
const oldParse = require('./src/language-js/parser-babel-master').parsers.babel.parse
const newParse = require('./src/language-js/parser-babel').parsers.babel.parse
const files = require('fast-glob').sync('src/**/*').map(file => fs.readFileSync(file, 'utf8'))


console.log('first run')
console.time('new')
for (const code of files) {
  newParse(code)
}
console.timeEnd('new')
console.time('old')
for (const code of files) {
  oldParse(code)
}
console.timeEnd('old')

console.log()
console.log('second run')

console.time('old')
for (const code of files) {
  oldParse(code)
}
console.timeEnd('old')


console.time('new')
for (const code of files) {
  newParse(code)
}
console.timeEnd('new')

</code></pre>
</details>

```text
first run
new: 517.260ms
old: 594.656ms

second run
old: 332.977ms
new: 186.684ms
```


-----

Run against this 18M js file from [here](https://github.com/prettier/prettier/issues/5004#issuecomment-459876800)


<details>
<summary>Test script</summary>
<pre><code>

const fs = require('fs')
const oldParse = require('./src/language-js/parser-babel-master').parsers.babel.parse
const newParse = require('./src/language-js/parser-babel').parsers.babel.parse

const code = fs.readFileSync('./AngryBots.js', 'utf8')


console.log('first run')
console.time('new')
  newParse(code)
console.timeEnd('new')
console.time('old')
  oldParse(code)
console.timeEnd('old')

console.log()
console.log('second run')

console.time('old')
  oldParse(code)
console.timeEnd('old')


console.time('new')
  newParse(code)
console.timeEnd('new')


</code></pre>
</details>

Actual Babel options (`jsx` `flow` and some other plugins are not enabled)

```text
[ { sourceType: 'module',
    allowAwaitOutsideFunction: true,
    allowImportExportEverywhere: true,
    allowReturnOutsideFunction: true,
    allowSuperOutsideMethod: true,
    allowUndeclaredExports: true,
    errorRecovery: true,
    createParenthesizedExpressions: true,
    plugins:
     [ 'asyncGenerators',
       'bigInt',
       'doExpressions',
       'exportDefaultFrom',
       'functionBind',
       'functionSent',
       'importMeta',
       'numericSeparator',
       'objectRestSpread',
       'optionalCatchBinding',
       'partialApplication',
       'throwExpressions',
       'v8intrinsic' ] } ]
```

Result

```text
first run
new: 26619.009ms
old: 47839.521ms

second run
old: 46681.359ms
new: 27487.509ms
```


----


The prototype can be found [here](https://github.com/fisker/prettier/pull/378/commits/9bf8f0a9bb168f019fdb5140ea25015761132f3a).
@thorn0 I've done removing `flow` (also simple tests for each babel plugin) from `babel` parser
https://github.com/fisker/prettier/pull/378 ,  it indeed makes the parse faster on pure js projects, but this is a breaking change.

Before,we can parse `flow` syntax by default.
After, users need use `@flow` or switch to `babel-flow` parser, should I send the PR?
Do people write Flow code in files with `.js` extension and without `@flow`?
I don't use flow , but some tests in `tests/flow/` don't has `@flow`
We can't proceed with this if people do that.
> Shouldn't we encourage people use `flow` or `babel-flow`? 

About this, what do you think?
Prettier should "just work", even without config files. But if people write Flow code in `.js` files, they'll have to know to create a config file to specify `parser`.
Copying my comment from #8508:

> That's fine. The babel parser isn't safe for Flow anyway: https://prettier.io/blog/2019/01/20/1.16.0.html#flow
I agree that it's a mess. BTW, probably this means that if we proceed with #8204 (parse Flow syntax only if the pragma is present), it won't really be a breaking change as we'd break something that is already broken.

Patch:
diff --git a/src/language-js/parse/babel.js b/src/language-js/parse/babel.js
--- a/src/language-js/parse/babel.js
+++ b/src/language-js/parse/babel.js
@@ -218,8 +218,9 @@ const allowedMessageCodes = new Set([
   "DuplicateExport",
 ]);
 
+const babelParserOptionsCombinations = [appendPlugins(["jsx"])];
 const babel = createBabelParser({
-  optionsCombinations: [appendPlugins(["jsx", "flow"])],
+  optionsCombinations: babelParserOptionsCombinations,
 });
 const babelTs = createBabelParser({
   optionsCombinations: [
@@ -235,7 +236,6 @@ const babelTSExpression = createBabelParser({
   isExpression: true,
   optionsCombinations: [appendPlugins(["typescript"])],
 });
-
 const babelFlow = createBabelParser({
   optionsCombinations: [
     appendPlugins([
@@ -246,7 +246,9 @@ const babelFlow = createBabelParser({
   ],
 });
 const babelEstree = createBabelParser({
-  optionsCombinations: [appendPlugins(["jsx", "flow", "estree"])],
+  optionsCombinations: babelParserOptionsCombinations.map((options) =>
+    appendPlugins(["estree"], options)
+  ),
 });
 
 // Export as a plugin so we can reuse the same bundle for UMD loading


Test patch:
diff --git a/tests/config/format-test.js b/tests/config/format-test.js
--- a/tests/config/format-test.js
+++ b/tests/config/format-test.js
@@ -218,19 +218,6 @@ function runSpec(fixtures, parsers, options) {
   const allParsers = [...parsers];
 
   if (!IS_ERROR_TESTS) {
-    if (
-      parsers.includes("typescript") &&
-      !parsers.includes("babel-ts") &&
-      !IS_TYPESCRIPT_ONLY_TEST &&
-      !babelTsDisabledTest.has(dirname)
-    ) {
-      allParsers.push("babel-ts");
-    }
-
-    if (parsers.includes("flow") && !parsers.includes("babel-flow")) {
-      allParsers.push("babel-flow");
-    }
-
     if (
       parsers.includes("babel") &&
       (isTestDirectory(dirname, "js") || isTestDirectory(dirname, "jsx"))
@@ -246,6 +233,19 @@ function runSpec(fixtures, parsers, options) {
       }
     }
 
+    if (
+      parsers.includes("typescript") &&
+      !parsers.includes("babel-ts") &&
+      !IS_TYPESCRIPT_ONLY_TEST &&
+      !babelTsDisabledTest.has(dirname)
+    ) {
+      allParsers.push("babel-ts");
+    }
+
+    if (parsers.includes("flow") && !parsers.includes("babel-flow")) {
+      allParsers.push("babel-flow");
+    }
+
     if (parsers.includes("babel") && !parsers.includes("__babel_estree")) {
       allParsers.push("__babel_estree");
     }
diff --git a/tests/format/flow/decorator/__snapshots__/jsfmt.spec.js.snap b/tests/format/flow/decorator/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
--- /dev/null
+++ b/tests/format/flow/decorator/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,117 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`comments.js format 1`] = `
+====================================options=====================================
+parsers: ["babel-flow"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+class Something {
+    @Annotateme()
+    // comment
+    static property: Array<string>;
+}
+
+=====================================output=====================================
+class Something {
+  @Annotateme()
+  // comment
+  static property: Array<string>;
+}
+
+================================================================================
+`;
+
+exports[`mobx.js format 1`] = `
+====================================options=====================================
+parsers: ["babel-flow"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+import {observable} from "mobx";
+
+@observer class OrderLine {
+  @observable price:number = 0;
+  @observable amount:number = 1;
+
+  constructor(price) {
+    this.price = price;
+  }
+
+  @computed get total() {
+    return this.price * this.amount;
+  }
+
+  @action.bound setPrice(price) {
+    this.price = price;
+  }
+
+  @computed
+  get total() {
+    return this.price * this.amount;
+  }
+
+  @action.bound
+  setPrice(price) {
+    this.price = price;
+  }
+
+  @computed @computed @computed @computed @computed @computed @computed get total() {
+    return this.price * this.amount;
+  }
+
+  @action handleDecrease = (event: React.ChangeEvent<HTMLInputElement>) => this.count--;
+
+  @action handleSomething = (event: React.ChangeEvent<HTMLInputElement>) => doSomething();
+}
+
+=====================================output=====================================
+import { observable } from "mobx";
+
+@observer
+class OrderLine {
+  @observable price: number = 0;
+  @observable amount: number = 1;
+
+  constructor(price) {
+    this.price = price;
+  }
+
+  @computed get total() {
+    return this.price * this.amount;
+  }
+
+  @action.bound setPrice(price) {
+    this.price = price;
+  }
+
+  @computed
+  get total() {
+    return this.price * this.amount;
+  }
+
+  @action.bound
+  setPrice(price) {
+    this.price = price;
+  }
+
+  @computed
+  @computed
+  @computed
+  @computed
+  @computed
+  @computed
+  @computed
+  get total() {
+    return this.price * this.amount;
+  }
+
+  @action handleDecrease = (event: React.ChangeEvent<HTMLInputElement>) =>
+    this.count--;
+
+  @action handleSomething = (event: React.ChangeEvent<HTMLInputElement>) =>
+    doSomething();
+}
+
+================================================================================
+`;
diff --git a/tests/format/js/decorator-comments/comments.js b/tests/format/flow/decorator/comments.js
similarity index 100%
rename from tests/format/js/decorator-comments/comments.js
rename to tests/format/flow/decorator/comments.js
diff --git a/tests/format/flow/decorator/jsfmt.spec.js b/tests/format/flow/decorator/jsfmt.spec.js
new file mode 100644
--- /dev/null
+++ b/tests/format/flow/decorator/jsfmt.spec.js
@@ -0,0 +1,2 @@
+// TODO: Run tests with `flow` parser https://github.com/facebook/flow/issues/9003
+run_spec(import.meta, ["babel-flow"]);
diff --git a/tests/format/flow/decorator/mobx.js b/tests/format/flow/decorator/mobx.js
new file mode 100644
--- /dev/null
+++ b/tests/format/flow/decorator/mobx.js
@@ -0,0 +1,36 @@
+import {observable} from "mobx";
+
+@observer class OrderLine {
+  @observable price:number = 0;
+  @observable amount:number = 1;
+
+  constructor(price) {
+    this.price = price;
+  }
+
+  @computed get total() {
+    return this.price * this.amount;
+  }
+
+  @action.bound setPrice(price) {
+    this.price = price;
+  }
+
+  @computed
+  get total() {
+    return this.price * this.amount;
+  }
+
+  @action.bound
+  setPrice(price) {
+    this.price = price;
+  }
+
+  @computed @computed @computed @computed @computed @computed @computed get total() {
+    return this.price * this.amount;
+  }
+
+  @action handleDecrease = (event: React.ChangeEvent<HTMLInputElement>) => this.count--;
+
+  @action handleSomething = (event: React.ChangeEvent<HTMLInputElement>) => doSomething();
+}
diff --git a/tests/format/js/babel-plugins/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/babel-plugins/__snapshots__/jsfmt.spec.js.snap
--- a/tests/format/js/babel-plugins/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/babel-plugins/__snapshots__/jsfmt.spec.js.snap
@@ -937,8 +937,8 @@ export * as ns from "mod";
 ================================================================================
 `;
 
-exports[`flow.js [acorn] format 1`] = `
-"Unexpected token (3:17)
+exports[`flow.js [__babel_estree] format 1`] = `
+"Unexpected token, expected "," (3:17)
   1 | // https://babeljs.io/docs/en/babel-preset-flow
   2 |
 > 3 | function foo(one: any, two: number, three?): string {}
@@ -946,8 +946,8 @@ exports[`flow.js [acorn] format 1`] = `
   4 |"
 `;
 
-exports[`flow.js [espree] format 1`] = `
-"Unexpected token : (3:17)
+exports[`flow.js [acorn] format 1`] = `
+"Unexpected token (3:17)
   1 | // https://babeljs.io/docs/en/babel-preset-flow
   2 |
 > 3 | function foo(one: any, two: number, three?): string {}
@@ -955,8 +955,8 @@ exports[`flow.js [espree] format 1`] = `
   4 |"
 `;
 
-exports[`flow.js [meriyah] format 1`] = `
-"Expected ')' (3:17)
+exports[`flow.js [babel] format 1`] = `
+"Unexpected token, expected "," (3:17)
   1 | // https://babeljs.io/docs/en/babel-preset-flow
   2 |
 > 3 | function foo(one: any, two: number, three?): string {}
@@ -964,7 +964,7 @@ exports[`flow.js [meriyah] format 1`] = `
   4 |"
 `;
 
-exports[`flow.js format 1`] = `
+exports[`flow.js [babel-flow] format 1`] = `
 ====================================options=====================================
 parsers: ["babel", "babel-ts", "babel-flow"]
 printWidth: 80
@@ -982,6 +982,42 @@ function foo(one: any, two: number, three?): string {}
 ================================================================================
 `;
 
+exports[`flow.js [babel-ts] format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-preset-flow
+
+function foo(one: any, two: number, three?): string {}
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-preset-flow
+
+function foo(one: any, two: number, three?): string {}
+
+================================================================================
+`;
+
+exports[`flow.js [espree] format 1`] = `
+"Unexpected token : (3:17)
+  1 | // https://babeljs.io/docs/en/babel-preset-flow
+  2 |
+> 3 | function foo(one: any, two: number, three?): string {}
+    |                 ^
+  4 |"
+`;
+
+exports[`flow.js [meriyah] format 1`] = `
+"Expected ')' (3:17)
+  1 | // https://babeljs.io/docs/en/babel-preset-flow
+  2 |
+> 3 | function foo(one: any, two: number, three?): string {}
+    |                 ^
+  4 |"
+`;
+
 exports[`function-bind.js [acorn] format 1`] = `
 "Unexpected token (3:5)
   1 | // https://babeljs.io/docs/en/babel-plugin-proposal-function-bind
@@ -2381,7 +2417,7 @@ function test(param = throw new Error("required!")) {
 ================================================================================
 `;
 
-exports[`typescript.js [acorn] format 1`] = `
+exports[`typescript.js [__babel_estree] format 1`] = `
 "Unexpected token (3:8)
   1 | // https://babeljs.io/docs/en/babel-preset-typescript
   2 |
@@ -2390,8 +2426,8 @@ exports[`typescript.js [acorn] format 1`] = `
   4 |"
 `;
 
-exports[`typescript.js [espree] format 1`] = `
-"Unexpected token : (3:8)
+exports[`typescript.js [acorn] format 1`] = `
+"Unexpected token (3:8)
   1 | // https://babeljs.io/docs/en/babel-preset-typescript
   2 |
 > 3 | const x: number = 0;
@@ -2399,8 +2435,8 @@ exports[`typescript.js [espree] format 1`] = `
   4 |"
 `;
 
-exports[`typescript.js [meriyah] format 1`] = `
-"Missing initializer in const declaration (3:8)
+exports[`typescript.js [babel] format 1`] = `
+"Unexpected token (3:8)
   1 | // https://babeljs.io/docs/en/babel-preset-typescript
   2 |
 > 3 | const x: number = 0;
@@ -2408,7 +2444,7 @@ exports[`typescript.js [meriyah] format 1`] = `
   4 |"
 `;
 
-exports[`typescript.js format 1`] = `
+exports[`typescript.js [babel-flow] format 1`] = `
 ====================================options=====================================
 parsers: ["babel", "babel-ts", "babel-flow"]
 printWidth: 80
@@ -2426,6 +2462,42 @@ const x: number = 0;
 ================================================================================
 `;
 
+exports[`typescript.js [babel-ts] format 1`] = `
+====================================options=====================================
+parsers: ["babel", "babel-ts", "babel-flow"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+// https://babeljs.io/docs/en/babel-preset-typescript
+
+const x: number = 0;
+
+=====================================output=====================================
+// https://babeljs.io/docs/en/babel-preset-typescript
+
+const x: number = 0;
+
+================================================================================
+`;
+
+exports[`typescript.js [espree] format 1`] = `
+"Unexpected token : (3:8)
+  1 | // https://babeljs.io/docs/en/babel-preset-typescript
+  2 |
+> 3 | const x: number = 0;
+    |        ^
+  4 |"
+`;
+
+exports[`typescript.js [meriyah] format 1`] = `
+"Missing initializer in const declaration (3:8)
+  1 | // https://babeljs.io/docs/en/babel-preset-typescript
+  2 |
+> 3 | const x: number = 0;
+    |        ^
+  4 |"
+`;
+
 exports[`v8intrinsic.js [acorn] format 1`] = `
 "Unexpected token (3:1)
   1 | // https://github.com/babel/babel/pull/10148
diff --git a/tests/format/js/babel-plugins/jsfmt.spec.js b/tests/format/js/babel-plugins/jsfmt.spec.js
--- a/tests/format/js/babel-plugins/jsfmt.spec.js
+++ b/tests/format/js/babel-plugins/jsfmt.spec.js
@@ -85,5 +85,7 @@ run_spec(import.meta, ["babel", "babel-ts", "babel-flow"], {
       "import-reflection.js",
       "explicit-resource-management.js",
     ],
+    babel: ["flow.js", "typescript.js"],
+    __babel_estree: ["flow.js", "typescript.js"],
   },
 });
diff --git a/tests/format/js/decorator-comments/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/decorator-comments/__snapshots__/jsfmt.spec.js.snap
deleted file mode 100644
--- a/tests/format/js/decorator-comments/__snapshots__/jsfmt.spec.js.snap
+++ /dev/null
@@ -1,53 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`comments.js [acorn] format 1`] = `
-"Unexpected character '@' (2:5)
-  1 | class Something {
-> 2 |     @Annotateme()
-    |     ^
-  3 |     // comment
-  4 |     static property: Array<string>;
-  5 | }"
-`;
-
-exports[`comments.js [espree] format 1`] = `
-"Unexpected character '@' (2:5)
-  1 | class Something {
-> 2 |     @Annotateme()
-    |     ^
-  3 |     // comment
-  4 |     static property: Array<string>;
-  5 | }"
-`;
-
-exports[`comments.js [meriyah] format 1`] = `
-"Unexpected token: ':' (4:20)
-  2 |     @Annotateme()
-  3 |     // comment
-> 4 |     static property: Array<string>;
-    |                    ^
-  5 | }
-  6 |"
-`;
-
-exports[`comments.js format 1`] = `
-====================================options=====================================
-parsers: ["babel", "typescript"]
-printWidth: 80
-                                                                                | printWidth
-=====================================input======================================
-class Something {
-    @Annotateme()
-    // comment
-    static property: Array<string>;
-}
-
-=====================================output=====================================
-class Something {
-  @Annotateme()
-  // comment
-  static property: Array<string>;
-}
-
-================================================================================
-`;
diff --git a/tests/format/js/decorator-comments/jsfmt.spec.js b/tests/format/js/decorator-comments/jsfmt.spec.js
deleted file mode 100644
--- a/tests/format/js/decorator-comments/jsfmt.spec.js
+++ /dev/null
@@ -1,3 +0,0 @@
-run_spec(import.meta, ["babel", "typescript"], {
-  errors: { acorn: true, espree: true, meriyah: true },
-});
diff --git a/tests/format/js/decorators/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/decorators/__snapshots__/jsfmt.spec.js.snap
--- a/tests/format/js/decorators/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/decorators/__snapshots__/jsfmt.spec.js.snap
@@ -129,6 +129,11 @@ export class AppModule {}
 // C
 export class Bar{}
 
+class Something {
+    @Annotateme()
+    property;
+}
+
 =====================================output=====================================
 var x = 100;
 
@@ -159,6 +164,11 @@ export class AppModule {}
 // C
 export class Bar {}
 
+class Something {
+  @Annotateme()
+  property;
+}
+
 ================================================================================
 `;
 
@@ -270,8 +280,8 @@ exports[`mobx.js [acorn] format 1`] = `
   2 |
 > 3 | @observer class OrderLine {
     | ^
-  4 |   @observable price:number = 0;
-  5 |   @observable amount:number = 1;
+  4 |   @observable price = 0;
+  5 |   @observable amount = 1;
   6 |"
 `;
 
@@ -281,22 +291,11 @@ exports[`mobx.js [espree] format 1`] = `
   2 |
 > 3 | @observer class OrderLine {
     | ^
-  4 |   @observable price:number = 0;
-  5 |   @observable amount:number = 1;
+  4 |   @observable price = 0;
+  5 |   @observable amount = 1;
   6 |"
 `;
 
-exports[`mobx.js [meriyah] format 1`] = `
-"Unexpected token: ':' (4:20)
-  2 |
-  3 | @observer class OrderLine {
-> 4 |   @observable price:number = 0;
-    |                    ^
-  5 |   @observable amount:number = 1;
-  6 |
-  7 |   constructor(price) {"
-`;
-
 exports[`mobx.js format 1`] = `
 ====================================options=====================================
 parsers: ["babel"]
@@ -306,8 +305,8 @@ printWidth: 80
 import {observable} from "mobx";
 
 @observer class OrderLine {
-  @observable price:number = 0;
-  @observable amount:number = 1;
+  @observable price = 0;
+  @observable amount = 1;
 
   constructor(price) {
     this.price = price;
@@ -330,14 +329,14 @@ import {observable} from "mobx";
   setPrice(price) {
     this.price = price;
   }
-  
+
   @computed @computed @computed @computed @computed @computed @computed get total() {
     return this.price * this.amount;
   }
 
-  @action handleDecrease = (event: React.ChangeEvent<HTMLInputElement>) => this.count--;
-  
-  @action handleSomething = (event: React.ChangeEvent<HTMLInputElement>) => doSomething();
+  @action handleDecrease = (event) => this.count--;
+
+  @action handleSomething = (event) => doSomething();
 }
 
 =====================================output=====================================
@@ -345,8 +344,8 @@ import { observable } from "mobx";
 
 @observer
 class OrderLine {
-  @observable price: number = 0;
-  @observable amount: number = 1;
+  @observable price = 0;
+  @observable amount = 1;
 
   constructor(price) {
     this.price = price;
@@ -381,11 +380,9 @@ class OrderLine {
     return this.price * this.amount;
   }
 
-  @action handleDecrease = (event: React.ChangeEvent<HTMLInputElement>) =>
-    this.count--;
+  @action handleDecrease = (event) => this.count--;
 
-  @action handleSomething = (event: React.ChangeEvent<HTMLInputElement>) =>
-    doSomething();
+  @action handleSomething = (event) => doSomething();
 }
 
 ================================================================================
diff --git a/tests/format/js/decorators/comments.js b/tests/format/js/decorators/comments.js
--- a/tests/format/js/decorators/comments.js
+++ b/tests/format/js/decorators/comments.js
@@ -29,3 +29,8 @@ export class AppModule {}
 @Bar()
 // C
 export class Bar{}
+
+class Something {
+    @Annotateme()
+    property;
+}
diff --git a/tests/format/js/decorators/jsfmt.spec.js b/tests/format/js/decorators/jsfmt.spec.js
--- a/tests/format/js/decorators/jsfmt.spec.js
+++ b/tests/format/js/decorators/jsfmt.spec.js
@@ -1,3 +1,3 @@
 run_spec(import.meta, ["babel"], {
-  errors: { acorn: true, espree: true, meriyah: ["multiple.js", "mobx.js"] },
+  errors: { acorn: true, espree: true, meriyah: ["multiple.js"] },
 });
diff --git a/tests/format/js/decorators/mobx.js b/tests/format/js/decorators/mobx.js
--- a/tests/format/js/decorators/mobx.js
+++ b/tests/format/js/decorators/mobx.js
@@ -1,8 +1,8 @@
 import {observable} from "mobx";
 
 @observer class OrderLine {
-  @observable price:number = 0;
-  @observable amount:number = 1;
+  @observable price = 0;
+  @observable amount = 1;
 
   constructor(price) {
     this.price = price;
@@ -25,12 +25,12 @@ import {observable} from "mobx";
   setPrice(price) {
     this.price = price;
   }
-  
+
   @computed @computed @computed @computed @computed @computed @computed get total() {
     return this.price * this.amount;
   }
 
-  @action handleDecrease = (event: React.ChangeEvent<HTMLInputElement>) => this.count--;
-  
-  @action handleSomething = (event: React.ChangeEvent<HTMLInputElement>) => doSomething();
+  @action handleDecrease = (event) => this.count--;
+
+  @action handleSomething = (event) => doSomething();
 }
diff --git a/tests/format/js/rest/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/rest/__snapshots__/jsfmt.spec.js.snap
--- a/tests/format/js/rest/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/rest/__snapshots__/jsfmt.spec.js.snap
@@ -1,44 +1,17 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`trailing-commas.js - {"trailingComma":"all"} [acorn] format 1`] = `
-"Unexpected token (1:9)
-> 1 | declare class C {
-    |         ^
-  2 |   f(
-  3 |     superSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperLong,
-  4 |     ...args"
-`;
-
-exports[`trailing-commas.js - {"trailingComma":"all"} [espree] format 1`] = `
-"Unexpected token class (1:9)
-> 1 | declare class C {
-    |         ^
-  2 |   f(
-  3 |     superSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperLong,
-  4 |     ...args"
-`;
-
-exports[`trailing-commas.js - {"trailingComma":"all"} [meriyah] format 1`] = `
-"Unexpected token: 'class' (1:13)
-> 1 | declare class C {
-    |             ^
-  2 |   f(
-  3 |     superSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperLong,
-  4 |     ...args"
-`;
-
 exports[`trailing-commas.js - {"trailingComma":"all"} format 1`] = `
 ====================================options=====================================
-parsers: ["babel", "flow"]
+parsers: ["babel", "flow", "typescript"]
 printWidth: 80
 trailingComma: "all"
                                                                                 | printWidth
 =====================================input======================================
-declare class C {
+class C {
   f(
     superSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperLong,
     ...args
-  ): void,
+  ) {}
 }
 
 function f(
@@ -46,7 +19,7 @@ function f(
   ...args
 ) {}
 
-declare class C { f(...superSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperLong): void; }
+class D { f(...superSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperLong) {}; }
 
 [superSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperLong,,];
 
@@ -55,11 +28,11 @@ var {veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLon
 
 
 =====================================output=====================================
-declare class C {
+class C {
   f(
     superSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperLong,
     ...args
-  ): void;
+  ) {}
 }
 
 function f(
@@ -67,10 +40,10 @@ function f(
   ...args
 ) {}
 
-declare class C {
+class D {
   f(
     ...superSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperLong
-  ): void;
+  ) {}
 }
 
 [
diff --git a/tests/format/js/rest/jsfmt.spec.js b/tests/format/js/rest/jsfmt.spec.js
--- a/tests/format/js/rest/jsfmt.spec.js
+++ b/tests/format/js/rest/jsfmt.spec.js
@@ -1,4 +1,3 @@
-run_spec(import.meta, ["babel", "flow"], {
+run_spec(import.meta, ["babel", "flow", "typescript"], {
   trailingComma: "all",
-  errors: { acorn: true, espree: true, meriyah: true },
 });
diff --git a/tests/format/js/rest/trailing-commas.js b/tests/format/js/rest/trailing-commas.js
--- a/tests/format/js/rest/trailing-commas.js
+++ b/tests/format/js/rest/trailing-commas.js
@@ -1,8 +1,8 @@
-declare class C {
+class C {
   f(
     superSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperLong,
     ...args
-  ): void,
+  ) {}
 }
 
 function f(
@@ -10,7 +10,7 @@ function f(
   ...args
 ) {}
 
-declare class C { f(...superSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperLong): void; }
+class D { f(...superSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperLong) {}; }
 
 [superSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperSuperLong,,];
 
diff --git a/tests/format/js/trailing-whitespace/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/trailing-whitespace/__snapshots__/jsfmt.spec.js.snap
--- a/tests/format/js/trailing-whitespace/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/trailing-whitespace/__snapshots__/jsfmt.spec.js.snap
@@ -1,41 +1,11 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`trailing.js [acorn] format 1`] = `
-"Unexpected token (1:8)
-> 1 | export type Result<T, V> = | { kind: "not-test-editor1" } | { kind: "not-test-editor2" };
-    |        ^
-  2 |
-  3 | // Note: there are trailing whitespace in this file
-  4 | \`"
-`;
-
-exports[`trailing.js [espree] format 1`] = `
-"Unexpected token type (1:8)
-> 1 | export type Result<T, V> = | { kind: "not-test-editor1" } | { kind: "not-test-editor2" };
-    |        ^
-  2 |
-  3 | // Note: there are trailing whitespace in this file
-  4 | \`"
-`;
-
-exports[`trailing.js [meriyah] format 1`] = `
-"Unexpected token: 'identifier' (1:11)
-> 1 | export type Result<T, V> = | { kind: "not-test-editor1" } | { kind: "not-test-editor2" };
-    |           ^
-  2 |
-  3 | // Note: there are trailing whitespace in this file
-  4 | \`"
-`;
-
-exports[`trailing.js format 1`] = `
+exports[`snippet: #0 format 1`] = `
 ====================================options=====================================
 parsers: ["babel", "flow", "typescript"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
-export type Result<T, V> = | { kind: "not-test-editor1" } | { kind: "not-test-editor2" };
-
-// Note: there are trailing whitespace in this file
 \`
    
    
@@ -43,13 +13,7 @@ export type Result<T, V> = | { kind: "not-test-editor1" } | { kind: "not-test-ed
     
     
 \`;
-
 =====================================output=====================================
-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/format/js/trailing-whitespace/jsfmt.spec.js b/tests/format/js/trailing-whitespace/jsfmt.spec.js
--- a/tests/format/js/trailing-whitespace/jsfmt.spec.js
+++ b/tests/format/js/trailing-whitespace/jsfmt.spec.js
@@ -1,3 +1,7 @@
-run_spec(import.meta, ["babel", "flow", "typescript"], {
-  errors: { acorn: true, espree: true, meriyah: true },
-});
+run_spec(
+  {
+    importMeta: import.meta,
+    snippets: ["`\n   \n   \n` + `\n    \n    \n`;"],
+  },
+  ["babel", "flow", "typescript"]
+);
diff --git a/tests/format/js/trailing-whitespace/trailing.js b/tests/format/js/trailing-whitespace/trailing.js
deleted file mode 100644
--- a/tests/format/js/trailing-whitespace/trailing.js
+++ /dev/null
@@ -1,10 +0,0 @@
-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/format/jsx/expression-with-types/__snapshots__/jsfmt.spec.js.snap b/tests/format/jsx/expression-with-types/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
--- /dev/null
+++ b/tests/format/jsx/expression-with-types/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,225 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`expression.js - {"singleQuote":false,"jsxSingleQuote":false} format 1`] = `
+====================================options=====================================
+jsxSingleQuote: false
+parsers: ["flow", "typescript"]
+printWidth: 80
+singleQuote: false
+                                                                                | printWidth
+=====================================input======================================
+<Component
+  onChange={(
+    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value)
+    });
+  }}
+/>;
+
+<Component>
+  {(
+    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value)
+    });
+  }}
+</Component>;
+
+=====================================output=====================================
+<Component
+  onChange={(
+    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value),
+    });
+  }}
+/>;
+
+<Component>
+  {(
+    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value),
+    });
+  }}
+</Component>;
+
+================================================================================
+`;
+
+exports[`expression.js - {"singleQuote":false,"jsxSingleQuote":true} format 1`] = `
+====================================options=====================================
+jsxSingleQuote: true
+parsers: ["flow", "typescript"]
+printWidth: 80
+singleQuote: false
+                                                                                | printWidth
+=====================================input======================================
+<Component
+  onChange={(
+    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value)
+    });
+  }}
+/>;
+
+<Component>
+  {(
+    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value)
+    });
+  }}
+</Component>;
+
+=====================================output=====================================
+<Component
+  onChange={(
+    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value),
+    });
+  }}
+/>;
+
+<Component>
+  {(
+    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value),
+    });
+  }}
+</Component>;
+
+================================================================================
+`;
+
+exports[`expression.js - {"singleQuote":true,"jsxSingleQuote":false} format 1`] = `
+====================================options=====================================
+jsxSingleQuote: false
+parsers: ["flow", "typescript"]
+printWidth: 80
+singleQuote: true
+                                                                                | printWidth
+=====================================input======================================
+<Component
+  onChange={(
+    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value)
+    });
+  }}
+/>;
+
+<Component>
+  {(
+    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value)
+    });
+  }}
+</Component>;
+
+=====================================output=====================================
+<Component
+  onChange={(
+    key: 'possible_key_1' | 'possible_key_2' | 'possible_key_3',
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value),
+    });
+  }}
+/>;
+
+<Component>
+  {(
+    key: 'possible_key_1' | 'possible_key_2' | 'possible_key_3',
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value),
+    });
+  }}
+</Component>;
+
+================================================================================
+`;
+
+exports[`expression.js - {"singleQuote":true,"jsxSingleQuote":true} format 1`] = `
+====================================options=====================================
+jsxSingleQuote: true
+parsers: ["flow", "typescript"]
+printWidth: 80
+singleQuote: true
+                                                                                | printWidth
+=====================================input======================================
+<Component
+  onChange={(
+    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value)
+    });
+  }}
+/>;
+
+<Component>
+  {(
+    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value)
+    });
+  }}
+</Component>;
+
+=====================================output=====================================
+<Component
+  onChange={(
+    key: 'possible_key_1' | 'possible_key_2' | 'possible_key_3',
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value),
+    });
+  }}
+/>;
+
+<Component>
+  {(
+    key: 'possible_key_1' | 'possible_key_2' | 'possible_key_3',
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value),
+    });
+  }}
+</Component>;
+
+================================================================================
+`;
diff --git a/tests/format/jsx/expression-with-types/expression.js b/tests/format/jsx/expression-with-types/expression.js
new file mode 100644
--- /dev/null
+++ b/tests/format/jsx/expression-with-types/expression.js
@@ -0,0 +1,21 @@
+<Component
+  onChange={(
+    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value)
+    });
+  }}
+/>;
+
+<Component>
+  {(
+    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
+    value: string | Immutable.List<string>,
+  ) => {
+    this.setState({
+      updatedTask: this.state.updatedTask.set(key, value)
+    });
+  }}
+</Component>;
diff --git a/tests/format/jsx/expression-with-types/jsfmt.spec.js b/tests/format/jsx/expression-with-types/jsfmt.spec.js
new file mode 100644
--- /dev/null
+++ b/tests/format/jsx/expression-with-types/jsfmt.spec.js
@@ -0,0 +1,16 @@
+run_spec(import.meta, ["flow", "typescript"], {
+  singleQuote: false,
+  jsxSingleQuote: false,
+});
+run_spec(import.meta, ["flow", "typescript"], {
+  singleQuote: false,
+  jsxSingleQuote: true,
+});
+run_spec(import.meta, ["flow", "typescript"], {
+  singleQuote: true,
+  jsxSingleQuote: false,
+});
+run_spec(import.meta, ["flow", "typescript"], {
+  singleQuote: true,
+  jsxSingleQuote: true,
+});
diff --git a/tests/format/jsx/jsx/__snapshots__/jsfmt.spec.js.snap b/tests/format/jsx/jsx/__snapshots__/jsfmt.spec.js.snap
--- a/tests/format/jsx/jsx/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/jsx/jsx/__snapshots__/jsfmt.spec.js.snap
@@ -2276,39 +2276,6 @@ foo ? (
 ================================================================================
 `;
 
-exports[`expression.js - {"singleQuote":false,"jsxSingleQuote":false} [acorn] format 1`] = `
-"Unexpected token (87:8)
-  85 | <Component
-  86 |   onChange={(
-> 87 |     key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-     |        ^
-  88 |     value: string | Immutable.List<string>,
-  89 |   ) => {
-  90 |     this.setState({"
-`;
-
-exports[`expression.js - {"singleQuote":false,"jsxSingleQuote":false} [espree] format 1`] = `
-"Unexpected token : (87:8)
-  85 | <Component
-  86 |   onChange={(
-> 87 |     key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-     |        ^
-  88 |     value: string | Immutable.List<string>,
-  89 |   ) => {
-  90 |     this.setState({"
-`;
-
-exports[`expression.js - {"singleQuote":false,"jsxSingleQuote":false} [meriyah] format 1`] = `
-"Expected ')' (87:8)
-  85 | <Component
-  86 |   onChange={(
-> 87 |     key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-     |        ^
-  88 |     value: string | Immutable.List<string>,
-  89 |   ) => {
-  90 |     this.setState({"
-`;
-
 exports[`expression.js - {"singleQuote":false,"jsxSingleQuote":false} format 1`] = `
 ====================================options=====================================
 jsxSingleQuote: false
@@ -2401,17 +2368,6 @@ singleQuote: false
   }}
 />;
 
-<Component
-  onChange={(
-    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value)
-    });
-  }}
-/>;
-
 <BookingIntroPanel>
   {data => doLogClick("short", "short", data)}
 </BookingIntroPanel>;
@@ -2428,17 +2384,6 @@ singleQuote: false
   }}
 </BookingIntroPanel>;
 
-<Component>
-  {(
-    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value)
-    });
-  }}
-</Component>;
-
 <SuspendyTree>
   <div style={{ height: 200, overflow: "scroll" }}>
     {Array(20)
@@ -2538,17 +2483,6 @@ singleQuote: false
   }}
 />;
 
-<Component
-  onChange={(
-    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value),
-    });
-  }}
-/>;
-
 <BookingIntroPanel>
   {(data) => doLogClick("short", "short", data)}
 </BookingIntroPanel>;
@@ -2573,17 +2507,6 @@ singleQuote: false
   }}
 </BookingIntroPanel>;
 
-<Component>
-  {(
-    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value),
-    });
-  }}
-</Component>;
-
 <SuspendyTree>
   <div style={{ height: 200, overflow: "scroll" }}>
     {Array(20)
@@ -2597,39 +2520,6 @@ singleQuote: false
 ================================================================================
 `;
 
-exports[`expression.js - {"singleQuote":false,"jsxSingleQuote":true} [acorn] format 1`] = `
-"Unexpected token (87:8)
-  85 | <Component
-  86 |   onChange={(
-> 87 |     key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-     |        ^
-  88 |     value: string | Immutable.List<string>,
-  89 |   ) => {
-  90 |     this.setState({"
-`;
-
-exports[`expression.js - {"singleQuote":false,"jsxSingleQuote":true} [espree] format 1`] = `
-"Unexpected token : (87:8)
-  85 | <Component
-  86 |   onChange={(
-> 87 |     key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-     |        ^
-  88 |     value: string | Immutable.List<string>,
-  89 |   ) => {
-  90 |     this.setState({"
-`;
-
-exports[`expression.js - {"singleQuote":false,"jsxSingleQuote":true} [meriyah] format 1`] = `
-"Expected ')' (87:8)
-  85 | <Component
-  86 |   onChange={(
-> 87 |     key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-     |        ^
-  88 |     value: string | Immutable.List<string>,
-  89 |   ) => {
-  90 |     this.setState({"
-`;
-
 exports[`expression.js - {"singleQuote":false,"jsxSingleQuote":true} format 1`] = `
 ====================================options=====================================
 jsxSingleQuote: true
@@ -2722,17 +2612,6 @@ singleQuote: false
   }}
 />;
 
-<Component
-  onChange={(
-    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value)
-    });
-  }}
-/>;
-
 <BookingIntroPanel>
   {data => doLogClick("short", "short", data)}
 </BookingIntroPanel>;
@@ -2749,17 +2628,6 @@ singleQuote: false
   }}
 </BookingIntroPanel>;
 
-<Component>
-  {(
-    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value)
-    });
-  }}
-</Component>;
-
 <SuspendyTree>
   <div style={{ height: 200, overflow: "scroll" }}>
     {Array(20)
@@ -2859,17 +2727,6 @@ singleQuote: false
   }}
 />;
 
-<Component
-  onChange={(
-    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value),
-    });
-  }}
-/>;
-
 <BookingIntroPanel>
   {(data) => doLogClick("short", "short", data)}
 </BookingIntroPanel>;
@@ -2894,17 +2751,6 @@ singleQuote: false
   }}
 </BookingIntroPanel>;
 
-<Component>
-  {(
-    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value),
-    });
-  }}
-</Component>;
-
 <SuspendyTree>
   <div style={{ height: 200, overflow: "scroll" }}>
     {Array(20)
@@ -2918,39 +2764,6 @@ singleQuote: false
 ================================================================================
 `;
 
-exports[`expression.js - {"singleQuote":true,"jsxSingleQuote":false} [acorn] format 1`] = `
-"Unexpected token (87:8)
-  85 | <Component
-  86 |   onChange={(
-> 87 |     key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-     |        ^
-  88 |     value: string | Immutable.List<string>,
-  89 |   ) => {
-  90 |     this.setState({"
-`;
-
-exports[`expression.js - {"singleQuote":true,"jsxSingleQuote":false} [espree] format 1`] = `
-"Unexpected token : (87:8)
-  85 | <Component
-  86 |   onChange={(
-> 87 |     key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-     |        ^
-  88 |     value: string | Immutable.List<string>,
-  89 |   ) => {
-  90 |     this.setState({"
-`;
-
-exports[`expression.js - {"singleQuote":true,"jsxSingleQuote":false} [meriyah] format 1`] = `
-"Expected ')' (87:8)
-  85 | <Component
-  86 |   onChange={(
-> 87 |     key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-     |        ^
-  88 |     value: string | Immutable.List<string>,
-  89 |   ) => {
-  90 |     this.setState({"
-`;
-
 exports[`expression.js - {"singleQuote":true,"jsxSingleQuote":false} format 1`] = `
 ====================================options=====================================
 jsxSingleQuote: false
@@ -3043,17 +2856,6 @@ singleQuote: true
   }}
 />;
 
-<Component
-  onChange={(
-    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value)
-    });
-  }}
-/>;
-
 <BookingIntroPanel>
   {data => doLogClick("short", "short", data)}
 </BookingIntroPanel>;
@@ -3070,17 +2872,6 @@ singleQuote: true
   }}
 </BookingIntroPanel>;
 
-<Component>
-  {(
-    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value)
-    });
-  }}
-</Component>;
-
 <SuspendyTree>
   <div style={{ height: 200, overflow: "scroll" }}>
     {Array(20)
@@ -3180,17 +2971,6 @@ singleQuote: true
   }}
 />;
 
-<Component
-  onChange={(
-    key: 'possible_key_1' | 'possible_key_2' | 'possible_key_3',
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value),
-    });
-  }}
-/>;
-
 <BookingIntroPanel>
   {(data) => doLogClick('short', 'short', data)}
 </BookingIntroPanel>;
@@ -3215,17 +2995,6 @@ singleQuote: true
   }}
 </BookingIntroPanel>;
 
-<Component>
-  {(
-    key: 'possible_key_1' | 'possible_key_2' | 'possible_key_3',
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value),
-    });
-  }}
-</Component>;
-
 <SuspendyTree>
   <div style={{ height: 200, overflow: 'scroll' }}>
     {Array(20)
@@ -3239,39 +3008,6 @@ singleQuote: true
 ================================================================================
 `;
 
-exports[`expression.js - {"singleQuote":true,"jsxSingleQuote":true} [acorn] format 1`] = `
-"Unexpected token (87:8)
-  85 | <Component
-  86 |   onChange={(
-> 87 |     key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-     |        ^
-  88 |     value: string | Immutable.List<string>,
-  89 |   ) => {
-  90 |     this.setState({"
-`;
-
-exports[`expression.js - {"singleQuote":true,"jsxSingleQuote":true} [espree] format 1`] = `
-"Unexpected token : (87:8)
-  85 | <Component
-  86 |   onChange={(
-> 87 |     key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-     |        ^
-  88 |     value: string | Immutable.List<string>,
-  89 |   ) => {
-  90 |     this.setState({"
-`;
-
-exports[`expression.js - {"singleQuote":true,"jsxSingleQuote":true} [meriyah] format 1`] = `
-"Expected ')' (87:8)
-  85 | <Component
-  86 |   onChange={(
-> 87 |     key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-     |        ^
-  88 |     value: string | Immutable.List<string>,
-  89 |   ) => {
-  90 |     this.setState({"
-`;
-
 exports[`expression.js - {"singleQuote":true,"jsxSingleQuote":true} format 1`] = `
 ====================================options=====================================
 jsxSingleQuote: true
@@ -3364,17 +3100,6 @@ singleQuote: true
   }}
 />;
 
-<Component
-  onChange={(
-    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value)
-    });
-  }}
-/>;
-
 <BookingIntroPanel>
   {data => doLogClick("short", "short", data)}
 </BookingIntroPanel>;
@@ -3391,17 +3116,6 @@ singleQuote: true
   }}
 </BookingIntroPanel>;
 
-<Component>
-  {(
-    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value)
-    });
-  }}
-</Component>;
-
 <SuspendyTree>
   <div style={{ height: 200, overflow: "scroll" }}>
     {Array(20)
@@ -3501,17 +3215,6 @@ singleQuote: true
   }}
 />;
 
-<Component
-  onChange={(
-    key: 'possible_key_1' | 'possible_key_2' | 'possible_key_3',
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value),
-    });
-  }}
-/>;
-
 <BookingIntroPanel>
   {(data) => doLogClick('short', 'short', data)}
 </BookingIntroPanel>;
@@ -3536,17 +3239,6 @@ singleQuote: true
   }}
 </BookingIntroPanel>;
 
-<Component>
-  {(
-    key: 'possible_key_1' | 'possible_key_2' | 'possible_key_3',
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value),
-    });
-  }}
-</Component>;
-
 <SuspendyTree>
   <div style={{ height: 200, overflow: 'scroll' }}>
     {Array(20)
diff --git a/tests/format/jsx/jsx/expression.js b/tests/format/jsx/jsx/expression.js
--- a/tests/format/jsx/jsx/expression.js
+++ b/tests/format/jsx/jsx/expression.js
@@ -82,17 +82,6 @@
   }}
 />;
 
-<Component
-  onChange={(
-    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value)
-    });
-  }}
-/>;
-
 <BookingIntroPanel>
   {data => doLogClick("short", "short", data)}
 </BookingIntroPanel>;
@@ -109,17 +98,6 @@
   }}
 </BookingIntroPanel>;
 
-<Component>
-  {(
-    key: "possible_key_1" | "possible_key_2" | "possible_key_3",
-    value: string | Immutable.List<string>,
-  ) => {
-    this.setState({
-      updatedTask: this.state.updatedTask.set(key, value)
-    });
-  }}
-</Component>;
-
 <SuspendyTree>
   <div style={{ height: 200, overflow: "scroll" }}>
     {Array(20)
diff --git a/tests/format/jsx/jsx/jsfmt.spec.js b/tests/format/jsx/jsx/jsfmt.spec.js
--- a/tests/format/jsx/jsx/jsfmt.spec.js
+++ b/tests/format/jsx/jsx/jsfmt.spec.js
@@ -1,26 +1,16 @@
-const errors = {
-  acorn: ["expression.js"],
-  espree: ["expression.js"],
-  meriyah: ["expression.js"],
-};
-
 run_spec(import.meta, ["flow", "babel", "typescript"], {
   singleQuote: false,
   jsxSingleQuote: false,
-  errors,
 });
 run_spec(import.meta, ["flow", "babel", "typescript"], {
   singleQuote: false,
   jsxSingleQuote: true,
-  errors,
 });
 run_spec(import.meta, ["flow", "babel", "typescript"], {
   singleQuote: true,
   jsxSingleQuote: false,
-  errors,
 });
 run_spec(import.meta, ["flow", "babel", "typescript"], {
   singleQuote: true,
   jsxSingleQuote: true,
-  errors,
 });
diff --git a/tests/format/misc/errors/babel-flow/__snapshots__/jsfmt.spec.js.snap b/tests/format/misc/errors/babel-flow/__snapshots__/jsfmt.spec.js.snap
--- a/tests/format/misc/errors/babel-flow/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/misc/errors/babel-flow/__snapshots__/jsfmt.spec.js.snap
@@ -1,11 +1,11 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`async-arrow-invalid.js [babel] format 1`] = `
-"Type parameters must come after the async keyword, e.g. instead of \`<T> async (3:1)
+"Unterminated JSX contents. (3:19)
   1 | // https://github.com/babel/babel/pull/11979
   2 |
 > 3 | <T> async () => {}
-    | ^
+    |                   ^
   4 |"
 `;
 
diff --git a/tests/format/misc/errors/js/import-assertions/__snapshots__/jsfmt.spec.js.snap b/tests/format/misc/errors/js/import-assertions/__snapshots__/jsfmt.spec.js.snap
--- a/tests/format/misc/errors/js/import-assertions/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/misc/errors/js/import-assertions/__snapshots__/jsfmt.spec.js.snap
@@ -22,30 +22,30 @@ exports[`invalid-export-from.mjs [babel] format 1`] = `
 `;
 
 exports[`invalid-flow-type-import-1.mjs [babel] format 1`] = `
-"Unexpected token, expected "from" (1:20)
+"Unexpected token, expected "from" (1:13)
 > 1 | import type module foo from "./module.wasm";
-    |                    ^
+    |             ^
   2 |"
 `;
 
 exports[`invalid-flow-type-import-2.mjs [babel] format 1`] = `
-"An \`import module\` declaration can not use \`type\` or \`typeof\` keyword. (1:20)
+"Unexpected token, expected "from" (1:20)
 > 1 | import module type foo from "./module.wasm";
     |                    ^
   2 |"
 `;
 
 exports[`invalid-flow-typeof-import-1.mjs [babel] format 1`] = `
-"Unexpected token, expected "from" (1:22)
+"Unexpected token, expected "{" (1:8)
 > 1 | import typeof module foo from "./module.wasm";
-    |                      ^
+    |        ^
   2 |"
 `;
 
 exports[`invalid-flow-typeof-import-2.mjs [babel] format 1`] = `
-"An \`import module\` declaration can not use \`type\` or \`typeof\` keyword. (1:22)
+"Unexpected token, expected "{" (1:15)
 > 1 | import module typeof foo from "./module.wasm";
-    |                      ^
+    |               ^
   2 |"
 `;
 
@@ -85,14 +85,14 @@ exports[`invalid-namespace-import.mjs [babel] format 1`] = `
 `;
 
 exports[`invalid-ts-type-import-1.mts [babel] format 1`] = `
-"Unexpected token, expected "from" (1:20)
+"Unexpected token, expected "from" (1:13)
 > 1 | import type module foo from "./module.wasm";
-    |                    ^
+    |             ^
   2 |"
 `;
 
 exports[`invalid-ts-type-import-2.mts [babel] format 1`] = `
-"An \`import module\` declaration can not use \`type\` or \`typeof\` keyword. (1:20)
+"Unexpected token, expected "from" (1:20)
 > 1 | import module type foo from "./module.wasm";
     |                    ^
   2 |"
diff --git a/tests/format/misc/flow-babel-only/__snapshots__/jsfmt.spec.js.snap b/tests/format/misc/flow-babel-only/__snapshots__/jsfmt.spec.js.snap
--- a/tests/format/misc/flow-babel-only/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/misc/flow-babel-only/__snapshots__/jsfmt.spec.js.snap
@@ -2,7 +2,7 @@
 
 exports[`class_with_generics.js format 1`] = `
 ====================================options=====================================
-parsers: ["babel"]
+parsers: ["babel-flow"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
@@ -21,21 +21,19 @@ class Component extends React.Component/*:: <Props, State> */ {
 =====================================output=====================================
 import React from "react";
 
-/*:: type Props = {
+type Props = {
   foo?: ?string,
   bar: number,
-}; */
-
-/*:: type State = { baz: number }; */
-
-class Component extends React.Component /*:: <Props, State> */ {}
+};
+type State = { baz: number };
+class Component extends React.Component<Props, State> {}
 
 ================================================================================
 `;
 
 exports[`constructor-field-override.js format 1`] = `
 ====================================options=====================================
-parsers: ["babel"]
+parsers: ["babel-flow"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
@@ -55,7 +53,7 @@ class Foo {
 
 exports[`functions.js format 1`] = `
 ====================================options=====================================
-parsers: ["babel"]
+parsers: ["babel-flow"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
@@ -67,8 +65,8 @@ export function updateStoreFromURL(
 
 =====================================output=====================================
 export function updateStoreFromURL(
-  store /*: Store*/,
-  { search, hash } /*: {search: string, hash: string}*/,
+  store: Store,
+  { search, hash }: { search: string, hash: string },
 ) {}
 
 ================================================================================
diff --git a/tests/format/misc/flow-babel-only/jsfmt.spec.js b/tests/format/misc/flow-babel-only/jsfmt.spec.js
--- a/tests/format/misc/flow-babel-only/jsfmt.spec.js
+++ b/tests/format/misc/flow-babel-only/jsfmt.spec.js
@@ -1 +1 @@
-run_spec(import.meta, ["babel"]);
+run_spec(import.meta, ["babel-flow"]);
diff --git a/tests/format/typescript/decorators/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/decorators/__snapshots__/jsfmt.spec.js.snap
--- a/tests/format/typescript/decorators/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/typescript/decorators/__snapshots__/jsfmt.spec.js.snap
@@ -37,6 +37,28 @@ class Foo {
 ================================================================================
 `;
 
+exports[`comments.ts format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+class Something {
+    @Annotateme()
+    // comment
+    static property: Array<string>;
+}
+
+=====================================output=====================================
+class Something {
+  @Annotateme()
+  // comment
+  static property: Array<string>;
+}
+
+================================================================================
+`;
+
 exports[`decorator-type-assertion.ts format 1`] = `
 ====================================options=====================================
 parsers: ["typescript"]
@@ -419,3 +441,97 @@ class MyContainerComponent {
 
 ================================================================================
 `;
+
+exports[`mobx.ts format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+import {observable} from "mobx";
+
+@observer class OrderLine {
+  @observable price:number = 0;
+  @observable amount:number = 1;
+
+  constructor(price) {
+    this.price = price;
+  }
+
+  @computed get total() {
+    return this.price * this.amount;
+  }
+
+  @action.bound setPrice(price) {
+    this.price = price;
+  }
+
+  @computed
+  get total() {
+    return this.price * this.amount;
+  }
+
+  @action.bound
+  setPrice(price) {
+    this.price = price;
+  }
+
+  @computed @computed @computed @computed @computed @computed @computed get total() {
+    return this.price * this.amount;
+  }
+
+  @action handleDecrease = (event: React.ChangeEvent<HTMLInputElement>) => this.count--;
+
+  @action handleSomething = (event: React.ChangeEvent<HTMLInputElement>) => doSomething();
+}
+
+=====================================output=====================================
+import { observable } from "mobx";
+
+@observer
+class OrderLine {
+  @observable price: number = 0;
+  @observable amount: number = 1;
+
+  constructor(price) {
+    this.price = price;
+  }
+
+  @computed get total() {
+    return this.price * this.amount;
+  }
+
+  @action.bound setPrice(price) {
+    this.price = price;
+  }
+
+  @computed
+  get total() {
+    return this.price * this.amount;
+  }
+
+  @action.bound
+  setPrice(price) {
+    this.price = price;
+  }
+
+  @computed
+  @computed
+  @computed
+  @computed
+  @computed
+  @computed
+  @computed
+  get total() {
+    return this.price * this.amount;
+  }
+
+  @action handleDecrease = (event: React.ChangeEvent<HTMLInputElement>) =>
+    this.count--;
+
+  @action handleSomething = (event: React.ChangeEvent<HTMLInputElement>) =>
+    doSomething();
+}
+
+================================================================================
+`;
diff --git a/tests/format/typescript/decorators/comments.ts b/tests/format/typescript/decorators/comments.ts
new file mode 100644
--- /dev/null
+++ b/tests/format/typescript/decorators/comments.ts
@@ -0,0 +1,5 @@
+class Something {
+    @Annotateme()
+    // comment
+    static property: Array<string>;
+}
diff --git a/tests/format/typescript/decorators/mobx.ts b/tests/format/typescript/decorators/mobx.ts
new file mode 100644
--- /dev/null
+++ b/tests/format/typescript/decorators/mobx.ts
@@ -0,0 +1,36 @@
+import {observable} from "mobx";
+
+@observer class OrderLine {
+  @observable price:number = 0;
+  @observable amount:number = 1;
+
+  constructor(price) {
+    this.price = price;
+  }
+
+  @computed get total() {
+    return this.price * this.amount;
+  }
+
+  @action.bound setPrice(price) {
+    this.price = price;
+  }
+
+  @computed
+  get total() {
+    return this.price * this.amount;
+  }
+
+  @action.bound
+  setPrice(price) {
+    this.price = price;
+  }
+
+  @computed @computed @computed @computed @computed @computed @computed get total() {
+    return this.price * this.amount;
+  }
+
+  @action handleDecrease = (event: React.ChangeEvent<HTMLInputElement>) => this.count--;
+
+  @action handleSomething = (event: React.ChangeEvent<HTMLInputElement>) => doSomething();
+}