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

Issue:
Rule for `bind` inside JSX
Is that possible to create rule that would throw warning on this code:
```jsx
<a onClick={this.onClick.bind(this, true)}>click me</a>
```
And maybe suggest to do this:
```jsx
<a onClick={() => this.onClick(true)}>click me</a>
```

Pull request:
Add rule for disallowing `.bind` in JSX props
A `bind` call or [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) in a JSX prop will create a brand new function on every single render. This is bad for performance, as it can result in the garbage collector being invoked way more than is necessary (especially on hot paths with components that have frequent updates). This rule disallows both `bind` calls and arrow functions, with options to re-enable them (`allowBind` and `allowArrowFunctions` respectively) in case you want to only disable one.

Fixes #184.

Patch:
diff --git a/README.md b/README.md
index 24706cc2..89dab748 100644
--- a/README.md
+++ b/README.md
@@ -53,6 +53,7 @@ Finally, enable all of the rules that you would like to use.
     "react/jsx-curly-spacing": 1,
     "react/jsx-indent-props": 1,
     "react/jsx-max-props-per-line": 1,
+    "react/jsx-no-bind": 1,
     "react/jsx-no-duplicate-props": 1,
     "react/jsx-no-literals": 1,
     "react/jsx-no-undef": 1,
@@ -88,6 +89,7 @@ Finally, enable all of the rules that you would like to use.
 * [jsx-curly-spacing](docs/rules/jsx-curly-spacing.md): Enforce or disallow spaces inside of curly braces in JSX attributes
 * [jsx-indent-props](docs/rules/jsx-indent-props.md): Validate props indentation in JSX
 * [jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md): Limit maximum of props on a single line in JSX
+* [jsx-no-bind](docs/rules/jsx-no-bind.md): Prevent usage of `.bind()` and arrow functions in JSX props
 * [jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md): Prevent duplicate props in JSX
 * [jsx-no-literals](docs/rules/jsx-no-literals.md): Prevent usage of unwrapped JSX strings
 * [jsx-no-undef](docs/rules/jsx-no-undef.md): Disallow undeclared variables in JSX
diff --git a/docs/rules/jsx-no-bind.md b/docs/rules/jsx-no-bind.md
new file mode 100644
index 00000000..9218335a
--- /dev/null
+++ b/docs/rules/jsx-no-bind.md
@@ -0,0 +1,101 @@
+# No `.bind()` or Arrow Functions in JSX Props
+
+A `bind` call or [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) in a JSX prop will create a brand new function on every single render. This is bad for performance, as it will result in the garbage collector being invoked way more than is necessary.
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```js
+<div onClick={this._handleClick.bind(this)}></div>
+```
+```js
+<div onClick={() => console.log('Hello!'))}></div>
+```
+
+The following patterns are not considered warnings:
+```js
+<div onClick={this._handleClick}></div>
+```
+
+## Protips
+
+### Lists of Items
+
+A common use case of `bind` in render is when rendering a list, to have a separate callback per list item:
+
+```js
+var List = React.createClass({
+  render() {
+    return (
+      <ul>
+        {this.props.items.map(item =>
+          <li key={item.id} onClick={this.props.onItemClick.bind(null, item.id)}>
+            ...
+          </li>
+        )}
+      </ul>
+    );
+  }
+});
+```
+
+Rather than doing it this way, pull the repeated section into its own component:
+
+```js
+var List = React.createClass({
+  render() {
+    return (
+      <ul>
+        {this.props.items.map(item =>
+          <ListItem key={item.id} item={item} onItemClick={this.props.onItemClick} />
+        )}
+      </ul>
+    );
+  }
+});
+
+var ListItem = React.createClass({
+  render() {
+    return (
+      <li onClick={this._onClick}>
+        ...
+      </li>
+    );
+  },
+  _onClick() {
+    this.props.onItemClick(this.props.item.id);
+  }
+});
+```
+
+This will speed up rendering, as it avoids the need to create new functions (through `bind` calls) on every render.
+
+### ES6 Classes
+
+Unfortunately [React ES6 classes](https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#es6-classes) do not autobind their methods like components created with the older `React.createClass` syntax. There are several approaches to binding methods for ES6 classes. A basic approach is to just manually bind the methods in the constructor:
+
+```js
+class Foo extends React.Component {
+  constructor() {
+    super();
+    this._onClick = this._onClick.bind(this);
+  }
+  render() {
+    return (
+      <div onClick={this._onClick}>
+        Hello!
+      </div>
+    );
+  }
+  _onClick() {
+    // Do whatever you like, referencing "this" as appropriate
+  }
+}
+```
+
+A more sophisticated approach would be to use something like an [autobind ES7 decorator](https://www.npmjs.com/package/core-decorators#autobind) or [property initializers](https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding).
+
+## When Not To Use It
+
+If you do not use JSX or do not want to enforce that `bind` or arrow functions are not used in props, then you can disable this rule.
diff --git a/index.js b/index.js
index 0a2ec93a..fdd87f7e 100644
--- a/index.js
+++ b/index.js
@@ -14,6 +14,7 @@ module.exports = {
     'no-did-update-set-state': require('./lib/rules/no-did-update-set-state'),
     'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'),
     'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'),
+    'jsx-no-bind': require('./lib/rules/jsx-no-bind'),
     'jsx-no-undef': require('./lib/rules/jsx-no-undef'),
     'jsx-quotes': require('./lib/rules/jsx-quotes'),
     'no-unknown-property': require('./lib/rules/no-unknown-property'),
@@ -45,6 +46,7 @@ module.exports = {
     'no-did-update-set-state': 0,
     'react-in-jsx-scope': 0,
     'jsx-uses-vars': 1,
+    'jsx-no-bind': 0,
     'jsx-no-undef': 0,
     'jsx-quotes': 0,
     'no-unknown-property': 0,
diff --git a/lib/rules/jsx-no-bind.js b/lib/rules/jsx-no-bind.js
new file mode 100644
index 00000000..8e6b1031
--- /dev/null
+++ b/lib/rules/jsx-no-bind.js
@@ -0,0 +1,50 @@
+/**
+ * @fileoverview Prevents usage of Function.prototype.bind and arrow functions
+ *               in React component definition.
+ * @author Daniel Lo Nigro <dan.cx>
+ */
+'use strict';
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+module.exports = function(context) {
+  var configuration = context.options[0] || {};
+
+  return {
+    JSXAttribute: function(node) {
+      if (!node.value || !node.value.expression) {
+        return;
+      }
+      var valueNode = node.value.expression;
+      if (
+        !configuration.allowBind &&
+        valueNode.type === 'CallExpression' &&
+        valueNode.callee.property.name === 'bind'
+      ) {
+        context.report(node, 'JSX props should not use .bind()');
+      } else if (
+        !configuration.allowArrowFunctions &&
+        valueNode.type === 'ArrowFunctionExpression'
+      ) {
+        context.report(node, 'JSX props should not use arrow functions');
+      }
+    }
+  };
+};
+
+module.exports.schema = [{
+  type: 'object',
+  properties: {
+    allowArrowFunctions: {
+      default: false,
+      type: 'boolean'
+    },
+    allowBind: {
+      default: false,
+      type: 'boolean'
+    }
+  },
+  additionalProperties: false
+}];


Test patch:
diff --git a/tests/lib/rules/jsx-no-bind.js b/tests/lib/rules/jsx-no-bind.js
new file mode 100644
index 00000000..1e6e4913
--- /dev/null
+++ b/tests/lib/rules/jsx-no-bind.js
@@ -0,0 +1,83 @@
+/**
+ * @fileoverview Prevents usage of Function.prototype.bind and arrow functions
+ *               in React component definition.
+ * @author Daniel Lo Nigro <dan.cx>
+ */
+'use strict';
+
+// -----------------------------------------------------------------------------
+// Requirements
+// -----------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/jsx-no-bind');
+var RuleTester = require('eslint').RuleTester;
+
+// -----------------------------------------------------------------------------
+// Tests
+// -----------------------------------------------------------------------------
+
+var ruleTester = new RuleTester();
+ruleTester.run('jsx-no-bind', rule, {
+
+  valid: [
+    // Not covered by the rule
+    {
+      code: '<div onClick={this._handleClick}></div>',
+      parser: 'babel-eslint'
+    },
+    {
+      code: '<div meaningOfLife={42}></div>',
+      parser: 'babel-eslint'
+    },
+
+    // bind() explicitly allowed
+    {
+      code: '<div onClick={this._handleClick.bind(this)}></div>',
+      options: [{allowBind: true}],
+      parser: 'babel-eslint'
+    },
+
+    // Arrow functions explicitly allowed
+    {
+      code: '<div onClick={() => alert("1337")}></div>',
+      options: [{allowArrowFunctions: true}],
+      parser: 'babel-eslint'
+    }
+  ],
+
+  invalid: [
+    // .bind()
+    {
+      code: '<div onClick={this._handleClick.bind(this)}></div>',
+      errors: [{message: 'JSX props should not use .bind()'}],
+      parser: 'babel-eslint'
+    },
+    {
+      code: '<div onClick={someGlobalFunction.bind(this)}></div>',
+      errors: [{message: 'JSX props should not use .bind()'}],
+      parser: 'babel-eslint'
+    },
+    {
+      code: '<div onClick={window.lol.bind(this)}></div>',
+      errors: [{message: 'JSX props should not use .bind()'}],
+      parser: 'babel-eslint'
+    },
+
+    // Arrow functions
+    {
+      code: '<div onClick={() => alert("1337")}></div>',
+      errors: [{message: 'JSX props should not use arrow functions'}],
+      parser: 'babel-eslint'
+    },
+    {
+      code: '<div onClick={() => 42}></div>',
+      errors: [{message: 'JSX props should not use arrow functions'}],
+      parser: 'babel-eslint'
+    },
+    {
+      code: '<div onClick={param => { first(); second(); }}></div>',
+      errors: [{message: 'JSX props should not use arrow functions'}],
+      parser: 'babel-eslint'
+    }
+  ]
+});