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

Issue:
no-danger-with-children exception with spread props
```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
<div />
```

```jsx
// fail.jsx
<div {...props} />
```

```sh
$ npm ls --depth=0
~/test
├── eslint@3.3.0
└── eslint-plugin-react@6.1.0

$ node_modules/.bin/eslint pass.jsx
$ node_modules/.bin/eslint fail.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:34
    at Array.find (native)
    at EventEmitter.JSXElement (~/test/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js:28:41)
    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)
    at Controller.traverser.traverse.enter (~/test/node_modules/eslint/lib/eslint.js:895:36)
    at Controller.__execute (~/test/node_modules/estraverse/estraverse.js:397:31)
```

Pull request description:
Make no-danger-with-children deal with props that are variables (Fixes #767)

Patch:
diff --git a/lib/rules/no-danger-with-children.js b/lib/rules/no-danger-with-children.js
index c9deb368..8482b0fc 100644
--- a/lib/rules/no-danger-with-children.js
+++ b/lib/rules/no-danger-with-children.js
@@ -4,6 +4,8 @@
  */
 'use strict';
 
+var variableUtil = require('../util/variable');
+
 // ------------------------------------------------------------------------------
 // Rule Definition
 // ------------------------------------------------------------------------------
@@ -17,31 +19,53 @@ module.exports = {
     schema: [] // no options
   },
   create: function(context) {
+    /**
+     * 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) {
+      return node.properties.find(function(prop) {
+        return prop.key.name === propName;
+      });
+    }
+
+    /**
+     * Takes a JSXElement and returns the value of the prop if it has it
+     * @param {object} node - JSXElement node
+     * @param {string} propName - name of the prop to look for
+     */
+    function findJsxProp(node, propName) {
+      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;
+          });
+          if (variable && variable.defs[0].node.init) {
+            return findObjectProp(variable.defs[0].node.init, propName);
+          }
+        }
+        return attribute.name && attribute.name.name === propName;
+      });
+    }
+
     return {
       JSXElement: function (node) {
         var hasChildren = false;
-        var attributes = node.openingElement.attributes;
 
         if (node.children.length) {
           hasChildren = true;
-        } else {
-          var childrenProp = attributes.find(function (attribute) {
-            return attribute.name.name === 'children';
-          });
-          if (childrenProp) {
-            hasChildren = true;
-          }
+        } else if (findJsxProp(node, 'children')) {
+          hasChildren = true;
         }
 
-        if (attributes && hasChildren) {
-          var jsxElement = attributes.find(function (attribute) {
-            return attribute.name.name === 'dangerouslySetInnerHTML';
-          });
-
-
-          if (jsxElement) {
-            context.report(node, 'Only set one of `children` or `props.dangerouslySetInnerHTML`');
-          }
+        if (
+          node.openingElement.attributes
+          && hasChildren
+          && findJsxProp(node, 'dangerouslySetInnerHTML')
+        ) {
+          context.report(node, 'Only set one of `children` or `props.dangerouslySetInnerHTML`');
         }
       },
       CallExpression: function (node) {
@@ -53,17 +77,21 @@ module.exports = {
         ) {
           var hasChildren = false;
 
-          var props = node.arguments[1].properties;
-          var dangerously = props.find(function(prop) {
-            return prop.key.name === 'dangerouslySetInnerHTML';
-          });
+          var props = node.arguments[1];
 
+          if (props.type === 'Identifier') {
+            var variable = variableUtil.variablesInScope(context).find(function (item) {
+              return item.name === props.name;
+            });
+            if (variable && variable.defs[0].node.init) {
+              props = variable.defs[0].node.init;
+            }
+          }
+
+          var dangerously = findObjectProp(props, 'dangerouslySetInnerHTML');
 
           if (node.arguments.length === 2) {
-            var childrenProp = props.find(function(prop) {
-              return prop.key.name === 'children';
-            });
-            if (childrenProp) {
+            if (findObjectProp(props, 'children')) {
               hasChildren = true;
             }
           } else {


Test patch:
diff --git a/tests/lib/rules/no-danger-with-children.js b/tests/lib/rules/no-danger-with-children.js
index a44124bc..cc045ae5 100644
--- a/tests/lib/rules/no-danger-with-children.js
+++ b/tests/lib/rules/no-danger-with-children.js
@@ -33,6 +33,24 @@ ruleTester.run('no-danger-with-children', rule, {
       code: '<div dangerouslySetInnerHTML={{ __html: "HTML" }} />',
       parserOptions: parserOptions
     },
+    {
+      code: '<div children="Children" />',
+      parserOptions: parserOptions
+    },
+    {
+      code: [
+        'const props = { dangerouslySetInnerHTML: { __html: "HTML" } };',
+        '<div {...props} />'
+      ].join('\n'),
+      parserOptions: parserOptions
+    },
+    {
+      code: [
+        'const props = { children: "Children" };',
+        '<div {...props} />'
+      ].join('\n'),
+      parserOptions: parserOptions
+    },
     {
       code: '<Hello>Children</Hello>',
       parserOptions: parserOptions
@@ -73,6 +91,23 @@ ruleTester.run('no-danger-with-children', rule, {
       errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
       parserOptions: parserOptions
     },
+    {
+      code: [
+        'const props = { dangerouslySetInnerHTML: { __html: "HTML" } };',
+        '<div {...props}>Children</div>'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      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
+    },
     {
       code: [
         '<Hello dangerouslySetInnerHTML={{ __html: "HTML" }}>',
@@ -134,6 +169,22 @@ ruleTester.run('no-danger-with-children', rule, {
       ].join('\n'),
       errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
       parserOptions: parserOptions
+    },
+    {
+      code: [
+        'const props = { dangerouslySetInnerHTML: { __html: "HTML" } };',
+        'React.createElement("div", props, "Children");'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      code: [
+        'const props = { children: "Children", dangerouslySetInnerHTML: { __html: "HTML" } };',
+        'React.createElement("div", props);'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
     }
   ]
 });