Repository: yannickcr/eslint-plugin-react
Pull request #725 (2016-07-28 23:15:01)

Add Pragma for createClass factory method
Allows the the plugin to lint components created in a non-standard way with
configuration such as

```js
var MyComponent = SpecialReact.createSpecialClass({
  ...
});
```

with config looking like:
```json
{
  "settings": {
    "react": {
      "createClass": "createSpecialClass",
      "pragma": "SpecialReact", 
    }
  }
}
```


Patch:
diff --git a/README.md b/README.md
index 3bb9a706..d95ac3e7 100644
--- a/README.md
+++ b/README.md
@@ -37,6 +37,7 @@ You can also specify some settings that will be shared across all the plugin rul
 {
   "settings": {
     "react": {
+      "createClass": "createClass", // Regex for Component Factory to use, default to "createClass"
       "pragma": "React",  // Pragma to use, default to "React"
       "version": "15.0" // React version, default to the latest React stable release
     }
@@ -80,8 +81,10 @@ 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-danger-with-children](docs/rules/no-danger-with-children.md): Prevent problem with children and props.dangerouslySetInnerHTML
 * [react/no-deprecated](docs/rules/no-deprecated.md): Prevent usage of deprecated methods
 * [react/no-did-mount-set-state](docs/rules/no-did-mount-set-state.md): Prevent usage of `setState` in `componentDidMount`
 * [react/no-did-update-set-state](docs/rules/no-did-update-set-state.md): Prevent usage of `setState` in `componentDidUpdate`
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-no-target-blank.md b/docs/rules/jsx-no-target-blank.md
index ade365b3..ef039d30 100644
--- a/docs/rules/jsx-no-target-blank.md
+++ b/docs/rules/jsx-no-target-blank.md
@@ -14,7 +14,7 @@ The following patterns are considered errors:
 var Hello = <a target='_blank'></a>
 ```
 
-The following patterns are not considered erros:
+The following patterns are not considered errors:
 
 ```jsx
 var Hello = <p target='_blank'></p>
diff --git a/docs/rules/no-danger-with-children.md b/docs/rules/no-danger-with-children.md
new file mode 100644
index 00000000..bb21a035
--- /dev/null
+++ b/docs/rules/no-danger-with-children.md
@@ -0,0 +1,53 @@
+# Prevent problem with children and props.dangerouslySetInnerHTML (no-danger-with-children)
+
+This rule helps prevent problems caused by using children and the dangerouslySetInnerHTML prop at the same time.
+React will throw a warning if this rule is ignored.
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```jsx
+<div dangerouslySetInnerHTML={{ __html: "HTML" }}>
+  Children
+</div>
+
+<Hello dangerouslySetInnerHTML={{ __html: "HTML" }}>
+  Children
+</Hello>
+
+```
+
+```js
+React.createElement("div", { dangerouslySetInnerHTML: { __html: "HTML" } }, "Children");
+
+React.createElement("Hello", { dangerouslySetInnerHTML: { __html: "HTML" } }, "Children");
+```
+
+The following patterns are not considered warnings:
+
+```jsx
+<div dangerouslySetInnerHTML={{ __html: "HTML" }} />
+
+<Hello dangerouslySetInnerHTML={{ __html: "HTML" }} />
+
+<div>
+  Children
+</div>
+
+<Hello>
+  Children
+</Hello>
+
+```
+
+```js
+React.createElement("div", { dangerouslySetInnerHTML: { __html: "HTML" } });
+
+React.createElement("Hello", { dangerouslySetInnerHTML: { __html: "HTML" } });
+
+React.createElement("div", {}, "Children");
+
+React.createElement("Hello", {}, "Children");
+```
+
diff --git a/index.js b/index.js
index f6302eba..88ab4dcb 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'),
@@ -52,7 +53,8 @@ var rules = {
   'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank'),
   'jsx-filename-extension': require('./lib/rules/jsx-filename-extension'),
   'require-optimization': require('./lib/rules/require-optimization'),
-  'no-find-dom-node': require('./lib/rules/no-find-dom-node')
+  'no-find-dom-node': require('./lib/rules/no-find-dom-node'),
+  'no-danger-with-children': require('./lib/rules/no-danger-with-children')
 };
 
 var ruleNames = Object.keys(rules);
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/jsx-uses-vars.js b/lib/rules/jsx-uses-vars.js
index bd877e58..530e1b48 100644
--- a/lib/rules/jsx-uses-vars.js
+++ b/lib/rules/jsx-uses-vars.js
@@ -38,9 +38,13 @@ module.exports = {
         } else if (node.name.name) {
           // <Foo>
           name = node.name.name;
-        } else if (node.name.object && node.name.object.name) {
-          // <Foo.Bar> - node.name.object.name
-          name = node.name.object.name;
+        } else if (node.name.object) {
+          // <Foo...Bar>
+          var parent = node.name.object;
+          while (parent.object) {
+            parent = parent.object;
+          }
+          name = parent.name;
         } else {
           return;
         }
diff --git a/lib/rules/no-danger-with-children.js b/lib/rules/no-danger-with-children.js
new file mode 100644
index 00000000..c9deb368
--- /dev/null
+++ b/lib/rules/no-danger-with-children.js
@@ -0,0 +1,80 @@
+/**
+ * @fileoverview Report when a DOM element is using both children and dangerouslySetInnerHTML
+ * @author David Petersen
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Report when a DOM element is using both children and dangerouslySetInnerHTML',
+      category: '',
+      recommended: false
+    },
+    schema: [] // no options
+  },
+  create: function(context) {
+    return {
+      JSXElement: function (node) {
+        var hasChildren = false;
+        var attributes = node.openingElement.attributes;
+
+        if (node.children.length) {
+          hasChildren = true;
+        } else {
+          var childrenProp = attributes.find(function (attribute) {
+            return attribute.name.name === 'children';
+          });
+          if (childrenProp) {
+            hasChildren = true;
+          }
+        }
+
+        if (attributes && hasChildren) {
+          var jsxElement = attributes.find(function (attribute) {
+            return attribute.name.name === 'dangerouslySetInnerHTML';
+          });
+
+
+          if (jsxElement) {
+            context.report(node, 'Only set one of `children` or `props.dangerouslySetInnerHTML`');
+          }
+        }
+      },
+      CallExpression: function (node) {
+        if (
+          node.callee
+          && node.callee.type === 'MemberExpression'
+          && node.callee.property.name === 'createElement'
+          && node.arguments.length > 1
+        ) {
+          var hasChildren = false;
+
+          var props = node.arguments[1].properties;
+          var dangerously = props.find(function(prop) {
+            return prop.key.name === 'dangerouslySetInnerHTML';
+          });
+
+
+          if (node.arguments.length === 2) {
+            var childrenProp = props.find(function(prop) {
+              return prop.key.name === 'children';
+            });
+            if (childrenProp) {
+              hasChildren = true;
+            }
+          } else {
+            hasChildren = true;
+          }
+
+          if (dangerously && hasChildren) {
+            context.report(node, 'Only set one of `children` or `props.dangerouslySetInnerHTML`');
+          }
+        }
+      }
+    };
+  }
+};
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);
       },
diff --git a/lib/util/Components.js b/lib/util/Components.js
index f180e5da..80f4a1e8 100644
--- a/lib/util/Components.js
+++ b/lib/util/Components.js
@@ -136,6 +136,7 @@ Components.prototype.length = function() {
 
 function componentRule(rule, context) {
 
+  var createClass = pragmaUtil.getCreateClassFromContext(context);
   var pragma = pragmaUtil.getFromContext(context);
   var sourceCode = context.getSourceCode();
   var components = new Components();
@@ -153,7 +154,7 @@ function componentRule(rule, context) {
       if (!node.parent) {
         return false;
       }
-      return new RegExp('^(' + pragma + '\\.)?createClass$').test(sourceCode.getText(node.parent.callee));
+      return new RegExp('^(' + pragma + '\\.)?' + createClass + '$').test(sourceCode.getText(node.parent.callee));
     },
 
     /**
diff --git a/lib/util/pragma.js b/lib/util/pragma.js
index 03f08c39..7938ed28 100644
--- a/lib/util/pragma.js
+++ b/lib/util/pragma.js
@@ -5,6 +5,21 @@
 'use strict';
 
 var JSX_ANNOTATION_REGEX = /^\*\s*@jsx\s+([^\s]+)/;
+// Does not check for reserved keywords or unicode characters
+var JS_IDENTIFIER_REGEX = /^[_$a-zA-Z][_$a-zA-Z0-9]*$/;
+
+
+function getCreateClassFromContext(context) {
+  var pragma = 'createClass';
+  // .eslintrc shared settings (http://eslint.org/docs/user-guide/configuring#adding-shared-settings)
+  if (context.settings.react && context.settings.react.createClass) {
+    pragma = context.settings.react.createClass;
+  }
+  if (!JS_IDENTIFIER_REGEX.test(pragma)) {
+    throw new Error('createClass pragma ' + pragma + 'is not a valid function name');
+  }
+  return pragma;
+}
 
 function getFromContext(context) {
   var pragma = 'React';
@@ -12,7 +27,10 @@ function getFromContext(context) {
   if (context.settings.react && context.settings.react.pragma) {
     pragma = context.settings.react.pragma;
   }
-  return pragma.split('.')[0];
+  if (!JS_IDENTIFIER_REGEX.test(pragma)) {
+    throw new Error('React pragma ' + pragma + 'is not a valid identifier');
+  }
+  return pragma;
 }
 
 function getFromNode(node) {
@@ -24,6 +42,7 @@ function getFromNode(node) {
 }
 
 module.exports = {
+  getCreateClassFromContext: getCreateClassFromContext,
   getFromContext: getFromContext,
   getFromNode: getFromNode
 };


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/jsx-uses-vars.js b/tests/lib/rules/jsx-uses-vars.js
index 95598ed1..ac61a449 100644
--- a/tests/lib/rules/jsx-uses-vars.js
+++ b/tests/lib/rules/jsx-uses-vars.js
@@ -97,6 +97,26 @@ ruleTester.run('no-unused-vars', rule, {
         }\
         <HelloMessage />',
       parserOptions: parserOptions
+    }, {
+      code: '\
+        /*eslint jsx-uses-vars:1*/\
+        function foo() {\
+          var App = { Foo: { Bar: {}}};\
+          var bar = React.render(<App.Foo.Bar/>);\
+          return bar;\
+        };\
+        foo()',
+      parserOptions: parserOptions
+    }, {
+      code: '\
+        /*eslint jsx-uses-vars:1*/\
+        function foo() {\
+          var App = { Foo: { Bar: { Baz: {}}}};\
+          var bar = React.render(<App.Foo.Bar.Baz/>);\
+          return bar;\
+        };\
+        foo()',
+      parserOptions: parserOptions
     }
   ],
   invalid: [
diff --git a/tests/lib/rules/no-danger-with-children.js b/tests/lib/rules/no-danger-with-children.js
new file mode 100644
index 00000000..a44124bc
--- /dev/null
+++ b/tests/lib/rules/no-danger-with-children.js
@@ -0,0 +1,139 @@
+/**
+ * @fileoverview Report when a DOM element is using both children and dangerouslySetInnerHTML
+ * @author David Petersen
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/no-danger-with-children');
+var RuleTester = require('eslint').RuleTester;
+
+var parserOptions = {
+  ecmaVersion: 6,
+  ecmaFeatures: {
+    jsx: true
+  }
+};
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+var ruleTester = new RuleTester();
+ruleTester.run('no-danger-with-children', rule, {
+  valid: [
+    {
+      code: '<div>Children</div>',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<div dangerouslySetInnerHTML={{ __html: "HTML" }} />',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<Hello>Children</Hello>',
+      parserOptions: parserOptions
+    },
+    {
+      code: '<Hello dangerouslySetInnerHTML={{ __html: "HTML" }} />',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", { dangerouslySetInnerHTML: { __html: "HTML" } });',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("div", {}, "Children");',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("Hello", { dangerouslySetInnerHTML: { __html: "HTML" } });',
+      parserOptions: parserOptions
+    },
+    {
+      code: 'React.createElement("Hello", {}, "Children");',
+      parserOptions: parserOptions
+    }
+  ],
+  invalid: [
+    {
+      code: [
+        '<div dangerouslySetInnerHTML={{ __html: "HTML" }}>',
+        '  Children',
+        '</div>'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      code: '<div dangerouslySetInnerHTML={{ __html: "HTML" }} children="Children" />',
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      code: [
+        '<Hello dangerouslySetInnerHTML={{ __html: "HTML" }}>',
+        '  Children',
+        '</Hello>'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      code: '<Hello dangerouslySetInnerHTML={{ __html: "HTML" }} children="Children" />',
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      code: [
+        'React.createElement(',
+        '  "div",',
+        '  { dangerouslySetInnerHTML: { __html: "HTML" } },',
+        '  "Children"',
+        ');'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      code: [
+        'React.createElement(',
+        '  "div",',
+        '  {',
+        '    dangerouslySetInnerHTML: { __html: "HTML" },',
+        '    children: "Children",',
+        '  }',
+        ');'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      code: [
+        'React.createElement(',
+        '  "Hello",',
+        '  { dangerouslySetInnerHTML: { __html: "HTML" } },',
+        '  "Children"',
+        ');'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    },
+    {
+      code: [
+        'React.createElement(',
+        '  "Hello",',
+        '  {',
+        '    dangerouslySetInnerHTML: { __html: "HTML" },',
+        '    children: "Children",',
+        '  }',
+        ');'
+      ].join('\n'),
+      errors: [{message: 'Only set one of `children` or `props.dangerouslySetInnerHTML`'}],
+      parserOptions: parserOptions
+    }
+  ]
+});
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";' +