Repository: eslint/eslint (JavaScript)

Issue:
[6.5.0] `no-useless-rename` with `babel-eslint` crash at rest properties
**Tell us about your environment**

* **ESLint Version:** 6.5.0
* **Node Version:** 12.10.0
* **npm Version:** 6.10.3

**What parser (default, Babel-ESLint, etc.) are you using?**
babel eslint

**Please show your full configuration:**

<details>
<summary>Configuration</summary>

<!-- Paste your configuration below: -->
```js
{
  "extends": [
    "airbnb",
    "plugin:react/all"
  ],
  "env": {
    "browser": true,
    "node": true,
    "es6": true
  },
  "parser": "babel-eslint",
  "plugins": [
    "react",
    "simple-import-sort"
  ],
  "rules": {
    "arrow-parens": [
      "error",
      "always"
    ],
    "camelcase": "error",
    "comma-dangle": [
      "error",
      "never"
    ],
    "curly": [
      "error",
      "all"
    ],
    "func-names": "error",
    "indent": [
      "error",
      2
    ],
    "max-len": [
      "error",
      {
        "code": 120,
        "ignoreUrls": true,
        "tabWidth": 2
      }
    ],
    "no-param-reassign": "off",
    "no-underscore-dangle": "off",
    "no-plusplus": "off",
    "no-console": "error",
    "no-unused-vars": [
      "error",
      {
        "argsIgnorePattern": "dispatch"
      }
    ],
    "simple-import-sort/sort": "error",
    "sort-keys": [
      "error",
      "asc",
      {
        "natural": true,
        "caseSensitive": false
      }
    ],
    "sort-vars": "error",
    "react/jsx-indent": [
      "error",
      2
    ],
    "react/jsx-indent-props": [
      "error",
      2
    ],
    "react/jsx-max-depth": "off",
    "react/jsx-no-literals": "off",
    "react/jsx-one-expression-per-line": "off",
    "react/destructuring-assignment": "off",
    "react/jsx-key": "off",
    "react/no-did-mount-set-state": "off",
    "react/no-set-state": "off",
    "react/prefer-stateless-function": "off",
    "react/jsx-props-no-spreading": "off",
    "jsx-a11y/click-events-have-key-events": "off",
    "jsx-a11y/no-static-element-interactions": "off",
    // TODO: All rules below here will eventually be accounted for
    "react/jsx-handler-names": "off",
    "react/prop-types": "off",
    "react/jsx-fragments": "off"
  }
}
```

</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: -->
```js
import { Button as MuiButton, withStyles } from '@material-ui/core';
import { darken } from '@material-ui/core/styles';
import * as React from 'react';
import { Link } from 'react-router-dom';

class Button extends React.Component {
  // TODO: Implement
  shouldComponentUpdate() {
    return true;
  }

  render() {
    const {
      classes, href, icon, ...otherProps
    } = this.props;

    const buttonContent = (
      <div className={classes.buttonChildrenContainer}>
        {icon && (
          <div className={classes.buttonIcon}>
            <img
              alt="button-icon"
              className={classes.buttonIconImg}
              src={icon}
            />
          </div>
        )}
        <div className={classes.buttonText}>{this.props.children}</div>
      </div>
    );

    return href ? (
      <Link to={`/${href}`}>
        <MuiButton
          className={classes.root}
          color="primary"
          fullWidth
          variant="contained"
          {...otherProps}
        >
          {buttonContent}
        </MuiButton>
      </Link>
    ) : (
      <MuiButton
        className={classes.root}
        color="primary"
        fullWidth
        variant="contained"
        {...this.props}
      >
        {buttonContent}
      </MuiButton>
    );
  }
}

const styles = (theme) => ({
  buttonChildrenContainer: {
    display: 'flex',
    height: 48,
    width: '100%'
  },
  buttonIcon: {
    backgroundColor: 'white',
    borderRadius: '3px 0 0 3px',
    padding: '10px 4px',
    width: 48
  },
  buttonIconImg: {
    maxHeight: '100%',
    maxWidth: '100%'
  },
  buttonText: {
    flex: 1,
    fontSize: 16,
    paddingTop: 10,
    textAlign: 'center'
  },
  root: {
    '&:hover': {
      backgroundColor: darken(theme.palette.secondary.main, 0.1)
    },
    backgroundColor: theme.palette.secondary.main,
    boxShadow: 'none',
    padding: 2,
    textTransform: 'none'
  }
});

export default withStyles(styles, { withTheme: true })(Button);
```

<!-- Paste the command you used to run ESLint: -->
```bash
./node_modules/.bin/eslint --ext .js --ext .jsx .
```

**What did you expect to happen?**
Everything to lint fine as it does in `6.4.0`

**What actually happened? Please include the actual, raw output from ESLint.**
```
▶ npm run lint --prefix packages/client

> client@1.0.0 lint /Users/jakeleventhal/Projects/ecominate/packages/client
> ../../node_modules/.bin/eslint --ext .js --ext .jsx .

TypeError: Cannot read property 'type' of undefined
Occurred while linting /Users/jakeleventhal/Projects/ecominate/packages/client/src/view/SignInUp/Components/Button.jsx:13
    at checkDestructured (/Users/jakeleventhal/Projects/ecominate/node_modules/eslint/lib/rules/no-useless-rename.js:104:43)
    at /Users/jakeleventhal/Projects/ecominate/node_modules/eslint/lib/linter/safe-emitter.js:45:58
    at Array.forEach (<anonymous>)
    at Object.emit (/Users/jakeleventhal/Projects/ecominate/node_modules/eslint/lib/linter/safe-emitter.js:45:38)
    at NodeEventGenerator.applySelector (/Users/jakeleventhal/Projects/ecominate/node_modules/eslint/lib/linter/node-event-generator.js:253:26)
    at NodeEventGenerator.applySelectors (/Users/jakeleventhal/Projects/ecominate/node_modules/eslint/lib/linter/node-event-generator.js:282:22)
    at NodeEventGenerator.enterNode (/Users/jakeleventhal/Projects/ecominate/node_modules/eslint/lib/linter/node-event-generator.js:296:14)
    at CodePathAnalyzer.enterNode (/Users/jakeleventhal/Projects/ecominate/node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js:646:23)
    at /Users/jakeleventhal/Projects/ecominate/node_modules/eslint/lib/linter/linter.js:935:32
    at Array.forEach (<anonymous>)
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! client@1.0.0 lint: `../../node_modules/.bin/eslint --ext .js --ext .jsx .`
npm ERR! Exit status 2
npm ERR! 
npm ERR! Failed at the client@1.0.0 lint script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/jakeleventhal/.npm/_logs/2019-09-29T06_40_12_714Z-debug.log
```

**Are you willing to submit a pull request to fix this bug?**
No


Discussion:
Thanks for this issue.

Can you minimize your code example to help us ensure which part caused the bug? Thanks.
It looks like [this line](https://github.com/eslint/eslint/blob/master/lib/rules/no-useless-rename.js#L104) is throwing an error on this node:
```
    const {
      classes, href, icon, ...otherProps
    } = this.props;
```

Do you mind checking to see if the same error occurs with the default parser? It might be some difference between Espree and babel-eslint.
The problem is the spread operator here i think

The following code causes the error
```javascript
import * as React from 'react';

class CustomButton extends React.Component {
  render() {
    const { ...otherProps } = this.props;

    return (
      <div
        {...otherProps}
      />
    );
  }
}

export default CustomButton;
```

The following code does *not* cause an error:
```javascript
import * as React from 'react';

class CustomButton extends React.Component {
  render() {
    return (
      <div
        {...this.props.otherProps}
      />
    );
  }
}

export default CustomButton;
```
I'd rather not swap my parser around but this is the most simplified example I can produce
> It looks like [this line](https://github.com/eslint/eslint/blob/master/lib/rules/no-useless-rename.js#L104) is throwing an error on this node:
> 
> ```
>     const {
>       classes, href, icon, ...otherProps
>     } = this.props;
> ```
> 
> Do you mind checking to see if the same error occurs with the default parser? It might be some difference between Espree and babel-eslint.

same error, when change `...otherProps` everything ok
I think the issue is with [this section](https://github.com/eslint/eslint/blob/334ca7c8b9c18ac097849c1cefaa43097a4e51dc/lib/rules/no-useless-rename.js#L100-L104) of the no-useless-rename code, if babel-eslint is still using ExperimentalRestProperty. The code is fine for espree.

With babel-eslint (at least version 9 in ASTExplorer), the code uses ExperimentalRestProperty, so it does not meet the condition in line 100 and thus bail out via `continue` on line 101. Instead, it executes line 104, which looks for properties on `node.key` when `node.key` does not exist.

Not sure if babel-eslint@10 or babel-eslint@11 would run into the same problem.

Similarly, I don't think espree should be broken-- @douxc If you think that line crashes while using the default parser, please provide more information about your configuration so we have a better idea of how to reproduce that. Thanks!
I've tested with different parsers. Here is result:

|Parser|Did it work?|
|---|---|
|espree|Yes|
|babel-eslint@9|No|
|babel-eslint@10|No|
|babel-eslint@11|No|
Please note I wasn’t experiencing problems in 6.4.0
The root cause is that `babel-eslint` generates invalid AST for some syntax. I don't oppose to fix ESLint itself for this, but I hope `babel-eslint` to make valid AST.

| Syntax            | ESTree spec        | `babel-eslint`                |
| :---------------- | :----------------- | :---------------------------- |
| Rest properties   | `RestElement`      | `ExperimentalRestProperty`    |
| Spread properties | `SpreadElement`    | `ExperimentalSpreadProperty`  |
| `import()`        | `ImportExpression` | `CallExpression` with `Import` |
I had the same issue and adding the eslint rule `"no-useless-rename": 0` fixed the issue for me.

Is a fix scheduled for this?
I have the same thing. With this code:
```js
                    const key = (property.key.type === "Identifier" && property.key.name) || (property.key.type === "Literal" && property.key.value);
                    const renamedKey = property.value.type === "AssignmentPattern" ? property.value.left.name : property.value.name;

                    if (key === renamedKey) {
                        reportError(property, property.key, property.value, "Destructuring assignment");
                    }
```
and temporarily changed it in node-modules to this:
```js
                if (property.key) {
                    const key = (property.key.type === "Identifier" && property.key.name) || (property.key.type === "Literal" && property.key.value);
                    const renamedKey = property.value.type === "AssignmentPattern" ? property.value.left.name : property.value.name;

                    if (key === renamedKey) {
                        reportError(property, property.key, property.value, "Destructuring assignment");
                    }
                }
```
Error is:
```
$ yarn lint
yarn run v1.17.3
$ eslint --ext .js,.vue src
TypeError: Cannot read property 'type' of undefined
Occurred while linting /data2/projects/quasar/app-extensions/qcalendar/demo/src/examples/DayViewSwipe.vue:34
```
Line of code complaining about is:
```js
    handleSwipe ({ evt, ...info }) {
```

Hi @kopax, if we can identify an easy fix, there's a possibility it could get into a patch release early this coming week. Otherwise, the next release will be in about 2 weeks (close to October 11th). Please see our README for more information.

A PR would be welcome. I think adding a check for `!node.key` (maybe instead of the `node.type === "RestProperty"` check) would probably solve the issue. We would also want a unit test case that uses a mock babel-eslint parser and the generated AST, in order to avoid a regression here. :smile:

Just as a general reminder for everyone: This project, like most open-source projects, is 100% volunteer-run and the maintainers can only do so much by themselves. I appreciate the great discussion and the quick report of the issue, and I also appreciate everyone's patience as we figure out how best to fix this.
An option is also `node.type !== "Property"` instead of `node.type === "RestProperty"`
This is a bug in babel-eslint. I’m not opposed to adding a temporary fix to the rule, but it should be fixed by babel-eslint. I’ll take a look later today and see if we can get it patched there.
Off topic:

@kaicataldo Maybe it can be done at https://github.com/babel/babel-eslint/pull/785 .
@g-plane Yes, that should fix it! Whipping up a PR to fix this on our end in the meantime.
Just to reiterate - this is caused by babel-eslint not following the ESTree spec. The PR @g-plane linked to above (found [here](https://github.com/babel/babel-eslint/pull/785)) will fix babel-eslint when it is merged, but that project is maintained by a different team.

For those coming across this issue looking for a quick fix, pinning ESLint to v6.4.0 (or `~6.4.0`) will fix it.
PR [here](https://github.com/eslint/eslint/pull/12339). This should be fixed up on our side in a patch release in the next few days, and hopefully the Babel team is able to merge https://github.com/babel/babel-eslint/pull/785 in the near future to help prevent these kinds of regressions. Thanks for your patience!
Instead of disabling the entire `no-useless-rename` rule as a workaround, you can just disable the check for destructuring:

```json
{
  "no-useless-rename": [1, { "ignoreDestructuring": true }]
}
```
@dspacejs This has been fixed in v6.5.1.



Patch:
diff --git a/lib/rules/no-useless-rename.js b/lib/rules/no-useless-rename.js
index 67af97c8bc0f..f31459bd8ae2 100644
--- a/lib/rules/no-useless-rename.js
+++ b/lib/rules/no-useless-rename.js
@@ -93,6 +93,14 @@ module.exports = {
 
             for (const property of node.properties) {
 
+                /*
+                 * TODO: Remove after babel-eslint removes ExperimentalRestProperty
+                 * https://github.com/eslint/eslint/issues/12335
+                 */
+                if (property.type === "ExperimentalRestProperty") {
+                    continue;
+                }
+
                 /**
                  * Properties using shorthand syntax and rest elements can not be renamed.
                  * If the property is computed, we have no idea if a rename is useless or not.


Test patch:
diff --git a/tests/fixtures/parsers/babel-eslint10/object-pattern-with-rest-element.js b/tests/fixtures/parsers/babel-eslint10/object-pattern-with-rest-element.js
new file mode 100644
index 000000000000..84aea926bc4f
--- /dev/null
+++ b/tests/fixtures/parsers/babel-eslint10/object-pattern-with-rest-element.js
@@ -0,0 +1,151 @@
+/**
+ * Parser: babel-eslint@10.0.3
+ * Source code:
+ * const { ...foo } = bar;
+ */
+exports.parse = () => ({
+  type: "Program",
+  start: 0,
+  end: 24,
+  loc: { start: { line: 1, column: 0 }, end: { line: 2, column: 0 } },
+  range: [0, 24],
+  comments: [],
+  tokens: [
+      {
+          type: "Keyword",
+          value: "const",
+          start: 0,
+          end: 5,
+          loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 5 } },
+          range: [0, 5]
+      },
+      {
+          type: "Punctuator",
+          value: "{",
+          start: 6,
+          end: 7,
+          loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 7 } },
+          range: [6, 7]
+      },
+      {
+          type: "Punctuator",
+          value: "...",
+          start: 8,
+          end: 11,
+          loc: { start: { line: 1, column: 8 }, end: { line: 1, column: 11 } },
+          range: [8, 11]
+      },
+      {
+          type: "Identifier",
+          value: "foo",
+          start: 11,
+          end: 14,
+          loc: { start: { line: 1, column: 11 }, end: { line: 1, column: 14 } },
+          range: [11, 14]
+      },
+      {
+          type: "Punctuator",
+          value: "}",
+          start: 15,
+          end: 16,
+          loc: { start: { line: 1, column: 15 }, end: { line: 1, column: 16 } },
+          range: [15, 16]
+      },
+      {
+          type: "Punctuator",
+          value: "=",
+          start: 17,
+          end: 18,
+          loc: { start: { line: 1, column: 17 }, end: { line: 1, column: 18 } },
+          range: [17, 18]
+      },
+      {
+          type: "Identifier",
+          value: "bar",
+          start: 19,
+          end: 22,
+          loc: { start: { line: 1, column: 19 }, end: { line: 1, column: 22 } },
+          range: [19, 22]
+      },
+      {
+          type: "Punctuator",
+          value: ";",
+          start: 22,
+          end: 23,
+          loc: { start: { line: 1, column: 22 }, end: { line: 1, column: 23 } },
+          range: [22, 23]
+      }
+  ],
+  sourceType: "module",
+  body: [
+      {
+          type: "VariableDeclaration",
+          start: 0,
+          end: 23,
+          loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 23 } },
+          range: [0, 23],
+          declarations: [
+              {
+                  type: "VariableDeclarator",
+                  start: 6,
+                  end: 22,
+                  loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 22 } },
+                  range: [6, 22],
+                  id: {
+                      type: "ObjectPattern",
+                      start: 6,
+                      end: 16,
+                      loc: {
+                          start: { line: 1, column: 6 },
+                          end: { line: 1, column: 16 }
+                      },
+                      range: [6, 16],
+                      properties: [
+                          {
+                              type: "ExperimentalRestProperty",
+                              start: 8,
+                              end: 14,
+                              loc: {
+                                  start: { line: 1, column: 8 },
+                                  end: { line: 1, column: 14 }
+                              },
+                              range: [8, 14],
+                              argument: {
+                                  type: "Identifier",
+                                  start: 11,
+                                  end: 14,
+                                  loc: {
+                                      start: { line: 1, column: 11 },
+                                      end: { line: 1, column: 14 },
+                                      identifierName: "foo"
+                                  },
+                                  range: [11, 14],
+                                  name: "foo",
+                                  _babelType: "Identifier"
+                              },
+                              _babelType: "RestElement"
+                          }
+                      ],
+                      _babelType: "ObjectPattern"
+                  },
+                  init: {
+                      type: "Identifier",
+                      start: 19,
+                      end: 22,
+                      loc: {
+                          start: { line: 1, column: 19 },
+                          end: { line: 1, column: 22 },
+                          identifierName: "bar"
+                      },
+                      range: [19, 22],
+                      name: "bar",
+                      _babelType: "Identifier"
+                  },
+                  _babelType: "VariableDeclarator"
+              }
+          ],
+          kind: "const",
+          _babelType: "VariableDeclaration"
+      }
+  ]
+});
diff --git a/tests/lib/rules/no-useless-rename.js b/tests/lib/rules/no-useless-rename.js
index c9be46d695a5..0dccda27800e 100644
--- a/tests/lib/rules/no-useless-rename.js
+++ b/tests/lib/rules/no-useless-rename.js
@@ -113,6 +113,15 @@ ruleTester.run("no-useless-rename", rule, {
         {
             code: "export {foo as foo, bar as bar} from 'foo';",
             options: [{ ignoreExport: true }]
+        },
+
+        /*
+         * TODO: Remove after babel-eslint removes ExperimentalRestProperty
+         * https://github.com/eslint/eslint/issues/12335
+         */
+        {
+            code: "const { ...foo } = bar;",
+            parser: require.resolve("../../fixtures/parsers/babel-eslint10/object-pattern-with-rest-element")
         }
     ],