Repository: prettier/prettier (JavaScript)
Date: 2018-08-08 04:52:08+00:00
Task type: Bug Fix

Issue:
Typescript parser 
**Prettier 1.14.0**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEBLAtgBwgJxgAgCU4BDMAgMxwnXwB0QdTyGBuOqDyKAZwIEFMmfAF58ACgCUogHwSO+fAB4AJqgBu+MABsSPHgDkS6OCIaDMDGQuUB6NeutRJ7TlF0AjONpQAaEBCYMKjQPMigJDjUAO4ACpEIYSgk6hCoKiD+HjhkANZwMADKmGSoUADmyDA4AK5w-mU8cHixOeXoJMgUJNpN-gBWPAAeAEI5YPlFxnAAMmVwXT19ICU4TTjIIDAAnphwPGA4qEGZK0ewAOrpMAAWyAAcAAz+mNRNFzmYm6-7zeoL-iYAEcaqgmK0SO1Okhur16iAmuhUFVavCeGVyto4ABFGoQeCLOH+GAkDxXFS3ZAAJmJOVQ2gxAGEaB1NlBoACQDUmgAVUlJWFNAC+QqAA)
```sh
--parser typescript
```

**Input:**
```tsx
import React from "react";

const App = () => (
  <div className="App">
  </div>
);

label:
```

**Output:**
```tsx
SyntaxError: '>' expected. (4:8)
  2 | 
  3 | const App = () => (
> 4 |   <div className="App">
    |        ^
  5 |   </div>
  6 | );
  7 | 
```

**Expected Behavior:**

As it shows the error when there's no newline between the opening and closing tags:
**Input:**
```tsx
import React from "react";

const App = () => (
  <div className="App"></div>
);

label:
```

**Output:**
```tsx
SyntaxError: Expression expected. (7:7)
  5 | );
  6 | 
> 7 | label:
    |       ^
```


Issue discussion:
The issue here is that we try to parse with and without JSX mode enabled, and we just catch the latest error ~~(i.e., the non-JSX one)~~ when both of them throw errors. 

~~Changing to use the JSX error seems reasonable to me, especially when an error occurred in a TSX file.~~ (This may cause unexpected behavior.)

We should suppose our guess is correct then (i.e., throw the first error if both failed).

https://github.com/prettier/prettier/blob/308863e0611941ce5b75d9b93ac905e859f4631d/src/language-js/parser-typescript.js#L11-L28

Solution patch:
diff --git a/jest.config.js b/jest.config.js
index 2351c2b5ab50..dc12fda87132 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -12,7 +12,10 @@ const isOldNode = semver.parse(process.version).major <= 4;
 
 module.exports = {
   setupFiles: ["<rootDir>/tests_config/run_spec.js"],
-  snapshotSerializers: ["<rootDir>/tests_config/raw-serializer.js"],
+  snapshotSerializers: [
+    "<rootDir>/tests_config/raw-serializer.js",
+    "<rootDir>/tests_config/ansi-serializer.js"
+  ],
   testRegex: "jsfmt\\.spec\\.js$|__tests__/.*\\.js$",
   testPathIgnorePatterns: ["tests/new_react", "tests/more_react"].concat(
     isOldNode ? requiresPrettierInternals : []
diff --git a/package.json b/package.json
index 367ba18d8b58..1c97d4ce57f2 100644
--- a/package.json
+++ b/package.json
@@ -80,6 +80,7 @@
     "eslint-plugin-prettier": "2.6.0",
     "eslint-plugin-react": "7.7.0",
     "execa": "0.10.0",
+    "has-ansi": "3.0.0",
     "jest": "23.3.0",
     "jest-junit": "5.0.0",
     "jest-watch-typeahead": "0.1.0",
diff --git a/src/language-js/parser-typescript.js b/src/language-js/parser-typescript.js
index e2897411f34a..87cf6078e244 100644
--- a/src/language-js/parser-typescript.js
+++ b/src/language-js/parser-typescript.js
@@ -9,22 +9,24 @@ function parse(text /*, parsers, opts*/) {
   const jsx = isProbablyJsx(text);
   let ast;
   try {
+    // Try passing with our best guess first.
+    ast = tryParseTypeScript(text, jsx);
+  } catch (firstError) {
     try {
-      // Try passing with our best guess first.
-      ast = tryParseTypeScript(text, jsx);
-    } catch (e) {
       // But if we get it wrong, try the opposite.
-      /* istanbul ignore next */
       ast = tryParseTypeScript(text, !jsx);
-    }
-  } catch (e) /* istanbul ignore next */ {
-    if (typeof e.lineNumber === "undefined") {
-      throw e;
-    }
+    } catch (secondError) {
+      // suppose our guess is correct
+      const e = firstError;
 
-    throw createError(e.message, {
-      start: { line: e.lineNumber, column: e.column + 1 }
-    });
+      if (typeof e.lineNumber === "undefined") {
+        throw e;
+      }
+
+      throw createError(e.message, {
+        start: { line: e.lineNumber, column: e.column + 1 }
+      });
+    }
   }
 
   delete ast.tokens;
diff --git a/yarn.lock b/yarn.lock
index 1fe5f1562c49..d02bc5ff7ea4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2603,6 +2603,12 @@ har-validator@~5.0.3:
     ajv "^5.1.0"
     har-schema "^2.0.0"
 
+has-ansi@3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-3.0.0.tgz#36077ef1d15f333484aa7fa77a28606f1c655b37"
+  dependencies:
+    ansi-regex "^3.0.0"
+
 has-ansi@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"


Test patch:
diff --git a/tests_config/ansi-serializer.js b/tests_config/ansi-serializer.js
new file mode 100644
index 000000000000..e807dc5bb8f7
--- /dev/null
+++ b/tests_config/ansi-serializer.js
@@ -0,0 +1,13 @@
+"use strict";
+
+const hasAnsi = require("has-ansi");
+const stripAnsi = require("strip-ansi");
+
+module.exports = {
+  print(value, serialize) {
+    return serialize(stripAnsi(value));
+  },
+  test(value) {
+    return typeof value === "string" && hasAnsi(value);
+  }
+};
diff --git a/tests_integration/__tests__/__snapshots__/format.js.snap b/tests_integration/__tests__/__snapshots__/format.js.snap
index b9f307aed4f7..164d9cb180aa 100644
--- a/tests_integration/__tests__/__snapshots__/format.js.snap
+++ b/tests_integration/__tests__/__snapshots__/format.js.snap
@@ -1,5 +1,14 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`typescript parser should throw the first error when both JSX and non-JSX mode failed 1`] = `
+"Expression expected. (9:7)
+   7 | );
+   8 | 
+>  9 | label:
+     |       ^
+  10 |   "
+`;
+
 exports[`yaml parser should handle CRLF correctly 1`] = `
 "a: 123
 "
diff --git a/tests_integration/__tests__/format.js b/tests_integration/__tests__/format.js
index b1b4eee03fb2..bf730ae99b60 100644
--- a/tests_integration/__tests__/format.js
+++ b/tests_integration/__tests__/format.js
@@ -6,3 +6,19 @@ test("yaml parser should handle CRLF correctly", () => {
   const input = "a:\r\n  123\r\n";
   expect(prettier.format(input, { parser: "yaml" })).toMatchSnapshot();
 });
+
+test("typescript parser should throw the first error when both JSX and non-JSX mode failed", () => {
+  const input = `
+import React from "react";
+
+const App = () => (
+  <div className="App">
+  </div>
+);
+
+label:
+  `;
+  expect(() =>
+    prettier.format(input, { parser: "typescript" })
+  ).toThrowErrorMatchingSnapshot();
+});