Repository: yannickcr/eslint-plugin-react
Pull request #744 (2016-08-06 14:30:12)

[Fix] In require-optimization corrected test for function declaration
There was a problem with the ad functions and methods in classes, sample code -
```jsx
import React from "react";
class YourComponent extends React.Component {
  handleClick() {}
  render() {
     return <div onClick={this.handleClick}>123</div>;
  }
}
```
```json
{
  "rules": {
    "react/require-optimization": 2  
  }
}
```
This code has been checked and is not output error, it is wrong


Patch:
diff --git a/README.md b/README.md
index 59029121..46a9d595 100644
--- a/README.md
+++ b/README.md
@@ -80,6 +80,7 @@ Finally, enable all of the rules that you would like to use.  Use [our preset](#
 # List of supported rules
 
 * [react/display-name](docs/rules/display-name.md): Prevent missing `displayName` in a React component definition
+* [react/forbid-component-props](docs/rules/forbid-component-props.md): Forbid certain props on Components
 * [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes
 * [react/no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties
 * [react/no-deprecated](docs/rules/no-deprecated.md): Prevent usage of deprecated methods
@@ -130,9 +131,10 @@ Finally, enable all of the rules that you would like to use.  Use [our preset](#
 * [react/jsx-uses-vars](docs/rules/jsx-uses-vars.md): Prevent variables used in JSX to be incorrectly marked as unused
 * [react/jsx-wrap-multilines](docs/rules/jsx-wrap-multilines.md): Prevent missing parentheses around multilines JSX (fixable)
 
-## React Native rules
+## Other useful plugins
 
-If you're searching for React Native specific linting rules, check out [eslint-plugin-react-native](https://github.com/Intellicode/eslint-plugin-react-native).
+- JSX accessibility: [eslint-plugin-jsx-a11y](https://github.com/evcohen/eslint-plugin-jsx-a11y)
+- React Native: [eslint-plugin-react-native](https://github.com/Intellicode/eslint-plugin-react-native)
 
 # Shareable configurations
 
diff --git a/docs/rules/forbid-component-props.md b/docs/rules/forbid-component-props.md
new file mode 100644
index 00000000..924cbe6a
--- /dev/null
+++ b/docs/rules/forbid-component-props.md
@@ -0,0 +1,44 @@
+# Forbid certain props on Components (forbid-component-props)
+
+By default this rule prevents passing of [props that add lots of complexity](https://medium.com/brigade-engineering/don-t-pass-css-classes-between-components-e9f7ab192785) (`className`, `style`) to Components. This rule only applies to Components (e.g. `<Foo />`) and not DOM nodes (e.g. `<div />`). The list of forbidden props can be customized with the `forbid` option.
+
+## Rule Details
+
+This rule checks all JSX elements and verifies that no forbidden props are used
+on Components. This rule is off by default.
+
+The following patterns are considered warnings:
+
+```jsx
+<Hello className='foo' />
+```
+
+```jsx
+<Hello style={{color: 'red'}} />
+```
+
+The following patterns are not considered warnings:
+
+```jsx
+<Hello name='Joe' />
+```
+
+```jsx
+<div className='foo' />
+```
+
+```jsx
+<div style={{color: 'red'}} />
+```
+
+## Rule Options
+
+```js
+...
+"forbid-component-props": [<enabled>, { "forbid": [<string>] }]
+...
+```
+
+### `forbid`
+
+An array of strings, with the names of props that are forbidden. The default value of this option is `['className', 'style']`.
diff --git a/docs/rules/forbid-prop-types.md b/docs/rules/forbid-prop-types.md
index 28050726..d679e09a 100644
--- a/docs/rules/forbid-prop-types.md
+++ b/docs/rules/forbid-prop-types.md
@@ -50,7 +50,7 @@ class Component extends React.Component {
 
 ### `forbid`
 
-An array of strings, with the names of `React.PropTypes` keys that are forbidden.
+An array of strings, with the names of `React.PropTypes` keys that are forbidden. The default value for this option is `['any', 'array', 'object']`.
 
 ## When not to use
 
diff --git a/docs/rules/jsx-boolean-value.md b/docs/rules/jsx-boolean-value.md
index 7ed7fece..e06fd918 100644
--- a/docs/rules/jsx-boolean-value.md
+++ b/docs/rules/jsx-boolean-value.md
@@ -1,6 +1,6 @@
 # Enforce boolean attributes notation in JSX (jsx-boolean-value)
 
-In JSX when using a boolean attribute you can set the attribute value to `true` or omit the value. This rule will enforce one or the other to keep consistency in your code.
+[When using a boolean attribute in JSX](https://facebook.github.io/react/docs/jsx-in-depth.html#boolean-attributes), you can set the attribute value to `true` or omit the value. This rule will enforce one or the other to keep consistency in your code.
 
 **Fixable:** This rule is automatically fixable using the `--fix` flag on the command line.
 
diff --git a/docs/rules/jsx-no-target-blank.md b/docs/rules/jsx-no-target-blank.md
index 96f15dbe..ef039d30 100644
--- a/docs/rules/jsx-no-target-blank.md
+++ b/docs/rules/jsx-no-target-blank.md
@@ -1,22 +1,22 @@
 # Prevent usage of unsafe target='_blank' (jsx-no-target-blank)
 
 When creating a JSX element that has an a tag, it is often desired to have
-the link open in a new tab using the target='_blank' attribute. Using this
-attribute unaccompanied by rel='noreferrer noopener', however, is a severe
+the link open in a new tab using the `target='_blank'` attribute. Using this
+attribute unaccompanied by `rel='noreferrer noopener'`, however, is a severe
 security vulnerability ([see here for more details](https://mathiasbynens.github.io/rel-noopener))
-This rules requires that you accompany all target='_blank' attributes with rel='noreferrer noopener'.
+This rules requires that you accompany all `target='_blank'` attributes with `rel='noreferrer noopener'`.
 
 ## Rule Details
 
 The following patterns are considered errors:
 
-```javascript
+```jsx
 var Hello = <a target='_blank'></a>
 ```
 
-The following patterns are not considered erros:
+The following patterns are not considered errors:
 
-```javascript
+```jsx
 var Hello = <p target='_blank'></p>
 var Hello = <a target='_blank' rel='noopener noreferrer'></a>
 var Hello = <a></a>
diff --git a/docs/rules/self-closing-comp.md b/docs/rules/self-closing-comp.md
index be5aeee6..938a7b51 100644
--- a/docs/rules/self-closing-comp.md
+++ b/docs/rules/self-closing-comp.md
@@ -6,18 +6,22 @@ Components without children can be self-closed to avoid unnecessary extra closin
 
 The following patterns are considered warnings:
 
-```js
+```jsx
 var HelloJohn = <Hello name="John"></Hello>;
 ```
 
 The following patterns are not considered warnings:
 
-```js
+```jsx
 var contentContainer = <div className="content"></div>;
 
+var intentionalSpace = <div>{' '}</div>;
+
 var HelloJohn = <Hello name="John" />;
 
 var Profile = <Hello name="John"><img src="picture.png" /></Hello>;
+
+var HelloSpace = <Hello>{' '}</Hello>;
 ```
 
 ## Rule Options
@@ -39,15 +43,17 @@ When `true`, custom components tags should be self-closed.
 
 The following patterns are considered warnings:
 
-```js
+```jsx
 var HelloJohn = <Hello name="John"></Hello>;
 ```
 
 The following patterns are not considered warnings:
 
-```js
+```jsx
 var contentContainer = <div className="content"></div>;
 
+var intentionalSpace = <div>{' '}</div>;
+
 var HelloJohn = <Hello name="John" />;
 
 var Profile = <Hello name="John"><img src="picture.png" /></Hello>;
@@ -59,14 +65,16 @@ When `true`, html components tags should be self-closed.
 
 The following patterns are considered warnings:
 
-```js
+```jsx
 var contentContainer = <div className="content"></div>;
 ```
 
 The following patterns are not considered warnings:
 
-```js
+```jsx
 var contentContainer = <div className="content" />;
 
 var contentContainer = <div className="content"><div /></div>;
+
+var intentionalSpace = <div>{' '}</div>;
 ```
diff --git a/index.js b/index.js
index 733d8dd3..0a258473 100644
--- a/index.js
+++ b/index.js
@@ -42,6 +42,7 @@ var rules = {
   'jsx-closing-bracket-location': require('./lib/rules/jsx-closing-bracket-location'),
   'jsx-space-before-closing': require('./lib/rules/jsx-space-before-closing'),
   'no-direct-mutation-state': require('./lib/rules/no-direct-mutation-state'),
+  'forbid-component-props': require('./lib/rules/forbid-component-props'),
   'forbid-prop-types': require('./lib/rules/forbid-prop-types'),
   'prefer-es6-class': require('./lib/rules/prefer-es6-class'),
   'jsx-key': require('./lib/rules/jsx-key'),
@@ -61,9 +62,23 @@ for (var i = 0; i < ruleNames.length; i++) {
   allRules['react/' + ruleNames[i]] = 2;
 }
 
+var exportedRules = {};
+for (var key in rules) {
+  if (!rules.hasOwnProperty(key)) {
+    continue;
+  }
+  exportedRules[key] = rules[key];
+}
+for (var deprecatedKey in deprecatedRules) {
+  if (!deprecatedRules.hasOwnProperty(deprecatedKey)) {
+    continue;
+  }
+  exportedRules[deprecatedKey] = deprecatedRules[deprecatedKey];
+}
+
 module.exports = {
   deprecatedRules: deprecatedRules,
-  rules: Object.assign({}, rules, deprecatedRules),
+  rules: exportedRules,
   configs: {
     recommended: {
       parserOptions: {
diff --git a/lib/rules/forbid-component-props.js b/lib/rules/forbid-component-props.js
new file mode 100644
index 00000000..abbf5275
--- /dev/null
+++ b/lib/rules/forbid-component-props.js
@@ -0,0 +1,65 @@
+/**
+ * @fileoverview Forbid certain props on components
+ * @author Joe Lencioni
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+var DEFAULTS = ['className', 'style'];
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    docs: {},
+
+    schema: [{
+      type: 'object',
+      properties: {
+        forbid: {
+          type: 'array',
+          items: {
+            type: 'string'
+          }
+        }
+      },
+      additionalProperties: true
+    }]
+  },
+
+  create: function(context) {
+
+    function isForbidden(prop) {
+      var configuration = context.options[0] || {};
+
+      var forbid = configuration.forbid || DEFAULTS;
+      return forbid.indexOf(prop) >= 0;
+    }
+
+    return {
+      JSXAttribute: function(node) {
+        var tag = node.parent.name.name;
+        if (tag[0] !== tag[0].toUpperCase()) {
+          // This is a DOM node, not a Component, so exit.
+          return;
+        }
+
+        var prop = node.name.name;
+
+        if (!isForbidden(prop)) {
+          return;
+        }
+
+        context.report({
+          node: node,
+          message: 'Prop `' + prop + '` is forbidden on Components'
+        });
+      }
+    };
+  }
+};
diff --git a/lib/rules/require-optimization.js b/lib/rules/require-optimization.js
index 1e409c5d..afb80ca0 100644
--- a/lib/rules/require-optimization.js
+++ b/lib/rules/require-optimization.js
@@ -71,10 +71,10 @@ module.exports = {
      * @returns {Boolean} True if node is decorated name with a custom decorated, false if not.
      */
     var hasCustomDecorator = function (node) {
-      var allowLenght = allowDecorators.length;
+      var allowLength = allowDecorators.length;
 
-      if (allowLenght && node.decorators && node.decorators.length) {
-        for (var i = 0; i < allowLenght; i++) {
+      if (allowLength && node.decorators && node.decorators.length) {
+        for (var i = 0; i < allowLength; i++) {
           for (var j = 0, l = node.decorators.length; j < l; j++) {
             if (
               node.decorators[j].expression &&
@@ -161,6 +161,24 @@ module.exports = {
       });
     };
 
+    /**
+     * Checks if we are declaring function in class
+     * @returns {Boolean} True if we are declaring function in class, false if not.
+     */
+    var isFunctionInClass = function () {
+      var blockNode;
+      var scope = context.getScope();
+      while (scope) {
+        blockNode = scope.block;
+        if (blockNode && blockNode.type === 'ClassDeclaration') {
+          return true;
+        }
+        scope = scope.upper;
+      }
+
+      return false;
+    };
+
     return {
       ArrowFunctionExpression: function (node) {
         // Stateless Functional Components cannot be optimized (yet)
@@ -175,11 +193,19 @@ module.exports = {
       },
 
       FunctionDeclaration: function (node) {
+        // Skip if the function is declared in the class
+        if (isFunctionInClass()) {
+          return;
+        }
         // Stateless Functional Components cannot be optimized (yet)
         markSCUAsDeclared(node);
       },
 
       FunctionExpression: function (node) {
+        // Skip if the function is declared in the class
+        if (isFunctionInClass()) {
+          return;
+        }
         // Stateless Functional Components cannot be optimized (yet)
         markSCUAsDeclared(node);
       },


Test patch:
diff --git a/tests/lib/rules/forbid-component-props.js b/tests/lib/rules/forbid-component-props.js
new file mode 100644
index 00000000..9483f80e
--- /dev/null
+++ b/tests/lib/rules/forbid-component-props.js
@@ -0,0 +1,153 @@
+/**
+ * @fileoverview Tests for forbid-component-props
+ */
+'use strict';
+
+// -----------------------------------------------------------------------------
+// Requirements
+// -----------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/forbid-component-props');
+var RuleTester = require('eslint').RuleTester;
+
+var parserOptions = {
+  ecmaVersion: 6,
+  ecmaFeatures: {
+    experimentalObjectRestSpread: true,
+    jsx: true
+  }
+};
+
+require('babel-eslint');
+
+// -----------------------------------------------------------------------------
+// Tests
+// -----------------------------------------------------------------------------
+
+var CLASSNAME_ERROR_MESSAGE = 'Prop `className` is forbidden on Components';
+var STYLE_ERROR_MESSAGE = 'Prop `style` is forbidden on Components';
+
+var ruleTester = new RuleTester();
+ruleTester.run('forbid-component-props', rule, {
+
+  valid: [{
+    code: [
+      'var First = React.createClass({',
+      '  render: function() {',
+      '    return <div className="foo" />;',
+      '  }',
+      '});'
+    ].join('\n'),
+    parserOptions: parserOptions
+  }, {
+    code: [
+      'var First = React.createClass({',
+      '  render: function() {',
+      '    return <div style={{color: "red"}} />;',
+      '  }',
+      '});'
+    ].join('\n'),
+    options: [{forbid: ['style']}],
+    parserOptions: parserOptions
+  }, {
+    code: [
+      'var First = React.createClass({',
+      '  propTypes: externalPropTypes,',
+      '  render: function() {',
+      '    return <Foo bar="baz" />;',
+      '  }',
+      '});'
+    ].join('\n'),
+    parserOptions: parserOptions
+  }, {
+    code: [
+      'var First = React.createClass({',
+      '  propTypes: externalPropTypes,',
+      '  render: function() {',
+      '    return <Foo className="bar" />;',
+      '  }',
+      '});'
+    ].join('\n'),
+    options: [{forbid: ['style']}],
+    parserOptions: parserOptions
+  }, {
+    code: [
+      'var First = React.createClass({',
+      '  propTypes: externalPropTypes,',
+      '  render: function() {',
+      '    return <Foo className="bar" />;',
+      '  }',
+      '});'
+    ].join('\n'),
+    options: [{forbid: ['style', 'foo']}],
+    parserOptions: parserOptions
+  }],
+
+  invalid: [{
+    code: [
+      'var First = React.createClass({',
+      '  propTypes: externalPropTypes,',
+      '  render: function() {',
+      '    return <Foo className="bar" />;',
+      '  }',
+      '});'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    errors: [{
+      message: CLASSNAME_ERROR_MESSAGE,
+      line: 4,
+      column: 17,
+      type: 'JSXAttribute'
+    }]
+  }, {
+    code: [
+      'var First = React.createClass({',
+      '  propTypes: externalPropTypes,',
+      '  render: function() {',
+      '    return <Foo style={{color: "red"}} />;',
+      '  }',
+      '});'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    errors: [{
+      message: STYLE_ERROR_MESSAGE,
+      line: 4,
+      column: 17,
+      type: 'JSXAttribute'
+    }]
+  }, {
+    code: [
+      'var First = React.createClass({',
+      '  propTypes: externalPropTypes,',
+      '  render: function() {',
+      '    return <Foo className="bar" />;',
+      '  }',
+      '});'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    options: [{forbid: ['className', 'style']}],
+    errors: [{
+      message: CLASSNAME_ERROR_MESSAGE,
+      line: 4,
+      column: 17,
+      type: 'JSXAttribute'
+    }]
+  }, {
+    code: [
+      'var First = React.createClass({',
+      '  propTypes: externalPropTypes,',
+      '  render: function() {',
+      '    return <Foo style={{color: "red"}} />;',
+      '  }',
+      '});'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    options: [{forbid: ['className', 'style']}],
+    errors: [{
+      message: STYLE_ERROR_MESSAGE,
+      line: 4,
+      column: 17,
+      type: 'JSXAttribute'
+    }]
+  }]
+});
diff --git a/tests/lib/rules/require-optimization.js b/tests/lib/rules/require-optimization.js
index dc635dd6..3a8fb344 100644
--- a/tests/lib/rules/require-optimization.js
+++ b/tests/lib/rules/require-optimization.js
@@ -37,6 +37,17 @@ ruleTester.run('react-require-optimization', rule, {
       '}'
     ].join('\n'),
     parserOptions: parserOptions
+  }, {
+    code: [
+      'import React, {Component} from "react";',
+      '@reactMixin.decorate(PureRenderMixin)',
+      'class YourComponent extends Component {',
+      '  componetnDidMount () {}',
+      '  render() {}',
+      '}'
+    ].join('\n'),
+    parser: 'babel-eslint',
+    parserOptions: parserOptions
   }, {
     code: [
       'import React from "react";' +
@@ -123,6 +134,21 @@ ruleTester.run('react-require-optimization', rule, {
       message: MESSAGE
     }],
     parserOptions: parserOptions
+  }, {
+    code: [
+      'import React from "react";',
+      'class YourComponent extends React.Component {',
+      '  handleClick() {}',
+      '  render() {',
+      '    return <div onClick={this.handleClick}>123</div>',
+      '  }',
+      '}'
+    ].join('\n'),
+    parser: 'babel-eslint',
+    errors: [{
+      message: MESSAGE
+    }],
+    parserOptions: parserOptions
   }, {
     code: [
       'import React, {Component} from "react";' +