Repository: prettier/prettier (JavaScript)
Date: 2020-04-01 07:13:53+00:00
Task type: Bug Fix

Issue:
[Flow] Prettier forces an extra comma for inexact object type flow syntax, only for object indexer.
**Prettier 2.0.2**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEMCeAHOACAchAfQGEIBbUgQ2wF5tgAdKbbCpbAIwggBs4KoANI2YA6MYwC+w7I3RZsAFWJlKNOtNYcuvfkKbYxIvVP2zMOAJJQAJnAAecAE4F8y8lVoN9AbQDOMRwBLKABzAF02Th4+QWlDSWkzeStbB2cSdzUvZj8A4PDI7Ri9UTFjEAEQCAwYQOhfZFAKR0cIAHcABWaEBpQKbjaKNAbK9kcKMABrOBgAZQwJ-OQAgFc4SoALGFJuAHUNwPhfBbA4WZ7DwIA3Q7RkcF8RkGDfJxgO8ZDKZAAzftfKgArXx2ABC4ymM1mFFIcAAMsE4L9-usQMC7LN8rwAIorCDwZHcAEgBaOV6Oe4-bjtCok1qvXbjDD3DCOODkq5IyoARzx8A+1V6IAovgAtFA4HBbNZaWzeYE2R8KF8KITia9SIFlo41pVfFi4Lj8UikH8iaiYBR2LtAtYYBtkAAmSoBCiBbj5DLfFDsgCstJWrwUVt6ZuJVzWKQQczAQRqAEEbLN0Lw1ajWcEYDa7Q6kAAOAAMEgkQA)
```sh
--parser flow
```

**Input:**
```jsx
type No_Comma = {
  a: boolean,
  ...
}
  
type T_Comma = {
  a: boolean,
  ...,
}
  
type Indexer_No_Comma = {
  [string]: boolean,
  ...
}
  
type Indexer_Comma = {
  [string]: boolean,
  ...,
}
```

**Output:**
```jsx
type No_Comma = {
  a: boolean,
  ...
};

type T_Comma = {
  a: boolean,
  ...
};

type Indexer_No_Comma = {
  [string]: boolean,
  ...,
};

type Indexer_Comma = {
  [string]: boolean,
  ...,
};

```

**Expected behavior:**
The problem exists only for `trailing-comma: "es5"` setting. `trailing-comma: "none"` works as expected.

I think behavior for indexer object type should be the same as for simple one

I'd expect output to be 

```
type Indexer_No_Comma = {
  [string]: boolean,
  ...
};

type Indexer_Comma = {
  [string]: boolean,
  ...
};
```
[Flow] Prettier forces an extra comma for inexact object type flow syntax, only for object indexer.
**Prettier 2.0.2**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEMCeAHOACAchAfQGEIBbUgQ2wF5tgAdKbbCpbAIwggBs4KoANI2YA6MYwC+w7I3RZsAFWJlKNOtNYcuvfkKbYxIvVP2zMOAJJQAJnAAecAE4F8y8lVoN9AbQDOMRwBLKABzAF02Th4+QWlDSWkzeStbB2cSdzUvZj8A4PDI7Ri9UTFjEAEQCAwYQOhfZFAKR0cIAHcABWaEBpQKbjaKNAbK9kcKMABrOBgAZQwJ-OQAgFc4SoALGFJuAHUNwPhfBbA4WZ7DwIA3Q7RkcF8RkGDfJxgO8ZDKZAAzftfKgArXx2ABC4ymM1mFFIcAAMsE4L9-usQMC7LN8rwAIorCDwZHcAEgBaOV6Oe4-bjtCok1qvXbjDD3DCOODkq5IyoARzx8A+1V6IAovgAtFA4HBbNZaWzeYE2R8KF8KITia9SIFlo41pVfFi4Lj8UikH8iaiYBR2LtAtYYBtkAAmSoBCiBbj5DLfFDsgCstJWrwUVt6ZuJVzWKQQczAQRqAEEbLN0Lw1ajWcEYDa7Q6kAAOAAMEgkQA)
```sh
--parser flow
```

**Input:**
```jsx
type No_Comma = {
  a: boolean,
  ...
}
  
type T_Comma = {
  a: boolean,
  ...,
}
  
type Indexer_No_Comma = {
  [string]: boolean,
  ...
}
  
type Indexer_Comma = {
  [string]: boolean,
  ...,
}
```

**Output:**
```jsx
type No_Comma = {
  a: boolean,
  ...
};

type T_Comma = {
  a: boolean,
  ...
};

type Indexer_No_Comma = {
  [string]: boolean,
  ...,
};

type Indexer_Comma = {
  [string]: boolean,
  ...,
};

```

**Expected behavior:**
The problem exists only for `trailing-comma: "es5"` setting. `trailing-comma: "none"` works as expected.

I think behavior for indexer object type should be the same as for simple one

I'd expect output to be 

```
type Indexer_No_Comma = {
  [string]: boolean,
  ...
};

type Indexer_Comma = {
  [string]: boolean,
  ...
};
```


Issue discussion:

Solution patch:
diff --git a/changelog_unreleased/flow/pr-7923.md b/changelog_unreleased/flow/pr-7923.md
new file mode 100644
index 000000000000..2fc11058597a
--- /dev/null
+++ b/changelog_unreleased/flow/pr-7923.md
@@ -0,0 +1,52 @@
+#### Do not add comma for explicit inexact object with indexer property or no properties ([#7923](https://github.com/prettier/prettier/pull/7923) by [@DmitryGonchar](https://github.com/DmitryGonchar))
+
+<!-- prettier-ignore -->
+```jsx
+// Input
+type T = {
+  a: number,
+  ...,
+}
+
+type T = {
+   [string]: number,
+  ...,
+}
+
+type T = {
+  // comment
+  ...,
+}
+
+// Prettier stable
+type T = {
+  a: number,
+  ...
+}
+
+type T = {
+   [string]: number,
+  ...,
+}
+
+type T = {
+  // comment
+  ...,
+}
+
+// Prettier master
+type T = {
+  a: number,
+  ...
+}
+
+type T = {
+   [string]: number,
+  ...
+}
+
+type T = {
+  // comment
+  ...
+}
+```
diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js
index 2b136512a46f..2f2b6064adfe 100644
--- a/src/language-js/printer-estree.js
+++ b/src/language-js/printer-estree.js
@@ -1416,11 +1416,11 @@ function printPathNoParens(path, options, print, args) {
       const lastElem = getLast(n[propertiesField]);
 
       const canHaveTrailingSeparator = !(
-        lastElem &&
-        (lastElem.type === "RestProperty" ||
-          lastElem.type === "RestElement" ||
-          hasNodeIgnoreComment(lastElem) ||
-          n.inexact)
+        n.inexact ||
+        (lastElem &&
+          (lastElem.type === "RestProperty" ||
+            lastElem.type === "RestElement" ||
+            hasNodeIgnoreComment(lastElem)))
       );
 
       let content;


Test patch:
diff --git a/tests/flow_object_inexact/__snapshots__/jsfmt.spec.js.snap b/tests/flow_object_inexact/__snapshots__/jsfmt.spec.js.snap
index fa0d6af61b93..f873374c1b27 100644
--- a/tests/flow_object_inexact/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow_object_inexact/__snapshots__/jsfmt.spec.js.snap
@@ -4,6 +4,141 @@ exports[`comments.js 1`] = `
 ====================================options=====================================
 parsers: ["flow", "babel"]
 printWidth: 80
+trailingComma: "es5"
+                                                                                | printWidth
+=====================================input======================================
+// @flow
+
+type Foo = {
+  // comment
+  ...,
+};
+
+type Foo = {
+  /* comment */
+  ...,
+};
+
+type Foo = { /* comment */ ... };
+
+type Foo = { /* comment */
+  ...};
+
+type Foo = {
+  // comment0
+  // comment1
+  ...,
+};
+
+type Foo = {
+  /* comment0 */
+  /* comment1 */
+  ...,
+};
+
+type Foo = {
+  // comment
+  foo: string,
+  ...
+};
+
+type Foo = {
+  // comment0
+  // comment1
+  foo: string,
+  ...
+};
+
+type Foo = {
+  /* comment */
+  foo: string,
+  ...
+};
+
+type Foo = {
+  /* comment */
+  [string]: string,
+  ...
+};
+
+type Foo = {
+  /* comment0 */
+  /* comment1 */
+  foo: string,
+  ...
+};
+
+=====================================output=====================================
+// @flow
+
+type Foo = {
+  // comment
+  ...
+};
+
+type Foo = {
+  /* comment */
+  ...
+};
+
+type Foo = { /* comment */ ... };
+
+type Foo = {
+  /* comment */
+  ...
+};
+
+type Foo = {
+  // comment0
+  // comment1
+  ...
+};
+
+type Foo = {
+  /* comment0 */
+  /* comment1 */
+  ...
+};
+
+type Foo = {
+  // comment
+  foo: string,
+  ...
+};
+
+type Foo = {
+  // comment0
+  // comment1
+  foo: string,
+  ...
+};
+
+type Foo = {
+  /* comment */
+  foo: string,
+  ...
+};
+
+type Foo = {
+  /* comment */
+  [string]: string,
+  ...
+};
+
+type Foo = {
+  /* comment0 */
+  /* comment1 */
+  foo: string,
+  ...
+};
+
+================================================================================
+`;
+
+exports[`comments.js 2`] = `
+====================================options=====================================
+parsers: ["flow", "babel"]
+printWidth: 80
 trailingComma: "none"
                                                                                 | printWidth
 =====================================input======================================
@@ -55,6 +190,147 @@ type Foo = {
   ...
 };
 
+type Foo = {
+  /* comment */
+  [string]: string,
+  ...
+};
+
+type Foo = {
+  /* comment0 */
+  /* comment1 */
+  foo: string,
+  ...
+};
+
+=====================================output=====================================
+// @flow
+
+type Foo = {
+  // comment
+  ...
+};
+
+type Foo = {
+  /* comment */
+  ...
+};
+
+type Foo = { /* comment */ ... };
+
+type Foo = {
+  /* comment */
+  ...
+};
+
+type Foo = {
+  // comment0
+  // comment1
+  ...
+};
+
+type Foo = {
+  /* comment0 */
+  /* comment1 */
+  ...
+};
+
+type Foo = {
+  // comment
+  foo: string,
+  ...
+};
+
+type Foo = {
+  // comment0
+  // comment1
+  foo: string,
+  ...
+};
+
+type Foo = {
+  /* comment */
+  foo: string,
+  ...
+};
+
+type Foo = {
+  /* comment */
+  [string]: string,
+  ...
+};
+
+type Foo = {
+  /* comment0 */
+  /* comment1 */
+  foo: string,
+  ...
+};
+
+================================================================================
+`;
+
+exports[`comments.js 3`] = `
+====================================options=====================================
+parsers: ["flow", "babel"]
+printWidth: 80
+trailingComma: "all"
+                                                                                | printWidth
+=====================================input======================================
+// @flow
+
+type Foo = {
+  // comment
+  ...,
+};
+
+type Foo = {
+  /* comment */
+  ...,
+};
+
+type Foo = { /* comment */ ... };
+
+type Foo = { /* comment */
+  ...};
+
+type Foo = {
+  // comment0
+  // comment1
+  ...,
+};
+
+type Foo = {
+  /* comment0 */
+  /* comment1 */
+  ...,
+};
+
+type Foo = {
+  // comment
+  foo: string,
+  ...
+};
+
+type Foo = {
+  // comment0
+  // comment1
+  foo: string,
+  ...
+};
+
+type Foo = {
+  /* comment */
+  foo: string,
+  ...
+};
+
+type Foo = {
+  /* comment */
+  [string]: string,
+  ...
+};
+
 type Foo = {
   /* comment0 */
   /* comment1 */
@@ -113,6 +389,12 @@ type Foo = {
   ...
 };
 
+type Foo = {
+  /* comment */
+  [string]: string,
+  ...
+};
+
 type Foo = {
   /* comment0 */
   /* comment1 */
@@ -127,6 +409,119 @@ exports[`test.js 1`] = `
 ====================================options=====================================
 parsers: ["flow", "babel"]
 printWidth: 80
+trailingComma: "es5"
+                                                                                | printWidth
+=====================================input======================================
+//@flow
+type T = {
+  a: number,
+  ...,
+}
+
+type I = {
+  [string]: number,
+  ...,
+}
+
+type U = { a: number, b: number, c: number, d: number, e: number, f: number, g: number, ...};
+
+type V = {x: {...}, y: {x: {...}, a: number, b: number, c: number, d: number, e: number, f: number, ...}, z: {...}, foo: number, bar: {foo: number, ...}, ...};
+
+function test(x: {foo: number, bar: number, baz: number, qux: nunber, a: number, b: number, c: {a: number, ...}, ...}) { return x; }
+function test(x: {foo: number, bar: number, baz: number, qux: nunber, a: number, b: number, c: {a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, ...}, ...}) { return x; }
+
+type W = {...};
+type X = {
+  ...,
+};
+
+=====================================output=====================================
+//@flow
+type T = {
+  a: number,
+  ...
+};
+
+type I = {
+  [string]: number,
+  ...
+};
+
+type U = {
+  a: number,
+  b: number,
+  c: number,
+  d: number,
+  e: number,
+  f: number,
+  g: number,
+  ...
+};
+
+type V = {
+  x: { ... },
+  y: {
+    x: { ... },
+    a: number,
+    b: number,
+    c: number,
+    d: number,
+    e: number,
+    f: number,
+    ...
+  },
+  z: { ... },
+  foo: number,
+  bar: { foo: number, ... },
+  ...
+};
+
+function test(x: {
+  foo: number,
+  bar: number,
+  baz: number,
+  qux: nunber,
+  a: number,
+  b: number,
+  c: { a: number, ... },
+  ...
+}) {
+  return x;
+}
+function test(x: {
+  foo: number,
+  bar: number,
+  baz: number,
+  qux: nunber,
+  a: number,
+  b: number,
+  c: {
+    a: number,
+    b: number,
+    c: number,
+    d: number,
+    e: number,
+    f: number,
+    g: number,
+    h: number,
+    i: number,
+    ...
+  },
+  ...
+}) {
+  return x;
+}
+
+type W = { ... };
+type X = { ... };
+
+================================================================================
+`;
+
+exports[`test.js 2`] = `
+====================================options=====================================
+parsers: ["flow", "babel"]
+printWidth: 80
 trailingComma: "none"
                                                                                 | printWidth
 =====================================input======================================
@@ -136,6 +531,11 @@ type T = {
   ...,
 }
 
+type I = {
+  [string]: number,
+  ...,
+}
+
 type U = { a: number, b: number, c: number, d: number, e: number, f: number, g: number, ...};
 
 type V = {x: {...}, y: {x: {...}, a: number, b: number, c: number, d: number, e: number, f: number, ...}, z: {...}, foo: number, bar: {foo: number, ...}, ...};
@@ -155,6 +555,124 @@ type T = {
   ...
 };
 
+type I = {
+  [string]: number,
+  ...
+};
+
+type U = {
+  a: number,
+  b: number,
+  c: number,
+  d: number,
+  e: number,
+  f: number,
+  g: number,
+  ...
+};
+
+type V = {
+  x: { ... },
+  y: {
+    x: { ... },
+    a: number,
+    b: number,
+    c: number,
+    d: number,
+    e: number,
+    f: number,
+    ...
+  },
+  z: { ... },
+  foo: number,
+  bar: { foo: number, ... },
+  ...
+};
+
+function test(x: {
+  foo: number,
+  bar: number,
+  baz: number,
+  qux: nunber,
+  a: number,
+  b: number,
+  c: { a: number, ... },
+  ...
+}) {
+  return x;
+}
+function test(x: {
+  foo: number,
+  bar: number,
+  baz: number,
+  qux: nunber,
+  a: number,
+  b: number,
+  c: {
+    a: number,
+    b: number,
+    c: number,
+    d: number,
+    e: number,
+    f: number,
+    g: number,
+    h: number,
+    i: number,
+    ...
+  },
+  ...
+}) {
+  return x;
+}
+
+type W = { ... };
+type X = { ... };
+
+================================================================================
+`;
+
+exports[`test.js 3`] = `
+====================================options=====================================
+parsers: ["flow", "babel"]
+printWidth: 80
+trailingComma: "all"
+                                                                                | printWidth
+=====================================input======================================
+//@flow
+type T = {
+  a: number,
+  ...,
+}
+
+type I = {
+  [string]: number,
+  ...,
+}
+
+type U = { a: number, b: number, c: number, d: number, e: number, f: number, g: number, ...};
+
+type V = {x: {...}, y: {x: {...}, a: number, b: number, c: number, d: number, e: number, f: number, ...}, z: {...}, foo: number, bar: {foo: number, ...}, ...};
+
+function test(x: {foo: number, bar: number, baz: number, qux: nunber, a: number, b: number, c: {a: number, ...}, ...}) { return x; }
+function test(x: {foo: number, bar: number, baz: number, qux: nunber, a: number, b: number, c: {a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, ...}, ...}) { return x; }
+
+type W = {...};
+type X = {
+  ...,
+};
+
+=====================================output=====================================
+//@flow
+type T = {
+  a: number,
+  ...
+};
+
+type I = {
+  [string]: number,
+  ...
+};
+
 type U = {
   a: number,
   b: number,
diff --git a/tests/flow_object_inexact/comments.js b/tests/flow_object_inexact/comments.js
index 17ebf492e322..a0f914103309 100644
--- a/tests/flow_object_inexact/comments.js
+++ b/tests/flow_object_inexact/comments.js
@@ -46,6 +46,12 @@ type Foo = {
   ...
 };
 
+type Foo = {
+  /* comment */
+  [string]: string,
+  ...
+};
+
 type Foo = {
   /* comment0 */
   /* comment1 */
diff --git a/tests/flow_object_inexact/jsfmt.spec.js b/tests/flow_object_inexact/jsfmt.spec.js
index c242cec590ff..693dcdb086eb 100644
--- a/tests/flow_object_inexact/jsfmt.spec.js
+++ b/tests/flow_object_inexact/jsfmt.spec.js
@@ -1,1 +1,3 @@
+run_spec(__dirname, ["flow", "babel"], { trailingComma: "es5" });
 run_spec(__dirname, ["flow", "babel"], { trailingComma: "none" });
+run_spec(__dirname, ["flow", "babel"], { trailingComma: "all" });
diff --git a/tests/flow_object_inexact/test.js b/tests/flow_object_inexact/test.js
index 62fde15dc890..d53841cda2c2 100644
--- a/tests/flow_object_inexact/test.js
+++ b/tests/flow_object_inexact/test.js
@@ -4,6 +4,11 @@ type T = {
   ...,
 }
 
+type I = {
+  [string]: number,
+  ...,
+}
+
 type U = { a: number, b: number, c: number, d: number, e: number, f: number, g: number, ...};
 
 type V = {x: {...}, y: {x: {...}, a: number, b: number, c: number, d: number, e: number, f: number, ...}, z: {...}, foo: number, bar: {foo: number, ...}, ...};