Repository: prettier/prettier (JavaScript)
Date: 2019-10-15 04:27:55+00:00
Task type: Bug Fix

Issue:
[Typescript] Prettier removes question mark from optional computed class property
**Prettier 1.18.2**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuc0DOMAEAjAhgJ0wF5MByPfUgbgB0owAbXNNTAMQgk2Dsz8wDaFALoB+WlH6CAjGIlSB5XAC9ScugF86IADQgIABxgBLdMlAF8EAO4AFAgjTIQuAG4RjAE10hs+XGAA1nAwAMoGAcZQAObIMPgArnB6ABYwALYMAOopxvBoEWBwoY55xq55AJ7OYCw+UWhw+DC2-tHpuMgAZrgMjXoAVmgAHgBC-kEhobjpcAAyUXDdvf0gQ8OhUdEMcACKCRDwy33JIBH4jfjOMJUGcGhg+MZGPgZPsFleMCnIABwADHo3hBGll-AZnG97k1XEs9ABHA7wVqGJwoZgAWigcDgnlxPnwcERxkJrVw7U6SB6Jz0jXSxjiiVOaC2O32hyWVJWpxguGwn0832QACY9PFcMYGFsAMIQdIdZxQaBwkAJRoAFT5aOpjQ0GiAA)
```sh
--parser typescript
```

**Input:**
```tsx
const bar = 'bar';
class Foo {
    [bar]?;
    [1]?;
    ['baz']?;
}

```

**Output:**
```tsx
const bar = "bar";
class Foo {
  [bar];
  [1]?;
  ["baz"]?;
}

```

**Expected behavior:**
Prettier should preserve ? in output. Expected output:
```tsx
const bar = "bar";

class Foo {
  [bar]?;
  [1]?;
  ["baz"]?;
}

```

This only happens when the computed property is a variable.


Issue discussion:
minimum reproduct

**Prettier 1.18.2**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuc0DOMAEAjAhgJ0wF5MByPfUgbgB0owAbXNNTAMQgk2Dsz8wDaFALoB+WlAC+dEABoQEAA4wAlumSgC+CAHcACgQRpkIXADcIKgCZyQ2fLjABrODADKixyqgBzZDHwAVzh5AAsYAFsGAHVQlXg0TzA4NyN4lTN4gE8TMBZbbzQ4fBg9Bx8I3GQAM1wGIvkAKzQADwAhB2dXN1wIuAAZbzgauoaQZpa3bx8GOABFQIh4EfqQkE98IvwTGCzFODQwfBVlW0Vj2GjrGFDkAA4ABnlziCLoh0UTc4Pis2H5ACOi3gZSUxhQzAAtFA4HArHDbPg4ECVEiyrgKlUkLVVvIihEVP4gms0NNZgslsNsaM1jBcNgrlYbsgAEzyAK4FQMaYAYQgEUqJig0H+IECRQAKvTwTiipJJEA)
```sh
--parser typescript
```

**Input:**
```tsx
const bar = 'bar';
class Foo {
    [bar]?;
}

```

**Output:**
```tsx
const bar = "bar";
class Foo {
  [bar];
}

```

**Expected behavior:**
```tsx
const bar = "bar";
class Foo {
  [bar]?;
}

```

#6601 result

https://deploy-preview-6601--prettier.netlify.com/playground/#N4Igxg9gdgLgprEAuc0DOMAEAjAhgJ0wF5MByPfUgbgB0owAbXNNTAMQgk2Dsz8wDaFALoB+WlAC+IADQgIABxgBLdMlAF8EAO4AFAgjTIQuAG4RlAE1khs+XGADWcGAGUFD5VADmyGPgBXODkACxgAWwYAdRDleDQPMDhXQzjlUziAT2MwFhsvNDh8GF17b3DcZAAzXAZCuQArNAAPACF7JxdXXHC4ABkvOGra+pAm5tcvbwY4AEUAiHhhuuCQD3xC-GMYTIU4NDB8ZSUbBSPYKKsYEOQADgAGOTOIQqj7BWMz-aLTIbkARwW8FKiiMKGYAFooHA4JZYTZ8HBAcpEaVcOVKkgais5IVwso-IFVmgpjN5oshliRqsYLhsJdLNdkAAmOT+XDKBhTADCEHCFWMUGgfxAAUKABU6WDsaNTEEAJJQOGwVyHY4wACCStcOxmy0KkkkQA

Solution patch:
diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md
index 10b564aeee44..94da1ddcd127 100644
--- a/CHANGELOG.unreleased.md
+++ b/CHANGELOG.unreleased.md
@@ -44,6 +44,107 @@ const link = <a href="example.com">http://example.com</a>;
 
 -->
 
+#### TypeScript: Support for TypeScript 3.7 ([#6657] by [@cryrivers])
+
+Prettier 1.19 adds support for the features of the upcoming TypeScript 3.7 that introduce new syntax:
+
+- [Optional chaining](https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-rc/#optional-chaining)
+- [Nullish coalescing](https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-rc/#nullish-coalescing)
+- [Assertion functions](https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-rc/#assertion-functions)
+- [`declare` modifier on class fields](https://github.com/microsoft/TypeScript/pull/33509)
+
+**NOTE:** A dependency upgrade for TypeScript 3.7 led to dropping Node 6 support for direct installation from GitHub. Prettier installed from NPM stays compatible with Node 4.
+
+##### Optional Chaining
+
+<!-- prettier-ignore -->
+```ts
+// Input
+const longChain = obj?.a?.b?.c?.d?.e?.f?.g;
+const longChainCallExpression = obj.a?.(a,b,c).b?.(a,b,c).c?.(a,b,c).d?.(a,b,c).e?.(a,b,c).f?.(a,b,c)
+
+// Output (Prettier master)
+const longChain = obj?.a?.b?.c?.d?.e?.f?.g;
+const longChainCallExpression = obj
+  .a?.(a, b, c)
+  .b?.(a, b, c)
+  .c?.(a, b, c)
+  .d?.(a, b, c)
+  .e?.(a, b, c)
+  .f?.(a, b, c);
+```
+
+##### Nullish Coalescing
+
+<!-- prettier-ignore -->
+```ts
+// Input
+const cond = null;
+const result = cond??'a';
+const longChain = cond??cond??cond??'b';
+
+// Output (Prettier master)
+const cond = null;
+const result = cond ?? "a";
+const longChain = cond ?? cond ?? cond ?? "b";
+```
+
+##### Assertion Functions
+
+<!-- prettier-ignore -->
+```ts
+// Input
+function assertsString(x: any): asserts x {console.assert(typeof x === 'string');}
+function assertsStringWithGuard(x: any): asserts x is string {console.assert(typeof x === 'string');}
+
+// Output (Prettier master)
+function assertsString(x: any): asserts x {
+  console.assert(typeof x === "string");
+}
+function assertsStringWithGuard(x: any): asserts x is string {
+  console.assert(typeof x === "string");
+}
+```
+
+##### `declare` Modifier on Class Fields
+
+<!-- prettier-ignore -->
+```ts
+// Input
+class B {p: number;}
+class C extends B {declare p: 256 | 1000;}
+
+// Output (Prettier master)
+class B {
+  p: number;
+}
+class C extends B {
+  declare p: 256 | 1000;
+}
+```
+
+#### TypeScript: Prettier removed `?` from optional computed class fields ([#6657] by [@cryrivers])
+
+Still happens if the field key is a complex expression, but has been fixed in this case:
+
+<!-- prettier-ignore -->
+```ts
+// Input
+class Foo {
+  [bar]?: number;
+}
+
+// Output (Prettier stable)
+class Foo {
+  [bar]: number;
+}
+
+// Output (Prettier master)
+class Foo {
+  [bar]?: number;
+}
+```
+
 #### API: Add `resolveConfig` option to `getFileInfo()` ([#6666] by [@kaicataldo])
 
 Add a `resolveConfig: boolean` option to `prettier.getFileInfo()` that, when set to `true`, will resolve the configuration for the given file path. This allows consumers to take any overridden parsers into account.
@@ -52,6 +153,7 @@ Add a `resolveConfig: boolean` option to `prettier.getFileInfo()` that, when set
 
 <!-- prettier-ignore -->
 ```js
+// Input
 const addOne = add(1, ?); // apply from the left
 addOne(2); // 3
 
@@ -1168,6 +1270,7 @@ new Map([
 ```
 
 [#5682]: https://github.com/prettier/prettier/pull/5682
+[#6657]: https://github.com/prettier/prettier/pull/6657
 [#5910]: https://github.com/prettier/prettier/pull/5910
 [#6033]: https://github.com/prettier/prettier/pull/6033
 [#6186]: https://github.com/prettier/prettier/pull/6186
@@ -1224,4 +1327,5 @@ new Map([
 [@selvazhagan]: https://github.com/selvazhagan
 [@chadian]: https://github.com/chadian
 [@kaicataldo]: https://github.com/kaicataldo
+[@cryrivers]: https://github.com/Cryrivers
 [@voithos]: https://github.com/voithos
diff --git a/package.json b/package.json
index 851474d86dc7..3114cb470b37 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
   "license": "MIT",
   "main": "./index.js",
   "engines": {
-    "node": ">=6"
+    "node": ">=8"
   },
   "dependencies": {
     "@angular/compiler": "8.2.7",
@@ -19,7 +19,7 @@
     "@babel/parser": "7.6.3",
     "@glimmer/syntax": "0.41.0",
     "@iarna/toml": "2.2.3",
-    "@typescript-eslint/typescript-estree": "1.13.0",
+    "@typescript-eslint/typescript-estree": "2.5.1-alpha.5",
     "angular-estree-parser": "1.1.5",
     "angular-html-parser": "1.2.0",
     "camelcase": "5.3.1",
@@ -67,7 +67,7 @@
     "resolve": "1.12.0",
     "semver": "6.3.0",
     "string-width": "3.1.0",
-    "typescript": "3.6.4",
+    "typescript": "3.7.1-rc",
     "unicode-regex": "3.0.0",
     "unified": "6.1.6",
     "vnopts": "1.0.2",
diff --git a/src/common/util.js b/src/common/util.js
index 5db0e0ad0108..582f76eed2a4 100644
--- a/src/common/util.js
+++ b/src/common/util.js
@@ -342,6 +342,7 @@ function startsWithNoLookaheadToken(node, forbidFunctionClassAndDoExpr) {
     case "ObjectExpression":
       return true;
     case "MemberExpression":
+    case "OptionalMemberExpression":
       return startsWithNoLookaheadToken(
         node.object,
         forbidFunctionClassAndDoExpr
@@ -353,6 +354,7 @@ function startsWithNoLookaheadToken(node, forbidFunctionClassAndDoExpr) {
       }
       return startsWithNoLookaheadToken(node.tag, forbidFunctionClassAndDoExpr);
     case "CallExpression":
+    case "OptionalCallExpression":
       if (node.callee.type === "FunctionExpression") {
         // IIFEs are always already parenthesized
         return false;
diff --git a/src/language-js/comments.js b/src/language-js/comments.js
index cf264945316e..6f339594189d 100644
--- a/src/language-js/comments.js
+++ b/src/language-js/comments.js
@@ -564,6 +564,7 @@ function handleCommentInEmptyParens(text, enclosingNode, comment, options) {
       enclosingNode.type === "ObjectMethod") &&
       enclosingNode.params.length === 0) ||
       ((enclosingNode.type === "CallExpression" ||
+        enclosingNode.type === "OptionalCallExpression" ||
         enclosingNode.type === "NewExpression") &&
         enclosingNode.arguments.length === 0))
   ) {
@@ -686,7 +687,8 @@ function handleBreakAndContinueStatementComments(enclosingNode, comment) {
 function handleCallExpressionComments(precedingNode, enclosingNode, comment) {
   if (
     enclosingNode &&
-    enclosingNode.type === "CallExpression" &&
+    (enclosingNode.type === "CallExpression" ||
+      enclosingNode.type === "OptionalCallExpression") &&
     precedingNode &&
     enclosingNode.callee === precedingNode &&
     enclosingNode.arguments.length > 0
diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js
index c8596ecfb650..d8201676109f 100644
--- a/src/language-js/needs-parens.js
+++ b/src/language-js/needs-parens.js
@@ -254,14 +254,16 @@ function needsParens(path, options) {
           return true;
 
         case "MemberExpression":
-          return name === "object" && parent.object === node;
+        case "OptionalMemberExpression":
+          return name === "object";
 
         case "TaggedTemplateExpression":
           return true;
 
         case "NewExpression":
         case "CallExpression":
-          return name === "callee" && parent.callee === node;
+        case "OptionalCallExpression":
+          return name === "callee";
 
         case "BinaryExpression":
           return parent.operator === "**" && name === "left";
@@ -306,7 +308,8 @@ function needsParens(path, options) {
 
         case "CallExpression":
         case "NewExpression":
-          return name === "callee" && parent.callee === node;
+        case "OptionalCallExpression":
+          return name === "callee";
 
         case "ClassExpression":
         case "ClassDeclaration":
@@ -327,7 +330,7 @@ function needsParens(path, options) {
 
         case "MemberExpression":
         case "OptionalMemberExpression":
-          return name === "object" && parent.object === node;
+          return name === "object";
 
         case "AssignmentExpression":
           return (
@@ -427,15 +430,16 @@ function needsParens(path, options) {
         case "TSAsExpression":
         case "TSNonNullExpression":
         case "BindExpression":
-        case "OptionalMemberExpression":
           return true;
 
         case "MemberExpression":
-          return parent.object === node;
+        case "OptionalMemberExpression":
+          return name === "object";
 
         case "NewExpression":
         case "CallExpression":
-          return parent.callee === node;
+        case "OptionalCallExpression":
+          return name === "callee";
 
         case "ConditionalExpression":
           return parent.test === node;
@@ -591,18 +595,19 @@ function needsParens(path, options) {
         case "TypeCastExpression":
         case "TSAsExpression":
         case "TSNonNullExpression":
-        case "OptionalMemberExpression":
           return true;
 
         case "NewExpression":
         case "CallExpression":
-          return name === "callee" && parent.callee === node;
+        case "OptionalCallExpression":
+          return name === "callee";
 
         case "ConditionalExpression":
           return name === "test" && parent.test === node;
 
         case "MemberExpression":
-          return name === "object" && parent.object === node;
+        case "OptionalMemberExpression":
+          return name === "object";
 
         default:
           return false;
@@ -612,7 +617,10 @@ function needsParens(path, options) {
       switch (parent.type) {
         case "NewExpression":
         case "CallExpression":
-          return name === "callee"; // Not strictly necessary, but it's clearer to the reader if IIFEs are wrapped in parentheses.
+        case "OptionalCallExpression":
+          // Not always necessary, but it's clearer to the reader if IIFEs are wrapped in parentheses.
+          // Is necessary if it is `expression` of `ExpressionStatement`.
+          return name === "callee";
         case "TaggedTemplateExpression":
           return true; // This is basically a kind of IIFE.
         default:
@@ -621,13 +629,13 @@ function needsParens(path, options) {
 
     case "ArrowFunctionExpression":
       switch (parent.type) {
-        case "CallExpression":
-          return name === "callee";
-
         case "NewExpression":
+        case "CallExpression":
+        case "OptionalCallExpression":
           return name === "callee";
 
         case "MemberExpression":
+        case "OptionalMemberExpression":
           return name === "object";
 
         case "TSAsExpression":
@@ -656,23 +664,32 @@ function needsParens(path, options) {
       }
 
     case "OptionalMemberExpression":
-      return parent.type === "MemberExpression";
-
+    case "OptionalCallExpression":
+      if (
+        ((parent.type === "MemberExpression" && name === "object") ||
+          (parent.type === "CallExpression" && name === "callee")) &&
+        // workaround for https://github.com/facebook/flow/issues/8159
+        !(options.parser === "flow" && parent.range[0] === node.range[0])
+      ) {
+        return true;
+      }
+    // fallthrough
     case "CallExpression":
     case "MemberExpression":
     case "TaggedTemplateExpression":
     case "TSNonNullExpression":
       if (
         (parent.type === "BindExpression" || parent.type === "NewExpression") &&
-        name === "callee" &&
-        parent.callee === node
+        name === "callee"
       ) {
         let object = node;
         while (object) {
           switch (object.type) {
             case "CallExpression":
+            case "OptionalCallExpression":
               return true;
             case "MemberExpression":
+            case "OptionalMemberExpression":
             case "BindExpression":
               object = object.object;
               break;
@@ -692,20 +709,14 @@ function needsParens(path, options) {
       return false;
 
     case "BindExpression":
-      if (
-        (parent.type === "BindExpression" &&
-          name === "callee" &&
-          parent.callee === node) ||
-        (parent.type === "MemberExpression" &&
-          name === "object" &&
-          parent.object === node) ||
-        (parent.type === "NewExpression" &&
-          name === "callee" &&
-          parent.callee === node)
-      ) {
-        return true;
-      }
-      return false;
+      return (
+        ((parent.type === "BindExpression" ||
+          parent.type === "NewExpression") &&
+          name === "callee") ||
+        ((parent.type === "MemberExpression" ||
+          parent.type === "OptionalMemberExpression") &&
+          name === "object")
+      );
     case "NGPipeExpression":
       if (
         parent.type === "NGRoot" ||
@@ -725,25 +736,27 @@ function needsParens(path, options) {
     case "JSXFragment":
     case "JSXElement":
       return (
-        parent.type !== "ArrayExpression" &&
-        parent.type !== "ArrowFunctionExpression" &&
-        parent.type !== "AssignmentExpression" &&
-        parent.type !== "AssignmentPattern" &&
-        parent.type !== "BinaryExpression" &&
-        parent.type !== "CallExpression" &&
-        parent.type !== "ConditionalExpression" &&
-        parent.type !== "ExpressionStatement" &&
-        parent.type !== "JsExpressionRoot" &&
-        parent.type !== "JSXAttribute" &&
-        parent.type !== "JSXElement" &&
-        parent.type !== "JSXExpressionContainer" &&
-        parent.type !== "JSXFragment" &&
-        parent.type !== "LogicalExpression" &&
-        parent.type !== "ObjectProperty" &&
-        parent.type !== "Property" &&
-        parent.type !== "ReturnStatement" &&
-        parent.type !== "TypeCastExpression" &&
-        parent.type !== "VariableDeclarator"
+        name === "callee" ||
+        (parent.type !== "ArrayExpression" &&
+          parent.type !== "ArrowFunctionExpression" &&
+          parent.type !== "AssignmentExpression" &&
+          parent.type !== "AssignmentPattern" &&
+          parent.type !== "BinaryExpression" &&
+          parent.type !== "CallExpression" &&
+          parent.type !== "ConditionalExpression" &&
+          parent.type !== "ExpressionStatement" &&
+          parent.type !== "JsExpressionRoot" &&
+          parent.type !== "JSXAttribute" &&
+          parent.type !== "JSXElement" &&
+          parent.type !== "JSXExpressionContainer" &&
+          parent.type !== "JSXFragment" &&
+          parent.type !== "LogicalExpression" &&
+          parent.type !== "ObjectProperty" &&
+          parent.type !== "OptionalCallExpression" &&
+          parent.type !== "Property" &&
+          parent.type !== "ReturnStatement" &&
+          parent.type !== "TypeCastExpression" &&
+          parent.type !== "VariableDeclarator")
       );
   }
 
diff --git a/src/language-js/parser-typescript.js b/src/language-js/parser-typescript.js
index 8f99112f8b62..35401525d5fe 100644
--- a/src/language-js/parser-typescript.js
+++ b/src/language-js/parser-typescript.js
@@ -43,10 +43,7 @@ function tryParseTypeScript(text, jsx) {
     tokens: true,
     comment: true,
     useJSXTextNode: true,
-    jsx,
-    // Override logger function with noop,
-    // to avoid unsupported version errors being logged
-    loggerFn: () => {}
+    jsx
   });
 }
 
diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js
index 0696c55c095f..4bbb919341cf 100644
--- a/src/language-js/printer-estree.js
+++ b/src/language-js/printer-estree.js
@@ -570,7 +570,9 @@ function printPathNoParens(path, options, print, args) {
       //     c
       //   ).call()
       if (
-        (parent.type === "CallExpression" && parent.callee === n) ||
+        ((parent.type === "CallExpression" ||
+          parent.type === "OptionalCallExpression") &&
+          parent.callee === n) ||
         parent.type === "UnaryExpression" ||
         ((parent.type === "MemberExpression" ||
           parent.type === "OptionalMemberExpression") &&
@@ -596,7 +598,8 @@ function printPathNoParens(path, options, print, args) {
         (n !== parent.body && parent.type === "ForStatement") ||
         (parent.type === "ConditionalExpression" &&
           (parentParent.type !== "ReturnStatement" &&
-            parentParent.type !== "CallExpression"));
+            parentParent.type !== "CallExpression" &&
+            parentParent.type !== "OptionalCallExpression"));
 
       const shouldIndentIfInlining =
         parent.type === "AssignmentExpression" ||
@@ -2350,6 +2353,9 @@ function printPathNoParens(path, options, print, args) {
       if (n.accessibility) {
         parts.push(n.accessibility + " ");
       }
+      if (n.declare) {
+        parts.push("declare ");
+      }
       if (n.static) {
         parts.push("static ");
       }
@@ -3177,9 +3183,11 @@ function printPathNoParens(path, options, print, args) {
     }
     case "TSTypePredicate":
       return concat([
+        n.asserts ? "asserts " : "",
         path.call(print, "parameterName"),
-        " is ",
-        path.call(print, "typeAnnotation")
+        n.typeAnnotation
+          ? concat([" is ", path.call(print, "typeAnnotation")])
+          : ""
       ]);
     case "TSNonNullExpression":
       return concat([path.call(print, "expression"), "!"]);
@@ -5447,11 +5455,17 @@ function maybeWrapJSXElementInParens(path, elem, options) {
     return elem;
   }
 
-  const shouldBreak = matchAncestorTypes(path, [
-    "ArrowFunctionExpression",
-    "CallExpression",
-    "JSXExpressionContainer"
-  ]);
+  const shouldBreak =
+    matchAncestorTypes(path, [
+      "ArrowFunctionExpression",
+      "CallExpression",
+      "JSXExpressionContainer"
+    ]) ||
+    matchAncestorTypes(path, [
+      "ArrowFunctionExpression",
+      "OptionalCallExpression",
+      "JSXExpressionContainer"
+    ]);
 
   const needsParens = pathNeedsParens(path, options);
 
@@ -5816,7 +5830,8 @@ function willPrintOwnComments(path /*, options */) {
       (isJSXNode(node) ||
         hasFlowShorthandAnnotationComment(node) ||
         (parent &&
-          parent.type === "CallExpression" &&
+          (parent.type === "CallExpression" ||
+            parent.type === "OptionalCallExpression") &&
           (hasFlowAnnotationComment(node.leadingComments) ||
             hasFlowAnnotationComment(node.trailingComments))))) ||
       (parent &&
diff --git a/src/language-js/utils.js b/src/language-js/utils.js
index cd60e37857f9..8666b6eb06aa 100644
--- a/src/language-js/utils.js
+++ b/src/language-js/utils.js
@@ -425,25 +425,29 @@ function isSimpleTemplateLiteral(node) {
 
     // Allow `a.b.c`, `a.b[c]`, and `this.x.y`
     if (
-      (expr.type === "MemberExpression" ||
-        expr.type === "OptionalMemberExpression") &&
-      (expr.property.type === "Identifier" || expr.property.type === "Literal")
+      expr.type === "MemberExpression" ||
+      expr.type === "OptionalMemberExpression"
     ) {
-      let ancestor = expr;
+      let head = expr;
       while (
-        ancestor.type === "MemberExpression" ||
-        ancestor.type === "OptionalMemberExpression"
+        head.type === "MemberExpression" ||
+        head.type === "OptionalMemberExpression"
       ) {
-        ancestor = ancestor.object;
-        if (ancestor.comments) {
+        if (
+          head.property.type !== "Identifier" &&
+          head.property.type !== "Literal" &&
+          head.property.type !== "StringLiteral" &&
+          head.property.type !== "NumericLiteral"
+        ) {
+          return false;
+        }
+        head = head.object;
+        if (head.comments) {
           return false;
         }
       }
 
-      if (
-        ancestor.type === "Identifier" ||
-        ancestor.type === "ThisExpression"
-      ) {
+      if (head.type === "Identifier" || head.type === "ThisExpression") {
         return true;
       }
 
diff --git a/yarn.lock b/yarn.lock
index 8c91d83446ec..e22dc9ab5b81 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -975,13 +975,16 @@
   dependencies:
     "@types/yargs-parser" "*"
 
-"@typescript-eslint/typescript-estree@1.13.0":
-  version "1.13.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz#8140f17d0f60c03619798f1d628b8434913dc32e"
-  integrity sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==
+"@typescript-eslint/typescript-estree@2.5.1-alpha.5":
+  version "2.5.1-alpha.5"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.5.1-alpha.5.tgz#7dc6a29bc55a89640378948894039933c9c370ea"
+  integrity sha512-R0bXoqvgLw2epTtCRjZZUo+vwGFqRqcQ54kd8Dcm+lVY7Mxp5Wf9CDunojkB2EGwwG0olbihJIRQI1GZaewBtg==
   dependencies:
+    debug "^4.1.1"
+    glob "^7.1.4"
+    is-glob "^4.0.1"
     lodash.unescape "4.0.1"
-    semver "5.5.0"
+    semver "^6.3.0"
 
 "@webassemblyjs/ast@1.8.5":
   version "1.8.5"
@@ -2351,7 +2354,7 @@ debug@^3.1.0:
   dependencies:
     ms "2.0.0"
 
-debug@^4.0.1:
+debug@^4.0.1, debug@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
   integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
@@ -5254,6 +5257,7 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
 normalize-path@3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+  integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
 
 normalize-path@^2.0.1, normalize-path@^2.1.1:
   version "2.1.1"
@@ -6477,11 +6481,7 @@ schema-utils@^2.4.1:
   version "5.6.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
 
-semver@5.5.0:
-  version "5.5.0"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
-
-semver@6.3.0, semver@^6.0.0, semver@^6.1.2:
+semver@6.3.0, semver@^6.0.0, semver@^6.1.2, semver@^6.3.0:
   version "6.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
@@ -7161,10 +7161,10 @@ typedarray@^0.0.6:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
 
-typescript@3.6.4:
-  version "3.6.4"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d"
-  integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==
+typescript@3.7.1-rc:
+  version "3.7.1-rc"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.1-rc.tgz#2054b872d67f8dc732e36c1df397f9327f37ada0"
+  integrity sha512-2rMtWppLsaPvmpXsoIAXWDBQVnJMw1ITGGSnidMuayLj9iCmMRT69ncKZw/Mk5rXfJkilApKucWQZxproALoRw==
 
 uglify-js@^3.1.4:
   version "3.6.0"


Test patch:
diff --git a/tests/comments/__snapshots__/jsfmt.spec.js.snap b/tests/comments/__snapshots__/jsfmt.spec.js.snap
index 8782461805bf..34cf5455a6f0 100644
--- a/tests/comments/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/comments/__snapshots__/jsfmt.spec.js.snap
@@ -325,6 +325,11 @@ React.render( // Warm any cache
   container
 );
 
+render?.( // Warm any cache
+  <ChildUpdates renderAnchor={true} anchorClassOn={true} />,
+  container
+);
+
 =====================================output=====================================
 render(
   // Warm any cache
@@ -338,6 +343,12 @@ React.render(
   container
 );
 
+render?.(
+  // Warm any cache
+  <ChildUpdates renderAnchor={true} anchorClassOn={true} />,
+  container
+);
+
 ================================================================================
 `;
 
@@ -357,6 +368,7 @@ function d() {
 }
 new Thing(/* dangling */);
 Thing(/* dangling */);
+Thing?.(/* dangling */);
 declare class Foo extends Qux<string> {/* dangling */}
 export /* dangling */{};
 
@@ -375,6 +387,7 @@ function d() {
 }
 new Thing(/* dangling */);
 Thing(/* dangling */);
+Thing?.(/* dangling */);
 declare class Foo extends Qux<string> {
   /* dangling */
 }
diff --git a/tests/comments/call_comment.js b/tests/comments/call_comment.js
index ff2ea0eaf738..ac4af4132923 100644
--- a/tests/comments/call_comment.js
+++ b/tests/comments/call_comment.js
@@ -7,3 +7,8 @@ React.render( // Warm any cache
   <ChildUpdates renderAnchor={true} anchorClassOn={true} />,
   container
 );
+
+render?.( // Warm any cache
+  <ChildUpdates renderAnchor={true} anchorClassOn={true} />,
+  container
+);
diff --git a/tests/comments/dangling.js b/tests/comments/dangling.js
index 25939492e97f..30ae927249c2 100644
--- a/tests/comments/dangling.js
+++ b/tests/comments/dangling.js
@@ -8,5 +8,6 @@ function d() {
 }
 new Thing(/* dangling */);
 Thing(/* dangling */);
+Thing?.(/* dangling */);
 declare class Foo extends Qux<string> {/* dangling */}
 export /* dangling */{};
diff --git a/tests/jsx/__snapshots__/jsfmt.spec.js.snap b/tests/jsx/__snapshots__/jsfmt.spec.js.snap
index c07256ba1dd2..31cb9d86fb60 100644
--- a/tests/jsx/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/jsx/__snapshots__/jsfmt.spec.js.snap
@@ -4093,6 +4093,10 @@ a = [
 
 <div {...((foo || foo === null) ? {foo} : null)} />
 
+f?.(<div/>);
+(<div/>)();
+(<div/>)?.();
+
 =====================================output=====================================
 a = [
   <path
@@ -4107,6 +4111,10 @@ a = [
 
 <div {...(foo || foo === null ? { foo } : null)} />;
 
+f?.(<div />);
+(<div />)();
+(<div />)?.();
+
 ================================================================================
 `;
 
@@ -4131,6 +4139,10 @@ a = [
 
 <div {...((foo || foo === null) ? {foo} : null)} />
 
+f?.(<div/>);
+(<div/>)();
+(<div/>)?.();
+
 =====================================output=====================================
 a = [
   <path
@@ -4145,6 +4157,10 @@ a = [
 
 <div {...(foo || foo === null ? { foo } : null)} />;
 
+f?.(<div />);
+(<div />)();
+(<div />)?.();
+
 ================================================================================
 `;
 
@@ -4169,6 +4185,10 @@ a = [
 
 <div {...((foo || foo === null) ? {foo} : null)} />
 
+f?.(<div/>);
+(<div/>)();
+(<div/>)?.();
+
 =====================================output=====================================
 a = [
   <path
@@ -4183,6 +4203,10 @@ a = [
 
 <div {...(foo || foo === null ? { foo } : null)} />;
 
+f?.(<div />);
+(<div />)();
+(<div />)?.();
+
 ================================================================================
 `;
 
@@ -4207,6 +4231,10 @@ a = [
 
 <div {...((foo || foo === null) ? {foo} : null)} />
 
+f?.(<div/>);
+(<div/>)();
+(<div/>)?.();
+
 =====================================output=====================================
 a = [
   <path
@@ -4221,6 +4249,10 @@ a = [
 
 <div {...(foo || foo === null ? { foo } : null)} />;
 
+f?.(<div />);
+(<div />)();
+(<div />)?.();
+
 ================================================================================
 `;
 
diff --git a/tests/jsx/parens.js b/tests/jsx/parens.js
index 3a31f7c1bc4b..aacd7379728b 100644
--- a/tests/jsx/parens.js
+++ b/tests/jsx/parens.js
@@ -10,3 +10,7 @@ a = [
 ];
 
 <div {...((foo || foo === null) ? {foo} : null)} />
+
+f?.(<div/>);
+(<div/>)();
+(<div/>)?.();
diff --git a/tests/nullish_coalescing/__snapshots__/jsfmt.spec.js.snap b/tests/nullish_coalescing/__snapshots__/jsfmt.spec.js.snap
index a85951d9d650..57a8c76aaf3b 100644
--- a/tests/nullish_coalescing/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/nullish_coalescing/__snapshots__/jsfmt.spec.js.snap
@@ -2,7 +2,7 @@
 
 exports[`nullish_coalesing_operator.js 1`] = `
 ====================================options=====================================
-parsers: ["babel", "flow"]
+parsers: ["babel", "flow", "typescript"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
diff --git a/tests/nullish_coalescing/jsfmt.spec.js b/tests/nullish_coalescing/jsfmt.spec.js
index e3b2b3021cd3..eb85eda6bd02 100644
--- a/tests/nullish_coalescing/jsfmt.spec.js
+++ b/tests/nullish_coalescing/jsfmt.spec.js
@@ -1,1 +1,1 @@
-run_spec(__dirname, ["babel", "flow"]);
+run_spec(__dirname, ["babel", "flow", "typescript"]);
diff --git a/tests/optional_chaining/__snapshots__/jsfmt.spec.js.snap b/tests/optional_chaining/__snapshots__/jsfmt.spec.js.snap
index 72697a723e3c..d317b7b4f43f 100644
--- a/tests/optional_chaining/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/optional_chaining/__snapshots__/jsfmt.spec.js.snap
@@ -2,7 +2,7 @@
 
 exports[`chaining.js 1`] = `
 ====================================options=====================================
-parsers: ["babel", "flow"]
+parsers: ["babel", "flow", "typescript"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
@@ -35,8 +35,55 @@ a?.b[3].c?.(x).d.e?.f[3].g?.(y).h;
 
 async function HelloWorld() {
   var x = (await foo.bar.blah)?.hi;
+  a?.[await b];
+  (await x)?.();
 }
 
+a[b?.c].d();
+a?.[b?.c].d();
+a[b?.c]?.d();
+a?.[b?.c]?.d();
+
+(one?.fn());
+(one?.two).fn();
+(one.two?.fn());
+(one.two?.three).fn();
+(one.two?.three?.fn());
+
+(one?.());
+(one?.())();
+(one?.())?.();
+
+(one?.()).two;
+
+a?.[b ? c : d];
+
+(-1)?.toFixed();
+(void fn)?.();
+(a && b)?.();
+(a ? b : c)?.();
+(function(){})?.();
+(() => f)?.();
+(()=>f)?.x;
+(a?.(x)).x;
+(aaaaaaaaaaaaaaaaaaaaaaaa&&aaaaaaaaaaaaaaaaaaaaaaaa&&aaaaaaaaaaaaaaaaaaaaaaaa)?.();
+
+let f = () => ({}?.());
+let g = () => ({}?.b);
+a = () => ({}?.() && a);
+a = () => ({}?.()() && a);
+a = () => ({}?.().b && a);
+a = () => ({}?.b && a);
+a = () => ({}?.b() && a);
+(a) => ({}?.()?.b && 0);
+(a) => ({}?.b?.b && 0);
+(x) => ({}?.()());
+(x) => ({}?.().b);
+(x) => ({}?.b());
+(x) => ({}?.b.b);
+({}?.a().b());
+({ a: 1 }?.entries());
+
 =====================================output=====================================
 var street = user.address?.street;
 var fooValue = myForm.querySelector("input[name=foo]")?.value;
@@ -67,14 +114,65 @@ a?.b?.c.d?.e;
 
 async function HelloWorld() {
   var x = (await foo.bar.blah)?.hi;
+  a?.[await b];
+  (await x)?.();
 }
 
+a[b?.c].d();
+a?.[b?.c].d();
+a[b?.c]?.d();
+a?.[b?.c]?.d();
+
+one?.fn();
+(one?.two).fn();
+one.two?.fn();
+(one.two?.three).fn();
+one.two?.three?.fn();
+
+one?.();
+(one?.())();
+one?.()?.();
+
+(one?.()).two;
+
+a?.[b ? c : d];
+
+(-1)?.toFixed();
+(void fn)?.();
+(a && b)?.();
+(a ? b : c)?.();
+(function() {})?.();
+(() => f)?.();
+(() => f)?.x;
+(a?.(x)).x;
+(
+  aaaaaaaaaaaaaaaaaaaaaaaa &&
+  aaaaaaaaaaaaaaaaaaaaaaaa &&
+  aaaaaaaaaaaaaaaaaaaaaaaa
+)?.();
+
+let f = () => ({}?.());
+let g = () => ({}?.b);
+a = () => ({}?.() && a);
+a = () => ({}?.()() && a);
+a = () => ({}?.().b && a);
+a = () => ({}?.b && a);
+a = () => ({}?.b() && a);
+a => ({}?.()?.b && 0);
+a => ({}?.b?.b && 0);
+x => ({}?.()());
+x => ({}?.().b);
+x => ({}?.b());
+x => ({}?.b.b);
+({}?.a().b());
+({ a: 1 }?.entries());
+
 ================================================================================
 `;
 
 exports[`comments.js 1`] = `
 ====================================options=====================================
-parsers: ["babel", "flow"]
+parsers: ["babel", "flow", "typescript"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
diff --git a/tests/optional_chaining/chaining.js b/tests/optional_chaining/chaining.js
index eb30e5663227..a169179b262e 100644
--- a/tests/optional_chaining/chaining.js
+++ b/tests/optional_chaining/chaining.js
@@ -27,4 +27,51 @@ a?.b[3].c?.(x).d.e?.f[3].g?.(y).h;
 
 async function HelloWorld() {
   var x = (await foo.bar.blah)?.hi;
+  a?.[await b];
+  (await x)?.();
 }
+
+a[b?.c].d();
+a?.[b?.c].d();
+a[b?.c]?.d();
+a?.[b?.c]?.d();
+
+(one?.fn());
+(one?.two).fn();
+(one.two?.fn());
+(one.two?.three).fn();
+(one.two?.three?.fn());
+
+(one?.());
+(one?.())();
+(one?.())?.();
+
+(one?.()).two;
+
+a?.[b ? c : d];
+
+(-1)?.toFixed();
+(void fn)?.();
+(a && b)?.();
+(a ? b : c)?.();
+(function(){})?.();
+(() => f)?.();
+(()=>f)?.x;
+(a?.(x)).x;
+(aaaaaaaaaaaaaaaaaaaaaaaa&&aaaaaaaaaaaaaaaaaaaaaaaa&&aaaaaaaaaaaaaaaaaaaaaaaa)?.();
+
+let f = () => ({}?.());
+let g = () => ({}?.b);
+a = () => ({}?.() && a);
+a = () => ({}?.()() && a);
+a = () => ({}?.().b && a);
+a = () => ({}?.b && a);
+a = () => ({}?.b() && a);
+(a) => ({}?.()?.b && 0);
+(a) => ({}?.b?.b && 0);
+(x) => ({}?.()());
+(x) => ({}?.().b);
+(x) => ({}?.b());
+(x) => ({}?.b.b);
+({}?.a().b());
+({ a: 1 }?.entries());
diff --git a/tests/optional_chaining/jsfmt.spec.js b/tests/optional_chaining/jsfmt.spec.js
index e3b2b3021cd3..eb85eda6bd02 100644
--- a/tests/optional_chaining/jsfmt.spec.js
+++ b/tests/optional_chaining/jsfmt.spec.js
@@ -1,1 +1,1 @@
-run_spec(__dirname, ["babel", "flow"]);
+run_spec(__dirname, ["babel", "flow", "typescript"]);
diff --git a/tests/template_literals/__snapshots__/jsfmt.spec.js.snap b/tests/template_literals/__snapshots__/jsfmt.spec.js.snap
index 3457abfa62a5..4768cc0d2c0f 100644
--- a/tests/template_literals/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/template_literals/__snapshots__/jsfmt.spec.js.snap
@@ -2,7 +2,7 @@
 
 exports[`css-prop.js 1`] = `
 ====================================options=====================================
-parsers: ["flow"]
+parsers: ["babel", "flow", "typescript"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
@@ -73,15 +73,15 @@ const TestComponent = ({ children, ...props }) => (
 
 exports[`expressions.js 1`] = `
 ====================================options=====================================
-parsers: ["flow"]
+parsers: ["babel", "flow", "typescript"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
-const long = \`long \${a//comment
+const long1 = \`long \${a//comment
   .b} long longlong \${a.b.c.d.e} long longlong \${a.b.c.d.e} long longlong \${a.b.c.d.e} long long\`;
-const long = \`long \${a.b.c.d.e} long longlong \${loooooooooooooooooong} long longlong \${loooooooooooooooooong} long longlong \${loooooooooooooooooong} long long\`;
+const long2 = \`long \${a.b.c.d.e} long longlong \${loooooooooooooooooong} long longlong \${loooooooooooooooooong} long longlong \${loooooooooooooooooong} long long\`;
 
-const long = \`long long long long long long long long long long long \${a.b.c.d.e} long long long long long long long long long long long long long\`;
+const long3 = \`long long long long long long long long long long long \${a.b.c.d.e} long long long long long long long long long long long long long\`;
 
 const description =
   \`The value of the \${cssName} css of the \${this._name} element\`;
@@ -125,14 +125,14 @@ descirbe('something', () => {
 throw new Error(\`pretty-format: Option "theme" has a key "\${key}" whose value "\${value}" is undefined in ansi-styles.\`,)
 
 =====================================output=====================================
-const long = \`long \${
+const long1 = \`long \${
   a.b //comment
 } long longlong \${a.b.c.d.e} long longlong \${a.b.c.d.e} long longlong \${
   a.b.c.d.e
 } long long\`;
-const long = \`long \${a.b.c.d.e} long longlong \${loooooooooooooooooong} long longlong \${loooooooooooooooooong} long longlong \${loooooooooooooooooong} long long\`;
+const long2 = \`long \${a.b.c.d.e} long longlong \${loooooooooooooooooong} long longlong \${loooooooooooooooooong} long longlong \${loooooooooooooooooong} long long\`;
 
-const long = \`long long long long long long long long long long long \${a.b.c.d.e} long long long long long long long long long long long long long\`;
+const long3 = \`long long long long long long long long long long long \${a.b.c.d.e} long long long long long long long long long long long long long\`;
 
 const description = \`The value of the \${cssName} css of the \${this._name} element\`;
 
@@ -193,7 +193,7 @@ throw new Error(
 
 exports[`styled-components-with-expressions.js 1`] = `
 ====================================options=====================================
-parsers: ["flow"]
+parsers: ["babel", "flow", "typescript"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
@@ -294,7 +294,7 @@ const header = css\`
 
 exports[`styled-jsx.js 1`] = `
 ====================================options=====================================
-parsers: ["flow"]
+parsers: ["babel", "flow", "typescript"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
@@ -484,7 +484,7 @@ const headerGlobal = css.global\`
 
 exports[`styled-jsx-with-expressions.js 1`] = `
 ====================================options=====================================
-parsers: ["flow"]
+parsers: ["babel", "flow", "typescript"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
diff --git a/tests/template_literals/expressions.js b/tests/template_literals/expressions.js
index c6e3febd052d..c68f1176a1f3 100644
--- a/tests/template_literals/expressions.js
+++ b/tests/template_literals/expressions.js
@@ -1,8 +1,8 @@
-const long = `long ${a//comment
+const long1 = `long ${a//comment
   .b} long longlong ${a.b.c.d.e} long longlong ${a.b.c.d.e} long longlong ${a.b.c.d.e} long long`;
-const long = `long ${a.b.c.d.e} long longlong ${loooooooooooooooooong} long longlong ${loooooooooooooooooong} long longlong ${loooooooooooooooooong} long long`;
+const long2 = `long ${a.b.c.d.e} long longlong ${loooooooooooooooooong} long longlong ${loooooooooooooooooong} long longlong ${loooooooooooooooooong} long long`;
 
-const long = `long long long long long long long long long long long ${a.b.c.d.e} long long long long long long long long long long long long long`;
+const long3 = `long long long long long long long long long long long ${a.b.c.d.e} long long long long long long long long long long long long long`;
 
 const description =
   `The value of the ${cssName} css of the ${this._name} element`;
diff --git a/tests/template_literals/jsfmt.spec.js b/tests/template_literals/jsfmt.spec.js
index b9a908981a50..eb85eda6bd02 100644
--- a/tests/template_literals/jsfmt.spec.js
+++ b/tests/template_literals/jsfmt.spec.js
@@ -1,1 +1,1 @@
-run_spec(__dirname, ["flow"]);
+run_spec(__dirname, ["babel", "flow", "typescript"]);
diff --git a/tests/typescript_assert/__snapshots__/jsfmt.spec.js.snap b/tests/typescript_assert/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000000..aa33a20c98d7
--- /dev/null
+++ b/tests/typescript_assert/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,68 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`index.ts 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+const assertString = (x: any): asserts x => {
+  console.assert(typeof x === 'string');
+}
+
+function assertsString(x: any): asserts x {
+  console.assert(typeof x === 'string');
+}
+
+const assertStringWithGuard = (x: any): asserts x is string => {
+  console.assert(typeof x === 'string');
+}
+
+function assertsStringWithGuard(x: any): asserts x is string {
+  console.assert(typeof x === 'string');
+}
+
+interface AssertFoo {
+  isString(node: any): asserts node;
+}
+
+class AssertsFoo {
+  isBar(): asserts this {
+    return;
+  }
+  isBaz = (): asserts this => {
+    return;
+  }
+}
+=====================================output=====================================
+const assertString = (x: any): asserts x => {
+  console.assert(typeof x === "string");
+};
+
+function assertsString(x: any): asserts x {
+  console.assert(typeof x === "string");
+}
+
+const assertStringWithGuard = (x: any): asserts x is string => {
+  console.assert(typeof x === "string");
+};
+
+function assertsStringWithGuard(x: any): asserts x is string {
+  console.assert(typeof x === "string");
+}
+
+interface AssertFoo {
+  isString(node: any): asserts node;
+}
+
+class AssertsFoo {
+  isBar(): asserts this {
+    return;
+  }
+  isBaz = (): asserts this => {
+    return;
+  };
+}
+
+================================================================================
+`;
diff --git a/tests/typescript_assert/index.ts b/tests/typescript_assert/index.ts
new file mode 100644
index 000000000000..e09ee8eae24a
--- /dev/null
+++ b/tests/typescript_assert/index.ts
@@ -0,0 +1,28 @@
+const assertString = (x: any): asserts x => {
+  console.assert(typeof x === 'string');
+}
+
+function assertsString(x: any): asserts x {
+  console.assert(typeof x === 'string');
+}
+
+const assertStringWithGuard = (x: any): asserts x is string => {
+  console.assert(typeof x === 'string');
+}
+
+function assertsStringWithGuard(x: any): asserts x is string {
+  console.assert(typeof x === 'string');
+}
+
+interface AssertFoo {
+  isString(node: any): asserts node;
+}
+
+class AssertsFoo {
+  isBar(): asserts this {
+    return;
+  }
+  isBaz = (): asserts this => {
+    return;
+  }
+}
\ No newline at end of file
diff --git a/tests/typescript_assert/jsfmt.spec.js b/tests/typescript_assert/jsfmt.spec.js
new file mode 100644
index 000000000000..2ea3bb6eb2e4
--- /dev/null
+++ b/tests/typescript_assert/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname, ["typescript"]);
diff --git a/tests/typescript_declare/__snapshots__/jsfmt.spec.js.snap b/tests/typescript_declare/__snapshots__/jsfmt.spec.js.snap
index 44fb4d5909d1..2e79f1b0aa0c 100644
--- a/tests/typescript_declare/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/typescript_declare/__snapshots__/jsfmt.spec.js.snap
@@ -1,5 +1,25 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`declare_class_fields.ts 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+class B {p: number;}
+class C extends B {declare p: 256 | 1000;}
+
+=====================================output=====================================
+class B {
+  p: number;
+}
+class C extends B {
+  declare p: 256 | 1000;
+}
+
+================================================================================
+`;
+
 exports[`declare_enum.ts 1`] = `
 ====================================options=====================================
 parsers: ["typescript"]
diff --git a/tests/typescript_declare/declare_class_fields.ts b/tests/typescript_declare/declare_class_fields.ts
new file mode 100644
index 000000000000..447d7a37ef53
--- /dev/null
+++ b/tests/typescript_declare/declare_class_fields.ts
@@ -0,0 +1,2 @@
+class B {p: number;}
+class C extends B {declare p: 256 | 1000;}