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

Issue:
False positive with display-name rule
Seems like this: `document.createElement` makes the rule believe you're using `React.createElement`:

https://dxr.mozilla.org/mozilla-central/source/devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer.js#16

Pull request:
Check for createElement being called from React
Fixes #996. This will prevent document.createElement triggering a
false positive for react/display-name.

Patch:
diff --git a/lib/rules/sort-prop-types.js b/lib/rules/sort-prop-types.js
index 817e4123..b3c04f54 100644
--- a/lib/rules/sort-prop-types.js
+++ b/lib/rules/sort-prop-types.js
@@ -3,10 +3,6 @@
  */
 'use strict';
 
-var find = require('array.prototype.find');
-
-var variableUtil = require('../util/variable');
-
 // ------------------------------------------------------------------------------
 // Rule Definition
 // ------------------------------------------------------------------------------
@@ -154,32 +150,11 @@ module.exports = {
       },
 
       MemberExpression: function(node) {
-        if (!isPropTypesDeclaration(node.property)) {
-          return;
-        }
-        var right = node.parent.right;
-        var declarations;
-        switch (right && right.type) {
-          case 'ObjectExpression':
-            declarations = right.properties;
-            break;
-          case 'Identifier':
-            var variable = find(variableUtil.variablesInScope(context), function (item) {
-              return item.name === right.name;
-            });
-            if (
-              !variable || !variable.defs[0] ||
-              !variable.defs[0].node.init || !variable.defs[0].node.init.properties
-            ) {
-              break;
-            }
-            declarations = variable.defs[0].node.init.properties;
-            break;
-          default:
-            break;
-        }
-        if (declarations) {
-          checkSorted(declarations);
+        if (isPropTypesDeclaration(node.property)) {
+          var right = node.parent.right;
+          if (right && right.type === 'ObjectExpression') {
+            checkSorted(right.properties);
+          }
         }
       },
 
diff --git a/lib/util/Components.js b/lib/util/Components.js
index 37816a3b..a71884b7 100644
--- a/lib/util/Components.js
+++ b/lib/util/Components.js
@@ -255,9 +255,23 @@ 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'
       ;
diff --git a/lib/util/variable.js b/lib/util/variable.js
index 81ae18d4..6ec67c68 100644
--- a/lib/util/variable.js
+++ b/lib/util/variable.js
@@ -11,16 +11,21 @@
  * @returns {Boolean} True if the variable was found, false if not.
  */
 function findVariable(variables, name) {
-  var i;
-  var len;
-
-  for (i = 0, len = variables.length; i < len; i++) {
-    if (variables[i].name === name) {
-      return true;
-    }
-  }
+  return variables.some(function (variable) {
+    return variable.name === name;
+  });
+}
 
-  return false;
+/**
+ * Find and return a particular variable in a list
+ * @param {Array} variables The variables list.
+ * @param {Array} name The name of the variable to search.
+ * @returns {Object} Variable if the variable was found, null if not.
+ */
+function getVariable(variables, name) {
+  return variables.find(function (variable) {
+    return variable.name === name;
+  });
 }
 
 /**
@@ -52,5 +57,6 @@ function variablesInScope(context) {
 
 module.exports = {
   findVariable: findVariable,
+  getVariable: getVariable,
   variablesInScope: variablesInScope
 };


Test patch:
diff --git a/tests/lib/rules/display-name.js b/tests/lib/rules/display-name.js
index 28ef63d7..3f8db251 100644
--- a/tests/lib/rules/display-name.js
+++ b/tests/lib/rules/display-name.js
@@ -378,6 +378,19 @@ ruleTester.run('display-name', rule, {
       ')'
     ].join('\n'),
     parser: 'babel-eslint'
+  }, {
+    code: [
+      'module.exports = {',
+      '  createElement: tagName => document.createElement(tagName)',
+      '};'
+    ].join('\n'),
+    parser: 'babel-eslint'
+  }, {
+    code: [
+      'const { createElement } = document;',
+      'createElement("a");'
+    ].join('\n'),
+    parser: 'babel-eslint'
   }],
 
   invalid: [{
@@ -548,5 +561,40 @@ ruleTester.run('display-name', rule, {
     errors: [{
       message: 'Component definition is missing display name'
     }]
+  }, {
+    code: [
+      'import React, { createElement } from "react";',
+      'export default (props) => {',
+      '  return createElement("div", {}, "hello");',
+      '};'
+    ].join('\n'),
+    parser: 'babel-eslint',
+    errors: [{
+      message: 'Component definition is missing display name'
+    }]
+  }, {
+    code: [
+      'import React from "react";',
+      'const { createElement } = React;',
+      'export default (props) => {',
+      '  return createElement("div", {}, "hello");',
+      '};'
+    ].join('\n'),
+    parser: 'babel-eslint',
+    errors: [{
+      message: 'Component definition is missing display name'
+    }]
+  }, {
+    code: [
+      'import React from "react";',
+      'const createElement = React.createElement;',
+      'export default (props) => {',
+      '  return createElement("div", {}, "hello");',
+      '};'
+    ].join('\n'),
+    parser: 'babel-eslint',
+    errors: [{
+      message: 'Component definition is missing display name'
+    }]
   }]
 });
diff --git a/tests/lib/rules/sort-prop-types.js b/tests/lib/rules/sort-prop-types.js
index fd1fe835..d18b8aa1 100644
--- a/tests/lib/rules/sort-prop-types.js
+++ b/tests/lib/rules/sort-prop-types.js
@@ -323,15 +323,6 @@ ruleTester.run('sort-prop-types', rule, {
       '}'
     ].join('\n'),
     parser: 'babel-eslint'
-  }, {
-    code: [
-      'const propTypes = require(\'./externalPropTypes\')',
-      'const TextFieldLabel = (props) => {',
-      '  return <div />;',
-      '};',
-      'TextFieldLabel.propTypes = propTypes;'
-    ].join('\n'),
-    parserOptions: parserOptions
   }],
 
   invalid: [{
@@ -633,23 +624,5 @@ ruleTester.run('sort-prop-types', rule, {
       column: 5,
       type: 'Property'
     }]
-  }, {
-    code: [
-      'const propTypes = {',
-      '  b: PropTypes.string,',
-      '  a: PropTypes.string,',
-      '};',
-      'const TextFieldLabel = (props) => {',
-      '  return <div />;',
-      '};',
-      'TextFieldLabel.propTypes = propTypes;'
-    ].join('\n'),
-    parserOptions: parserOptions,
-    errors: [{
-      message: ERROR_MESSAGE,
-      line: 3,
-      column: 3,
-      type: 'Property'
-    }]
   }]
 });