diff --git a/README.md b/README.md
index 5137ca0fe3..01897602fa 100644
--- a/README.md
+++ b/README.md
@@ -111,6 +111,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
* [react/sort-comp](docs/rules/sort-comp.md): Enforce component methods order
* [react/sort-prop-types](docs/rules/sort-prop-types.md): Enforce propTypes declarations alphabetical sorting
* [react/style-prop-object](docs/rules/style-prop-object.md): Enforce style prop value being an object
+* [react/void-dom-elements-no-children](docs/rules/void-dom-elements-no-children.md): Prevent void DOM elements (e.g. ``, `
`) from receiving children
## JSX-specific rules
diff --git a/docs/rules/void-dom-elements-no-children.md b/docs/rules/void-dom-elements-no-children.md
new file mode 100644
index 0000000000..9bbdda7f90
--- /dev/null
+++ b/docs/rules/void-dom-elements-no-children.md
@@ -0,0 +1,30 @@
+# Prevent void DOM elements (e.g. ``, `
`) from receiving children
+
+There are some HTML elements that are only self-closing (e.g. `img`, `br`, `hr`). These are collectively known as void DOM elements. If you try to give these children, React will give you a warning like:
+
+> Invariant Violation: img is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`.
+
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```jsx
+
Children
+
+
+React.createElement('br', undefined, 'Children')
+React.createElement('br', { children: 'Children' })
+React.createElement('br', { dangerouslySetInnerHTML: { __html: 'HTML' } })
+```
+
+The following patterns are not considered warnings:
+
+```jsx
+
Children
+
+
+React.createElement('div', undefined, 'Children')
+React.createElement('div', { children: 'Children' })
+React.createElement('div', { dangerouslySetInnerHTML: { __html: 'HTML' } })
+```
diff --git a/index.js b/index.js
index df651dfbd1..aac6284a25 100644
--- a/index.js
+++ b/index.js
@@ -57,6 +57,7 @@ var allRules = {
'style-prop-object': require('./lib/rules/style-prop-object'),
'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'),
'no-children-prop': require('./lib/rules/no-children-prop'),
+ 'void-dom-elements-no-children': require('./lib/rules/void-dom-elements-no-children'),
'no-comment-textnodes': require('./lib/rules/no-comment-textnodes'),
'require-extension': require('./lib/rules/require-extension'),
'wrap-multilines': require('./lib/rules/wrap-multilines'),
diff --git a/lib/rules/void-dom-elements-no-children.js b/lib/rules/void-dom-elements-no-children.js
new file mode 100644
index 0000000000..323dbed3b2
--- /dev/null
+++ b/lib/rules/void-dom-elements-no-children.js
@@ -0,0 +1,141 @@
+/**
+ * @fileoverview Prevent void elements (e.g. ,
) from receiving
+ * children
+ * @author Joe Lencioni
+ */
+'use strict';
+
+var find = require('array.prototype.find');
+var has = require('has');
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+// Using an object here to avoid array scan. We should switch to Set once
+// support is good enough.
+var VOID_DOM_ELEMENTS = {
+ area: true,
+ base: true,
+ br: true,
+ col: true,
+ embed: true,
+ hr: true,
+ img: true,
+ input: true,
+ keygen: true,
+ link: true,
+ menuitem: true,
+ meta: true,
+ param: true,
+ source: true,
+ track: true,
+ wbr: true
+};
+
+function isVoidDOMElement(elementName) {
+ return has(VOID_DOM_ELEMENTS, elementName);
+}
+
+function errorMessage(elementName) {
+ return 'Void DOM element <' + elementName + ' /> cannot receive children.';
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ docs: {
+ description: 'Prevent passing of children to void DOM elements (e.g.
).',
+ category: 'Best Practices',
+ recommended: false
+ },
+ schema: []
+ },
+
+ create: function(context) {
+ return {
+ JSXElement: function(node) {
+ var elementName = node.openingElement.name.name;
+
+ if (!isVoidDOMElement(elementName)) {
+ // e.g.
+ return;
+ }
+
+ if (node.children.length > 0) {
+ // e.g.
Foo
+ context.report({
+ node: node,
+ message: errorMessage(elementName)
+ });
+ }
+
+ var attributes = node.openingElement.attributes;
+
+ var hasChildrenAttributeOrDanger = !!find(attributes, function(attribute) {
+ if (!attribute.name) {
+ return false;
+ }
+
+ return attribute.name.name === 'children' || attribute.name.name === 'dangerouslySetInnerHTML';
+ });
+
+ if (hasChildrenAttributeOrDanger) {
+ // e.g.
+ context.report({
+ node: node,
+ message: errorMessage(elementName)
+ });
+ }
+ },
+
+ CallExpression: function(node) {
+ if (node.callee.type !== 'MemberExpression') {
+ return;
+ }
+
+ if (node.callee.property.name !== 'createElement') {
+ return;
+ }
+
+ var args = node.arguments;
+ var elementName = args[0].value;
+
+ if (!isVoidDOMElement(elementName)) {
+ // e.g. React.createElement('div');
+ return;
+ }
+
+ var firstChild = args[2];
+ if (firstChild) {
+ // e.g. React.createElement('br', undefined, 'Foo')
+ context.report({
+ node: node,
+ message: errorMessage(elementName)
+ });
+ }
+
+ var props = args[1].properties;
+
+ var hasChildrenPropOrDanger = !!find(props, function(prop) {
+ if (!prop.key) {
+ return false;
+ }
+
+ return prop.key.name === 'children' || prop.key.name === 'dangerouslySetInnerHTML';
+ });
+
+ if (hasChildrenPropOrDanger) {
+ // e.g. React.createElement('br', { children: 'Foo' })
+ context.report({
+ node: node,
+ message: errorMessage(elementName)
+ });
+ }
+ }
+ };
+ }
+};
diff --git a/tests/lib/rules/void-dom-elements-no-children.js b/tests/lib/rules/void-dom-elements-no-children.js
new file mode 100644
index 0000000000..022d8e877d
--- /dev/null
+++ b/tests/lib/rules/void-dom-elements-no-children.js
@@ -0,0 +1,96 @@
+/**
+ * @fileoverview Tests for void-dom-elements-no-children
+ * @author Joe Lencioni
+ */
+
+'use strict';
+
+// -----------------------------------------------------------------------------
+// Requirements
+// -----------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/void-dom-elements-no-children');
+var RuleTester = require('eslint').RuleTester;
+
+var parserOptions = {
+ ecmaVersion: 6,
+ ecmaFeatures: {
+ experimentalObjectRestSpread: true,
+ jsx: true
+ }
+};
+
+function errorMessage(elementName) {
+ return 'Void DOM element <' + elementName + ' /> cannot receive children.';
+}
+
+// -----------------------------------------------------------------------------
+// Tests
+// -----------------------------------------------------------------------------
+
+var ruleTester = new RuleTester();
+ruleTester.run('void-dom-elements-no-children', rule, {
+ valid: [
+ {
+ code: 'Foo
;',
+ parserOptions: parserOptions
+ },
+ {
+ code: ';',
+ parserOptions: parserOptions
+ },
+ {
+ code: ';',
+ parserOptions: parserOptions
+ },
+ {
+ code: 'React.createElement("div", {}, "Foo");',
+ parserOptions: parserOptions
+ },
+ {
+ code: 'React.createElement("div", { children: "Foo" });',
+ parserOptions: parserOptions
+ },
+ {
+ code: 'React.createElement("div", { dangerouslySetInnerHTML: { __html: "Foo" } });',
+ parserOptions: parserOptions
+ }
+ ],
+ invalid: [
+ {
+ code: '
Foo;',
+ errors: [{message: errorMessage('br')}],
+ parserOptions: parserOptions
+ },
+ {
+ code: '
;',
+ errors: [{message: errorMessage('br')}],
+ parserOptions: parserOptions
+ },
+ {
+ code: ';',
+ errors: [{message: errorMessage('img')}],
+ parserOptions: parserOptions
+ },
+ {
+ code: '
;',
+ errors: [{message: errorMessage('br')}],
+ parserOptions: parserOptions
+ },
+ {
+ code: 'React.createElement("br", {}, "Foo");',
+ errors: [{message: errorMessage('br')}],
+ parserOptions: parserOptions
+ },
+ {
+ code: 'React.createElement("br", { children: "Foo" });',
+ errors: [{message: errorMessage('br')}],
+ parserOptions: parserOptions
+ },
+ {
+ code: 'React.createElement("br", { dangerouslySetInnerHTML: { __html: "Foo" } });',
+ errors: [{message: errorMessage('br')}],
+ parserOptions: parserOptions
+ }
+ ]
+});