Repository: prettier/prettier (JavaScript)
Date: 2021-08-25 14:29:55+00:00
Task type: Bug Fix

Issue:
Flow variance sigil with no-semi leads to incorrect output
**Prettier 2.3.2**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAucAbAhgZ0wAgII7AA6UOOA1NHDgLw4Bu6qAFAJQDcJZ5MA7hLQZM2JAL4kQAGhAQADjACW0TMlDoATuoi8AChoQqUTXugCeK6QCN16MAGs4MAMqzbCqAHNkMdQFc40nAAtpZwACZh4QAy6J6+6B5wAGIQ6kHoMIqeyCDovjAQUiAAFjBBqADqxQrwmK5gcE4GNQr0NaY5YNhF7phw6jA6Nh7pyABmTH3SAFaYAB4AQjb2jk7oQXBR7nDjkwEgs3NO7h6ocACKvhDwu6hTIK7qfeo5Y6jaRbLq7jAVCmEwYrIAAcAAZpF8IH0KjZZDkvnBnvQdtIAI5XeBDOSGXKYAC0UDg4XCRXUcHRCjJQwSoyQEzu+z6QQUt3umBOZ0u1x2dL20hg6EsfwBQKQACZ+TYFKgTgBhCBBWkgREAViKvj6ABVBYZ6fd6P4AJJQSKwJxgb7yPAmpwwUxnVlwUSiIA)
<!-- prettier-ignore -->
```sh
--parser flow
--no-semi
```

**Input:**
<!-- prettier-ignore -->
```jsx
class A {
  +one = val();
  +two = val()
}

```

**Output:**
<!-- prettier-ignore -->
```jsx
class A {
  +one = val()
  +two = val()
}

```
Without semicolons, the code above is equivalent to 
<!-- prettier-ignore -->
```jsx
class A {
  +one = val() + two = val()
}
```
i.e. the `+` is treated as a normal arithmetic plus operator instead of the intended [Flow variance sigil](https://flow.org/blog/2016/10/04/Property-Variance/#property-variance). This leads to a parsing error, or incorrect runtime behavior if there were no assignment or type definition in lower property `two`.

**Expected behavior:**
Flow variance sigil `+` and `-` should get semicolon at the end of the previous line, like computed properties do.

<!-- prettier-ignore -->
```jsx
class A {
  +one = val();
  +two = val()
}

```


Issue discussion:

Solution patch:
diff --git a/changelog_unreleased/flow/11398.md b/changelog_unreleased/flow/11398.md
new file mode 100644
index 000000000000..02b564d6f8bb
--- /dev/null
+++ b/changelog_unreleased/flow/11398.md
@@ -0,0 +1,26 @@
+#### Print leading semi before Flow variance sigils (#11398 by @noppa)
+
+<!-- prettier-ignore -->
+```jsx
+// Input
+class A {
+  +one = function() {};
+  -two = val();
+  +#privOne = val();
+  static +#privTwo = val();
+}
+// Prettier stable
+class A {
+  +one = function() {}
+  -two = val()
+  +#privOne = val()
+  static +#privTwo = val()
+}
+// Prettier main
+class A {
+  +one = function() {};
+  -two = val();
+  +#privOne = val()
+  static +#privTwo = val()
+}
+```
diff --git a/src/language-js/print/statement.js b/src/language-js/print/statement.js
index 9dc33a7426c5..c7573faf753f 100644
--- a/src/language-js/print/statement.js
+++ b/src/language-js/print/statement.js
@@ -201,6 +201,17 @@ function shouldPrintSemicolonAfterClassProperty(node, nextNode) {
     }
   }
 
+  // Flow variance sigil +/- requires semi if there's no
+  // "declare" or "static" keyword before it.
+  if (
+    isClassProperty(nextNode) &&
+    nextNode.variance &&
+    !nextNode.static &&
+    !nextNode.declare
+  ) {
+    return true;
+  }
+
   switch (nextNode.type) {
     case "ClassProperty":
     case "PropertyDefinition":


Test patch:
diff --git a/tests/format/flow/no-semi/__snapshots__/jsfmt.spec.js.snap b/tests/format/flow/no-semi/__snapshots__/jsfmt.spec.js.snap
index 2a25e9c704b9..36cf0be3cf9f 100644
--- a/tests/format/flow/no-semi/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/flow/no-semi/__snapshots__/jsfmt.spec.js.snap
@@ -2,7 +2,7 @@
 
 exports[`comments.js - {"semi":false} format 1`] = `
 ====================================options=====================================
-parsers: ["flow", "babel"]
+parsers: ["flow", "babel", "babel-flow"]
 printWidth: 80
 semi: false
                                                                                 | printWidth
@@ -37,7 +37,7 @@ x
 
 exports[`comments.js format 1`] = `
 ====================================options=====================================
-parsers: ["flow", "babel"]
+parsers: ["flow", "babel", "babel-flow"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
@@ -69,9 +69,66 @@ x;
 ================================================================================
 `;
 
+exports[`flow-class-properties.js - {"semi":false} format 1`] = `
+====================================options=====================================
+parsers: ["flow", "babel", "babel-flow"]
+printWidth: 80
+semi: false
+                                                                                | printWidth
+=====================================input======================================
+class A {
+  +one = function() {};
+  -two = val();
+  static +three = val();
+  +#privOne = val();
+  static +#privTwo = val();
+  +[computed] = val();
+}
+
+=====================================output=====================================
+class A {
+  +one = function () {};
+  -two = val()
+  static +three = val();
+  +#privOne = val()
+  static +#privTwo = val();
+  +[computed] = val()
+}
+
+================================================================================
+`;
+
+exports[`flow-class-properties.js format 1`] = `
+====================================options=====================================
+parsers: ["flow", "babel", "babel-flow"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+class A {
+  +one = function() {};
+  -two = val();
+  static +three = val();
+  +#privOne = val();
+  static +#privTwo = val();
+  +[computed] = val();
+}
+
+=====================================output=====================================
+class A {
+  +one = function () {};
+  -two = val();
+  static +three = val();
+  +#privOne = val();
+  static +#privTwo = val();
+  +[computed] = val();
+}
+
+================================================================================
+`;
+
 exports[`flow-interfaces.js - {"semi":false} format 1`] = `
 ====================================options=====================================
-parsers: ["flow", "babel"]
+parsers: ["flow", "babel", "babel-flow"]
 printWidth: 80
 semi: false
                                                                                 | printWidth
@@ -157,7 +214,7 @@ interface E {
 
 exports[`flow-interfaces.js format 1`] = `
 ====================================options=====================================
-parsers: ["flow", "babel"]
+parsers: ["flow", "babel", "babel-flow"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
@@ -242,7 +299,7 @@ interface E {
 
 exports[`no-semi.js - {"semi":false} format 1`] = `
 ====================================options=====================================
-parsers: ["flow", "babel"]
+parsers: ["flow", "babel", "babel-flow"]
 printWidth: 80
 semi: false
                                                                                 | printWidth
@@ -263,7 +320,7 @@ semi: false
 
 exports[`no-semi.js format 1`] = `
 ====================================options=====================================
-parsers: ["flow", "babel"]
+parsers: ["flow", "babel", "babel-flow"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
diff --git a/tests/format/flow/no-semi/flow-class-properties.js b/tests/format/flow/no-semi/flow-class-properties.js
new file mode 100644
index 000000000000..e6cd0014e636
--- /dev/null
+++ b/tests/format/flow/no-semi/flow-class-properties.js
@@ -0,0 +1,8 @@
+class A {
+  +one = function() {};
+  -two = val();
+  static +three = val();
+  +#privOne = val();
+  static +#privTwo = val();
+  +[computed] = val();
+}
diff --git a/tests/format/flow/no-semi/jsfmt.spec.js b/tests/format/flow/no-semi/jsfmt.spec.js
index 2f93d9870caa..b01ec07781b3 100644
--- a/tests/format/flow/no-semi/jsfmt.spec.js
+++ b/tests/format/flow/no-semi/jsfmt.spec.js
@@ -1,2 +1,2 @@
-run_spec(__dirname, ["flow", "babel"]);
-run_spec(__dirname, ["flow", "babel"], { semi: false });
+run_spec(__dirname, ["flow", "babel", "babel-flow"]);
+run_spec(__dirname, ["flow", "babel", "babel-flow"], { semi: false });