Repository: yannickcr/eslint-plugin-react (js)

Issue:
no-danger-with-children exception when props contains spread
#768 fixed #767 directly by allowing `{...props}` on JSX elements, but it's getting tripped up when it goes to find the definition of `props` and finds spreads there.

```js
// .eslintrc.js
module.exports = {
	"root": true,
	"parserOptions": {
		"ecmaVersion": 6,
		"ecmaFeatures": {
			"experimentalObjectRestSpread": true,
			"jsx": true
		}
	},
	"plugins": ["react"],
	"rules": {
		"react/no-danger-with-children": "error"
	}
};
```

```jsx
// pass.jsx
const props = { foo: 42 };
(<div {...props}>Children</div>);
```

```jsx
// fail1.jsx
const { a, b, ...props } = this.props;
(<div {...props}>Children</div>);
```

```jsx
// fail2.jsx
const props = { foo: 42, ...rest };
(<div {...props}>Children</div>);
```

```sh
$ npm ls --depth=0
~/test
├── eslint@3.3.1
└── eslint-plugin-react@6.1.1

$ node_modules/.bin/eslint pass.jsx
$ node_modules/.bin/eslint fail1.jsx
Cannot read property 'find' of undefined
TypeError: Cannot read property 'find' of undefined
    at findObjectProp (~/test/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js:28:29)
    at ~/test/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js:46:20
    at Array.find (native)
    at findJsxProp (~/test/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js:40:25)
    at EventEmitter.JSXElement (~/test/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js:66:14)
    at emitOne (events.js:96:13)
    at EventEmitter.emit (events.js:188:7)
    at NodeEventGenerator.enterNode (~/test/node_modules/eslint/lib/util/node-event-generator.js:40:22)
    at CodePathAnalyzer.enterNode (~/test/node_modules/eslint/lib/code-path-analysis/code-path-analyzer.js:608:23)
    at CommentEventGenerator.enterNode (~/test/node_modules/eslint/lib/util/comment-event-generator.js:97:23)
$ node_modules/.bin/eslint fail2.jsx
Cannot read property 'name' of undefined
TypeError: Cannot read property 'name' of undefined
    at ~/test/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js:29:24
    at Array.find (native)
    at findObjectProp (~/test/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js:28:30)
    at ~/test/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js:46:20
    at Array.find (native)
    at findJsxProp (~/test/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js:40:25)
    at EventEmitter.JSXElement (~/test/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js:66:14)
    at emitOne (events.js:96:13)
    at EventEmitter.emit (events.js:188:7)
    at NodeEventGenerator.enterNode (~/test/node_modules/eslint/lib/util/node-event-generator.js:40:22)
```

Pull request:
Handle nested levels of spread properties (Fixes #771)
Also no longer breaks for `...rest` properties.

Patch:
diff --git a/lib/rules/no-danger-with-children.js b/lib/rules/no-danger-with-children.js
index 8482b0fc..98bcb73c 100644
--- a/lib/rules/no-danger-with-children.js
+++ b/lib/rules/no-danger-with-children.js
@@ -19,14 +19,30 @@ module.exports = {
     schema: [] // no options
   },
   create: function(context) {
+    function findSpreadVariable(name) {
+      return variableUtil.variablesInScope(context).find(function (item) {
+        return item.name === name;
+      });
+    }
     /**
      * Takes a ObjectExpression and returns the value of the prop if it has it
      * @param {object} node - ObjectExpression node
      * @param {string} propName - name of the prop to look for
      */
     function findObjectProp(node, propName) {
+      if (!node.properties) {
+        return false;
+      }
       return node.properties.find(function(prop) {
-        return prop.key.name === propName;
+        if (prop.type === 'Property') {
+          return prop.key.name === propName;
+        } else if (prop.type === 'ExperimentalSpreadProperty') {
+          var variable = findSpreadVariable(prop.argument.name);
+          if (variable && variable.defs[0].node.init) {
+            return findObjectProp(variable.defs[0].node.init, propName);
+          }
+        }
+        return false;
       });
     }
 
@@ -39,9 +55,7 @@ module.exports = {
       var attributes = node.openingElement.attributes;
       return attributes.find(function (attribute) {
         if (attribute.type === 'JSXSpreadAttribute') {
-          var variable = variableUtil.variablesInScope(context).find(function (item) {
-            return item.name === attribute.argument.name;
-          });
+          var variable = findSpreadVariable(attribute.argument.name);
           if (variable && variable.defs[0].node.init) {
             return findObjectProp(variable.defs[0].node.init, propName);
           }


Test patch:
diff --git a/tests/lib/rules/no-danger-with-children.js b/tests/lib/rules/no-danger-with-children.js
index cc045ae5..4713d2e9 100644
--- a/tests/lib/rules/no-danger-with-children.js
+++ b/tests/lib/rules/no-danger-with-children.js
@@ -14,6 +14,7 @@ var RuleTester = require('eslint').RuleTester;
 var parserOptions = {
   ecmaVersion: 6,
   ecmaFeatures: {
+    experimentalObjectRestSpread: true,
     jsx: true
   }
 };
@@ -46,11 +47,20 @@ ruleTester.run('no-danger-with-children', rule, {
     },
     {
       code: [
-        'const props = { children: "Children" };',
+        'const moreProps = { className: "eslint" };',
+        'const props = { children: "Children", ...moreProps };',
         '<div {...props} />'
       ].join('\n'),
       parserOptions: parserOptions
     },
+    {
+      code: [
+        'const otherProps = { children: "Children" };',
+        'const { a, b, ...props } = otherProps;',
+        '<div {...props} />',
+      ].join('\n'),
+      parserOptions: parserOptions
+    },
     {
       code: '<Hello>Children</Hello>',
       parserOptions: parserOptions
@@ -103,7 +113,6 @@ ruleTester.run('no-danger-with-children', rule, {
       code: [
         'const props = { children: "Children", dangerouslySetInnerHTML: { __html: "HTML" } };',
         '<div {...props} />',
-        '//foobar'
       ].join('\n'),
       errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
       parserOptions: parserOptions
@@ -185,6 +194,16 @@ ruleTester.run('no-danger-with-children', rule, {
       ].join('\n'),
       errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
       parserOptions: parserOptions
+    },
+    {
+      code: [
+        'const moreProps = { children: "Children" };',
+        'const otherProps = { ...moreProps };',
+        'const props = { ...otherProps, dangerouslySetInnerHTML: { __html: "HTML" } };',
+        'React.createElement("div", props);'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
     }
   ]
 });