Repository: eslint/eslint (JavaScript)

Issue:
TypeScript: `eslint --fix` command crash the code due to the `prefer-object-spread` rule
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).

    This template is for bug reports. If you are here for another reason, please see below:

    1. To propose a new rule: https://eslint.org/docs/developer-guide/contributing/new-rules
    2. To request a rule change: https://eslint.org/docs/developer-guide/contributing/rule-changes
    3. To request a change that is not a bug fix, rule change, or new rule: https://eslint.org/docs/developer-guide/contributing/changes
    4. If you have any questions, please stop by our chatroom: https://gitter.im/eslint/eslint

    Note that leaving sections blank will make it difficult for us to troubleshoot and we may have to close the issue.
-->

**Tell us about your environment**

<!--
    If you are using ESLint v6.5.0 or later, you can run ESLint with the `--env-info` flag and paste the output here.
-->

- **ESLint Version:** v6.8.0
- **Node Version:** v13.11.0
- **npm Version:** v6.14.2

**What parser (default, Babel-ESLint, etc.) are you using?**

`@typescript-eslint/parser`

**Please show your full configuration:**

<details>
<summary>Configuration</summary>

<!-- Paste your configuration below: -->

```yml
env:
  browser: true
  es6: true
  node: true
extends:
  - eslint:recommended
  - airbnb-base
  - plugin:@typescript-eslint/recommended
  - plugin:@typescript-eslint/eslint-recommended
  - prettier
  - plugin:prettier/recommended
  - prettier/@typescript-eslint
globals:
  Atomics: readonly
  SharedArrayBuffer: readonly
parser: '@typescript-eslint/parser'
parserOptions:
  ecmaVersion: 2020
  sourceType: module
plugins:
  - '@typescript-eslint'
  - import
  - prettier
root: true
rules:
  '@typescript-eslint/explicit-function-return-type':
    - 'off' # Because type inference cannot be used :(
```

</details>

**What did you do? Please include the actual source code causing the issue, as well as the command that you used to run ESLint.**

<!-- Paste the source code below: -->

```ts
const getObject = () => ({ huge: ['sized'], dynamic: ['object'] });

const obj = Object.assign<{}, Record<string, string[]>>({}, getObject());
console.log(obj);
```

<!-- Paste the command you used to run ESLint: -->

```bash
eslint --fix ./*.ts
```

**What did you expect to happen?**

I expected the fix to occur as below:

```ts
const getObject = () => ({ huge: ['sized'], dynamic: ['object'] });

const obj = { ...getObject() };
console.log(obj);
```

**What actually happened? Please include the actual, raw output from ESLint.**

Actual output code:

```ts
const getObject = () => ({ huge: ['sized'], dynamic: ['object'] });

const obj = <{}, Record<string, string[]>>{ ...getObject()};
console.log(obj);
```

This repository can reproduce the above problem: [kurone-kito/example-for-eslint-POS](https://github.com/kurone-kito/example-for-eslint-POS)

**Are you willing to submit a pull request to fix this bug?**

Probably no, because I don't know the cause.


Discussion:
Thanks for making this issue. I think it's definitely worth exploring if we can change the rule in a generic way to avoid crashing on this syntax, if possible.



Patch:
diff --git a/lib/rules/prefer-object-spread.js b/lib/rules/prefer-object-spread.js
index 1344433f52fa..ab252c73ae3a 100644
--- a/lib/rules/prefer-object-spread.js
+++ b/lib/rules/prefer-object-spread.js
@@ -181,8 +181,8 @@ function defineFixer(node, sourceCode) {
         const leftParen = sourceCode.getTokenAfter(node.callee, isOpeningParenToken);
         const rightParen = sourceCode.getLastToken(node);
 
-        // Remove the callee `Object.assign`
-        yield fixer.remove(node.callee);
+        // Remove everything before the opening paren: callee `Object.assign`, type arguments, and whitespace between the callee and the paren.
+        yield fixer.removeRange([node.range[0], leftParen.range[0]]);
 
         // Replace the parens of argument list to braces.
         if (needsParens(node, sourceCode)) {


Test patch:
diff --git a/tests/fixtures/parsers/typescript-parsers/object-assign-with-generic/object-assign-with-generic-1.js b/tests/fixtures/parsers/typescript-parsers/object-assign-with-generic/object-assign-with-generic-1.js
new file mode 100644
index 000000000000..1981f50857ae
--- /dev/null
+++ b/tests/fixtures/parsers/typescript-parsers/object-assign-with-generic/object-assign-with-generic-1.js
@@ -0,0 +1,343 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@2.24.0
+ * Source code:
+ * const obj = Object.assign<{}, Record<string, string[]>>({}, getObject());
+ */
+
+exports.parse = () => ({
+  type: "Program",
+  body: [
+    {
+      type: "VariableDeclaration",
+      declarations: [
+        {
+          type: "VariableDeclarator",
+          id: {
+            type: "Identifier",
+            name: "obj",
+            range: [6, 9],
+            loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 9 } }
+          },
+          init: {
+            type: "CallExpression",
+            callee: {
+              type: "MemberExpression",
+              object: {
+                type: "Identifier",
+                name: "Object",
+                range: [12, 18],
+                loc: {
+                  start: { line: 1, column: 12 },
+                  end: { line: 1, column: 18 }
+                }
+              },
+              property: {
+                type: "Identifier",
+                name: "assign",
+                range: [19, 25],
+                loc: {
+                  start: { line: 1, column: 19 },
+                  end: { line: 1, column: 25 }
+                }
+              },
+              computed: false,
+              optional: false,
+              range: [12, 25],
+              loc: {
+                start: { line: 1, column: 12 },
+                end: { line: 1, column: 25 }
+              }
+            },
+            arguments: [
+              {
+                type: "ObjectExpression",
+                properties: [],
+                range: [56, 58],
+                loc: {
+                  start: { line: 1, column: 56 },
+                  end: { line: 1, column: 58 }
+                }
+              },
+              {
+                type: "CallExpression",
+                callee: {
+                  type: "Identifier",
+                  name: "getObject",
+                  range: [60, 69],
+                  loc: {
+                    start: { line: 1, column: 60 },
+                    end: { line: 1, column: 69 }
+                  }
+                },
+                arguments: [],
+                optional: false,
+                range: [60, 71],
+                loc: {
+                  start: { line: 1, column: 60 },
+                  end: { line: 1, column: 71 }
+                }
+              }
+            ],
+            optional: false,
+            range: [12, 72],
+            loc: {
+              start: { line: 1, column: 12 },
+              end: { line: 1, column: 72 }
+            },
+            typeParameters: {
+              type: "TSTypeParameterInstantiation",
+              range: [25, 55],
+              params: [
+                {
+                  type: "TSTypeLiteral",
+                  members: [],
+                  range: [26, 28],
+                  loc: {
+                    start: { line: 1, column: 26 },
+                    end: { line: 1, column: 28 }
+                  }
+                },
+                {
+                  type: "TSTypeReference",
+                  typeName: {
+                    type: "Identifier",
+                    name: "Record",
+                    range: [30, 36],
+                    loc: {
+                      start: { line: 1, column: 30 },
+                      end: { line: 1, column: 36 }
+                    }
+                  },
+                  typeParameters: {
+                    type: "TSTypeParameterInstantiation",
+                    range: [36, 54],
+                    params: [
+                      {
+                        type: "TSStringKeyword",
+                        range: [37, 43],
+                        loc: {
+                          start: { line: 1, column: 37 },
+                          end: { line: 1, column: 43 }
+                        }
+                      },
+                      {
+                        type: "TSArrayType",
+                        elementType: {
+                          type: "TSStringKeyword",
+                          range: [45, 51],
+                          loc: {
+                            start: { line: 1, column: 45 },
+                            end: { line: 1, column: 51 }
+                          }
+                        },
+                        range: [45, 53],
+                        loc: {
+                          start: { line: 1, column: 45 },
+                          end: { line: 1, column: 53 }
+                        }
+                      }
+                    ],
+                    loc: {
+                      start: { line: 1, column: 36 },
+                      end: { line: 1, column: 54 }
+                    }
+                  },
+                  range: [30, 54],
+                  loc: {
+                    start: { line: 1, column: 30 },
+                    end: { line: 1, column: 54 }
+                  }
+                }
+              ],
+              loc: {
+                start: { line: 1, column: 25 },
+                end: { line: 1, column: 55 }
+              }
+            }
+          },
+          range: [6, 72],
+          loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 72 } }
+        }
+      ],
+      kind: "const",
+      range: [0, 73],
+      loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 73 } }
+    }
+  ],
+  sourceType: "script",
+  range: [0, 73],
+  loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 73 } },
+  tokens: [
+    {
+      type: "Keyword",
+      value: "const",
+      range: [0, 5],
+      loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 5 } }
+    },
+    {
+      type: "Identifier",
+      value: "obj",
+      range: [6, 9],
+      loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 9 } }
+    },
+    {
+      type: "Punctuator",
+      value: "=",
+      range: [10, 11],
+      loc: { start: { line: 1, column: 10 }, end: { line: 1, column: 11 } }
+    },
+    {
+      type: "Identifier",
+      value: "Object",
+      range: [12, 18],
+      loc: { start: { line: 1, column: 12 }, end: { line: 1, column: 18 } }
+    },
+    {
+      type: "Punctuator",
+      value: ".",
+      range: [18, 19],
+      loc: { start: { line: 1, column: 18 }, end: { line: 1, column: 19 } }
+    },
+    {
+      type: "Identifier",
+      value: "assign",
+      range: [19, 25],
+      loc: { start: { line: 1, column: 19 }, end: { line: 1, column: 25 } }
+    },
+    {
+      type: "Punctuator",
+      value: "<",
+      range: [25, 26],
+      loc: { start: { line: 1, column: 25 }, end: { line: 1, column: 26 } }
+    },
+    {
+      type: "Punctuator",
+      value: "{",
+      range: [26, 27],
+      loc: { start: { line: 1, column: 26 }, end: { line: 1, column: 27 } }
+    },
+    {
+      type: "Punctuator",
+      value: "}",
+      range: [27, 28],
+      loc: { start: { line: 1, column: 27 }, end: { line: 1, column: 28 } }
+    },
+    {
+      type: "Punctuator",
+      value: ",",
+      range: [28, 29],
+      loc: { start: { line: 1, column: 28 }, end: { line: 1, column: 29 } }
+    },
+    {
+      type: "Identifier",
+      value: "Record",
+      range: [30, 36],
+      loc: { start: { line: 1, column: 30 }, end: { line: 1, column: 36 } }
+    },
+    {
+      type: "Punctuator",
+      value: "<",
+      range: [36, 37],
+      loc: { start: { line: 1, column: 36 }, end: { line: 1, column: 37 } }
+    },
+    {
+      type: "Identifier",
+      value: "string",
+      range: [37, 43],
+      loc: { start: { line: 1, column: 37 }, end: { line: 1, column: 43 } }
+    },
+    {
+      type: "Punctuator",
+      value: ",",
+      range: [43, 44],
+      loc: { start: { line: 1, column: 43 }, end: { line: 1, column: 44 } }
+    },
+    {
+      type: "Identifier",
+      value: "string",
+      range: [45, 51],
+      loc: { start: { line: 1, column: 45 }, end: { line: 1, column: 51 } }
+    },
+    {
+      type: "Punctuator",
+      value: "[",
+      range: [51, 52],
+      loc: { start: { line: 1, column: 51 }, end: { line: 1, column: 52 } }
+    },
+    {
+      type: "Punctuator",
+      value: "]",
+      range: [52, 53],
+      loc: { start: { line: 1, column: 52 }, end: { line: 1, column: 53 } }
+    },
+    {
+      type: "Punctuator",
+      value: ">",
+      range: [53, 54],
+      loc: { start: { line: 1, column: 53 }, end: { line: 1, column: 54 } }
+    },
+    {
+      type: "Punctuator",
+      value: ">",
+      range: [54, 55],
+      loc: { start: { line: 1, column: 54 }, end: { line: 1, column: 55 } }
+    },
+    {
+      type: "Punctuator",
+      value: "(",
+      range: [55, 56],
+      loc: { start: { line: 1, column: 55 }, end: { line: 1, column: 56 } }
+    },
+    {
+      type: "Punctuator",
+      value: "{",
+      range: [56, 57],
+      loc: { start: { line: 1, column: 56 }, end: { line: 1, column: 57 } }
+    },
+    {
+      type: "Punctuator",
+      value: "}",
+      range: [57, 58],
+      loc: { start: { line: 1, column: 57 }, end: { line: 1, column: 58 } }
+    },
+    {
+      type: "Punctuator",
+      value: ",",
+      range: [58, 59],
+      loc: { start: { line: 1, column: 58 }, end: { line: 1, column: 59 } }
+    },
+    {
+      type: "Identifier",
+      value: "getObject",
+      range: [60, 69],
+      loc: { start: { line: 1, column: 60 }, end: { line: 1, column: 69 } }
+    },
+    {
+      type: "Punctuator",
+      value: "(",
+      range: [69, 70],
+      loc: { start: { line: 1, column: 69 }, end: { line: 1, column: 70 } }
+    },
+    {
+      type: "Punctuator",
+      value: ")",
+      range: [70, 71],
+      loc: { start: { line: 1, column: 70 }, end: { line: 1, column: 71 } }
+    },
+    {
+      type: "Punctuator",
+      value: ")",
+      range: [71, 72],
+      loc: { start: { line: 1, column: 71 }, end: { line: 1, column: 72 } }
+    },
+    {
+      type: "Punctuator",
+      value: ";",
+      range: [72, 73],
+      loc: { start: { line: 1, column: 72 }, end: { line: 1, column: 73 } }
+    }
+  ],
+  comments: []
+});
diff --git a/tests/fixtures/parsers/typescript-parsers/object-assign-with-generic/object-assign-with-generic-2.js b/tests/fixtures/parsers/typescript-parsers/object-assign-with-generic/object-assign-with-generic-2.js
new file mode 100644
index 000000000000..8cac4784686b
--- /dev/null
+++ b/tests/fixtures/parsers/typescript-parsers/object-assign-with-generic/object-assign-with-generic-2.js
@@ -0,0 +1,198 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@2.24.0
+ * Source code:
+ * Object.assign<{}, A>({}, foo);
+ */
+
+exports.parse = () => ({
+  type: "Program",
+  body: [
+    {
+      type: "ExpressionStatement",
+      expression: {
+        type: "CallExpression",
+        callee: {
+          type: "MemberExpression",
+          object: {
+            type: "Identifier",
+            name: "Object",
+            range: [0, 6],
+            loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 6 } }
+          },
+          property: {
+            type: "Identifier",
+            name: "assign",
+            range: [7, 13],
+            loc: { start: { line: 1, column: 7 }, end: { line: 1, column: 13 } }
+          },
+          computed: false,
+          optional: false,
+          range: [0, 13],
+          loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 13 } }
+        },
+        arguments: [
+          {
+            type: "ObjectExpression",
+            properties: [],
+            range: [21, 23],
+            loc: {
+              start: { line: 1, column: 21 },
+              end: { line: 1, column: 23 }
+            }
+          },
+          {
+            type: "Identifier",
+            name: "foo",
+            range: [25, 28],
+            loc: {
+              start: { line: 1, column: 25 },
+              end: { line: 1, column: 28 }
+            }
+          }
+        ],
+        optional: false,
+        range: [0, 29],
+        loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 29 } },
+        typeParameters: {
+          type: "TSTypeParameterInstantiation",
+          range: [13, 20],
+          params: [
+            {
+              type: "TSTypeLiteral",
+              members: [],
+              range: [14, 16],
+              loc: {
+                start: { line: 1, column: 14 },
+                end: { line: 1, column: 16 }
+              }
+            },
+            {
+              type: "TSTypeReference",
+              typeName: {
+                type: "Identifier",
+                name: "A",
+                range: [18, 19],
+                loc: {
+                  start: { line: 1, column: 18 },
+                  end: { line: 1, column: 19 }
+                }
+              },
+              range: [18, 19],
+              loc: {
+                start: { line: 1, column: 18 },
+                end: { line: 1, column: 19 }
+              }
+            }
+          ],
+          loc: { start: { line: 1, column: 13 }, end: { line: 1, column: 20 } }
+        }
+      },
+      range: [0, 30],
+      loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 30 } }
+    }
+  ],
+  sourceType: "script",
+  range: [0, 30],
+  loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 30 } },
+  tokens: [
+    {
+      type: "Identifier",
+      value: "Object",
+      range: [0, 6],
+      loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 6 } }
+    },
+    {
+      type: "Punctuator",
+      value: ".",
+      range: [6, 7],
+      loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 7 } }
+    },
+    {
+      type: "Identifier",
+      value: "assign",
+      range: [7, 13],
+      loc: { start: { line: 1, column: 7 }, end: { line: 1, column: 13 } }
+    },
+    {
+      type: "Punctuator",
+      value: "<",
+      range: [13, 14],
+      loc: { start: { line: 1, column: 13 }, end: { line: 1, column: 14 } }
+    },
+    {
+      type: "Punctuator",
+      value: "{",
+      range: [14, 15],
+      loc: { start: { line: 1, column: 14 }, end: { line: 1, column: 15 } }
+    },
+    {
+      type: "Punctuator",
+      value: "}",
+      range: [15, 16],
+      loc: { start: { line: 1, column: 15 }, end: { line: 1, column: 16 } }
+    },
+    {
+      type: "Punctuator",
+      value: ",",
+      range: [16, 17],
+      loc: { start: { line: 1, column: 16 }, end: { line: 1, column: 17 } }
+    },
+    {
+      type: "Identifier",
+      value: "A",
+      range: [18, 19],
+      loc: { start: { line: 1, column: 18 }, end: { line: 1, column: 19 } }
+    },
+    {
+      type: "Punctuator",
+      value: ">",
+      range: [19, 20],
+      loc: { start: { line: 1, column: 19 }, end: { line: 1, column: 20 } }
+    },
+    {
+      type: "Punctuator",
+      value: "(",
+      range: [20, 21],
+      loc: { start: { line: 1, column: 20 }, end: { line: 1, column: 21 } }
+    },
+    {
+      type: "Punctuator",
+      value: "{",
+      range: [21, 22],
+      loc: { start: { line: 1, column: 21 }, end: { line: 1, column: 22 } }
+    },
+    {
+      type: "Punctuator",
+      value: "}",
+      range: [22, 23],
+      loc: { start: { line: 1, column: 22 }, end: { line: 1, column: 23 } }
+    },
+    {
+      type: "Punctuator",
+      value: ",",
+      range: [23, 24],
+      loc: { start: { line: 1, column: 23 }, end: { line: 1, column: 24 } }
+    },
+    {
+      type: "Identifier",
+      value: "foo",
+      range: [25, 28],
+      loc: { start: { line: 1, column: 25 }, end: { line: 1, column: 28 } }
+    },
+    {
+      type: "Punctuator",
+      value: ")",
+      range: [28, 29],
+      loc: { start: { line: 1, column: 28 }, end: { line: 1, column: 29 } }
+    },
+    {
+      type: "Punctuator",
+      value: ";",
+      range: [29, 30],
+      loc: { start: { line: 1, column: 29 }, end: { line: 1, column: 30 } }
+    }
+  ],
+  comments: []
+});
diff --git a/tests/lib/rules/prefer-object-spread.js b/tests/lib/rules/prefer-object-spread.js
index c2b32e15b196..91bf08a2f74f 100644
--- a/tests/lib/rules/prefer-object-spread.js
+++ b/tests/lib/rules/prefer-object-spread.js
@@ -103,7 +103,18 @@ ruleTester.run("prefer-object-spread", rule, {
                 }
             ]
         },
-
+        {
+            code: "Object.assign  ({}, foo)",
+            output: "({ ...foo})",
+            errors: [
+                {
+                    messageId: "useSpreadMessage",
+                    type: "CallExpression",
+                    line: 1,
+                    column: 1
+                }
+            ]
+        },
         {
             code: "Object.assign({}, { foo: 'bar' })",
             output: "({ foo: 'bar'})",
@@ -473,6 +484,18 @@ ruleTester.run("prefer-object-spread", rule, {
                 }
             ]
         },
+        {
+            code: "let a = Object.assign   ({}, a)",
+            output: "let a = { ...a}",
+            errors: [
+                {
+                    messageId: "useSpreadMessage",
+                    type: "CallExpression",
+                    line: 1,
+                    column: 9
+                }
+            ]
+        },
         {
             code: "let a = Object.assign({ a: 1 }, b)",
             output: "let a = {a: 1, ...b}",
@@ -940,6 +963,34 @@ ruleTester.run("prefer-object-spread", rule, {
                     column: 1
                 }
             ]
+        },
+
+        // https://github.com/eslint/eslint/issues/13058
+        {
+            code: "const obj = Object.assign<{}, Record<string, string[]>>({}, getObject());",
+            output: "const obj = { ...getObject()};",
+            parser: require.resolve("../../fixtures/parsers/typescript-parsers/object-assign-with-generic/object-assign-with-generic-1"),
+            errors: [
+                {
+                    messageId: "useSpreadMessage",
+                    type: "CallExpression",
+                    line: 1,
+                    column: 13
+                }
+            ]
+        },
+        {
+            code: "Object.assign<{}, A>({}, foo);",
+            output: "({ ...foo});",
+            parser: require.resolve("../../fixtures/parsers/typescript-parsers/object-assign-with-generic/object-assign-with-generic-2"),
+            errors: [
+                {
+                    messageId: "useSpreadMessage",
+                    type: "CallExpression",
+                    line: 1,
+                    column: 1
+                }
+            ]
         }
     ]
 });