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

Issue:
`jsx-no-bind` errors on non-react components.
I have Backbone views with a "render" method that are being warned when they have `.bind` used within them. Did something change recently that loosened the criteria for identifying a React component?

If the `render` method doesn't return JSX, and the component doesn't `extend React.Component`, and most importantly if the file itself doesn't even import/require `React`, why are these rules applying to it?

Pull request:
Fix jsx-no-bind reporting errors on render functions
Fix jsx-no-bind reporting errors on render functions that don't return JSX (Fixes #663)

Patch:
diff --git a/lib/rules/jsx-no-bind.js b/lib/rules/jsx-no-bind.js
index 091c1a8f..2d63df31 100644
--- a/lib/rules/jsx-no-bind.js
+++ b/lib/rules/jsx-no-bind.js
@@ -5,13 +5,14 @@
  */
 'use strict';
 
+var Components = require('../util/Components');
 var propName = require('jsx-ast-utils/propName');
 
 // -----------------------------------------------------------------------------
 // Rule Definition
 // -----------------------------------------------------------------------------
 
-module.exports = function(context) {
+module.exports = Components.detect(function(context, components, utils) {
   var configuration = context.options[0] || {};
 
   return {
@@ -30,10 +31,12 @@ module.exports = function(context) {
           (ancestors[i].type === 'MethodDefinition' && ancestors[i].key.name === 'render') ||
           (ancestors[i].type === 'Property' && ancestors[i].key.name === 'render')
         ) {
-          context.report({
-            node: callee,
-            message: 'JSX props should not use .bind()'
-          });
+          if (utils.isReturningJSX(ancestors[i])) {
+            context.report({
+              node: callee,
+              message: 'JSX props should not use .bind()'
+            });
+          }
           break;
         }
       }
@@ -74,7 +77,7 @@ module.exports = function(context) {
       }
     }
   };
-};
+});
 
 module.exports.schema = [{
   type: 'object',
diff --git a/lib/util/Components.js b/lib/util/Components.js
index 72d9e80d..df5fc357 100644
--- a/lib/util/Components.js
+++ b/lib/util/Components.js
@@ -199,12 +199,13 @@ function componentRule(rule, context) {
     /**
      * Check if the node is returning JSX
      *
-     * @param {ASTNode} node The AST node being checked (can be a ReturnStatement or an ArrowFunctionExpression).
+     * @param {ASTNode} ASTnode The AST node being checked
      * @param {Boolean} strict If true, in a ternary condition the node must return JSX in both cases
      * @returns {Boolean} True if the node is returning JSX, false if not
      */
-    isReturningJSX: function(node, strict) {
+    isReturningJSX: function(ASTnode, strict) {
       var property;
+      var node = ASTnode;
       switch (node.type) {
         case 'ReturnStatement':
           property = 'argument';
@@ -213,7 +214,11 @@ function componentRule(rule, context) {
           property = 'body';
           break;
         default:
-          return false;
+          node = utils.findReturnStatement(node);
+          if (!node) {
+            return false;
+          }
+          property = 'argument';
       }
 
       var returnsConditionalJSXConsequent =
@@ -248,6 +253,24 @@ function componentRule(rule, context) {
       );
     },
 
+    /**
+     * Find a return statment in the current node
+     *
+     * @param {ASTNode} ASTnode The AST node being checked
+     */
+    findReturnStatement: function(node) {
+      if (!node.value || !node.value.body) {
+        return false;
+      }
+      var i = node.value.body.body.length - 1;
+      for (; i >= 0; i--) {
+        if (node.value.body.body[i].type === 'ReturnStatement') {
+          return node.value.body.body[i];
+        }
+      }
+      return false;
+    },
+
     /**
      * Get the parent component node from the current scope
      *


Test patch:
diff --git a/tests/lib/rules/jsx-no-bind.js b/tests/lib/rules/jsx-no-bind.js
index 5e81d168..02362089 100644
--- a/tests/lib/rules/jsx-no-bind.js
+++ b/tests/lib/rules/jsx-no-bind.js
@@ -72,6 +72,40 @@ ruleTester.run('jsx-no-bind', rule, {
       ].join('\n'),
       options: [{allowBind: true}],
       parser: 'babel-eslint'
+    },
+    // Backbone view with a bind
+    {
+      code: [
+        'var DocumentRow = Backbone.View.extend({',
+        '  tagName: "li",',
+        '  render: function() {',
+        '    this.onTap.bind(this);',
+        '  }',
+        '});'
+      ].join('\n'),
+      parser: 'babel-eslint'
+    },
+    {
+      code: [
+        'const foo = {',
+        '  render: function() {',
+        '    this.onTap.bind(this);',
+        '    return true;',
+        '  }',
+        '};'
+      ].join('\n'),
+      parser: 'babel-eslint'
+    },
+    {
+      code: [
+        'const foo = {',
+        '  render() {',
+        '    this.onTap.bind(this);',
+        '    return true;',
+        '  }',
+        '};'
+      ].join('\n'),
+      parser: 'babel-eslint'
     }
   ],
 
@@ -121,6 +155,30 @@ ruleTester.run('jsx-no-bind', rule, {
       errors: [{message: 'JSX props should not use .bind()'}],
       parser: 'babel-eslint'
     },
+    {
+      code: [
+        'const foo = {',
+        '  render: function() {',
+        '    const click = this.onTap.bind(this);',
+        '    return <div onClick={onClick}>Hello</div>;',
+        '  }',
+        '};'
+      ].join('\n'),
+      errors: [{message: 'JSX props should not use .bind()'}],
+      parser: 'babel-eslint'
+    },
+    {
+      code: [
+        'const foo = {',
+        '  render() {',
+        '    const click = this.onTap.bind(this);',
+        '    return <div onClick={onClick}>Hello</div>;',
+        '  }',
+        '};'
+      ].join('\n'),
+      errors: [{message: 'JSX props should not use .bind()'}],
+      parser: 'babel-eslint'
+    },
 
     // Arrow functions
     {