Repository: jlongster/prettier (js)

Issue:
Leave un-printable characters escaped
I love the idea of converting `'\uE###'` strings into the correct UTF8 character, but the following example doesn't really make sense:

before:

```js
/**
 * Web-driver keys
 */
const KeyboardKeyEnum = {

    /**
     * NULL key
     */
    NULL: '\uE000',


    /**
     * Break key
     */
    BREAK: '\uE001',

    /**
     * Pause key
     */
    PAUSE: '\uE00B',

    /**
     * Help key
     */
    HELP: '\uE002',

    /**
     * Clear key
     */
    CLEAR: '\uE005',


    /**
     * Backspace key
     */
    BACKSPACE: '\uE003',

    /**
     * Escape key
     */
    ESCAPE: '\uE00C',

    /**
     * Tab key
     */
    TAB: '\uE004',


    /**
     * Return key (not enter)
     */
    RETURN: '\uE006',

    /**
     * Enter key (not return)
     */
    ENTER: '\uE007',


    /**
     * Shift key
     */
    SHIFT: '\uE008',

    /**
     * Control key
     */
    CONTROL: '\uE009',

    /**
     * Alternate key
     */
    ALT: '\uE00A',


    /**
     * Space-bar key
     */
    SPACE: '\uE00D',


    /**
     * Page-up key
     */
    PAGE_UP: '\uE00E',

    /**
     * Page-down key
     */
    PAGE_DOWN: '\uE00F',


    /**
     * End key
     */
    END: '\uE010',

    /**
     * Home key
     */
    HOME: '\uE011',


    /**
     * Left-arrow key
     */
    LEFT_ARROW: '\uE012',

    /**
     * Up-arrow key
     */
    UP_ARROW: '\uE013',

    /**
     * Right-arrow key
     */
    RIGHT_ARROW: '\uE014',

    /**
     * Down-arrow key
     */
    DOWN_ARROW: '\uE015',


    /**
     * Insert key
     */
    INSERT: '\uE016',

    /**
     * Delete key
     */
    DELETE: '\uE017',


    /**
     * Semicolon key
     */
    SEMICOLON: '\uE018',

    /**
     * Equals key
     */
    EQUALS: '\uE019',


    /**
     * number 0 on number-pad
     */
    NUMPAD0: '\uE01A',

    /**
     * number 1 on number-pad
     */
    NUMPAD1: '\uE01B',

    /**
     * number 2 on number-pad
     */
    NUMPAD2: '\uE01C',

    /**
     * number 3 on number-pad
     */
    NUMPAD3: '\uE01D',

    /**
     * number 4 on number-pad
     */
    NUMPAD4: '\uE01E',

    /**
     * number 5 on number-pad
     */
    NUMPAD5: '\uE01F',

    /**
     * number 6 on number-pad
     */
    NUMPAD6: '\uE020',

    /**
     * number 7 on number-pad
     */
    NUMPAD7: '\uE021',

    /**
     * number 8 on number-pad
     */
    NUMPAD8: '\uE022',

    /**
     * number 9 on number-pad
     */
    NUMPAD9: '\uE023',


    /**
     * "*" key
     */
    MULTIPLY: '\uE024',

    /**
     * "+" key
     */
    ADD: '\uE025',

    /**
     * "-" key
     */
    SUBTRACT: '\uE027',

    /**
     * "/" key
     */
    DIVIDE: '\uE029',

    /**
     * Separator key (Locale independent)
     */
    SEPARATOR: '\uE026',

    /**
     * "." key
     */
    DECIMAL: '\uE028',


    /**
     * F1 key
     */
    F1: '\uE031',

    /**
     * F2 key
     */
    F2: '\uE032',

    /**
     * F3 key
     */
    F3: '\uE033',

    /**
     * F4 key
     */
    F4: '\uE034',

    /**
     * F5 key
     */
    F5: '\uE035',

    /**
     * F6 key
     */
    F6: '\uE036',

    /**
     * F7 key
     */
    F7: '\uE037',

    /**
     * F8 key
     */
    F8: '\uE038',

    /**
     * F9 key
     */
    F9: '\uE039',

    /**
     * F10 key
     */
    F10: '\uE03A',

    /**
     * F11 key
     */
    F11: '\uE03B',

    /**
     * F12 key
     */
    F12: '\uE03C',


    /**
     * "Windows" key
     */
    WINDOWS: '\uE03D',

    /**
     * "Command" (Apple) key
     */
    COMMAND: '\uE03D'
};

export default KeyboardKeyEnum;
```
after:

```js
/**
 * Web-driver keys
 */
const KeyboardKeyEnum = {
  /**
     * NULL key
     */
  NULL: '',
  /**
     * Break key
     */
  BREAK: '',
  /**
     * Pause key
     */
  PAUSE: '',
  /**
     * Help key
     */
  HELP: '',
  /**
     * Clear key
     */
  CLEAR: '',
  /**
     * Backspace key
     */
  BACKSPACE: '',
  /**
     * Escape key
     */
  ESCAPE: '',
  /**
     * Tab key
     */
  TAB: '',
  /**
     * Return key (not enter)
     */
  RETURN: '',
  /**
     * Enter key (not return)
     */
  ENTER: '',
  /**
     * Shift key
     */
  SHIFT: '',
  /**
     * Control key
     */
  CONTROL: '',
  /**
     * Alternate key
     */
  ALT: '',
  /**
     * Space-bar key
     */
  SPACE: '',
  /**
     * Page-up key
     */
  PAGE_UP: '',
  /**
     * Page-down key
     */
  PAGE_DOWN: '',
  /**
     * End key
     */
  END: '',
  /**
     * Home key
     */
  HOME: '',
  /**
     * Left-arrow key
     */
  LEFT_ARROW: '',
  /**
     * Up-arrow key
     */
  UP_ARROW: '',
  /**
     * Right-arrow key
     */
  RIGHT_ARROW: '',
  /**
     * Down-arrow key
     */
  DOWN_ARROW: '',
  /**
     * Insert key
     */
  INSERT: '',
  /**
     * Delete key
     */
  DELETE: '',
  /**
     * Semicolon key
     */
  SEMICOLON: '',
  /**
     * Equals key
     */
  EQUALS: '',
  /**
     * number 0 on number-pad
     */
  NUMPAD0: '',
  /**
     * number 1 on number-pad
     */
  NUMPAD1: '',
  /**
     * number 2 on number-pad
     */
  NUMPAD2: '',
  /**
     * number 3 on number-pad
     */
  NUMPAD3: '',
  /**
     * number 4 on number-pad
     */
  NUMPAD4: '',
  /**
     * number 5 on number-pad
     */
  NUMPAD5: '',
  /**
     * number 6 on number-pad
     */
  NUMPAD6: '',
  /**
     * number 7 on number-pad
     */
  NUMPAD7: '',
  /**
     * number 8 on number-pad
     */
  NUMPAD8: '',
  /**
     * number 9 on number-pad
     */
  NUMPAD9: '',
  /**
     * "*" key
     */
  MULTIPLY: '',
  /**
     * "+" key
     */
  ADD: '',
  /**
     * "-" key
     */
  SUBTRACT: '',
  /**
     * "/" key
     */
  DIVIDE: '',
  /**
     * Separator key (Locale independent)
     */
  SEPARATOR: '',
  /**
     * "." key
     */
  DECIMAL: '',
  /**
     * F1 key
     */
  F1: '',
  /**
     * F2 key
     */
  F2: '',
  /**
     * F3 key
     */
  F3: '',
  /**
     * F4 key
     */
  F4: '',
  /**
     * F5 key
     */
  F5: '',
  /**
     * F6 key
     */
  F6: '',
  /**
     * F7 key
     */
  F7: '',
  /**
     * F8 key
     */
  F8: '',
  /**
     * F9 key
     */
  F9: '',
  /**
     * F10 key
     */
  F10: '',
  /**
     * F11 key
     */
  F11: '',
  /**
     * F12 key
     */
  F12: '',
  /**
     * "Windows" key
     */
  WINDOWS: '',
  /**
     * "Command" (Apple) key
     */
  COMMAND: '',
};

export default KeyboardKeyEnum;

```

Pull request description:
Escape strings using jsesc
The current implementation with `JSON.stringify()` is clever but unfortunately generates incorrect JavaScript. Using `jsesc` seems like a better and safer option. https://github.com/mathiasbynens/jsesc It doesn't have any dependencies and is pretty small.

I opted for escaping all the non ascii characters, so we don't display emojis anymore. I don't think that the world is ready yet for having random unicode characters inside of source files, there still are so many parts of the toolchain that breaks with them. If we want to revert back on this decision, there's a `minimal` option on jsesc which only escapes values that need to in order to generate valid JavaScript file (assuming the encoding of the file is set to utf8).

Also, while working on React Native, we've seen that there is an optimization inside of jsc for js files that are all ascii: it doesn't do a copy for the conversion to ucs16.

Fixes #163

Patch:
diff --git a/README.md b/README.md
index a4a5b25af..d0d13fbbc 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
-# Prettier [![Build Status](https://travis-ci.org/jlongster/prettier.svg?branch=master)](https://travis-ci.org/jlongster/prettier)
+# Prettier 
 
 [![Gitter](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/jlongster/prettier)
+[![Build Status](https://travis-ci.org/jlongster/prettier.svg?branch=master)](https://travis-ci.org/jlongster/prettier)
+[![NPM version](https://img.shields.io/npm/v/prettier.svg)](https://www.npmjs.com/package/prettier)
 
 Prettier is an opinionated JavaScript formatter inspired by
 [refmt](https://facebook.github.io/reason/tools.html) with advanced
diff --git a/index.js b/index.js
index 1f7fed44d..546c60ac9 100644
--- a/index.js
+++ b/index.js
@@ -28,7 +28,11 @@ function format(text, opts) {
   let ast;
 
   if (opts.useFlowParser) {
-    ast = flowParser.parse(text);
+    ast = flowParser.parse(text, {
+      esproposal_class_instance_fields: true,
+      esproposal_class_static_fields: true,
+      esproposal_export_star_as: true,
+    });
     if (ast.errors.length > 0) {
       let msg = ast.errors[(0)].message + " on line " +
         ast.errors[(0)].loc.start.line;
diff --git a/package.json b/package.json
index 04c16ab22..5b682278e 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
     "esutils": "^2.0.2",
     "flow-parser": "^0.37.0",
     "get-stdin": "^5.0.1",
+    "jsesc": "^2.4.0",
     "minimist": "^1.2.0",
     "private": "^0.1.6"
   },
diff --git a/src/fast-path.js b/src/fast-path.js
index bcc19b505..af4392cb6 100644
--- a/src/fast-path.js
+++ b/src/fast-path.js
@@ -229,16 +229,26 @@ FPp.needsParens = function(assumeExpressionContext) {
 
   switch (node.type) {
     case "UpdateExpression":
-    case "UnaryExpression":
     case "SpreadElement":
     case "SpreadProperty":
       return parent.type === "MemberExpression" && name === "object" &&
         parent.object === node;
 
+    case "UnaryExpression":
+      switch (parent.type) {
+        case "UnaryExpression":
+          return node.operator === parent.operator &&
+            (node.operator === "+" || node.operator === "-");
+
+        case "MemberExpression":
+          return name === "object" && parent.object === node;
+      }
+
     case "BinaryExpression":
     case "LogicalExpression":
       switch (parent.type) {
         case "CallExpression":
+        case "NewExpression":
           return name === "callee" && parent.callee === node;
 
         case "UnaryExpression":
@@ -340,6 +350,7 @@ FPp.needsParens = function(assumeExpressionContext) {
         case "SpreadProperty":
         case "BinaryExpression":
         case "LogicalExpression":
+        case "NewExpression":
           return true;
 
         case "CallExpression":
@@ -360,6 +371,10 @@ FPp.needsParens = function(assumeExpressionContext) {
         return true;
       }
 
+      if (parent.type === "MemberExpression") {
+        return true;
+      }
+
       return isBinary(parent);
 
     case "ClassExpression":
diff --git a/src/printer.js b/src/printer.js
index b2cd3d04e..b8943c76a 100644
--- a/src/printer.js
+++ b/src/printer.js
@@ -25,6 +25,7 @@ var isObject = types.builtInTypes.object;
 var FastPath = require("./fast-path");
 var util = require("./util");
 var isIdentifierName = require("esutils").keyword.isIdentifierNameES6;
+var jsesc = require("jsesc");
 
 function Printer(originalOptions) {
   assert.ok(this instanceof Printer);
@@ -1870,6 +1871,8 @@ function printExportDeclaration(path, options, print) {
     }
 
     parts.push(";");
+  } else {
+    parts.push("{};");
   }
 
   return concat(parts);
@@ -2125,12 +2128,6 @@ function lastNonSpaceCharacter(lines) {
   } while (lines.prevPos(pos));
 }
 
-function swapQuotes(str) {
-  return str.replace(/['"]/g, function(m) {
-    return m === '"' ? "'" : '"';
-  });
-}
-
 function nodeStr(str, options) {
   isString.assert(str);
 
@@ -2145,11 +2142,10 @@ function nodeStr(str, options) {
     shouldUseSingleQuote = true;
   }
 
-  if (shouldUseSingleQuote) {
-    return swapQuotes(JSON.stringify(swapQuotes(str)));
-  } else {
-    return JSON.stringify(str);
-  }
+  return jsesc(str, {
+    quotes: shouldUseSingleQuote ? 'single' : 'double',
+    wrap: true
+  });
 }
 
 function isFirstStatement(path) {
diff --git a/src/util.js b/src/util.js
index bc51155ef..ae727036c 100644
--- a/src/util.js
+++ b/src/util.js
@@ -41,9 +41,7 @@ function expandLoc(parentNode, childNode) {
 }
 
 util.fixFaultyLocations = function(node, text) {
-  if (node.type === "TemplateLiteral") {
-    fixTemplateLiteral(node, text);
-  } else if (node.decorators) {
+  if (node.decorators) {
     // Expand the loc of the node responsible for printing the decorators
     // (here, the decorated node) so that it includes node.decorators.
     node.decorators.forEach(function(decorator) {
@@ -79,68 +77,6 @@ util.fixFaultyLocations = function(node, text) {
   }
 };
 
-function fixTemplateLiteral(node, text) {
-  assert.strictEqual(node.type, "TemplateLiteral");
-
-  if (node.quasis.length === 0) {
-    // If there are no quasi elements, then there is nothing to fix.
-    return;
-  }
-
-  // First we need to exclude the opening ` from the loc of the first
-  // quasi element, in case the parser accidentally decided to include it.
-  var afterLeftBackTickPos = locStart(node);
-  assert.strictEqual(text.charAt(afterLeftBackTickPos), "`");
-  assert.ok(afterLeftBackTickPos < text.length);
-  var firstQuasi = node.quasis[(0)];
-  if (locStart(firstQuasi) - afterLeftBackTickPos < 0) {
-    setLocStart(firstQuasi, afterLeftBackTickPos);
-  }
-
-  // Next we need to exclude the closing ` from the loc of the last quasi
-  // element, in case the parser accidentally decided to include it.
-  var rightBackTickPos = locEnd(node);
-  assert.ok(rightBackTickPos >= 0);
-  assert.strictEqual(text.charAt(rightBackTickPos), "`");
-  var lastQuasi = node.quasis[node.quasis.length - 1];
-  if (rightBackTickPos - locEnd(lastQuasi) < 0) {
-    setLocEnd(locEnd(lastQuasi), rightBackTickPos);
-  }
-
-  // Now we need to exclude ${ and } characters from the loc's of all
-  // quasi elements, since some parsers accidentally include them.
-  node.expressions.forEach(function(expr, i) {
-    // Rewind from the start loc over any whitespace and the ${ that
-    // precedes the expression. The position of the $ should be the
-    // same as the end of the preceding quasi element, but some
-    // parsers accidentally include the ${ in the loc of the quasi
-    // element.
-    var dollarCurlyPos = skipSpaces(text, locStart(expr) - 1, true);
-    if (
-      dollarCurlyPos - 1 >= 0 && text.charAt(dollarCurlyPos - 1) === "{" &&
-        dollarCurlyPos - 2 >= 0 &&
-        text.charAt(dollarCurlyPos - 2) === "$"
-    ) {
-      var quasiBefore = node.quasis[i];
-      if (dollarCurlyPos - locEnd(quasiBefore) < 0) {
-        setLocEnd(quasiBefore, dollarCurlyPos);
-      }
-    }
-
-    // Likewise, some parsers accidentally include the } that follows
-    // the expression in the loc of the following quasi element.
-    var rightCurlyPos = skipSpaces(text, locEnd(expr));
-    if (text.charAt(rightCurlyPos) === "}") {
-      assert.ok(rightCurlyPos + 1 < text.length);
-      // Now rightCurlyPos is technically the position just after the }.
-      var quasiAfter = node.quasis[i + 1];
-      if (locStart(quasiAfter) - rightCurlyPos < 0) {
-        setLocStart(locStart(quasiAfter), rightCurlyPos);
-      }
-    }
-  });
-}
-
 util.isExportDeclaration = function(node) {
   if (node)
     switch (node.type) {


Test patch:
diff --git a/tests/arrows/__snapshots__/jsfmt.spec.js.snap b/tests/arrows/__snapshots__/jsfmt.spec.js.snap
index f1b48892b..24c85d6b9 100644
--- a/tests/arrows/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/arrows/__snapshots__/jsfmt.spec.js.snap
@@ -25,6 +25,13 @@ var ident = <T>(x: T): T => x;
 "
 `;
 
+exports[`test arrow_function_expression.js 1`] = `
+"(a => {}).length
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+(a => {}).length;
+"
+`;
+
 exports[`test arrows.js 1`] = `
 "function selectBestEffortImageForWidth(
     maxWidth: number,
diff --git a/tests/arrows/arrow_function_expression.js b/tests/arrows/arrow_function_expression.js
new file mode 100644
index 000000000..ecea2142f
--- /dev/null
+++ b/tests/arrows/arrow_function_expression.js
@@ -0,0 +1,1 @@
+(a => {}).length
diff --git a/tests/export/__snapshots__/jsfmt.spec.js.snap b/tests/export/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000..0b506fe66
--- /dev/null
+++ b/tests/export/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,6 @@
+exports[`test empty.js 1`] = `
+"export {};
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+export {};
+"
+`;
diff --git a/tests/export/empty.js b/tests/export/empty.js
new file mode 100644
index 000000000..cb0ff5c3b
--- /dev/null
+++ b/tests/export/empty.js
@@ -0,0 +1,1 @@
+export {};
diff --git a/tests/export/jsfmt.spec.js b/tests/export/jsfmt.spec.js
new file mode 100644
index 000000000..989047bcc
--- /dev/null
+++ b/tests/export/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname);
diff --git a/tests/new_expression/__snapshots__/jsfmt.spec.js.snap b/tests/new_expression/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000..b59887491
--- /dev/null
+++ b/tests/new_expression/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,8 @@
+exports[`test new_expression.js 1`] = `
+"new (memoize.Cache || MapCache)
+new (typeof this == \"function\" ? this : Dict())
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+new (memoize.Cache || MapCache)();
+new (typeof this == \"function\" ? this : Dict())();
+"
+`;
diff --git a/tests/new_expression/jsfmt.spec.js b/tests/new_expression/jsfmt.spec.js
new file mode 100644
index 000000000..989047bcc
--- /dev/null
+++ b/tests/new_expression/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname);
diff --git a/tests/new_expression/new_expression.js b/tests/new_expression/new_expression.js
new file mode 100644
index 000000000..23166585a
--- /dev/null
+++ b/tests/new_expression/new_expression.js
@@ -0,0 +1,2 @@
+new (memoize.Cache || MapCache)
+new (typeof this == "function" ? this : Dict())
diff --git a/tests/quotes/__snapshots__/jsfmt.spec.js.snap b/tests/quotes/__snapshots__/jsfmt.spec.js.snap
index cbc3e7bd5..fe175ae9e 100644
--- a/tests/quotes/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/quotes/__snapshots__/jsfmt.spec.js.snap
@@ -70,8 +70,8 @@ exports[`test strings.js 1`] = `
 \"\\\\\";
 \"\\\\\";
 
-\"\\u0000\";
-\"🐶\";
+\"\\0\";
+\"\\uD83D\\uDC36\";
 "
 `;
 
@@ -117,7 +117,7 @@ exports[`test strings.js 2`] = `
 \'\\\\\';
 \'\\\\\';
 
-\'\\u0000\';
-\'🐶\';
+\'\\0\';
+\'\\uD83D\\uDC36\';
 "
 `;
diff --git a/tests/strings/__snapshots__/jsfmt.spec.js.snap b/tests/strings/__snapshots__/jsfmt.spec.js.snap
index 3e21efa76..b97984909 100644
--- a/tests/strings/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/strings/__snapshots__/jsfmt.spec.js.snap
@@ -37,8 +37,8 @@ exports[`test strings.js 1`] = `
   \"\'\\\"\",
   \"\\\\\",
   \"\\\\\",
-  \"\\u0000\",
-  \"🐶\"
+  \"\\0\",
+  \"\\uD83D\\uDC36\"
 ];
 "
 `;
diff --git a/tests/template/__snapshots__/jsfmt.spec.js.snap b/tests/template/__snapshots__/jsfmt.spec.js.snap
index 55258e7bd..70c3fb59a 100644
--- a/tests/template/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/template/__snapshots__/jsfmt.spec.js.snap
@@ -1,3 +1,32 @@
+exports[`test faulty-locations.js 1`] = `
+"var o = {
+  [\`key\`]: () => {
+    // Comment
+  }
+};
+
+var x = {
+  y: () => Relay.QL\`
+    query {
+      \${foo},
+      field,
+    }
+  \`
+};
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+var o = { [\`key\`]: () => {} };
+
+var x = {
+  y: () => Relay.QL\`
+    query {
+      \${foo},
+      field,
+    }
+  \`
+};
+"
+`;
+
 exports[`test template.js 1`] = `
 "/* @flow */
 
diff --git a/tests/template/faulty-locations.js b/tests/template/faulty-locations.js
new file mode 100644
index 000000000..15a8559c0
--- /dev/null
+++ b/tests/template/faulty-locations.js
@@ -0,0 +1,14 @@
+var o = {
+  [`key`]: () => {
+    // Comment
+  }
+};
+
+var x = {
+  y: () => Relay.QL`
+    query {
+      ${foo},
+      field,
+    }
+  `
+};
diff --git a/tests/urnary_expression/__snapshots__/jsfmt.spec.js.snap b/tests/urnary_expression/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000..bbc577738
--- /dev/null
+++ b/tests/urnary_expression/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,16 @@
+exports[`test urnary_expression.js 1`] = `
+"!!x
+x++
+x--;
+-+1;
+x + + + + 1;
+x + (+ (+ (+ 1)))
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+!!x;
+x++;
+x--;
+-+1;
+x + (+(+(+1)));
+x + (+(+(+1)));
+"
+`;
diff --git a/tests/urnary_expression/jsfmt.spec.js b/tests/urnary_expression/jsfmt.spec.js
new file mode 100644
index 000000000..989047bcc
--- /dev/null
+++ b/tests/urnary_expression/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname);
diff --git a/tests/urnary_expression/urnary_expression.js b/tests/urnary_expression/urnary_expression.js
new file mode 100644
index 000000000..1d22e1916
--- /dev/null
+++ b/tests/urnary_expression/urnary_expression.js
@@ -0,0 +1,6 @@
+!!x
+x++
+x--;
+-+1;
+x + + + + 1;
+x + (+ (+ (+ 1)))
diff --git a/tests_config/run_spec.js b/tests_config/run_spec.js
index 9be8940fe..78ed9ca07 100644
--- a/tests_config/run_spec.js
+++ b/tests_config/run_spec.js
@@ -76,7 +76,11 @@ function stripLocation(ast) {
 
 function parse(string) {
   const flowParser = require('flow-parser');
-  return stripLocation(flowParser.parse(string));
+  return stripLocation(flowParser.parse(string, {
+    esproposal_class_instance_fields: true,
+    esproposal_class_static_fields: true,
+    esproposal_export_star_as: true,
+  }));
 }
 
 function prettyprint(src, filename, options) {