Repository: prettier/prettier (JavaScript)
Date: 2020-11-02 01:54:46+00:00
Task type: Bug Fix

Issue:
Mixing Flow comment will cause generic syntax to be wrapped with Flow comment
**Prettier 2.1.0**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEBLAtgBwgJxgAhgE9M59h8B5KOAFRLIF98AzHCdfAcgDoB6aHABiqADZweAKwDOXADpQ+AKiRIFGbHkINy+ABJxRoiPVL5mbDt34QYACzg4R4qbIVK+ChcTMAFdpimcNL4ALzkAD4K+Pi0wTAAwhzYNLBI+ABKcACGYDA8AIIARtIwOLmJyYKwADzZUEQAfAA00fgORhDpBp1BrVARjADcIM0gEJgwqNDSyKDZOOwA7r4LCLMo2aJL2USzY0XlYADWcDAAypi5qFAA5shlAK5wY3DoRXAAJp9fADL1t0e2Vuwlw6GyMCmd2QIGyjxgEFGIDsMHQogA6nZUPBpFcwHBzutsagAG7YogwsDSfZoKDSRwwfzA8HIFhbeljGQADwAQkdThdsug4L8bnBWeyXiBuecbrdxABFR62cVINmiDkgK44ek4GFFbIfUQAWhYxiWSMwOBuMHRqE+9mQAA4AAxjK0Qeno8qYGFW4KOEnisYAR2V8H8Ew2sOkxpoXy+SJwcDDqGTTNuLLVkrG9PQqAeOGeublivDqqeUpghrtDrsyAATGMytkxHKkugsyBggBWJGPem0Q0bdWaknPACSUB+sHOYGtkwK0-OxHEEo1cEYjCAA)
```sh
--parser babel-flow
--single-quote
```

**Input:**
```jsx
import type { OneType } from './oneFile.js'
/*::
import type { HelloType } from './otherFile.js'
*/

type PropTypes = {|
  TestComponent: React.AbstractComponent<any>,
  hello: HelloType,
|};
```

**Output:**
```jsx
import type { OneType } from './oneFile.js';
/*::
import type { HelloType } from './otherFile.js'
*/

type PropTypes = {|
  TestComponent: React.AbstractComponent/*:: <any> */, // <--- unexpected!
  hello: HelloType,
|};

```

**Expected behavior:**

```jsx
import type { OneType } from './oneFile.js';
/*::
import type { HelloType } from './otherFile.js'
*/

type PropTypes = {|
  TestComponent: React.AbstractComponent<any>, // <--- This is the behavior of Prettier 2.0.5
  hello: HelloType,
|};

```


Issue discussion:
/cc @prettier/core regression
I'll work on this
Note: with `--parser=flow`
**Prettier 2.1.1**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEBLAtgBwgJxgAhgE9M59h8B5KOAFRLIF98AzHCdfAcgDoB6aHABiqADZweAKwDOXADpQ+AKiRIFGbHkINy+ABJxRoiPVL5mbDt34QYACzg4R4qbIVK+ChcTMAFdpimcNL4ALzkAD4K+Pi0wTAAwhzYNLBI+ABKcACGYDA8AIIARtIwOLmJyYKwADzZUEQAfAA00fgORhDpBp1BrVARjADcIM0gEJgwqNDSyKDZOOwA7r4LCLMo2aJL2USzY0XlYADWcDAAypi5qFAA5shlAK5wY3DoRXAAJp9fADL1t0e2Vuwlw6GyMCmd2QIGyjxgEFGIDsMHQogA6nZUPBpFcwHBzutsagAG7YogwsDSfZoKDSRwwfzA8HIFhbeljGQADwAQkdThdsug4L8bnBWeyXiBuecbrdxABFR62cVINmiDkgK44ek4GEsYxLJGYHA3GDo1CfezIAAcAAYxiaIPT0eVMDCTcFHCTxWMAI7K+D+CYbWHSAC0NC+XyRODgAdQcaZtxZaslY3p6FQDxwzwzcsVgdVTylMGyRQtVrsyAATGMytkxHKkuhUyBggBWJGPem0csbdWaknPACSUB+sHOYFNkwK4-OxHEEo1cEYjCAA)
```sh
--parser flow
--single-quote
```

**Input:**
```jsx
import type { OneType } from './oneFile.js'
/*::
import type { HelloType } from './otherFile.js'
*/

type PropTypes = {|
  TestComponent: React.AbstractComponent<any>,
  hello: HelloType,
|};
```

**Output:**
```jsx
import type { OneType } from './oneFile.js';
import type { HelloType } from './otherFile.js';
type PropTypes = {|
  TestComponent: React.AbstractComponent/*:: <any> */,
  hello: HelloType,
|};

```
Flow comments are a huge mess. See https://github.com/facebook/flow/issues/8355#issuecomment-627442127

Also note that Babel has support for these comments in the parser now (the `flowComments` plugin, see https://babeljs.io/docs/en/babel-parser#language-extensions). We probably should use it for `babel-flow` and unify the code for `flow` and `babel-flow`. That might fix [a lot of issues](https://github.com/prettier/prettier/labels/area%3Aflow%20comment%20types).
Probably caused by #8724, and `ranges` introduced in #8810.
`range` are different from `location` in  these  nodes?
No, `ranges` has not problem. This regression was caused by [the change](https://github.com/prettier/prettier/pull/8724/files#diff-3abca41914b4b214a2507e33cc54c28bL2853-L2855). So I reverted the change, but the regression wasn't fixed. Upon investigation, the behavior in 2.0.5 depended on `ranges` not being enabled.

Solution patch:
diff --git a/changelog_unreleased/flow/pr-9563.md b/changelog_unreleased/flow/pr-9563.md
new file mode 100644
index 000000000000..8260bb391b9f
--- /dev/null
+++ b/changelog_unreleased/flow/pr-9563.md
@@ -0,0 +1,22 @@
+#### Improve comment types detection (#9563 by @fisker)
+
+<!-- prettier-ignore -->
+```jsx
+// Input
+foo/*::<bar>*/(baz);
+class Foo {
+  bar( data: Array<string>) {}
+}
+
+// Prettier master
+foo/*:: <bar> */(baz);
+class Foo {
+  bar(data: Array/*:: <string> */) {}
+}
+
+// Prettier stable
+foo/*:: <bar> */(baz);
+class Foo {
+  bar(data: Array<string>) {}
+}
+```
diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js
index 35dc6cbe9bd9..379258bf609a 100644
--- a/src/language-js/printer-estree.js
+++ b/src/language-js/printer-estree.js
@@ -2899,21 +2899,25 @@ function printPathNoParens(path, options, print, args) {
 
     case "TypeParameterDeclaration":
     case "TypeParameterInstantiation": {
-      const value = path.getValue();
-      const commentStart = options.originalText
-        .slice(0, locStart(value))
-        .lastIndexOf("/*");
-      // As noted in the TypeCastExpression comments above, we're able to use a normal whitespace regex here
-      // because we know for sure that this is a type definition.
-      const commentSyntax =
-        commentStart >= 0 &&
-        options.originalText.slice(commentStart).match(/^\/\*\s*::/);
-      if (commentSyntax) {
-        return concat([
-          "/*:: ",
-          printTypeParameters(path, options, print, "params"),
-          " */",
-        ]);
+      const start = locStart(n);
+      const end = locEnd(n);
+      const commentStartIndex = options.originalText.lastIndexOf("/*", start);
+      const commentEndIndex = options.originalText.indexOf("*/", end);
+      if (commentStartIndex !== -1 && commentEndIndex !== -1) {
+        const comment = options.originalText
+          .slice(commentStartIndex + 2, commentEndIndex)
+          .trim();
+        if (
+          comment.startsWith("::") &&
+          !comment.includes("/*") &&
+          !comment.includes("*/")
+        ) {
+          return concat([
+            "/*:: ",
+            printTypeParameters(path, options, print, "params"),
+            " */",
+          ]);
+        }
       }
 
       return printTypeParameters(path, options, print, "params");


Test patch:
diff --git a/tests/flow/comments/__snapshots__/jsfmt.spec.js.snap b/tests/flow/comments/__snapshots__/jsfmt.spec.js.snap
index c1523e352541..77210f6a8be9 100644
--- a/tests/flow/comments/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/comments/__snapshots__/jsfmt.spec.js.snap
@@ -293,6 +293,25 @@ const x = (input /*: string */) /*: string */ => {};
 ================================================================================
 `;
 
+exports[`type_annotations-2.js format 1`] = `
+====================================options=====================================
+parsers: ["flow", "babel"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+foo/*::<bar>*/(baz);
+class Foo {
+  bar( data: Array<string>) {}
+}
+=====================================output=====================================
+foo/*:: <bar> */(baz);
+class Foo {
+  bar(data: Array<string>) {}
+}
+
+================================================================================
+`;
+
 exports[`union.js format 1`] = `
 ====================================options=====================================
 parsers: ["flow", "babel"]
diff --git a/tests/flow/comments/babel-only/__snapshots__/jsfmt.spec.js.snap b/tests/flow/comments/babel-only/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000000..b76c2cc42ed5
--- /dev/null
+++ b/tests/flow/comments/babel-only/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,30 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`type_annotations-3.js format 1`] = `
+====================================options=====================================
+parsers: ["babel-flow"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+import type { OneType } from './oneFile.js'
+/*::
+import type { HelloType } from './otherFile.js'
+*/
+
+type PropTypes = {|
+  TestComponent: React.AbstractComponent<any>,
+  hello: HelloType,
+|};
+=====================================output=====================================
+import type { OneType } from "./oneFile.js";
+/*::
+import type { HelloType } from './otherFile.js'
+*/
+
+type PropTypes = {|
+  TestComponent: React.AbstractComponent<any>,
+  hello: HelloType,
+|};
+
+================================================================================
+`;
diff --git a/tests/flow/comments/babel-only/jsfmt.spec.js b/tests/flow/comments/babel-only/jsfmt.spec.js
new file mode 100644
index 000000000000..e90814b2741b
--- /dev/null
+++ b/tests/flow/comments/babel-only/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname, ["babel-flow"]);
diff --git a/tests/flow/comments/babel-only/type_annotations-3.js b/tests/flow/comments/babel-only/type_annotations-3.js
new file mode 100644
index 000000000000..2668a17a91cc
--- /dev/null
+++ b/tests/flow/comments/babel-only/type_annotations-3.js
@@ -0,0 +1,9 @@
+import type { OneType } from './oneFile.js'
+/*::
+import type { HelloType } from './otherFile.js'
+*/
+
+type PropTypes = {|
+  TestComponent: React.AbstractComponent<any>,
+  hello: HelloType,
+|};
\ No newline at end of file
diff --git a/tests/flow/comments/type_annotations-2.js b/tests/flow/comments/type_annotations-2.js
new file mode 100644
index 000000000000..e656b717d615
--- /dev/null
+++ b/tests/flow/comments/type_annotations-2.js
@@ -0,0 +1,4 @@
+foo/*::<bar>*/(baz);
+class Foo {
+  bar( data: Array<string>) {}
+}
\ No newline at end of file