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

Issue:
Rule proposal: prevent both children and dangerouslySetInnerHTML at the same time
React will issue a warning if you try to use dangerouslySetInnerHTML and children at the same time.

https://github.com/facebook/react/blob/85dcbf83/src/renderers/dom/shared/ReactDOMComponent.js#L170

This should be easy enough to enforce for most cases with a linter rule.

Bad:

```jsx
<div dangerouslySetInnerHTML={{ __html: 'HTML' }}>
  Children
</div>
```

Good:

```jsx
<div dangerouslySetInnerHTML={{ __html: 'HTML' }} />
```

```jsx
<div>
  Children
</div>
```

Pull request description:
Add new dom-elements-no-danger-with-children rule (Fixes #710)
Prevents dangerouslySetInnerHTML and children from being used at the same time (Fixes #710)

Patch:
diff --git a/README.md b/README.md
index 46a9d595..163a8c71 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,7 @@ Finally, enable all of the rules that you would like to use.  Use [our preset](#
 * [react/forbid-component-props](docs/rules/forbid-component-props.md): Forbid certain props on Components
 * [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes
 * [react/no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties
+* [react/no-danger-with-children](docs/rules/no-danger-with-children.md): Prevent problem with children and props.dangerouslySetInnerHTML
 * [react/no-deprecated](docs/rules/no-deprecated.md): Prevent usage of deprecated methods
 * [react/no-did-mount-set-state](docs/rules/no-did-mount-set-state.md): Prevent usage of `setState` in `componentDidMount`
 * [react/no-did-update-set-state](docs/rules/no-did-update-set-state.md): Prevent usage of `setState` in `componentDidUpdate`
diff --git a/docs/rules/no-danger-with-children.md b/docs/rules/no-danger-with-children.md
new file mode 100644
index 00000000..bb21a035
--- /dev/null
+++ b/docs/rules/no-danger-with-children.md
@@ -0,0 +1,53 @@
+# Prevent problem with children and props.dangerouslySetInnerHTML (no-danger-with-children)
+
+This rule helps prevent problems caused by using children and the dangerouslySetInnerHTML prop at the same time.
+React will throw a warning if this rule is ignored.
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```jsx
+<div dangerouslySetInnerHTML={{ __html: "HTML" }}>
+  Children
+</div>
+
+<Hello dangerouslySetInnerHTML={{ __html: "HTML" }}>
+  Children
+</Hello>
+
+```
+
+```js
+React.createElement("div", { dangerouslySetInnerHTML: { __html: "HTML" } }, "Children");
+
+React.createElement("Hello", { dangerouslySetInnerHTML: { __html: "HTML" } }, "Children");
+```
+
+The following patterns are not considered warnings:
+
+```jsx
+<div dangerouslySetInnerHTML={{ __html: "HTML" }} />
+
+<Hello dangerouslySetInnerHTML={{ __html: "HTML" }} />
+
+<div>
+  Children
+</div>
+
+<Hello>
+  Children
+</Hello>
+
+```
+
+```js
+React.createElement("div", { dangerouslySetInnerHTML: { __html: "HTML" } });
+
+React.createElement("Hello", { dangerouslySetInnerHTML: { __html: "HTML" } });
+
+React.createElement("div", {}, "Children");
+
+React.createElement("Hello", {}, "Children");
+```
+
diff --git a/index.js b/index.js
index 0a258473..88ab4dcb 100644
--- a/index.js
+++ b/index.js
@@ -53,7 +53,8 @@ var rules = {
   'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank'),
   'jsx-filename-extension': require('./lib/rules/jsx-filename-extension'),
   'require-optimization': require('./lib/rules/require-optimization'),
-  'no-find-dom-node': require('./lib/rules/no-find-dom-node')
+  'no-find-dom-node': require('./lib/rules/no-find-dom-node'),
+  'no-danger-with-children': require('./lib/rules/no-danger-with-children')
 };
 
 var ruleNames = Object.keys(rules);
diff --git a/lib/rules/no-danger-with-children.js b/lib/rules/no-danger-with-children.js
new file mode 100644
index 00000000..c9deb368
--- /dev/null
+++ b/lib/rules/no-danger-with-children.js
@@ -0,0 +1,80 @@
+/**
+ * @fileoverview Report when a DOM element is using both children and dangerouslySetInnerHTML
+ * @author David Petersen
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Report when a DOM element is using both children and dangerouslySetInnerHTML',
+      category: '',
+      recommended: false
+    },
+    schema: [] // no options
+  },
+  create: function(context) {
+    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;
+          }
+        }
+
+        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`');
+          }
+        }
+      },
+      CallExpression: function (node) {
+        if (
+          node.callee
+          && node.callee.type === 'MemberExpression'
+          && node.callee.property.name === 'createElement'
+          && node.arguments.length > 1
+        ) {
+          var hasChildren = false;
+
+          var props = node.arguments[1].properties;
+          var dangerously = props.find(function(prop) {
+            return prop.key.name === 'dangerouslySetInnerHTML';
+          });
+
+
+          if (node.arguments.length === 2) {
+            var childrenProp = props.find(function(prop) {
+              return prop.key.name === 'children';
+            });
+            if (childrenProp) {
+              hasChildren = true;
+            }
+          } else {
+            hasChildren = true;
+          }
+
+          if (dangerously && hasChildren) {
+            context.report(node, 'Only set one of `children` or `props.dangerouslySetInnerHTML`');
+          }
+        }
+      }
+    };
+  }
+};


Test patch:
diff --git a/tests/lib/rules/no-danger-with-children.js b/tests/lib/rules/no-danger-with-children.js
new file mode 100644
index 00000000..a44124bc
--- /dev/null
+++ b/tests/lib/rules/no-danger-with-children.js
@@ -0,0 +1,139 @@
+/**
+ * @fileoverview Report when a DOM element is using both children and dangerouslySetInnerHTML
+ * @author David Petersen
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/no-danger-with-children');
+var RuleTester = require('eslint').RuleTester;
+
+var parserOptions = {
+  ecmaVersion: 6,
+  ecmaFeatures: {
+    jsx: true
+  }
+};
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+var ruleTester = new RuleTester();
+ruleTester.run('no-danger-with-children', rule, {
+  valid: [
+    {
+      code: '<div>Children</div>',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<div dangerouslySetInnerHTML={{ __html: "HTML" }} />',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<Hello>Children</Hello>',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<Hello dangerouslySetInnerHTML={{ __html: "HTML" }} />',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", { dangerouslySetInnerHTML: { __html: "HTML" } });',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", {}, "Children");',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("Hello", { dangerouslySetInnerHTML: { __html: "HTML" } });',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("Hello", {}, "Children");',
+      parserOptions: parserOptions
+    }
+  ],
+  invalid: [
+    {
+      code: [
+        '<div dangerouslySetInnerHTML={{ __html: "HTML" }}>',
+        '  Children',
+        '</div>'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      code: '<div dangerouslySetInnerHTML={{ __html: "HTML" }} children="Children" />',
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      code: [
+        '<Hello dangerouslySetInnerHTML={{ __html: "HTML" }}>',
+        '  Children',
+        '</Hello>'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      code: '<Hello dangerouslySetInnerHTML={{ __html: "HTML" }} children="Children" />',
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      code: [
+        'React.createElement(',
+        '  "div",',
+        '  { dangerouslySetInnerHTML: { __html: "HTML" } },',
+        '  "Children"',
+        ');'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      code: [
+        'React.createElement(',
+        '  "div",',
+        '  {',
+        '    dangerouslySetInnerHTML: { __html: "HTML" },',
+        '    children: "Children",',
+        '  }',
+        ');'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      code: [
+        'React.createElement(',
+        '  "Hello",',
+        '  { dangerouslySetInnerHTML: { __html: "HTML" } },',
+        '  "Children"',
+        ');'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      code: [
+        'React.createElement(',
+        '  "Hello",',
+        '  {',
+        '    dangerouslySetInnerHTML: { __html: "HTML" },',
+        '    children: "Children",',
+        '  }',
+        ');'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    }
+  ]
+});