Repository: yannickcr/eslint-plugin-react
Pull request #1080 (2017-02-21 05:24:50)

Update void-dom-elements-no-children createElement checks
This ensures that the rule only checks for a `createElement` call from
React. It will also prevent the rule from failing when it hits a
`createElement` that only has an element as an argument.

This PR addresses #1073.


Patch:
diff --git a/lib/rules/void-dom-elements-no-children.js b/lib/rules/void-dom-elements-no-children.js
index 323dbed3..17ac934a 100644
--- a/lib/rules/void-dom-elements-no-children.js
+++ b/lib/rules/void-dom-elements-no-children.js
@@ -8,6 +8,8 @@
 var find = require('array.prototype.find');
 var has = require('has');
 
+var Components = require('../util/Components');
+
 // ------------------------------------------------------------------------------
 // Helpers
 // ------------------------------------------------------------------------------
@@ -55,7 +57,7 @@ module.exports = {
     schema: []
   },
 
-  create: function(context) {
+  create: Components.detect(function(context, components, utils) {
     return {
       JSXElement: function(node) {
         var elementName = node.openingElement.name.name;
@@ -93,11 +95,11 @@ module.exports = {
       },
 
       CallExpression: function(node) {
-        if (node.callee.type !== 'MemberExpression') {
+        if (node.callee.type !== 'MemberExpression' && node.callee.type !== 'Identifier') {
           return;
         }
 
-        if (node.callee.property.name !== 'createElement') {
+        if (!utils.hasDestructuredReactCreateElement() && !utils.isReactCreateElement(node)) {
           return;
         }
 
@@ -109,6 +111,10 @@ module.exports = {
           return;
         }
 
+        if (args.length < 2) {
+          return;
+        }
+
         var firstChild = args[2];
         if (firstChild) {
           // e.g. React.createElement('br', undefined, 'Foo')
@@ -137,5 +143,5 @@ module.exports = {
         }
       }
     };
-  }
+  })
 };
diff --git a/lib/util/Components.js b/lib/util/Components.js
index eb2bad53..1eacf099 100644
--- a/lib/util/Components.js
+++ b/lib/util/Components.js
@@ -213,6 +213,40 @@ function componentRule(rule, context) {
       return false;
     },
 
+    /**
+     * Check if createElement is destructured from React import
+     *
+     * @returns {Boolean} True if createElement is destructured from React
+     */
+    hasDestructuredReactCreateElement: function() {
+      var variables = variableUtil.variablesInScope(context);
+      var variable = variableUtil.getVariable(variables, 'createElement');
+      if (variable) {
+        var map = variable.scope.set;
+        if (map.has('React')) {
+          return true;
+        }
+      }
+      return false;
+    },
+
+    /**
+     * Checks to see if node is called within React.createElement
+     *
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {Boolean} True if React.createElement called
+     */
+    isReactCreateElement: function(node) {
+      return (
+        node &&
+        node.callee &&
+        node.callee.object &&
+        node.callee.object.name === 'React' &&
+        node.callee.property &&
+        node.callee.property.name === 'createElement'
+      );
+    },
+
     /**
      * Check if the node is returning JSX
      *
@@ -256,25 +290,9 @@ function componentRule(rule, context) {
         node[property] &&
         node[property].type === 'JSXElement'
       ;
-      var destructuredReactCreateElement = function () {
-        var variables = variableUtil.variablesInScope(context);
-        var variable = variableUtil.getVariable(variables, 'createElement');
-        if (variable) {
-          var map = variable.scope.set;
-          if (map.has('React')) {
-            return true;
-          }
-        }
-        return false;
-      };
       var returnsReactCreateElement =
-        destructuredReactCreateElement() ||
-        node[property] &&
-        node[property].callee &&
-        node[property].callee.object &&
-        node[property].callee.object.name === 'React' &&
-        node[property].callee.property &&
-        node[property].callee.property.name === 'createElement'
+        this.hasDestructuredReactCreateElement() ||
+        this.isReactCreateElement(node[property])
       ;
 
       return Boolean(


Test patch:
diff --git a/tests/lib/rules/void-dom-elements-no-children.js b/tests/lib/rules/void-dom-elements-no-children.js
index 022d8e87..97352b5f 100644
--- a/tests/lib/rules/void-dom-elements-no-children.js
+++ b/tests/lib/rules/void-dom-elements-no-children.js
@@ -54,6 +54,28 @@ ruleTester.run('void-dom-elements-no-children', rule, {
     {
       code: 'React.createElement("div", { dangerouslySetInnerHTML: { __html: "Foo" } });',
       parserOptions: parserOptions
+    }, {
+      code: 'document.createElement("img")',
+      parserOptions: parserOptions
+    }, {
+      code: 'React.createElement("img");',
+      parserOptions: parserOptions
+    }, {
+      code: [
+        'import React from "react";',
+        'const { createElement } = React;',
+        'createElement("div")'
+      ].join('\n'),
+      parser: 'babel-eslint',
+      parserOptions: parserOptions
+    }, {
+      code: [
+        'import React from "react";',
+        'const { createElement } = React;',
+        'createElement("img")'
+      ].join('\n'),
+      parser: 'babel-eslint',
+      parserOptions: parserOptions
     }
   ],
   invalid: [
@@ -91,6 +113,36 @@ ruleTester.run('void-dom-elements-no-children', rule, {
       code: 'React.createElement("br", { dangerouslySetInnerHTML: { __html: "Foo" } });',
       errors: [{message: errorMessage('br')}],
       parserOptions: parserOptions
+    },
+    {
+      code: [
+        'import React from "react";',
+        'const createElement = React.createElement;',
+        'createElement("img", {}, "Foo");'
+      ].join('\n'),
+      errors: [{message: errorMessage('img')}],
+      parser: 'babel-eslint',
+      parserOptions: parserOptions
+    },
+    {
+      code: [
+        'import React from "react";',
+        'const createElement = React.createElement;',
+        'createElement("img", { children: "Foo" });'
+      ].join('\n'),
+      errors: [{message: errorMessage('img')}],
+      parser: 'babel-eslint',
+      parserOptions: parserOptions
+    },
+    {
+      code: [
+        'import React from "react";',
+        'const createElement = React.createElement;',
+        'createElement("img", { dangerouslySetInnerHTML: { __html: "Foo" } });'
+      ].join('\n'),
+      errors: [{message: errorMessage('img')}],
+      parser: 'babel-eslint',
+      parserOptions: parserOptions
     }
   ]
 });