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

Issue:
prefer-stateless-function with decorators 
i am using decorators, decorators work just with class so i must not use stateless function
```javascript
@dragDropContext(HTML5Backend)
class ToDoList extends Component {
  render() {
    const { data } = this.props;
    return (
      <div>
        {data.map(((todo, i) => <SingleToDo
          data={todo}
        />))}
      </div>
    );
  }
}
```
and in this case i have error prefer-stateless-function

Pull request:
prefer-stateless-function w/ decorators
Currently prefer-stateless-function warns when using a decorated class
over a stateless function. The decorator syntax only works with classes,
so it makes sense not to warn in this case.

Fixes #1034

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e28279f7..06d1e4ed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,33 +3,6 @@ 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.10.3] - 2017-03-20
-### Fixed
-* Revert [#1057][] due to issues with [`jsx-indent`][] ([#1117][])
-
-[6.10.3]: https://github.com/yannickcr/eslint-plugin-react/compare/v6.10.2...v6.10.3
-
-## [6.10.2] - 2017-03-19
-### Fixed
-* Fix [`jsx-indent`][] indentation calculation with nested JSX ([#1117][])
-
-[6.10.2]: https://github.com/yannickcr/eslint-plugin-react/compare/v6.10.1...v6.10.2
-[#1117]: https://github.com/yannickcr/eslint-plugin-react/issues/1117
-
-## [6.10.1] - 2017-03-19
-### Fixed
-* Fix [`jsx-indent`][] auto fix with tabs ([#1057][] @kentcdodds @webOS101)
-* Fix [`jsx-indent`][] crash ([#1061][] @iancmyers)
-* Fix [`void-dom-elements-no-children`][] crash and fix it to only checks for a createElement call from
-React ([#1073][] @jomasti)
-* Fix component detection that caused a false positive in [`no-multi-comp`][] ([#1088][] @benstepp)
-
-[6.10.1]: https://github.com/yannickcr/eslint-plugin-react/compare/v6.10.0...v6.10.1
-[#1057]: https://github.com/yannickcr/eslint-plugin-react/issues/1057
-[#1061]: https://github.com/yannickcr/eslint-plugin-react/issues/1061
-[#1073]: https://github.com/yannickcr/eslint-plugin-react/issues/1073
-[#1088]: https://github.com/yannickcr/eslint-plugin-react/issues/1088
-
 ## [6.10.0] - 2017-02-16
 ### Added
 * Add [`forbid-foreign-prop-types`][] rule ([#696][] @iancmyers)
@@ -97,7 +70,7 @@ React ([#1073][] @jomasti)
 * Fix [`jsx-indent`][] with multiline jsx in ternaries ([#966][])
 * Fix component detection to ignore async functions ([#989][] @taion)
 * Fix [`jsx-curly-spacing`][] autofix to not delete comments ([#648][])
-* Fix auto-enabling of `eslint-plugin-react` in exported configurations ([#984][] @jamischarles)
+* FIx auto-enabling of `eslint-plugin-react` in exported configurations ([#984][] @jamischarles)
 
 ### Changed
 * Update dependencies
diff --git a/docs/rules/jsx-indent.md b/docs/rules/jsx-indent.md
index 909c5818..336e72e3 100644
--- a/docs/rules/jsx-indent.md
+++ b/docs/rules/jsx-indent.md
@@ -77,6 +77,68 @@ The following patterns are not warnings:
 </App>
 ```
 
+#### indentLogicalExpressions
+
+```js
+...
+"react/jsx-indent": [<enabled>, 'tab'|<number>, {indentLogicalExpressions: true}]
+...
+```
+
+By default this is set to false. When enabled, an additional indentation is required when the JSX is the right of a LogicalExpression
+
+The following patterns are considered warnings:
+
+```jsx
+// 2 spaces indentation with indentLogicalExpressions as false
+// [2, 2, {indentLogicalExpressions: false}]
+<App>
+  {
+    condition &&
+      <Container>
+        <Child></Child>
+      </Container>
+  }
+</App>
+
+// 2 spaces indentation with indentLogicalExpressions as true
+// [2, 2, {indentLogicalExpressions: true}]
+<App>
+  {
+    condition &&
+    <Container>
+      <Child></Child>
+    </Container>
+  }
+</App>
+```
+
+The following patterns are not warnings:
+
+```jsx
+// 2 spaces indentation with indentLogicalExpressions as true
+// [2, 2, {indentLogicalExpressions: true}]
+<App>
+  {
+    condition &&
+      <Container>
+        <Child></Child>
+      </Container>
+  }
+</App>
+
+// 2 spaces indentation with indentLogicalExpressions as false
+// [2, 2, {indentLogicalExpressions: false}]
+<App>
+  {
+    condition &&
+    <Container>
+      <Child></Child>
+    </Container>
+  }
+</App>
+```
+
 ## When not to use
 
 If you are not using JSX then you can disable this rule.
diff --git a/docs/rules/prefer-stateless-function.md b/docs/rules/prefer-stateless-function.md
index 0eaf88d9..d136f614 100644
--- a/docs/rules/prefer-stateless-function.md
+++ b/docs/rules/prefer-stateless-function.md
@@ -10,6 +10,7 @@ This rule will check your class based React components for
 * instance property other than `this.props` and `this.context`
 * extension of `React.PureComponent` (if the `ignorePureComponents` flag is true)
 * presence of `ref` attribute in JSX
+* the use of decorators
 * `render` method that return anything but JSX: `undefined`, `null`, etc. (only in React <15.0.0, see [shared settings](https://github.com/yannickcr/eslint-plugin-react/blob/master/README.md#configuration) for React version configuration)
 
 If none of these elements are found, the rule will warn you to write this component as a pure function.
diff --git a/lib/rules/jsx-indent.js b/lib/rules/jsx-indent.js
index 8d5af542..f6ed3ac5 100644
--- a/lib/rules/jsx-indent.js
+++ b/lib/rules/jsx-indent.js
@@ -46,6 +46,14 @@ module.exports = {
       }, {
         type: 'integer'
       }]
+    }, {
+      type: 'object',
+      properties: {
+        indentLogicalExpressions: {
+          type: 'boolean'
+        }
+      },
+      additionalProperties: false
     }]
   },
 
@@ -56,6 +64,7 @@ module.exports = {
     var extraColumnStart = 0;
     var indentType = 'space';
     var indentSize = 4;
+    var indentLogicalExpressions = false;
 
     var sourceCode = context.getSourceCode();
 
@@ -67,24 +76,25 @@ module.exports = {
         indentSize = context.options[0];
         indentType = 'space';
       }
+      if (context.options[1]) {
+        indentLogicalExpressions = context.options[1].indentLogicalExpressions || false;
+      }
     }
 
     var indentChar = indentType === 'space' ? ' ' : '\t';
 
     /**
      * Responsible for fixing the indentation issue fix
-     * @param {ASTNode} node Node violating the indent rule
+     * @param {Boolean} rangeToReplace is used to specify the range
+     *   to replace with the correct indentation.
      * @param {Number} needed Expected indentation character count
      * @returns {Function} function to be executed by the fixer
      * @private
      */
-    function getFixerFunction(node, needed) {
+    function getFixerFunction(rangeToReplace, needed) {
       return function(fixer) {
         var indent = Array(needed + 1).join(indentChar);
-        return fixer.replaceTextRange(
-          [node.start - node.loc.start.column, node.start],
-          indent
-        );
+        return fixer.replaceTextRange(rangeToReplace, indent);
       };
     }
 
@@ -93,46 +103,38 @@ module.exports = {
      * @param {ASTNode} node Node violating the indent rule
      * @param {Number} needed Expected indentation character count
      * @param {Number} gotten Indentation character count in the actual node/code
-     * @param {Object} loc Error line and column location
+     * @param {Array} rangeToReplace is used in the fixer.
+     *   Defaults to the indent of the start of the node
+     * @param {Object} loc Error line and column location (defaults to node.loc
      */
-    function report(node, needed, gotten, loc) {
+    function report(node, needed, gotten, rangeToReplace, loc) {
       var msgContext = {
         needed: needed,
         type: indentType,
         characters: needed === 1 ? 'character' : 'characters',
         gotten: gotten
       };
+      rangeToReplace = rangeToReplace || [node.start - node.loc.start.column, node.start];
 
-      if (loc) {
-        context.report({
-          node: node,
-          loc: loc,
-          message: MESSAGE,
-          data: msgContext,
-          fix: getFixerFunction(node, needed)
-        });
-      } else {
-        context.report({
-          node: node,
-          message: MESSAGE,
-          data: msgContext,
-          fix: getFixerFunction(node, needed)
-        });
-      }
+      context.report({
+        node: node,
+        loc: loc || node.loc,
+        message: MESSAGE,
+        data: msgContext,
+        fix: getFixerFunction(rangeToReplace, needed)
+      });
     }
 
     /**
-     * Get node indent
-     * @param {ASTNode} node Node to examine
-     * @param {Boolean} byLastLine get indent of node's last line
-     * @param {Boolean} excludeCommas skip comma on start of line
-     * @return {Number} Indent
+     * Get the indentation (of the proper indentType) that exists in the source
+     * @param {String} src the source string
+     * @param {Boolean} byLastLine whether the line checked should be the last
+     *   Defaults to the first line
+     * @param {Boolean} excludeCommas whether to skip commas in the check
+     *   Defaults to false
+     * @return {Number} the indentation of the indentType that exists on the line
      */
-    function getNodeIndent(node, byLastLine, excludeCommas) {
-      byLastLine = byLastLine || false;
-      excludeCommas = excludeCommas || false;
-
-      var src = sourceCode.getText(node, node.loc.start.column + extraColumnStart);
+    function getIndentFromString(src, byLastLine, excludeCommas) {
       var lines = src.split('\n');
       if (byLastLine) {
         src = lines[lines.length - 1];
@@ -154,7 +156,24 @@ module.exports = {
     }
 
     /**
-     * Checks node is the first in its own start line. By default it looks by start line.
+     * Get node indent
+     * @param {ASTNode} node Node to examine
+     * @param {Boolean} byLastLine get indent of node's last line
+     * @param {Boolean} excludeCommas skip comma on start of line
+     * @return {Number} Indent
+     */
+    function getNodeIndent(node, byLastLine, excludeCommas) {
+      byLastLine = byLastLine || false;
+      excludeCommas = excludeCommas || false;
+
+      var src = sourceCode.getText(node, node.loc.start.column + extraColumnStart);
+
+      return getIndentFromString(src, byLastLine, excludeCommas);
+    }
+
+    /**
+     * Checks if the node is the first in its own start line. By default it looks by start line.
+     * One exception is closing tags with preceeding whitespace
      * @param {ASTNode} node The node to check
      * @return {Boolean} true if its the first in the its start line
      */
@@ -165,8 +184,9 @@ module.exports = {
       } while (token.type === 'JSXText' && /^\s*$/.test(token.value));
       var startLine = node.loc.start.line;
       var endLine = token ? token.loc.end.line : -1;
+      var whitespaceOnly = token ? /\n\s*$/.test(token.value) : false;
 
-      return startLine !== endLine;
+      return startLine !== endLine || whitespaceOnly;
     }
 
   /**
@@ -218,41 +238,83 @@ module.exports = {
       }
     }
 
+    /**
+     * Checks the end of the tag (>) to determine whether it's on its own line
+     * If so, it verifies the indentation is correct and reports if it is not
+     * @param {ASTNode} node The node to check
+     * @param {Number} startIndent The indentation of the start of the tag
+     */
+    function checkTagEndIndent(node, startIndent) {
+      var source = sourceCode.getText(node);
+      var isTagEndOnOwnLine = /\n\s*\/?>$/.exec(source);
+      if (isTagEndOnOwnLine) {
+        var endIndent = getIndentFromString(source, true, false);
+        if (endIndent !== startIndent) {
+          var rangeToReplace = [node.end - node.loc.end.column, node.end - 1];
+          report(node, startIndent, endIndent, rangeToReplace);
+        }
+      }
+    }
+
+    /**
+     * Gets what the JSXOpeningElement's indentation should be
+     * @param {ASTNode} node The JSXOpeningElement
+     * @return {Number} the number of indentation characters it should have
+     */
+    function getOpeningElementIndent(node) {
+      var prevToken = sourceCode.getTokenBefore(node);
+      if (!prevToken) {
+        return 0;
+      }
+      if (prevToken.type === 'JSXText' || prevToken.type === 'Punctuator' && prevToken.value === ',') {
+        // Use the parent in a list or an array
+        prevToken = sourceCode.getNodeByRangeIndex(prevToken.start);
+        prevToken = prevToken.type === 'Literal' ? prevToken.parent : prevToken;
+      } else if (prevToken.type === 'Punctuator' && prevToken.value === ':') {
+        // Use the first non-punctuator token in a conditional expression
+        do {
+          prevToken = sourceCode.getTokenBefore(prevToken);
+        } while (prevToken.type === 'Punctuator');
+        prevToken = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
+
+        while (prevToken.parent && prevToken.parent.type !== 'ConditionalExpression') {
+          prevToken = prevToken.parent;
+        }
+      }
+      prevToken = prevToken.type === 'JSXExpressionContainer' ? prevToken.expression : prevToken;
+
+      var parentElementIndent = getNodeIndent(prevToken);
+      if (prevToken.type === 'JSXElement') {
+        parentElementIndent = getOpeningElementIndent(prevToken.openingElement);
+      }
+
+      if (isRightInLogicalExp(node) && indentLogicalExpressions) {
+        parentElementIndent += indentSize;
+      }
+
+      var indent = (
+        prevToken.loc.start.line === node.loc.start.line ||
+        isRightInLogicalExp(node) ||
+        isAlternateInConditionalExp(node)
+      ) ? 0 : indentSize;
+      return parentElementIndent + indent;
+    }
+
     return {
       JSXOpeningElement: function(node) {
         var prevToken = sourceCode.getTokenBefore(node);
         if (!prevToken) {
           return;
         }
-        // Use the parent in a list or an array
-        if (prevToken.type === 'JSXText' || prevToken.type === 'Punctuator' && prevToken.value === ',') {
-          prevToken = sourceCode.getNodeByRangeIndex(prevToken.start);
-          prevToken = prevToken.type === 'Literal' ? prevToken.parent : prevToken;
-        // Use the first non-punctuator token in a conditional expression
-        } else if (prevToken.type === 'Punctuator' && prevToken.value === ':') {
-          do {
-            prevToken = sourceCode.getTokenBefore(prevToken);
-          } while (prevToken.type === 'Punctuator');
-          prevToken = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
-          while (prevToken.parent && prevToken.parent.type !== 'ConditionalExpression') {
-            prevToken = prevToken.parent;
-          }
-        }
-        prevToken = prevToken.type === 'JSXExpressionContainer' ? prevToken.expression : prevToken;
-
-        var parentElementIndent = getNodeIndent(prevToken);
-        var indent = (
-          prevToken.loc.start.line === node.loc.start.line ||
-          isRightInLogicalExp(node) ||
-          isAlternateInConditionalExp(node)
-        ) ? 0 : indentSize;
-        checkNodesIndent(node, parentElementIndent + indent);
+        var startIndent = getOpeningElementIndent(node);
+        checkNodesIndent(node, startIndent);
+        checkTagEndIndent(node, startIndent);
       },
       JSXClosingElement: function(node) {
         if (!node.parent) {
           return;
         }
-        var peerElementIndent = getNodeIndent(node.parent.openingElement);
+        var peerElementIndent = getOpeningElementIndent(node.parent.openingElement);
         checkNodesIndent(node, peerElementIndent);
       },
       JSXExpressionContainer: function(node) {
@@ -261,6 +323,34 @@ module.exports = {
         }
         var parentNodeIndent = getNodeIndent(node.parent);
         checkNodesIndent(node, parentNodeIndent + indentSize);
+      },
+      Literal: function(node) {
+        if (!node.parent || node.parent.type !== 'JSXElement' || node.loc.start.line === node.parent.loc.start.line) {
+          return;
+        }
+        var parentElementIndent = getOpeningElementIndent(node.parent.openingElement);
+        var expectedIndent = parentElementIndent + indentSize;
+        var source = sourceCode.getText(node);
+        var lines = source.split('\n');
+        var currentIndex = 0;
+        lines.forEach(function(line, lineNumber) {
+          if (line.trim()) {
+            var lineIndent = getIndentFromString(line);
+            if (lineIndent !== expectedIndent) {
+              var lineStart = source.indexOf(line, currentIndex);
+              var lineIndentStart = line.search(/\S/);
+              var lineIndentEnd = lineStart + lineIndentStart;
+              var rangeToReplace = [node.start + lineStart, node.start + lineIndentEnd];
+              var locLine = lineNumber + node.loc.start.line;
+              var loc = {
+                start: {line: locLine, column: lineIndentStart},
+                end: {line: locLine, column: lineIndentEnd}
+              };
+              report(node, expectedIndent, lineIndent, rangeToReplace, loc);
+            }
+          }
+          currentIndex += line.length;
+        });
       }
     };
 
diff --git a/lib/rules/prefer-stateless-function.js b/lib/rules/prefer-stateless-function.js
index 9cf896a6..4403273d 100644
--- a/lib/rules/prefer-stateless-function.js
+++ b/lib/rules/prefer-stateless-function.js
@@ -286,11 +286,25 @@ module.exports = {
       });
     }
 
+    /**
+     * Mark a ClassDeclaration as having used decorators
+     * @param {ASTNode} node The AST node being checked.
+     */
+    function markDecoratorsAsUsed(node) {
+      components.set(node, {
+        useDecorators: true
+      });
+    }
+
     return {
       ClassDeclaration: function (node) {
         if (ignorePureComponents && utils.isPureComponent(node)) {
           markSCUAsDeclared(node);
         }
+
+        if (node.decorators && node.decorators.length) {
+          markDecoratorsAsUsed(node);
+        }
       },
 
       // Mark `this` destructuring as a usage of `this`
@@ -378,6 +392,7 @@ module.exports = {
             list[component].useRef ||
             list[component].invalidReturn ||
             list[component].hasChildContextTypes ||
+            list[component].useDecorators ||
             (!utils.isES5Component(list[component].node) && !utils.isES6Component(list[component].node))
           ) {
             continue;
diff --git a/lib/util/Components.js b/lib/util/Components.js
index 688a87df..1eacf099 100644
--- a/lib/util/Components.js
+++ b/lib/util/Components.js
@@ -237,7 +237,7 @@ function componentRule(rule, context) {
      * @returns {Boolean} True if React.createElement called
      */
     isReactCreateElement: function(node) {
-      var calledOnReact = (
+      return (
         node &&
         node.callee &&
         node.callee.object &&
@@ -245,17 +245,6 @@ function componentRule(rule, context) {
         node.callee.property &&
         node.callee.property.name === 'createElement'
       );
-
-      var calledDirectly = (
-        node &&
-        node.callee &&
-        node.callee.name === 'createElement'
-      );
-
-      if (this.hasDestructuredReactCreateElement()) {
-        return calledDirectly || calledOnReact;
-      }
-      return calledOnReact;
     },
 
     /**
@@ -301,7 +290,10 @@ function componentRule(rule, context) {
         node[property] &&
         node[property].type === 'JSXElement'
       ;
-      var returnsReactCreateElement = this.isReactCreateElement(node[property]);
+      var returnsReactCreateElement =
+        this.hasDestructuredReactCreateElement() ||
+        this.isReactCreateElement(node[property])
+      ;
 
       return Boolean(
         returnsConditionalJSX ||
diff --git a/package.json b/package.json
index 5c211c24..3950bcc0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-react",
-  "version": "6.10.3",
+  "version": "6.10.0",
   "author": "Yannick Croissant <yannick.croissant+npm@gmail.com>",
   "description": "React specific linting rules for ESLint",
   "main": "index.js",


Test patch:
diff --git a/tests/lib/rules/jsx-indent.js b/tests/lib/rules/jsx-indent.js
index 4d1789ec..960a1009 100644
--- a/tests/lib/rules/jsx-indent.js
+++ b/tests/lib/rules/jsx-indent.js
@@ -51,14 +51,6 @@ ruleTester.run('jsx-indent', rule, {
     ].join('\n'),
     options: [0],
     parserOptions: parserOptions
-  }, {
-    code: [
-      '  <App>',
-      '<Foo />',
-      '  </App>'
-    ].join('\n'),
-    options: [-2],
-    parserOptions: parserOptions
   }, {
     code: [
       '<App>',
@@ -211,17 +203,6 @@ ruleTester.run('jsx-indent', rule, {
       '</div>'
     ].join('\n'),
     parserOptions: parserOptions
-  }, {
-    // Literals indentation is not touched
-    code: [
-      '<div>',
-      'bar <div>',
-      '   bar',
-      '   bar {foo}',
-      'bar </div>',
-      '</div>'
-    ].join('\n'),
-    parserOptions: parserOptions
   }, {
     // Multiline ternary
     // (colon at the end of the first expression)
@@ -416,6 +397,19 @@ ruleTester.run('jsx-indent', rule, {
       ')'
     ].join('\n'),
     parserOptions: parserOptions
+  }, {
+    code: [
+      '<App>',
+      '    {',
+      '        condition &&',
+      '            <Container>',
+      '                <Child></Child>',
+      '            </Container>',
+      '    }',
+      '</App>'
+    ].join('\n'),
+    options: [4, {indentLogicalExpressions: true}],
+    parserOptions: parserOptions
   }, {
     code: [
       '<span>',
@@ -457,6 +451,14 @@ ruleTester.run('jsx-indent', rule, {
     ].join('\n'),
     options: [2],
     parserOptions: parserOptions
+  }, {
+    code: [
+      'function foo() {',
+      '  <span>Text</span>',
+      '}'
+    ].join('\n'),
+    options: [2],
+    parserOptions: parserOptions
   }],
 
   invalid: [{
@@ -500,6 +502,39 @@ ruleTester.run('jsx-indent', rule, {
     options: ['tab'],
     parserOptions: parserOptions,
     errors: [{message: 'Expected indentation of 1 tab character but found 0.'}]
+  }, {
+    code: [
+      'function MyComponent(props) {',
+      '\treturn (',
+      '    <div',
+      '\t\t\tclassName="foo-bar"',
+      '\t\t\tid="thing"',
+      '    >',
+      '      Hello world!',
+      '    </div>',
+      '\t)',
+      '}'
+    ].join('\n'),
+    output: [
+      'function MyComponent(props) {',
+      '\treturn (',
+      '\t\t<div',
+      '\t\t\tclassName="foo-bar"',
+      '\t\t\tid="thing"',
+      '\t\t>',
+      '\t\t\tHello world!',
+      '\t\t</div>',
+      '\t)',
+      '}'
+    ].join('\n'),
+    options: ['tab'],
+    parserOptions: parserOptions,
+    errors: [
+      {message: 'Expected indentation of 2 tab characters but found 0.'},
+      {message: 'Expected indentation of 2 tab characters but found 0.'},
+      {message: 'Expected indentation of 3 tab characters but found 0.'},
+      {message: 'Expected indentation of 2 tab characters but found 0.'}
+    ]
   }, {
     code: [
       'function App() {',
@@ -546,11 +581,7 @@ ruleTester.run('jsx-indent', rule, {
       '  );',
       '}'
     ].join('\n'),
-    // The detection logic only thinks <App> is indented wrong, not the other
-    // two lines following. I *think* because it incorrectly uses <App>'s indention
-    // as the baseline for the next two, instead of the realizing the entire three
-    // lines are wrong together. See #608
-    /* output: [
+    output: [
       'function App() {',
       '  return (',
       '    <App>',
@@ -558,10 +589,14 @@ ruleTester.run('jsx-indent', rule, {
       '    </App>',
       '  );',
       '}'
-    ].join('\n'), */
+    ].join('\n'),
     options: [2],
     parserOptions: parserOptions,
-    errors: [{message: 'Expected indentation of 4 space characters but found 0.'}]
+    errors: [
+      {message: 'Expected indentation of 4 space characters but found 0.'},
+      {message: 'Expected indentation of 6 space characters but found 2.'},
+      {message: 'Expected indentation of 4 space characters but found 0.'}
+    ]
   }, {
     code: [
       '<App>',
diff --git a/tests/lib/rules/no-multi-comp.js b/tests/lib/rules/no-multi-comp.js
index 747538e2..bbf2f32d 100644
--- a/tests/lib/rules/no-multi-comp.js
+++ b/tests/lib/rules/no-multi-comp.js
@@ -10,7 +10,6 @@
 
 var rule = require('../../../lib/rules/no-multi-comp');
 var RuleTester = require('eslint').RuleTester;
-var assign = require('object.assign');
 
 var parserOptions = {
   ecmaVersion: 6,
@@ -90,21 +89,6 @@ ruleTester.run('no-multi-comp', rule, {
     options: [{
       ignoreStateless: true
     }]
-  }, {
-    // multiple non-components
-    code: [
-      'import React, { createElement } from "react"',
-      'const helperFoo = () => {',
-      '  return true;',
-      '};',
-      'function helperBar() {',
-      '  return false;',
-      '};',
-      'function RealComponent() {',
-      '  return createElement("img");',
-      '};'
-    ].join('\n'),
-    parserOptions: assign({sourceType: 'module'}, parserOptions)
   }],
 
   invalid: [{
diff --git a/tests/lib/rules/prefer-stateless-function.js b/tests/lib/rules/prefer-stateless-function.js
index 34e46016..a7d86817 100644
--- a/tests/lib/rules/prefer-stateless-function.js
+++ b/tests/lib/rules/prefer-stateless-function.js
@@ -268,6 +268,40 @@ ruleTester.run('prefer-stateless-function', rule, {
         '};'
       ].join('\n'),
       parser: 'babel-eslint'
+    }, {
+      // Uses a decorator
+      code: [
+        '@foo',
+        'class Foo extends React.Component {',
+        '  render() {',
+        '    return <div>{this.props.foo}</div>;',
+        '  }',
+        '}'
+      ].join('\n'),
+      parser: 'babel-eslint'
+    }, {
+      // Uses a called decorator
+      code: [
+        '@foo("bar")',
+        'class Foo extends React.Component {',
+        '  render() {',
+        '    return <div>{this.props.foo}</div>;',
+        '  }',
+        '}'
+      ].join('\n'),
+      parser: 'babel-eslint'
+    }, {
+      // Uses multiple decorators
+      code: [
+        '@foo',
+        '@bar()',
+        'class Foo extends React.Component {',
+        '  render() {',
+        '    return <div>{this.props.foo}</div>;',
+        '  }',
+        '}'
+      ].join('\n'),
+      parser: 'babel-eslint'
     }
   ],