Repository: yannickcr/eslint-plugin-react
Pull request #669 (2016-07-07 20:53:50)

New rule options allowDecorators in require-optimization
I like this require-optimization rule, but it is not suitable for our implementation. I think a lot of people use https://www.npmjs.com/package/pure-render-decorator or similar solutions. It is the reason I made an additional option. Now it will be possible to register a list of names of decorators, which allow to pass validation.

I hope my change will be useful and will get in release =)

ljharb: How does this handle that decorators are actually in-scope bindings, and not string names? In other words, the name "foo" might mean different things in different files.
Tom910: > In other words, the name "foo" might mean different things in different files.

Yes, the name of the decorator may vary. It's up to the developer to make up some convention and to name imports conveniently and in the same way.

There is no need to perform any deep checks. This addition allows the developer to use any optimization decorator, not only pure mixin, but any other, including his own utilities. The only purpose of allowDecorators option is to provide names of this tools to the rule.

How can this be made in a different way? Analyze the decorator? Or check imports? There can be too many variations.
ljharb: That's totally a fair point - just wanted to make sure it was considered.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3bdef24a..3d57a7e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,26 @@ All notable changes to this project will be documented in this file.
 This project adheres to [Semantic Versioning](http://semver.org/).
 This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).
 
+## [6.0.0-alpha.1] - 2016-07-08
+### Fixed
+* Fix `prop-types` crash when accessing constructor on props ([#654][])
+* Fix `jsx-filename-extension` to not check filenames on text input ([#662][] @ljharb)
+* Fix `no-comment-textnodes` incorrectly catching urls ([#664][] @petersendidit)
+
+### Breaking
+* Add ESLint as peerDependency ([#657][] @jokeyrhyme)
+* Add Node.js 0.10 as minimum required version ([#657][] @jokeyrhyme)
+
+### Changed
+* Update dependencies
+* Update Travis CI and AppVeyor CI configurations (@ljharb)
+
+[6.0.0-alpha.1]: https://github.com/yannickcr/eslint-plugin-react/compare/v5.2.2...v6.0.0-alpha.1
+[#654]: https://github.com/yannickcr/eslint-plugin-react/issues/654
+[#662]: https://github.com/yannickcr/eslint-plugin-react/issues/662
+[#664]: https://github.com/yannickcr/eslint-plugin-react/issues/664
+[#657]: https://github.com/yannickcr/eslint-plugin-react/pull/657
+
 ## [5.2.2] - 2016-06-17
 ### Fixed
 * Fix `jsx-no-bind` crash ([#641][])
diff --git a/README.md b/README.md
index a5497302..9bf65866 100644
--- a/README.md
+++ b/README.md
@@ -81,12 +81,12 @@ Finally, enable all of the rules that you would like to use.  Use [our preset](#
 
 * [react/display-name](docs/rules/display-name.md): Prevent missing `displayName` in a React component definition
 * [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes
-* [react/no-comment-textnodes](docs/rules/no-comment-textnodes.md): Prevent comments from being inserted as text nodes
 * [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
 * [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`
 * [react/no-direct-mutation-state](docs/rules/no-direct-mutation-state.md): Prevent direct mutation of `this.state`
+* [react/no-find-dom-node](docs/rules/no-find-dom-node.md): Prevent usage of `findDOMNode`
 * [react/no-is-mounted](docs/rules/no-is-mounted.md): Prevent usage of `isMounted`
 * [react/no-multi-comp](docs/rules/no-multi-comp.md): Prevent multiple component definition per file
 * [react/no-render-return-value](docs/rules/no-render-return-value.md): Prevent usage of the return value of `React.render`
@@ -97,13 +97,11 @@ Finally, enable all of the rules that you would like to use.  Use [our preset](#
 * [react/prefer-stateless-function](docs/rules/prefer-stateless-function.md): Enforce stateless React Components to be written as a pure function
 * [react/prop-types](docs/rules/prop-types.md): Prevent missing props validation in a React component definition
 * [react/react-in-jsx-scope](docs/rules/react-in-jsx-scope.md): Prevent missing `React` when using JSX
-* [react/require-extension](docs/rules/require-extension.md): Restrict file extensions that may be required
 * [react/require-optimization](docs/rules/require-optimization.md): Enforce React components to have a shouldComponentUpdate method
 * [react/require-render-return](docs/rules/require-render-return.md): Enforce ES5 or ES6 class for returning value in render function
 * [react/self-closing-comp](docs/rules/self-closing-comp.md): Prevent extra closing tags for components without children
 * [react/sort-comp](docs/rules/sort-comp.md): Enforce component methods order
 * [react/sort-prop-types](docs/rules/sort-prop-types.md): Enforce propTypes declarations alphabetical sorting
-* [react/wrap-multilines](docs/rules/wrap-multilines.md): Prevent missing parentheses around multilines JSX (fixable)
 
 ## JSX-specific rules
 
@@ -119,15 +117,18 @@ Finally, enable all of the rules that you would like to use.  Use [our preset](#
 * [react/jsx-key](docs/rules/jsx-key.md): Validate JSX has key prop when in array or iterator
 * [react/jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md): Limit maximum of props on a single line in JSX
 * [react/jsx-no-bind](docs/rules/jsx-no-bind.md): Prevent usage of `.bind()` and arrow functions in JSX props
+* [react/jsx-no-comment-textnodes](docs/rules/jsx-no-comment-textnodes.md): Prevent comments from being inserted as text nodes
 * [react/jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md): Prevent duplicate props in JSX
 * [react/jsx-no-literals](docs/rules/jsx-no-literals.md): Prevent usage of unwrapped JSX strings
 * [react/jsx-no-target-blank](docs/rules/jsx-no-target-blank.md): Prevent usage of unsafe `target='_blank'`
 * [react/jsx-no-undef](docs/rules/jsx-no-undef.md): Disallow undeclared variables in JSX
 * [react/jsx-pascal-case](docs/rules/jsx-pascal-case.md): Enforce PascalCase for user-defined JSX components
+* [react/jsx-require-extension](docs/rules/jsx-require-extension.md): Restrict file extensions that may be required
 * [react/jsx-sort-props](docs/rules/jsx-sort-props.md): Enforce props alphabetical sorting
 * [react/jsx-space-before-closing](docs/rules/jsx-space-before-closing.md): Validate spacing before closing bracket in JSX (fixable)
 * [react/jsx-uses-react](docs/rules/jsx-uses-react.md): Prevent React to be incorrectly marked as unused
 * [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
 
@@ -162,6 +163,7 @@ The rules enabled in this configuration are:
 * [react/no-did-mount-set-state](docs/rules/no-did-mount-set-state.md) with `allow-in-func` option
 * [react/no-did-update-set-state](docs/rules/no-did-update-set-state.md) with `allow-in-func` option
 * [react/no-direct-mutation-state](docs/rules/no-direct-mutation-state.md)
+* [react/no-find-dom-node](docs/rules/no-find-dom-node.md)
 * [react/no-is-mounted](docs/rules/no-is-mounted.md)
 * [react/no-unknown-property](docs/rules/no-unknown-property.md)
 * [react/prop-types](docs/rules/prop-types.md)
@@ -169,6 +171,18 @@ The rules enabled in this configuration are:
 
 **Note**: This configuration will also enable JSX in [parser options](http://eslint.org/docs/user-guide/configuring#specifying-parser-options).
 
+This plugin also exports an `all` configuration that includes every available rule.
+This pairs well with the `eslint:all` rule.
+
+```js
+{
+  "plugins": [
+    "react"
+  ],
+  "extends": ["eslint:all", "plugin:react/all"]
+}
+```
+
 # License
 
 ESLint-plugin-React is licensed under the [MIT License](http://www.opensource.org/licenses/mit-license.php).
diff --git a/docs/rules/no-comment-textnodes.md b/docs/rules/jsx-no-comment-textnodes.md
similarity index 94%
rename from docs/rules/no-comment-textnodes.md
rename to docs/rules/jsx-no-comment-textnodes.md
index 22e73b16..9dfd339c 100644
--- a/docs/rules/no-comment-textnodes.md
+++ b/docs/rules/jsx-no-comment-textnodes.md
@@ -1,4 +1,4 @@
-# Prevent comments from being inserted as text nodes (no-comment-textnodes)
+# Prevent comments from being inserted as text nodes (jsx-no-comment-textnodes)
 
 This rule prevents comment strings (e.g. beginning with `//` or `/*`) from being accidentally
 injected as a text node in JSX statements.
diff --git a/docs/rules/require-extension.md b/docs/rules/jsx-require-extension.md
similarity index 89%
rename from docs/rules/require-extension.md
rename to docs/rules/jsx-require-extension.md
index 1e91d6d2..00761375 100644
--- a/docs/rules/require-extension.md
+++ b/docs/rules/jsx-require-extension.md
@@ -1,4 +1,4 @@
-# Restrict file extensions that may be required (require-extension)
+# Restrict file extensions that may be required (jsx-require-extension)
 
 `require()` statements should generally not include a file extension as there is a well defined mechanism for resolving a module ID to a specific file. This rule inspects the module ID being required and creates a warning if the ID contains a '.jsx' file extension.
 
@@ -29,7 +29,7 @@ The set of forbidden extensions is configurable. By default '.jsx' is blocked. I
 
 ```js
 "rules": {
-  "react/require-extension": [1, { "extensions": [".js", ".jsx"] }],
+  "react/jsx-require-extension": [1, { "extensions": [".js", ".jsx"] }],
 }
 ```
 
diff --git a/docs/rules/jsx-sort-props.md b/docs/rules/jsx-sort-props.md
index e2cc8660..2fb8ca56 100644
--- a/docs/rules/jsx-sort-props.md
+++ b/docs/rules/jsx-sort-props.md
@@ -26,6 +26,7 @@ The following patterns are considered okay and do not cause warnings:
 "jsx-sort-props": [<enabled>, {
   "callbacksLast": <boolean>,
   "shorthandFirst": <boolean>,
+  "shorthandLast": <boolean>,
   "ignoreCase": <boolean>
 }]
 ...
@@ -43,7 +44,7 @@ The following patterns are considered okay and do not cause warnings:
 
 ### `callbacksLast`
 
-When `true`, callbacks must be listed after all other props:
+When `true`, callbacks must be listed after all other props, even if `shorthandLast` is set :
 
 ```js
 <Hello tel={5555555} onClick={this._handleClick} />
@@ -57,6 +58,14 @@ When `true`, short hand props must be listed before all other props, but still r
 <Hello active validate name="John" tel={5555555} />
 ```
 
+### `shorthandLast`
+
+When `true`, short hand props must be listed after all other props (unless `callbacksLast` is set), but still respecting the alphabetical order:
+
+```js
+<Hello name="John" tel={5555555} active validate />
+```
+
 ## When not to use
 
 This rule is a formatting preference and not following it won't negatively affect the quality of your code. If alphabetizing props isn't a part of your coding standards, then you can leave this rule off.
diff --git a/docs/rules/wrap-multilines.md b/docs/rules/jsx-wrap-multilines.md
similarity index 93%
rename from docs/rules/wrap-multilines.md
rename to docs/rules/jsx-wrap-multilines.md
index b24e892b..3179f8cc 100644
--- a/docs/rules/wrap-multilines.md
+++ b/docs/rules/jsx-wrap-multilines.md
@@ -1,4 +1,4 @@
-# Prevent missing parentheses around multiline JSX (wrap-multilines)
+# Prevent missing parentheses around multiline JSX (jsx-wrap-multilines)
 
 Wrapping multiline JSX in parentheses can improve readability and/or convenience. It optionally takes a second parameter in the form of an object, containing places to apply the rule. By default, `"declaration"`, `"assignment"`, and `"return"` syntax is checked, but these can be explicitly disabled. Any syntax type missing in the object will follow the default behavior (become enabled).
 
diff --git a/docs/rules/no-find-dom-node.md b/docs/rules/no-find-dom-node.md
new file mode 100644
index 00000000..f981c003
--- /dev/null
+++ b/docs/rules/no-find-dom-node.md
@@ -0,0 +1,33 @@
+# Prevent usage of findDOMNode (no-find-dom-node)
+
+Facebook will eventually deprecate `findDOMNode` as it blocks certain improvements in React in the future.
+
+It is recommended to use callback refs instead. See [Dan Abramov comments and examples](https://github.com/yannickcr/eslint-plugin-react/issues/678#issue-165177220).
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```js
+class MyComponent extends Component {
+  componentDidMount() {
+    findDOMNode(this).scrollIntoView();
+  }
+  render() {
+    return <div />
+  }
+}
+```
+
+The following patterns are not considered warnings:
+
+```js
+class MyComponent extends Component {
+  componentDidMount() {
+    this.node.scrollIntoView();
+  }
+  render() {
+    return <div ref={node => this.node = node} />
+  }
+}
+```
diff --git a/docs/rules/require-optimization.md b/docs/rules/require-optimization.md
index 62a45a3b..4758e7a5 100644
--- a/docs/rules/require-optimization.md
+++ b/docs/rules/require-optimization.md
@@ -52,14 +52,30 @@ React.createClass({
 
 ```js
 ...
-"require-optimization": [<enabled>]
+"require-optimization": [<enabled>, { allowDecorators: [<allowDecorator>] }]
 ...
 ```
 
+* `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0.
+* `allowDecorators`: optional array of decorators names to allow validation.
+
+
+### `allowDecorators`
+
+Sets the allowed names of decorators. If the variable is present in the chain of decorators, it validates
+
+The following patterns are not warnings:
+
+```js
+// ['pureRender']
+@pureRender
+class Hello extends React.Component {}
+```
+
 ### Example
 
 ```js
 ...
-"require-optimization": 2
+"require-optimization": [2, {allowDecorators: ['customDecorators']}]
 ...
 ```
diff --git a/index.js b/index.js
index 25cbdb4c..a28494f7 100644
--- a/index.js
+++ b/index.js
@@ -1,55 +1,66 @@
 'use strict';
 
+var rules = {
+  'jsx-uses-react': require('./lib/rules/jsx-uses-react'),
+  'no-multi-comp': require('./lib/rules/no-multi-comp'),
+  'prop-types': require('./lib/rules/prop-types'),
+  'display-name': require('./lib/rules/display-name'),
+  'wrap-multilines': require('./lib/rules/wrap-multilines'),
+  'jsx-wrap-multilines': require('./lib/rules/jsx-wrap-multilines'),
+  'self-closing-comp': require('./lib/rules/self-closing-comp'),
+  'no-comment-textnodes': require('./lib/rules/no-comment-textnodes'),
+  'jsx-no-comment-textnodes': require('./lib/rules/jsx-no-comment-textnodes'),
+  'no-danger': require('./lib/rules/no-danger'),
+  'no-set-state': require('./lib/rules/no-set-state'),
+  'no-is-mounted': require('./lib/rules/no-is-mounted'),
+  'no-deprecated': require('./lib/rules/no-deprecated'),
+  'no-did-mount-set-state': require('./lib/rules/no-did-mount-set-state'),
+  'no-did-update-set-state': require('./lib/rules/no-did-update-set-state'),
+  'no-render-return-value': require('./lib/rules/no-render-return-value'),
+  'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'),
+  'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'),
+  'jsx-handler-names': require('./lib/rules/jsx-handler-names'),
+  'jsx-pascal-case': require('./lib/rules/jsx-pascal-case'),
+  'jsx-no-bind': require('./lib/rules/jsx-no-bind'),
+  'jsx-no-undef': require('./lib/rules/jsx-no-undef'),
+  'no-unknown-property': require('./lib/rules/no-unknown-property'),
+  'jsx-curly-spacing': require('./lib/rules/jsx-curly-spacing'),
+  'jsx-equals-spacing': require('./lib/rules/jsx-equals-spacing'),
+  'jsx-sort-props': require('./lib/rules/jsx-sort-props'),
+  'sort-prop-types': require('./lib/rules/sort-prop-types'),
+  'jsx-boolean-value': require('./lib/rules/jsx-boolean-value'),
+  'sort-comp': require('./lib/rules/sort-comp'),
+  'require-extension': require('./lib/rules/require-extension'),
+  'jsx-require-extension': require('./lib/rules/jsx-require-extension'),
+  'jsx-no-duplicate-props': require('./lib/rules/jsx-no-duplicate-props'),
+  'jsx-max-props-per-line': require('./lib/rules/jsx-max-props-per-line'),
+  'jsx-no-literals': require('./lib/rules/jsx-no-literals'),
+  'jsx-indent-props': require('./lib/rules/jsx-indent-props'),
+  'jsx-indent': require('./lib/rules/jsx-indent'),
+  '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-prop-types': require('./lib/rules/forbid-prop-types'),
+  'prefer-es6-class': require('./lib/rules/prefer-es6-class'),
+  'jsx-key': require('./lib/rules/jsx-key'),
+  'no-string-refs': require('./lib/rules/no-string-refs'),
+  'prefer-stateless-function': require('./lib/rules/prefer-stateless-function'),
+  'require-render-return': require('./lib/rules/require-render-return'),
+  'jsx-first-prop-new-line': require('./lib/rules/jsx-first-prop-new-line'),
+  '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')
+};
+
+var ruleNames = Object.keys(rules);
+var allRules = {};
+for (var i = 0; i < ruleNames.length; i++) {
+  allRules['react/' + ruleNames[i]] = 2;
+}
+
 module.exports = {
-  rules: {
-    'jsx-uses-react': require('./lib/rules/jsx-uses-react'),
-    'no-multi-comp': require('./lib/rules/no-multi-comp'),
-    'prop-types': require('./lib/rules/prop-types'),
-    'display-name': require('./lib/rules/display-name'),
-    'wrap-multilines': require('./lib/rules/wrap-multilines'),
-    'self-closing-comp': require('./lib/rules/self-closing-comp'),
-    'no-comment-textnodes': require('./lib/rules/no-comment-textnodes'),
-    'no-danger': require('./lib/rules/no-danger'),
-    'no-set-state': require('./lib/rules/no-set-state'),
-    'no-is-mounted': require('./lib/rules/no-is-mounted'),
-    'no-deprecated': require('./lib/rules/no-deprecated'),
-    'no-did-mount-set-state': require('./lib/rules/no-did-mount-set-state'),
-    'no-did-update-set-state': require('./lib/rules/no-did-update-set-state'),
-    'no-render-return-value': require('./lib/rules/no-render-return-value'),
-    'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'),
-    'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'),
-    'jsx-handler-names': require('./lib/rules/jsx-handler-names'),
-    'jsx-pascal-case': require('./lib/rules/jsx-pascal-case'),
-    'jsx-no-bind': require('./lib/rules/jsx-no-bind'),
-    'jsx-no-undef': require('./lib/rules/jsx-no-undef'),
-    'no-unknown-property': require('./lib/rules/no-unknown-property'),
-    'jsx-curly-spacing': require('./lib/rules/jsx-curly-spacing'),
-    'jsx-equals-spacing': require('./lib/rules/jsx-equals-spacing'),
-    'jsx-sort-props': require('./lib/rules/jsx-sort-props'),
-    'sort-prop-types': require('./lib/rules/sort-prop-types'),
-    'jsx-sort-prop-types': require('./lib/rules/jsx-sort-prop-types'),
-    'jsx-boolean-value': require('./lib/rules/jsx-boolean-value'),
-    'sort-comp': require('./lib/rules/sort-comp'),
-    'require-extension': require('./lib/rules/require-extension'),
-    'jsx-no-duplicate-props': require('./lib/rules/jsx-no-duplicate-props'),
-    'jsx-max-props-per-line': require('./lib/rules/jsx-max-props-per-line'),
-    'jsx-no-literals': require('./lib/rules/jsx-no-literals'),
-    'jsx-indent-props': require('./lib/rules/jsx-indent-props'),
-    'jsx-indent': require('./lib/rules/jsx-indent'),
-    '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-prop-types': require('./lib/rules/forbid-prop-types'),
-    'prefer-es6-class': require('./lib/rules/prefer-es6-class'),
-    'jsx-key': require('./lib/rules/jsx-key'),
-    'no-string-refs': require('./lib/rules/no-string-refs'),
-    'prefer-stateless-function': require('./lib/rules/prefer-stateless-function'),
-    'require-render-return': require('./lib/rules/require-render-return'),
-    'jsx-first-prop-new-line': require('./lib/rules/jsx-first-prop-new-line'),
-    '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')
-  },
+  rules: rules,
   configs: {
     recommended: {
       parserOptions: {
@@ -68,12 +79,21 @@ module.exports = {
         'react/no-did-mount-set-state': [2, 'allow-in-func'],
         'react/no-did-update-set-state': [2, 'allow-in-func'],
         'react/no-direct-mutation-state': 2,
+        'react/no-find-dom-node': 2,
         'react/no-is-mounted': 2,
         'react/no-unknown-property': 2,
         'react/no-render-return-value': 2,
         'react/prop-types': 2,
         'react/react-in-jsx-scope': 2
       }
+    },
+    all: {
+      parserOptions: {
+        ecmaFeatures: {
+          jsx: true
+        }
+      },
+      rules: allRules
     }
   }
 };
diff --git a/lib/rules/jsx-closing-bracket-location.js b/lib/rules/jsx-closing-bracket-location.js
index 8a8f180d..47bd4bd2 100644
--- a/lib/rules/jsx-closing-bracket-location.js
+++ b/lib/rules/jsx-closing-bracket-location.js
@@ -167,7 +167,6 @@ module.exports = function(context) {
     'JSXOpeningElement:exit': function(node) {
       var attributeNode = lastAttributeNode[getOpeningElementId(node)];
       var cachedLastAttributeEndPos = attributeNode ? attributeNode.end : null;
-      var cachedLastAttributeStartPos = attributeNode ? attributeNode.start : null;
       var expectedNextLine;
       var tokens = getTokensLocations(node);
       var expectedLocation = getExpectedLocation(tokens);
@@ -205,17 +204,11 @@ module.exports = function(context) {
               return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end],
                 (expectedNextLine ? '\n' : '') + closingTag);
             case 'props-aligned':
-              var spaces = new Array(cachedLastAttributeEndPos - cachedLastAttributeStartPos);
-              return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end],
-                '\n' + spaces.join(' ') + closingTag);
             case 'tag-aligned':
-              var tagSpaces = new Array(+correctColumn + 1);
-              return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end],
-                '\n' + tagSpaces.join(' ') + closingTag);
             case 'line-aligned':
-              var lineSpaces = new Array(+correctColumn + 1);
+              var spaces = new Array(+correctColumn + 1);
               return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end],
-                '\n' + lineSpaces.join(' ') + closingTag);
+                '\n' + spaces.join(' ') + closingTag);
             default:
               return true;
           }
diff --git a/lib/rules/jsx-curly-spacing.js b/lib/rules/jsx-curly-spacing.js
index 0cdafb90..e42a04a6 100644
--- a/lib/rules/jsx-curly-spacing.js
+++ b/lib/rules/jsx-curly-spacing.js
@@ -155,6 +155,10 @@ module.exports = function(context) {
    * @returns {void}
    */
   function validateBraceSpacing(node) {
+    // Only validate attributes
+    if (node.parent.type === 'JSXElement') {
+      return;
+    }
     var first = context.getFirstToken(node);
     var last = sourceCode.getLastToken(node);
     var second = context.getTokenAfter(first);
diff --git a/lib/rules/jsx-filename-extension.js b/lib/rules/jsx-filename-extension.js
index 6fa7e1dc..727a3c16 100644
--- a/lib/rules/jsx-filename-extension.js
+++ b/lib/rules/jsx-filename-extension.js
@@ -25,16 +25,25 @@ module.exports = function(context) {
     return context.options[0] && context.options[0].extensions || DEFAULTS.extensions;
   }
 
+  var invalidExtension;
+  var invalidNode;
+
   // --------------------------------------------------------------------------
   // Public
   // --------------------------------------------------------------------------
 
   return {
-
     JSXElement: function(node) {
-      var allowedExtensions = getExtensionsConfig();
       var filename = context.getFilename();
+      if (filename === '<text>') {
+        return;
+      }
+
+      if (invalidNode) {
+        return;
+      }
 
+      var allowedExtensions = getExtensionsConfig();
       var isAllowedExtension = allowedExtensions.some(function (extension) {
         return filename.slice(-extension.length) === extension;
       });
@@ -43,11 +52,18 @@ module.exports = function(context) {
         return;
       }
 
-      var extension = path.extname(filename);
+      invalidNode = node;
+      invalidExtension = path.extname(filename);
+    },
+
+    'Program:exit': function() {
+      if (!invalidNode) {
+        return;
+      }
 
       context.report({
-        node: node,
-        message: 'JSX not allowed in files with extension \'' + extension + '\''
+        node: invalidNode,
+        message: 'JSX not allowed in files with extension \'' + invalidExtension + '\''
       });
     }
   };
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/rules/jsx-no-comment-textnodes.js b/lib/rules/jsx-no-comment-textnodes.js
new file mode 100644
index 00000000..7c3cf71d
--- /dev/null
+++ b/lib/rules/jsx-no-comment-textnodes.js
@@ -0,0 +1,38 @@
+/**
+ * @fileoverview Comments inside children section of tag should be placed inside braces.
+ * @author Ben Vinegar
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = function(context) {
+  function reportLiteralNode(node) {
+    context.report(node, 'Comments inside children section of tag should be placed inside braces');
+  }
+
+  // --------------------------------------------------------------------------
+  // Public
+  // --------------------------------------------------------------------------
+
+  return {
+    Literal: function(node) {
+      if (/^\s*\/(\/|\*)/m.test(node.value)) {
+        // inside component, e.g. <div>literal</div>
+        if (node.parent.type !== 'JSXAttribute' &&
+            node.parent.type !== 'JSXExpressionContainer' &&
+            node.parent.type.indexOf('JSX') !== -1) {
+          reportLiteralNode(node);
+        }
+      }
+    }
+  };
+};
+
+module.exports.schema = [{
+  type: 'object',
+  properties: {},
+  additionalProperties: false
+}];
diff --git a/lib/rules/jsx-require-extension.js b/lib/rules/jsx-require-extension.js
new file mode 100644
index 00000000..dde310db
--- /dev/null
+++ b/lib/rules/jsx-require-extension.js
@@ -0,0 +1,88 @@
+/**
+ * @fileoverview Restrict file extensions that may be required
+ * @author Scott Andrews
+ */
+'use strict';
+
+var path = require('path');
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+var DEFAULTS = {
+  extensions: ['.jsx']
+};
+
+var PKG_REGEX = /^[^\.]((?!\/).)*$/;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+  function isPackage(id) {
+    return PKG_REGEX.test(id);
+  }
+
+  function isRequire(expression) {
+    return expression.callee.name === 'require';
+  }
+
+  function getId(expression) {
+    return expression.arguments[0] && expression.arguments[0].value;
+  }
+
+  function getExtension(id) {
+    return path.extname(id || '');
+  }
+
+  function getExtensionsConfig() {
+    return context.options[0] && context.options[0].extensions || DEFAULTS.extensions;
+  }
+
+  var forbiddenExtensions = getExtensionsConfig().reduce(function (extensions, extension) {
+    extensions[extension] = true;
+    return extensions;
+  }, Object.create(null));
+
+  function isForbiddenExtension(ext) {
+    return ext in forbiddenExtensions;
+  }
+
+  // --------------------------------------------------------------------------
+  // Public
+  // --------------------------------------------------------------------------
+
+  return {
+
+    CallExpression: function(node) {
+      if (isRequire(node)) {
+        var id = getId(node);
+        var ext = getExtension(id);
+        if (!isPackage(id) && isForbiddenExtension(ext)) {
+          context.report({
+            node: node,
+            message: 'Unable to require module with extension \'' + ext + '\''
+          });
+        }
+      }
+    }
+
+  };
+
+};
+
+module.exports.schema = [{
+  type: 'object',
+  properties: {
+    extensions: {
+      type: 'array',
+      items: {
+        type: 'string'
+      }
+    }
+  },
+  additionalProperties: false
+}];
diff --git a/lib/rules/jsx-sort-prop-types.js b/lib/rules/jsx-sort-prop-types.js
deleted file mode 100644
index 549654ad..00000000
--- a/lib/rules/jsx-sort-prop-types.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * @fileoverview Enforce propTypes declarations alphabetical sorting
- * @deprecated
- */
-'use strict';
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-
-var util = require('util');
-var sortPropTypes = require('./sort-prop-types');
-var isWarnedForDeprecation = false;
-
-module.exports = function(context) {
-  return util._extend(sortPropTypes(context), {
-    Program: function() {
-      if (isWarnedForDeprecation || /\=-(f|-format)=/.test(process.argv.join('='))) {
-        return;
-      }
-
-      /* eslint-disable no-console */
-      console.log('The react/jsx-sort-prop-types rule is deprecated. Please ' +
-                  'use the react/sort-prop-types rule instead.');
-      /* eslint-enable no-console */
-      isWarnedForDeprecation = true;
-    }
-  });
-};
-
-module.exports.schema = [{
-  type: 'object',
-  properties: {
-    requiredFirst: {
-      type: 'boolean'
-    },
-    callbacksLast: {
-      type: 'boolean'
-    },
-    ignoreCase: {
-      type: 'boolean'
-    }
-  },
-  additionalProperties: false
-}];
diff --git a/lib/rules/jsx-sort-props.js b/lib/rules/jsx-sort-props.js
index 1a3e3640..a448e2eb 100644
--- a/lib/rules/jsx-sort-props.js
+++ b/lib/rules/jsx-sort-props.js
@@ -20,6 +20,7 @@ module.exports = function(context) {
   var ignoreCase = configuration.ignoreCase || false;
   var callbacksLast = configuration.callbacksLast || false;
   var shorthandFirst = configuration.shorthandFirst || false;
+  var shorthandLast = configuration.shorthandLast || false;
 
   return {
     JSXOpeningElement: function(node) {
@@ -68,6 +69,19 @@ module.exports = function(context) {
           }
         }
 
+        if (shorthandLast) {
+          if (!currentValue && previousValue) {
+            return decl;
+          }
+          if (currentValue && !previousValue) {
+            context.report({
+              node: memo,
+              message: 'Shorthand props must be listed after all other props'
+            });
+            return memo;
+          }
+        }
+
         if (currentPropName < previousPropName) {
           context.report({
             node: decl,
@@ -86,7 +100,7 @@ module.exports.schema = [{
   type: 'object',
   properties: {
     // Whether callbacks (prefixed with "on") should be listed at the very end,
-    // after all other props.
+    // after all other props. Supersedes shorthandLast.
     callbacksLast: {
       type: 'boolean'
     },
@@ -94,6 +108,10 @@ module.exports.schema = [{
     shorthandFirst: {
       type: 'boolean'
     },
+    // Whether shorthand properties (without a value) should be listed last
+    shorthandLast: {
+      type: 'boolean'
+    },
     ignoreCase: {
       type: 'boolean'
     }
diff --git a/lib/rules/jsx-wrap-multilines.js b/lib/rules/jsx-wrap-multilines.js
new file mode 100644
index 00000000..a28b9a73
--- /dev/null
+++ b/lib/rules/jsx-wrap-multilines.js
@@ -0,0 +1,103 @@
+/**
+ * @fileoverview Prevent missing parentheses around multilines JSX
+ * @author Yannick Croissant
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+var DEFAULTS = {
+  declaration: true,
+  assignment: true,
+  return: true
+};
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+  var sourceCode = context.getSourceCode();
+
+  function isParenthesised(node) {
+    var previousToken = sourceCode.getTokenBefore(node);
+    var nextToken = sourceCode.getTokenAfter(node);
+
+    return previousToken && nextToken &&
+      previousToken.value === '(' && previousToken.range[1] <= node.range[0] &&
+      nextToken.value === ')' && nextToken.range[0] >= node.range[1];
+  }
+
+  function isMultilines(node) {
+    return node.loc.start.line !== node.loc.end.line;
+  }
+
+  function check(node) {
+    if (!node || node.type !== 'JSXElement') {
+      return;
+    }
+
+    if (!isParenthesised(node) && isMultilines(node)) {
+      context.report({
+        node: node,
+        message: 'Missing parentheses around multilines JSX',
+        fix: function(fixer) {
+          return fixer.replaceText(node, '(' + sourceCode.getText(node) + ')');
+        }
+      });
+    }
+  }
+
+  function isEnabled(type) {
+    var userOptions = context.options[0] || {};
+    if (({}).hasOwnProperty.call(userOptions, type)) {
+      return userOptions[type];
+    }
+    return DEFAULTS[type];
+  }
+
+  // --------------------------------------------------------------------------
+  // Public
+  // --------------------------------------------------------------------------
+
+  return {
+
+    VariableDeclarator: function(node) {
+      if (isEnabled('declaration')) {
+        check(node.init);
+      }
+    },
+
+    AssignmentExpression: function(node) {
+      if (isEnabled('assignment')) {
+        check(node.right);
+      }
+    },
+
+    ReturnStatement: function(node) {
+      if (isEnabled('return')) {
+        check(node.argument);
+      }
+    }
+  };
+
+};
+
+module.exports.schema = [{
+  type: 'object',
+  properties: {
+    declaration: {
+      type: 'boolean'
+    },
+    assignment: {
+      type: 'boolean'
+    },
+    return: {
+      type: 'boolean'
+    }
+  },
+  additionalProperties: false
+}];
diff --git a/lib/rules/no-comment-textnodes.js b/lib/rules/no-comment-textnodes.js
index 7a3bfc58..954999e0 100644
--- a/lib/rules/no-comment-textnodes.js
+++ b/lib/rules/no-comment-textnodes.js
@@ -1,6 +1,7 @@
 /**
  * @fileoverview Comments inside children section of tag should be placed inside braces.
  * @author Ben Vinegar
+ * @deprecated
  */
 'use strict';
 
@@ -8,27 +9,24 @@
 // Rule Definition
 // ------------------------------------------------------------------------------
 
-module.exports = function(context) {
-  function reportLiteralNode(node) {
-    context.report(node, 'Comments inside children section of tag should be placed inside braces');
-  }
-
-  // --------------------------------------------------------------------------
-  // Public
-  // --------------------------------------------------------------------------
+var util = require('util');
+var jsxNoCommentTextnodes = require('./jsx-no-comment-textnodes');
+var isWarnedForDeprecation = false;
 
-  return {
-    Literal: function(node) {
-      if (/\s*\/(\/|\*)/.test(node.value)) {
-        // inside component, e.g. <div>literal</div>
-        if (node.parent.type !== 'JSXAttribute' &&
-            node.parent.type !== 'JSXExpressionContainer' &&
-            node.parent.type.indexOf('JSX') !== -1) {
-          reportLiteralNode(node);
-        }
+module.exports = function(context) {
+  return util._extend(jsxNoCommentTextnodes(context), {
+    Program: function() {
+      if (isWarnedForDeprecation || /\=-(f|-format)=/.test(process.argv.join('='))) {
+        return;
       }
+
+      /* eslint-disable no-console */
+      console.log('The react/no-comment-textnodes rule is deprecated. Please ' +
+                  'use the react/jsx-no-comment-textnodes rule instead.');
+      /* eslint-enable no-console */
+      isWarnedForDeprecation = true;
     }
-  };
+  });
 };
 
 module.exports.schema = [{
diff --git a/lib/rules/no-find-dom-node.js b/lib/rules/no-find-dom-node.js
new file mode 100644
index 00000000..f619f551
--- /dev/null
+++ b/lib/rules/no-find-dom-node.js
@@ -0,0 +1,40 @@
+/**
+ * @fileoverview Prevent usage of findDOMNode
+ * @author Yannick Croissant
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+  // --------------------------------------------------------------------------
+  // Public
+  // --------------------------------------------------------------------------
+
+  return {
+
+    CallExpression: function(node) {
+      var callee = node.callee;
+
+      var isfindDOMNode =
+        (callee.object && callee.object.callee && callee.object.callee.name === 'findDOMNode') ||
+        (callee.property && callee.property.name === 'findDOMNode')
+      ;
+
+      if (!isfindDOMNode) {
+        return;
+      }
+
+      context.report({
+        node: callee,
+        message: 'Do not use findDOMNode'
+      });
+    }
+  };
+
+};
+
+module.exports.schema = [];
diff --git a/lib/rules/require-extension.js b/lib/rules/require-extension.js
index dde310db..8e25c665 100644
--- a/lib/rules/require-extension.js
+++ b/lib/rules/require-extension.js
@@ -1,77 +1,32 @@
 /**
  * @fileoverview Restrict file extensions that may be required
  * @author Scott Andrews
+ * @deprecated
  */
 'use strict';
 
-var path = require('path');
-
-// ------------------------------------------------------------------------------
-// Constants
-// ------------------------------------------------------------------------------
-
-var DEFAULTS = {
-  extensions: ['.jsx']
-};
-
-var PKG_REGEX = /^[^\.]((?!\/).)*$/;
-
 // ------------------------------------------------------------------------------
 // Rule Definition
 // ------------------------------------------------------------------------------
 
-module.exports = function(context) {
-
-  function isPackage(id) {
-    return PKG_REGEX.test(id);
-  }
-
-  function isRequire(expression) {
-    return expression.callee.name === 'require';
-  }
-
-  function getId(expression) {
-    return expression.arguments[0] && expression.arguments[0].value;
-  }
+var util = require('util');
+var jsxRequireExtension = require('./jsx-require-extension');
+var isWarnedForDeprecation = false;
 
-  function getExtension(id) {
-    return path.extname(id || '');
-  }
-
-  function getExtensionsConfig() {
-    return context.options[0] && context.options[0].extensions || DEFAULTS.extensions;
-  }
-
-  var forbiddenExtensions = getExtensionsConfig().reduce(function (extensions, extension) {
-    extensions[extension] = true;
-    return extensions;
-  }, Object.create(null));
-
-  function isForbiddenExtension(ext) {
-    return ext in forbiddenExtensions;
-  }
-
-  // --------------------------------------------------------------------------
-  // Public
-  // --------------------------------------------------------------------------
-
-  return {
-
-    CallExpression: function(node) {
-      if (isRequire(node)) {
-        var id = getId(node);
-        var ext = getExtension(id);
-        if (!isPackage(id) && isForbiddenExtension(ext)) {
-          context.report({
-            node: node,
-            message: 'Unable to require module with extension \'' + ext + '\''
-          });
-        }
+module.exports = function(context) {
+  return util._extend(jsxRequireExtension(context), {
+    Program: function() {
+      if (isWarnedForDeprecation || /\=-(f|-format)=/.test(process.argv.join('='))) {
+        return;
       }
-    }
-
-  };
 
+      /* eslint-disable no-console */
+      console.log('The react/require-extension rule is deprecated. Please ' +
+                  'use the react/jsx-require-extension rule instead.');
+      /* eslint-enable no-console */
+      isWarnedForDeprecation = true;
+    }
+  });
 };
 
 module.exports.schema = [{
diff --git a/lib/rules/require-optimization.js b/lib/rules/require-optimization.js
index a64afef7..35699bbf 100644
--- a/lib/rules/require-optimization.js
+++ b/lib/rules/require-optimization.js
@@ -8,6 +8,8 @@ var Components = require('../util/Components');
 
 module.exports = Components.detect(function (context, components) {
   var MISSING_MESSAGE = 'Component is not optimized. Please add a shouldComponentUpdate method.';
+  var configuration = context.options[0] || {};
+  var allowDecorators = configuration.allowDecorators || [];
 
   /**
    * Checks to see if our component is decorated by PureRenderMixin via reactMixin
@@ -36,6 +38,30 @@ module.exports = Components.detect(function (context, components) {
     return false;
   };
 
+  /**
+   * Checks to see if our component is custom decorated
+   * @param {ASTNode} node The AST node being checked.
+   * @returns {Boolean} True if node is decorated name with a custom decorated, false if not.
+   */
+  var hasCustomDecorator = function (node) {
+    var allowLenght = allowDecorators.length;
+
+    if (allowLenght && node.decorators && node.decorators.length) {
+      for (var i = 0; i < allowLenght; i++) {
+        for (var j = 0, l = node.decorators.length; j < l; j++) {
+          if (
+            node.decorators[j].expression &&
+            node.decorators[j].expression.name === allowDecorators[i]
+          ) {
+            return true;
+          }
+        }
+      }
+    }
+
+    return false;
+  };
+
   /**
    * Checks if we are declaring a shouldComponentUpdate method
    * @param {ASTNode} node The AST node being checked.
@@ -102,7 +128,7 @@ module.exports = Components.detect(function (context, components) {
     },
 
     ClassDeclaration: function (node) {
-      if (!hasPureRenderDecorator(node)) {
+      if (!(hasPureRenderDecorator(node) || hasCustomDecorator(node))) {
         return;
       }
       markSCUAsDeclared(node);
@@ -148,3 +174,16 @@ module.exports = Components.detect(function (context, components) {
     }
   };
 });
+
+module.exports.schema = [{
+  type: 'object',
+  properties: {
+    allowDecorators: {
+      type: 'array',
+      items: {
+        type: 'string'
+      }
+    }
+  },
+  additionalProperties: false
+}];
diff --git a/lib/rules/wrap-multilines.js b/lib/rules/wrap-multilines.js
index a28b9a73..f1266e2e 100644
--- a/lib/rules/wrap-multilines.js
+++ b/lib/rules/wrap-multilines.js
@@ -1,89 +1,32 @@
 /**
  * @fileoverview Prevent missing parentheses around multilines JSX
  * @author Yannick Croissant
+ * @deprecated
  */
 'use strict';
 
-// ------------------------------------------------------------------------------
-// Constants
-// ------------------------------------------------------------------------------
-
-var DEFAULTS = {
-  declaration: true,
-  assignment: true,
-  return: true
-};
-
 // ------------------------------------------------------------------------------
 // Rule Definition
 // ------------------------------------------------------------------------------
 
-module.exports = function(context) {
-
-  var sourceCode = context.getSourceCode();
+var util = require('util');
+var jsxWrapMultilines = require('./jsx-wrap-multilines');
+var isWarnedForDeprecation = false;
 
-  function isParenthesised(node) {
-    var previousToken = sourceCode.getTokenBefore(node);
-    var nextToken = sourceCode.getTokenAfter(node);
-
-    return previousToken && nextToken &&
-      previousToken.value === '(' && previousToken.range[1] <= node.range[0] &&
-      nextToken.value === ')' && nextToken.range[0] >= node.range[1];
-  }
-
-  function isMultilines(node) {
-    return node.loc.start.line !== node.loc.end.line;
-  }
-
-  function check(node) {
-    if (!node || node.type !== 'JSXElement') {
-      return;
-    }
-
-    if (!isParenthesised(node) && isMultilines(node)) {
-      context.report({
-        node: node,
-        message: 'Missing parentheses around multilines JSX',
-        fix: function(fixer) {
-          return fixer.replaceText(node, '(' + sourceCode.getText(node) + ')');
-        }
-      });
-    }
-  }
-
-  function isEnabled(type) {
-    var userOptions = context.options[0] || {};
-    if (({}).hasOwnProperty.call(userOptions, type)) {
-      return userOptions[type];
-    }
-    return DEFAULTS[type];
-  }
-
-  // --------------------------------------------------------------------------
-  // Public
-  // --------------------------------------------------------------------------
-
-  return {
-
-    VariableDeclarator: function(node) {
-      if (isEnabled('declaration')) {
-        check(node.init);
-      }
-    },
-
-    AssignmentExpression: function(node) {
-      if (isEnabled('assignment')) {
-        check(node.right);
+module.exports = function(context) {
+  return util._extend(jsxWrapMultilines(context), {
+    Program: function() {
+      if (isWarnedForDeprecation || /\=-(f|-format)=/.test(process.argv.join('='))) {
+        return;
       }
-    },
 
-    ReturnStatement: function(node) {
-      if (isEnabled('return')) {
-        check(node.argument);
-      }
+      /* eslint-disable no-console */
+      console.log('The react/wrap-multilines rule is deprecated. Please ' +
+                  'use the react/jsx-wrap-multilines rule instead.');
+      /* eslint-enable no-console */
+      isWarnedForDeprecation = true;
     }
-  };
-
+  });
 };
 
 module.exports.schema = [{
diff --git a/lib/util/Components.js b/lib/util/Components.js
index 72d9e80d..1ae133d3 100644
--- a/lib/util/Components.js
+++ b/lib/util/Components.js
@@ -98,7 +98,9 @@ Components.prototype.list = function() {
       component = this.get(node);
     }
     if (component) {
-      usedPropTypes[this._getId(component.node)] = this._list[i].usedPropTypes;
+      usedPropTypes[this._getId(component.node)] = (this._list[i].usedPropTypes || []).filter(function(propType) {
+        return propType.node.kind !== 'init';
+      });
     }
   }
   // Assign used props in not confident components to the parent component
@@ -199,12 +201,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 +216,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 +255,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
      *
@@ -305,15 +330,15 @@ function componentRule(rule, context) {
       while (scope) {
         var node = scope.block;
         var isClass = node.type === 'ClassExpression';
-        var isFunction = /Function/.test(node.type); // Ignore non functions
-        var isNotMethod = !node.parent || node.parent.type !== 'MethodDefinition'; // Ignore classes methods
-        var isNotArgument = !node.parent || node.parent.type !== 'CallExpression'; // Ignore arguments (callback, etc.)
-        // Stop moving up if we reach a class
-        if (isClass) {
+        var isFunction = /Function/.test(node.type); // Functions
+        var isMethod = node.parent && node.parent.type === 'MethodDefinition'; // Classes methods
+        var isArgument = node.parent && node.parent.type === 'CallExpression'; // Arguments (callback, etc.)
+        // Stop moving up if we reach a class or an argument (like a callback)
+        if (isClass || isArgument) {
           return null;
         }
-        // Return the node if it is a function that is not a class method or an argument (like a callback)
-        if (isFunction && isNotMethod && isNotArgument) {
+        // Return the node if it is a function that is not a class method
+        if (isFunction && !isMethod) {
           return node;
         }
         scope = scope.upper;
@@ -509,6 +534,8 @@ function componentRule(rule, context) {
       }
       node = utils.getParentComponent();
       if (!node) {
+        var scope = context.getScope();
+        components.add(scope.block, 1);
         return;
       }
       components.add(node, 2);
diff --git a/package.json b/package.json
index f5a3a092..ac107d16 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-react",
-  "version": "5.2.2",
+  "version": "6.0.0-alpha.1",
   "author": "Yannick Croissant <yannick.croissant+npm@gmail.com>",
   "description": "React specific linting rules for ESLint",
   "main": "index.js",
@@ -27,14 +27,14 @@
     "jsx-ast-utils": "^1.2.1"
   },
   "devDependencies": {
-    "babel-eslint": "^6.0.5",
+    "babel-eslint": "6.1.0",
     "coveralls": "2.11.9",
-    "eslint": "^3.0.0",
+    "eslint": "3.0.1",
     "istanbul": "0.4.4",
     "mocha": "2.5.3"
   },
   "peerDependencies": {
-    "eslint": "^2.13.1 || ^3.0.0"
+    "eslint": "^2.0.0 || ^3.0.0"
   },
   "engines": {
     "node": ">=0.10"


Test patch:
diff --git a/tests/index.js b/tests/index.js
index 100c0ee6..13c2e6b5 100644
--- a/tests/index.js
+++ b/tests/index.js
@@ -26,5 +26,20 @@ describe('all rule files should be exported by the plugin', function() {
 describe('configurations', function() {
   it('should export a \'recommended\' configuration', function() {
     assert(plugin.configs.recommended);
+    Object.keys(plugin.configs.recommended.rules).forEach(function (configName) {
+      assert.equal(configName.indexOf('react/'), 0);
+      var ruleName = configName.substring('react/'.length);
+      assert(plugin.rules[ruleName]);
+    });
+  });
+  it('should export a \'all\' configuration', function() {
+    assert(plugin.configs.all);
+    Object.keys(plugin.configs.all.rules).forEach(function(configName) {
+      assert.equal(configName.indexOf('react/'), 0);
+      assert.equal(plugin.configs.all.rules[configName], 2);
+    });
+    rules.forEach(function(ruleName) {
+      assert(plugin.configs.all.rules['react/' + ruleName]);
+    });
   });
 });
diff --git a/tests/lib/rules/jsx-closing-bracket-location.js b/tests/lib/rules/jsx-closing-bracket-location.js
index 1f126947..588e89c5 100644
--- a/tests/lib/rules/jsx-closing-bracket-location.js
+++ b/tests/lib/rules/jsx-closing-bracket-location.js
@@ -651,6 +651,102 @@ ruleTester.run('jsx-closing-bracket-location', rule, {
       line: 2,
       column: 8
     }]
+  }, {
+    code: [
+      'const Button = function(props) {',
+      '  return (',
+      '    <Button',
+      '      size={size}',
+      '      onClick={onClick}',
+      '                                    >',
+      '      Button Text',
+      '    </Button>',
+      '  );',
+      '};'
+    ].join('\n'),
+    output: [
+      'const Button = function(props) {',
+      '  return (',
+      '    <Button',
+      '      size={size}',
+      '      onClick={onClick}',
+      '      >',
+      '      Button Text',
+      '    </Button>',
+      '  );',
+      '};'
+    ].join('\n'),
+    options: ['props-aligned'],
+    parserOptions: parserOptions,
+    errors: [{
+      message: messageWithDetails(MESSAGE_PROPS_ALIGNED, 7, false),
+      line: 6,
+      column: 37
+    }]
+  }, {
+    code: [
+      'const Button = function(props) {',
+      '  return (',
+      '    <Button',
+      '      size={size}',
+      '      onClick={onClick}',
+      '                                    >',
+      '      Button Text',
+      '    </Button>',
+      '  );',
+      '};'
+    ].join('\n'),
+    output: [
+      'const Button = function(props) {',
+      '  return (',
+      '    <Button',
+      '      size={size}',
+      '      onClick={onClick}',
+      '    >',
+      '      Button Text',
+      '    </Button>',
+      '  );',
+      '};'
+    ].join('\n'),
+    options: ['tag-aligned'],
+    parserOptions: parserOptions,
+    errors: [{
+      message: messageWithDetails(MESSAGE_TAG_ALIGNED, 5, false),
+      line: 6,
+      column: 37
+    }]
+  }, {
+    code: [
+      'const Button = function(props) {',
+      '  return (',
+      '    <Button',
+      '      size={size}',
+      '      onClick={onClick}',
+      '                                    >',
+      '      Button Text',
+      '    </Button>',
+      '  );',
+      '};'
+    ].join('\n'),
+    output: [
+      'const Button = function(props) {',
+      '  return (',
+      '    <Button',
+      '      size={size}',
+      '      onClick={onClick}',
+      '    >',
+      '      Button Text',
+      '    </Button>',
+      '  );',
+      '};'
+    ].join('\n'),
+    options: ['line-aligned'],
+    parserOptions: parserOptions,
+    errors: [{
+      message: messageWithDetails(MESSAGE_LINE_ALIGNED, 5, false),
+      line: 6,
+      column: 37
+    }]
   }, {
     code: [
       '<Provider',
diff --git a/tests/lib/rules/jsx-curly-spacing.js b/tests/lib/rules/jsx-curly-spacing.js
index fc975a92..456f6b02 100644
--- a/tests/lib/rules/jsx-curly-spacing.js
+++ b/tests/lib/rules/jsx-curly-spacing.js
@@ -182,6 +182,10 @@ ruleTester.run('jsx-curly-spacing', rule, {
     code: '<App foo={3} bar={ {a: 2} } />',
     options: ['never', {spacing: {objectLiterals: 'always'}}],
     parserOptions: parserOptions
+  }, {
+    code: '<div foo={ bar }>{bar}</div>',
+    options: ['always'],
+    parserOptions: parserOptions
   }],
 
   invalid: [{
diff --git a/tests/lib/rules/jsx-filename-extension.js b/tests/lib/rules/jsx-filename-extension.js
index f40e3d5c..0efa99c0 100644
--- a/tests/lib/rules/jsx-filename-extension.js
+++ b/tests/lib/rules/jsx-filename-extension.js
@@ -22,7 +22,7 @@ var parserOptions = {
 // Code Snippets
 // ------------------------------------------------------------------------------
 
-var withJSX = 'module.exports = function MyComponent() { return <div />; }';
+var withJSX = 'module.exports = function MyComponent() { return <div>\n<div />\n</div>; }';
 var withoutJSX = 'module.exports = {}';
 
 // ------------------------------------------------------------------------------
@@ -33,6 +33,11 @@ var ruleTester = new RuleTester();
 ruleTester.run('jsx-filename-extension', rule, {
 
   valid: [
+    {
+      filename: '<text>',
+      code: withJSX,
+      parserOptions: parserOptions
+    },
     {
       filename: 'MyComponent.jsx',
       code: withJSX,
diff --git a/tests/lib/rules/jsx-handler-names.js b/tests/lib/rules/jsx-handler-names.js
index be8deaf9..111aaa00 100644
--- a/tests/lib/rules/jsx-handler-names.js
+++ b/tests/lib/rules/jsx-handler-names.js
@@ -83,6 +83,16 @@ ruleTester.run('jsx-handler-names', rule, {
       '<TestComponent onChange={props::handleChange} />'
     ].join('\n'),
     parser: 'babel-eslint'
+  }, {
+    code: [
+      '<TestComponent onChange={::props.onChange} />'
+    ].join('\n'),
+    parser: 'babel-eslint'
+  }, {
+    code: [
+      '<TestComponent onChange={props.foo::handleChange} />'
+    ].join('\n'),
+    parser: 'babel-eslint'
   }],
 
   invalid: [{
@@ -109,5 +119,11 @@ ruleTester.run('jsx-handler-names', rule, {
     ].join('\n'),
     parser: 'babel-eslint',
     errors: [{message: 'Handler function for onChange prop key must begin with \'handle\''}]
+  }, {
+    code: [
+      '<TestComponent onChange={props.foo::onChange} />'
+    ].join('\n'),
+    parser: 'babel-eslint',
+    errors: [{message: 'Handler function for onChange prop key must begin with \'handle\''}]
   }]
 });
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
     {
diff --git a/tests/lib/rules/no-comment-textnodes.js b/tests/lib/rules/jsx-no-comment-textnodes.js
similarity index 93%
rename from tests/lib/rules/no-comment-textnodes.js
rename to tests/lib/rules/jsx-no-comment-textnodes.js
index fcfd78d6..aa688c3b 100644
--- a/tests/lib/rules/no-comment-textnodes.js
+++ b/tests/lib/rules/jsx-no-comment-textnodes.js
@@ -8,7 +8,7 @@
 // Requirements
 // ------------------------------------------------------------------------------
 
-var rule = require('../../../lib/rules/no-comment-textnodes');
+var rule = require('../../../lib/rules/jsx-no-comment-textnodes');
 var RuleTester = require('eslint').RuleTester;
 
 // ------------------------------------------------------------------------------
@@ -16,7 +16,7 @@ var RuleTester = require('eslint').RuleTester;
 // ------------------------------------------------------------------------------
 
 var ruleTester = new RuleTester();
-ruleTester.run('jsx-needs-i18n', rule, {
+ruleTester.run('jsx-no-comment-textnodes', rule, {
 
   valid: [
     {
@@ -109,6 +109,15 @@ ruleTester.run('jsx-needs-i18n', rule, {
       args: [1],
       parser: 'babel-eslint'
     },
+    {
+      code: [
+        '<strong>',
+        '  &nbsp;https://www.example.com/attachment/download/1',
+        '</strong>'
+      ].join('\n'),
+      args: [1],
+      parser: 'babel-eslint'
+    },
 
     // inside element declarations
     {
diff --git a/tests/lib/rules/require-extension.js b/tests/lib/rules/jsx-require-extension.js
similarity index 95%
rename from tests/lib/rules/require-extension.js
rename to tests/lib/rules/jsx-require-extension.js
index 8fb037c5..952a83a0 100644
--- a/tests/lib/rules/require-extension.js
+++ b/tests/lib/rules/jsx-require-extension.js
@@ -8,7 +8,7 @@
 // Requirements
 // ------------------------------------------------------------------------------
 
-var rule = require('../../../lib/rules/require-extension');
+var rule = require('../../../lib/rules/jsx-require-extension');
 var RuleTester = require('eslint').RuleTester;
 
 // ------------------------------------------------------------------------------
@@ -34,7 +34,7 @@ var REQUIRE_OBJECT = 'require({})';
 // ------------------------------------------------------------------------------
 
 var ruleTester = new RuleTester();
-ruleTester.run('require-extension', rule, {
+ruleTester.run('jsx-require-extension', rule, {
 
   valid: [
     {
diff --git a/tests/lib/rules/jsx-sort-props.js b/tests/lib/rules/jsx-sort-props.js
index 76a62264..53503863 100644
--- a/tests/lib/rules/jsx-sort-props.js
+++ b/tests/lib/rules/jsx-sort-props.js
@@ -33,16 +33,27 @@ var expectedCallbackError = {
   message: 'Callbacks must be listed after all other props',
   type: 'JSXAttribute'
 };
-var expectedShorthandError = {
+var expectedShorthandFirstError = {
   message: 'Shorthand props must be listed before all other props',
   type: 'JSXAttribute'
 };
+var expectedShorthandLastError = {
+  message: 'Shorthand props must be listed after all other props',
+  type: 'JSXAttribute'
+};
 var callbacksLastArgs = [{
   callbacksLast: true
 }];
 var shorthandFirstArgs = [{
   shorthandFirst: true
 }];
+var shorthandLastArgs = [{
+  shorthandLast: true
+}];
+var shorthandAndCallbackLastArgs = [{
+  callbacksLast: true,
+  shorthandLast: true
+}];
 var ignoreCaseArgs = [{
   ignoreCase: true
 }];
@@ -67,7 +78,13 @@ ruleTester.run('jsx-sort-props', rule, {
     // Sorting shorthand props before others
     {code: '<App a b="b" />;', options: shorthandFirstArgs, parserOptions: parserOptions},
     {code: '<App z a="a" />;', options: shorthandFirstArgs, parserOptions: parserOptions},
-    {code: '<App x y z a="a" b="b" />;', options: shorthandFirstArgs, parserOptions: parserOptions}
+    {code: '<App x y z a="a" b="b" />;', options: shorthandFirstArgs, parserOptions: parserOptions},
+    {code: '<App a="a" b="b" x y z />;', options: shorthandLastArgs, parserOptions: parserOptions},
+    {
+      code: '<App a="a" b="b" x y z onBar onFoo />;',
+      options: shorthandAndCallbackLastArgs,
+      parserOptions: parserOptions
+    }
   ],
   invalid: [
     {code: '<App b a />;', errors: [expectedError], parserOptions: parserOptions},
@@ -91,10 +108,20 @@ ruleTester.run('jsx-sort-props', rule, {
       parserOptions: parserOptions
     }, {
       code: '<App a="a" b />;',
-      errors: [expectedShorthandError],
+      errors: [expectedShorthandFirstError],
       options: shorthandFirstArgs,
       parserOptions: parserOptions
     },
-    {code: '<App z x a="a" />;', errors: [expectedError], options: shorthandFirstArgs, parserOptions: parserOptions}
+    {code: '<App z x a="a" />;', errors: [expectedError], options: shorthandFirstArgs, parserOptions: parserOptions}, {
+      code: '<App b a="a" />;',
+      errors: [expectedShorthandLastError],
+      options: shorthandLastArgs,
+      parserOptions: parserOptions
+    }, {
+      code: '<App a="a" onBar onFoo z x />;',
+      errors: [shorthandAndCallbackLastArgs],
+      options: shorthandLastArgs,
+      parserOptions: parserOptions
+    }
   ]
 });
diff --git a/tests/lib/rules/wrap-multilines.js b/tests/lib/rules/jsx-wrap-multilines.js
similarity index 97%
rename from tests/lib/rules/wrap-multilines.js
rename to tests/lib/rules/jsx-wrap-multilines.js
index e169ff73..8fe41fd7 100644
--- a/tests/lib/rules/wrap-multilines.js
+++ b/tests/lib/rules/jsx-wrap-multilines.js
@@ -8,7 +8,7 @@
 // Requirements
 // ------------------------------------------------------------------------------
 
-var rule = require('../../../lib/rules/wrap-multilines');
+var rule = require('../../../lib/rules/jsx-wrap-multilines');
 var RuleTester = require('eslint').RuleTester;
 
 var parserOptions = {
@@ -78,7 +78,7 @@ var ASSIGNMENT_NO_PAREN = '\
 // ------------------------------------------------------------------------------
 
 var ruleTester = new RuleTester();
-ruleTester.run('wrap-multilines', rule, {
+ruleTester.run('jsx-wrap-multilines', rule, {
 
   valid: [
     {
diff --git a/tests/lib/rules/no-find-dom-node.js b/tests/lib/rules/no-find-dom-node.js
new file mode 100644
index 00000000..3cecacf3
--- /dev/null
+++ b/tests/lib/rules/no-find-dom-node.js
@@ -0,0 +1,115 @@
+/**
+ * @fileoverview Prevent usage of findDOMNode
+ * @author Yannick Croissant
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require('../../../lib/rules/no-find-dom-node');
+var RuleTester = require('eslint').RuleTester;
+
+var parserOptions = {
+  ecmaVersion: 6,
+  ecmaFeatures: {
+    jsx: true
+  }
+};
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+var ruleTester = new RuleTester();
+ruleTester.run('no-find-dom-node', rule, {
+
+  valid: [{
+    code: [
+      'var Hello = function() {};'
+    ].join('\n'),
+    parserOptions: parserOptions
+  }, {
+    code: [
+      'var Hello = React.createClass({',
+      '  render: function() {',
+      '    return <div>Hello</div>;',
+      '  }',
+      '});'
+    ].join('\n'),
+    parserOptions: parserOptions
+  }, {
+    code: [
+      'var Hello = React.createClass({',
+      '  componentDidMount: function() {',
+      '    someNonMemberFunction(arg);',
+      '    this.someFunc = React.findDOMNode;',
+      '  },',
+      '  render: function() {',
+      '    return <div>Hello</div>;',
+      '  }',
+      '});'
+    ].join('\n'),
+    parserOptions: parserOptions
+  }, {
+    code: [
+      'var Hello = React.createClass({',
+      '  componentDidMount: function() {',
+      '    React.someFunc(this);',
+      '  },',
+      '  render: function() {',
+      '    return <div>Hello</div>;',
+      '  }',
+      '});'
+    ].join('\n'),
+    parserOptions: parserOptions
+  }],
+
+  invalid: [{
+    code: [
+      'var Hello = React.createClass({',
+      '  componentDidMount: function() {',
+      '    React.findDOMNode(this).scrollIntoView();',
+      '  },',
+      '  render: function() {',
+      '    return <div>Hello</div>;',
+      '  }',
+      '});'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    errors: [{
+      message: 'Do not use findDOMNode'
+    }]
+  }, {
+    code: [
+      'var Hello = React.createClass({',
+      '  componentDidMount: function() {',
+      '    ReactDOM.findDOMNode(this).scrollIntoView();',
+      '  },',
+      '  render: function() {',
+      '    return <div>Hello</div>;',
+      '  }',
+      '});'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    errors: [{
+      message: 'Do not use findDOMNode'
+    }]
+  }, {
+    code: [
+      'class Hello extends Component {',
+      '  componentDidMount() {',
+      '    findDOMNode(this).scrollIntoView();',
+      '  }',
+      '  render() {',
+      '    return <div>Hello</div>;',
+      '  }',
+      '};'
+    ].join('\n'),
+    parserOptions: parserOptions,
+    errors: [{
+      message: 'Do not use findDOMNode'
+    }]
+  }]
+});
diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js
index 3aa45fd0..7405a61e 100644
--- a/tests/lib/rules/prop-types.js
+++ b/tests/lib/rules/prop-types.js
@@ -1271,6 +1271,15 @@ ruleTester.run('prop-types', rule, {
         '}'
       ].join('\n'),
       parserOptions: parserOptions
+    }, {
+      code: [
+        'function Hello({names}) {',
+        '  return names.map((name) => {',
+        '    return <div>{name}</div>;',
+        '  });',
+        '}'
+      ].join('\n'),
+      parser: 'babel-eslint'
     }
   ],
 
@@ -2221,6 +2230,17 @@ ruleTester.run('prop-types', rule, {
       errors: [
         {message: '\'name.constructor.firstname\' is missing in props validation'}
       ]
+    }, {
+      code: [
+        'function SomeComponent({bar}) {',
+        '  function f({foo}) {}',
+        '  return <div className={f()}>{bar}</div>;',
+        '}'
+      ].join('\n'),
+      parserOptions: parserOptions,
+      errors: [
+        {message: '\'bar\' is missing in props validation'}
+      ]
     }
   ]
 });
diff --git a/tests/lib/rules/require-optimization.js b/tests/lib/rules/require-optimization.js
index a973d613..09f555e1 100644
--- a/tests/lib/rules/require-optimization.js
+++ b/tests/lib/rules/require-optimization.js
@@ -77,6 +77,17 @@ ruleTester.run('react-require-optimization', rule, {
     ].join('\n'),
     parser: 'babel-eslint',
     parserOptions: parserOptions
+  }, {
+    code: [
+      '@bar',
+      '@pureRender',
+      '@foo',
+      'class DecoratedComponent extends Component {' +
+      '}'
+    ].join('\n'),
+    parser: 'babel-eslint',
+    options: [{allowDecorators: ['renderPure', 'pureRender']}],
+    parserOptions: parserOptions
   }],
 
   invalid: [{
@@ -129,5 +140,19 @@ ruleTester.run('react-require-optimization', rule, {
     }],
     parser: 'babel-eslint',
     parserOptions: parserOptions
+  }, {
+    code: [
+      '@bar',
+      '@pure',
+      '@foo',
+      'class DecoratedComponent extends Component {' +
+      '}'
+    ].join('\n'),
+    errors: [{
+      message: MESSAGE
+    }],
+    parser: 'babel-eslint',
+    options: [{allowDecorators: ['renderPure', 'pureRender']}],
+    parserOptions: parserOptions
   }]
 });