Repository: yannickcr/eslint-plugin-react
Pull request #608 (2016-05-20 23:18:25)

Autofix: `jsx-indent`: Add support for tabs
Everything seems correct except for the very last test. 

The real output is:

``` js
function App() {
  return (
    <App>
  <Foo />
</App>
  );
}
```

But I would _expect_ the output to be:

``` js
function App() {
  return (
    <App>
        <Foo />
    </App>
  );
}
```

Anyone have any ideas what I'm doing wrong or if somehow this is indeed expected behavior?


coveralls: 
[![Coverage Status](https://coveralls.io/builds/6272893/badge)](https://coveralls.io/builds/6272893)

Coverage increased (+0.002%) to 96.771% when pulling **f4d0ad4b7ef44ead144c1892c982c48ff6e03f31 on jayphelps:jsx-indent-fix** into **82b3aa9101aa2124b934add61734cec026b4c278 on yannickcr:master**.

CiGit: In this case, only the `<App>` tag is an error so the `fix` function only applies to it.

[Test case](https://github.com/yannickcr/eslint-plugin-react/blob/be33738b098ea145a6598d9ab4b009952d06c913/tests/lib/rules/jsx-indent.js#L164)

To have the output you (and I) expect, the way errors are computed should be changed.
ie JSXElement's indentation compared to something like `ROOT - 1line`, not to its direct parent / opening tag.

IMO that's an other issue

zouxuoz: @jayphelps @CiGit How can I help to merge this PR?
jayphelps: Abandoning due to lack of response from maintainers.
jayphelps: @ljharb oooo when I tried to rebase, I discovered it appears this has already been added in #830. Our two implementation seem mutually exclusive. Do you know if there is something this fixes that #830 did not?
mmiller42: @jayphelps I know of one -- I don't think #830 doesn't support tabs. That could be filed as a separate bug. (Currently it counts the number of characters in the indent and inserts spaces, I believe.)
jayphelps: @mmiller42 oh! my PR takes a different approach that _does_ support that. Instead of trying to insert or remove indention, it just always just replaces the indention entirely using `fixer.replaceTextRange`.

I've rebased and updated my PR, uncommented the tests which weren't working in #830 but now are. Seems OK, but it's been a while since I've looked at this code so definitely have a critical eye.
jayphelps: Note that I have no fixed the pre-existing issue of the previously mentioned odd output in the test:

```js
function App() {
  return (
    <App>
  <Foo />
</App>
  );
}
```

Presumably, #830 just didn't catch that.
jayphelps: > I'd rather leave the broken but correct test, and comment it out, than leave this incorrect test. Could we do that?

Done.

Patch:
diff --git a/README.md b/README.md
index dd63ac0d..11b8861f 100644
--- a/README.md
+++ b/README.md
@@ -151,9 +151,6 @@ To enable this configuration use the `extends` property in your `.eslintrc` conf
 
 ```js
 {
-  "plugins": [
-    "react"
-  ],
   "extends": ["eslint:recommended", "plugin:react/recommended"]
 }
 ```
@@ -177,8 +174,6 @@ The rules enabled in this configuration are:
 * [react/react-in-jsx-scope](docs/rules/react-in-jsx-scope.md)
 * [react/require-render-return](docs/rules/require-render-return.md)
 
-**Note**: This configuration will also enable JSX in [parser options](http://eslint.org/docs/user-guide/configuring#specifying-parser-options).
-
 ## All
 
 This plugin also exports an `all` configuration that includes every available rule.
@@ -193,6 +188,8 @@ This pairs well with the `eslint:all` rule.
 }
 ```
 
+**Note**: These configurations will import `eslint-plugin-react` and enable JSX in [parser options](http://eslint.org/docs/user-guide/configuring#specifying-parser-options).
+
 # License
 
 ESLint-plugin-React is licensed under the [MIT License](http://www.opensource.org/licenses/mit-license.php).
diff --git a/docs/rules/jsx-first-prop-new-line.md b/docs/rules/jsx-first-prop-new-line.md
index 0a6a6213..309efb96 100644
--- a/docs/rules/jsx-first-prop-new-line.md
+++ b/docs/rules/jsx-first-prop-new-line.md
@@ -7,11 +7,12 @@ Ensure correct position of the first property.
 This rule checks whether the first property of all JSX elements is correctly placed. There are three possible configurations:
 * `always`: The first property should always be placed on a new line.
 * `never` : The first property should never be placed on a new line, e.g. should always be on the same line as the Component opening tag.
-* `multiline`: The first property should always be placed on a new line when the properties are spread over multiple lines.
+* `multiline`: The first property should always be placed on a new line when the JSX tag takes up multiple lines.
+* `multiline-multiprop`: The first property should always be placed on a new line if the JSX tag takes up multiple lines and there are multiple properties.
 
 The following patterns are considered warnings when configured `"always"`:
 
-```js
+```jsx
 <Hello personal={true} />
 
 <Hello personal={true}
@@ -21,7 +22,7 @@ The following patterns are considered warnings when configured `"always"`:
 
 The following patterns are not considered warnings when configured `"always"`:
 
-```js
+```jsx
 <Hello
     personal />
 
@@ -32,7 +33,7 @@ The following patterns are not considered warnings when configured `"always"`:
 
 The following patterns are considered warnings when configured `"never"`:
 
-```js
+```jsx
 <Hello
     personal />
 
@@ -43,7 +44,7 @@ The following patterns are considered warnings when configured `"never"`:
 
 The following patterns are not considered warnings when configured `"never"`:
 
-```js
+```jsx
 <Hello personal={true} />
 
 <Hello personal={true}
@@ -53,14 +54,19 @@ The following patterns are not considered warnings when configured `"never"`:
 
 The following patterns are considered warnings when configured `"multiline"`:
 
-```js
+```jsx
 <Hello personal
     prop />
 ```
 
+```jsx
+<Hello foo={{
+}} />
+```
+
 The following patterns are not considered warnings when configured `"multiline"`:
 
-```js
+```jsx
 <Hello personal={true} />
 
 <Hello
@@ -69,6 +75,27 @@ The following patterns are not considered warnings when configured `"multiline"`
 />
 ```
 
+The following patterns are considered warnings when configured `"multiline-multiprop"`:
+
+```jsx
+<Hello foo={{
+    }}
+    bar />
+```
+
+The following patterns are not considered warnings when configured `"multiline-multiprop"`:
+
+```jsx
+<Hello foo={{
+}} />
+
+<Hello
+    foo={{
+    }}
+    bar
+/>
+```
+
 ## When not to use
 
 If you are not using JSX then you can disable this rule.
diff --git a/docs/rules/jsx-indent.md b/docs/rules/jsx-indent.md
index cea6faae..13b9c8be 100644
--- a/docs/rules/jsx-indent.md
+++ b/docs/rules/jsx-indent.md
@@ -3,7 +3,7 @@
 This option validates a specific indentation style for JSX.
 
 **Fixable:** This rule is automatically fixable using the `--fix` flag on the command line. 
-Fixer will fix whitespace and tabs indentation. It won't replace tabs with whitespaces and vice-versa.
+Fixer will fix whitespace and tabs indentation.
 
 ## Rule Details
 
diff --git a/docs/rules/prefer-stateless-function.md b/docs/rules/prefer-stateless-function.md
index 3efee0ca..aa8285e5 100644
--- a/docs/rules/prefer-stateless-function.md
+++ b/docs/rules/prefer-stateless-function.md
@@ -8,7 +8,7 @@ This rule will check your class based React components for
 
 * methods/properties other than `displayName`, `propTypes`, `render` and useless constructor (same detection as ESLint [no-useless-constructor rule](http://eslint.org/docs/rules/no-useless-constructor))
 * instance property other than `this.props` and `this.context`
-* extension of `React.PureComponent` ()
+* extension of `React.PureComponent` (if the `ignorePureComponents` flag is true)
 * presence of `ref` attribute in JSX
 * `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)
 
diff --git a/index.js b/index.js
index 6bc26713..758e8d84 100644
--- a/index.js
+++ b/index.js
@@ -1,12 +1,6 @@
 'use strict';
 
-var deprecatedRules = {
-  'no-comment-textnodes': require('./lib/rules/no-comment-textnodes'),
-  'require-extension': require('./lib/rules/require-extension'),
-  'wrap-multilines': require('./lib/rules/wrap-multilines')
-};
-
-var rules = {
+var allRules = {
   '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'),
@@ -58,34 +52,50 @@ var rules = {
   'no-danger-with-children': require('./lib/rules/no-danger-with-children'),
   'style-prop-object': require('./lib/rules/style-prop-object'),
   'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'),
-  'no-children-prop': require('./lib/rules/no-children-prop')
+  'no-children-prop': require('./lib/rules/no-children-prop'),
+  'no-comment-textnodes': require('./lib/rules/no-comment-textnodes'),
+  'require-extension': require('./lib/rules/require-extension'),
+  'wrap-multilines': require('./lib/rules/wrap-multilines')
 };
 
-var ruleNames = Object.keys(rules);
-var allRules = {};
-for (var i = 0; i < ruleNames.length; i++) {
-  allRules['react/' + ruleNames[i]] = 2;
-}
-
-var exportedRules = {};
-for (var key in rules) {
-  if (!rules.hasOwnProperty(key)) {
-    continue;
+function filterRules(rules, predicate) {
+  var result = {};
+  for (var key in rules) {
+    if (rules.hasOwnProperty(key) && predicate(rules[key])) {
+      result[key] = rules[key];
+    }
   }
-  exportedRules[key] = rules[key];
+  return result;
 }
-for (var deprecatedKey in deprecatedRules) {
-  if (!deprecatedRules.hasOwnProperty(deprecatedKey)) {
-    continue;
+
+function configureAsError(rules) {
+  var result = {};
+  for (var key in rules) {
+    if (!rules.hasOwnProperty(key)) {
+      continue;
+    }
+    result['react/' + key] = 2;
   }
-  exportedRules[deprecatedKey] = deprecatedRules[deprecatedKey];
+  return result;
 }
 
+var activeRules = filterRules(allRules, function(rule) {
+  return !rule.meta.deprecated;
+});
+var activeRulesConfig = configureAsError(activeRules);
+
+var deprecatedRules = filterRules(allRules, function(rule) {
+  return rule.meta.deprecated;
+});
+
 module.exports = {
   deprecatedRules: deprecatedRules,
-  rules: exportedRules,
+  rules: allRules,
   configs: {
     recommended: {
+      plugin: [
+        'react'
+      ],
       parserOptions: {
         ecmaFeatures: {
           jsx: true
@@ -109,12 +119,15 @@ module.exports = {
       }
     },
     all: {
+      plugin: [
+        'react'
+      ],
       parserOptions: {
         ecmaFeatures: {
           jsx: true
         }
       },
-      rules: allRules
+      rules: activeRulesConfig
     }
   }
 };
diff --git a/lib/rules/jsx-first-prop-new-line.js b/lib/rules/jsx-first-prop-new-line.js
index 4a377465..981dc62e 100644
--- a/lib/rules/jsx-first-prop-new-line.js
+++ b/lib/rules/jsx-first-prop-new-line.js
@@ -17,7 +17,7 @@ module.exports = {
     },
 
     schema: [{
-      enum: ['always', 'never', 'multiline']
+      enum: ['always', 'never', 'multiline', 'multiline-multiprop']
     }]
   },
 
@@ -30,7 +30,11 @@ module.exports = {
 
     return {
       JSXOpeningElement: function (node) {
-        if ((configuration === 'multiline' && isMultilineJSX(node)) || (configuration === 'always')) {
+        if (
+          (configuration === 'multiline' && isMultilineJSX(node)) ||
+          (configuration === 'multiline-multiprop' && isMultilineJSX(node) && node.attributes.length > 1) ||
+          (configuration === 'always')
+        ) {
           node.attributes.forEach(function(decl) {
             if (decl.loc.start.line === node.loc.start.line) {
               context.report({
diff --git a/lib/rules/jsx-indent.js b/lib/rules/jsx-indent.js
index 14c1a640..bffbf973 100644
--- a/lib/rules/jsx-indent.js
+++ b/lib/rules/jsx-indent.js
@@ -39,8 +39,6 @@ module.exports = {
       category: 'Stylistic Issues',
       recommended: false
     },
-    // fixer will fix whitespace and tabs indentation.
-    // It won't replace tabs with whitespaces and vice-versa.
     fixable: 'whitespace',
     schema: [{
       oneOf: [{
@@ -77,20 +75,16 @@ module.exports = {
      * Responsible for fixing the indentation issue fix
      * @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
      * @returns {Function} function to be executed by the fixer
      * @private
      */
-    function getFixerFunction(node, needed, gotten) {
-      if (needed > gotten) {
-        var spaces = indentChar.repeat(needed - gotten);
-        return function(fixer) {
-          return fixer.insertTextBeforeRange([node.range[0], node.range[0]], spaces);
-        };
-      }
-
+    function getFixerFunction(node, needed) {
       return function(fixer) {
-        return fixer.removeRange([node.range[0] - (gotten - needed), node.range[0]]);
+        var indent = Array(needed + 1).join(indentChar);
+        return fixer.replaceTextRange(
+          [node.start - node.loc.start.column, node.start],
+          indent
+        );
       };
     }
 
@@ -99,7 +93,7 @@ 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 {Object} loc Error line and column location
      */
     function report(node, needed, gotten, loc) {
       var msgContext = {
@@ -115,14 +109,14 @@ module.exports = {
           loc: loc,
           message: MESSAGE,
           data: msgContext,
-          fix: getFixerFunction(node, needed, gotten)
+          fix: getFixerFunction(node, needed)
         });
       } else {
         context.report({
           node: node,
           message: MESSAGE,
           data: msgContext,
-          fix: getFixerFunction(node, needed, gotten)
+          fix: getFixerFunction(node, needed)
         });
       }
     }
diff --git a/lib/rules/no-comment-textnodes.js b/lib/rules/no-comment-textnodes.js
index 92e7f482..83ae7450 100644
--- a/lib/rules/no-comment-textnodes.js
+++ b/lib/rules/no-comment-textnodes.js
@@ -15,6 +15,7 @@ var isWarnedForDeprecation = false;
 
 module.exports = {
   meta: {
+    deprecated: true,
     docs: {
       description: 'Comments inside children section of tag should be placed inside braces',
       category: 'Possible Errors',
diff --git a/lib/rules/no-set-state.js b/lib/rules/no-set-state.js
index 455554f4..8c2dffc9 100644
--- a/lib/rules/no-set-state.js
+++ b/lib/rules/no-set-state.js
@@ -4,6 +4,8 @@
  */
 'use strict';
 
+var Components = require('../util/Components');
+
 // ------------------------------------------------------------------------------
 // Rule Definition
 // ------------------------------------------------------------------------------
@@ -18,7 +20,31 @@ module.exports = {
     schema: []
   },
 
-  create: function(context) {
+  create: Components.detect(function(context, components, utils) {
+
+    /**
+     * Checks if the component is valid
+     * @param {Object} component The component to process
+     * @returns {Boolean} True if the component is valid, false if not.
+     */
+    function isValid(component) {
+      return Boolean(component && !component.useSetState);
+    }
+
+    /**
+     * Reports usages of setState for a given component
+     * @param {Object} component The component to process
+     */
+    function reportSetStateUsages(component) {
+      var setStateUsage;
+      for (var i = 0, j = component.setStateUsages.length; i < j; i++) {
+        setStateUsage = component.setStateUsages[i];
+        context.report({
+          node: setStateUsage,
+          message: 'Do not use setState'
+        });
+      }
+    }
 
     // --------------------------------------------------------------------------
     // Public
@@ -28,24 +54,31 @@ module.exports = {
 
       CallExpression: function(node) {
         var callee = node.callee;
-        if (callee.type !== 'MemberExpression') {
+        if (
+          callee.type !== 'MemberExpression' ||
+          callee.object.type !== 'ThisExpression' ||
+          callee.property.name !== 'setState'
+        ) {
           return;
         }
-        if (callee.object.type !== 'ThisExpression' || callee.property.name !== 'setState') {
-          return;
-        }
-        var ancestors = context.getAncestors(callee);
-        for (var i = 0, j = ancestors.length; i < j; i++) {
-          if (ancestors[i].type === 'Property' || ancestors[i].type === 'MethodDefinition') {
-            context.report({
-              node: callee,
-              message: 'Do not use setState'
-            });
-            break;
+        var component = components.get(utils.getParentComponent());
+        var setStateUsages = component && component.setStateUsages || [];
+        setStateUsages.push(callee);
+        components.set(node, {
+          useSetState: true,
+          setStateUsages: setStateUsages
+        });
+      },
+
+      'Program:exit': function() {
+        var list = components.list();
+        for (var component in list) {
+          if (!list.hasOwnProperty(component) || isValid(list[component])) {
+            continue;
           }
+          reportSetStateUsages(list[component]);
         }
       }
     };
-
-  }
+  })
 };
diff --git a/lib/rules/require-extension.js b/lib/rules/require-extension.js
index 1a8a2ed2..273d3563 100644
--- a/lib/rules/require-extension.js
+++ b/lib/rules/require-extension.js
@@ -24,6 +24,7 @@ var PKG_REGEX = /^[^\.]((?!\/).)*$/;
 
 module.exports = {
   meta: {
+    deprecated: true,
     docs: {
       description: 'Restrict file extensions that may be required',
       category: 'Stylistic Issues',
diff --git a/lib/rules/wrap-multilines.js b/lib/rules/wrap-multilines.js
index 2b6900ee..439acbbe 100644
--- a/lib/rules/wrap-multilines.js
+++ b/lib/rules/wrap-multilines.js
@@ -15,6 +15,7 @@ var isWarnedForDeprecation = false;
 
 module.exports = {
   meta: {
+    deprecated: true,
     docs: {
       description: 'Prevent missing parentheses around multilines JSX',
       category: 'Stylistic Issues',


Test patch:
diff --git a/tests/index.js b/tests/index.js
index a85b4cb6..8e058037 100644
--- a/tests/index.js
+++ b/tests/index.js
@@ -23,6 +23,20 @@ describe('all rule files should be exported by the plugin', function() {
   });
 });
 
+describe('deprecated rules', function() {
+  it('marks all deprecated rules as deprecated', function() {
+    ruleFiles.forEach(function(ruleName) {
+      var inDeprecatedRules = Boolean(plugin.deprecatedRules[ruleName]);
+      var isDeprecated = plugin.rules[ruleName].meta.deprecated;
+      if (inDeprecatedRules) {
+        assert(isDeprecated, ruleName + ' metadata should mark it as deprecated');
+      } else {
+        assert(!isDeprecated, ruleName + ' metadata should not mark it as deprecated');
+      }
+    });
+  });
+});
+
 describe('configurations', function() {
   it('should export a \'recommended\' configuration', function() {
     assert(plugin.configs.recommended);
diff --git a/tests/lib/rules/jsx-first-prop-new-line.js b/tests/lib/rules/jsx-first-prop-new-line.js
index 2fd2dab2..9feb9cfd 100644
--- a/tests/lib/rules/jsx-first-prop-new-line.js
+++ b/tests/lib/rules/jsx-first-prop-new-line.js
@@ -90,6 +90,39 @@ ruleTester.run('jsx-first-prop-new-line', rule, {
       options: ['multiline'],
       parser: parserOptions
     },
+    {
+      code: [
+        '<Foo bar />'
+      ].join('\n'),
+      options: ['multiline-multiprop'],
+      parser: parserOptions
+    },
+    {
+      code: [
+        '<Foo bar baz />'
+      ].join('\n'),
+      options: ['multiline-multiprop'],
+      parser: parserOptions
+    },
+    {
+      code: [
+        '<Foo prop={{',
+        '}} />'
+      ].join('\n'),
+      options: ['multiline-multiprop'],
+      parser: parserOptions
+    },
+    {
+      code: [
+        '<Foo ',
+        '  foo={{',
+        '  }}',
+        '  bar',
+        '/>'
+      ].join('\n'),
+      options: ['multiline-multiprop'],
+      parser: parserOptions
+    },
     {
       code: '<Foo />',
       options: ['always'],
@@ -144,6 +177,24 @@ ruleTester.run('jsx-first-prop-new-line', rule, {
       options: ['never'],
       errors: [{message: 'Property should be placed on the same line as the component declaration'}],
       parser: parserOptions
+    },
+    {
+      code: [
+        '<Foo prop={{',
+        '}} />'
+      ].join('\n'),
+      options: ['multiline'],
+      errors: [{message: 'Property should be placed on a new line'}],
+      parser: parserOptions
+    },
+    {
+      code: [
+        '<Foo bar={{',
+        '}} baz />'
+      ].join('\n'),
+      options: ['multiline-multiprop'],
+      errors: [{message: 'Property should be placed on a new line'}],
+      parser: parserOptions
     }
   ]
 });
diff --git a/tests/lib/rules/jsx-indent.js b/tests/lib/rules/jsx-indent.js
index 04af0d1c..0c73c5e2 100644
--- a/tests/lib/rules/jsx-indent.js
+++ b/tests/lib/rules/jsx-indent.js
@@ -233,6 +233,11 @@ ruleTester.run('jsx-indent', rule, {
       '    <Foo />',
       '</App>'
     ].join('\n'),
+    output: [
+      '<App>',
+      '\t<Foo />',
+      '</App>'
+    ].join('\n'),
     options: ['tab'],
     parserOptions: parserOptions,
     errors: [{message: 'Expected indentation of 1 tab character but found 0.'}]
@@ -282,6 +287,19 @@ 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: [
+      'function App() {',
+      '  return (',
+      '    <App>',
+      '      <Foo />',
+      '    </App>',
+      '  );',
+      '}'
+    ].join('\n'), */
     options: [2],
     parserOptions: parserOptions,
     errors: [{message: 'Expected indentation of 4 space characters but found 0.'}]
@@ -291,6 +309,11 @@ ruleTester.run('jsx-indent', rule, {
       '   {test}',
       '</App>'
     ].join('\n'),
+    output: [
+      '<App>',
+      '    {test}',
+      '</App>'
+    ].join('\n'),
     parserOptions: parserOptions,
     errors: [
       {message: 'Expected indentation of 4 space characters but found 3.'}
@@ -305,6 +328,15 @@ ruleTester.run('jsx-indent', rule, {
       '    ))}',
       '</App>'
     ].join('\n'),
+    output: [
+      '<App>',
+      '    {options.map((option, index) => (',
+      '        <option key={index} value={option.key}>',
+      '            {option.name}',
+      '        </option>',
+      '    ))}',
+      '</App>'
+    ].join('\n'),
     parserOptions: parserOptions,
     errors: [
       {message: 'Expected indentation of 12 space characters but found 11.'}
@@ -315,6 +347,11 @@ ruleTester.run('jsx-indent', rule, {
       '{test}',
       '</App>'
     ].join('\n'),
+    output: [
+      '<App>',
+      '\t{test}',
+      '</App>'
+    ].join('\n'),
     parserOptions: parserOptions,
     options: ['tab'],
     errors: [
@@ -330,6 +367,15 @@ ruleTester.run('jsx-indent', rule, {
       '\t))}',
       '</App>'
     ].join('\n'),
+    output: [
+      '<App>',
+      '\t{options.map((option, index) => (',
+      '\t\t<option key={index} value={option.key}>',
+      '\t\t\t{option.name}',
+      '\t\t</option>',
+      '\t))}',
+      '</App>'
+    ].join('\n'),
     parserOptions: parserOptions,
     options: ['tab'],
     errors: [
@@ -369,10 +415,7 @@ ruleTester.run('jsx-indent', rule, {
     errors: [
       {message: 'Expected indentation of 2 space characters but found 4.'}
     ]
-  }
-  // Tests for future work. See the comment on line 42-43 in the rule near to fixable: 'whitespace' meta property,
-  // Right now fixer function doesn't support replacing tabs with whitespaces and vice-versa.
-  /* , {
+  }, {
     code: [
       '<App>\n',
       ' <Foo />\n',
@@ -404,5 +447,5 @@ ruleTester.run('jsx-indent', rule, {
     errors: [
       {message: 'Expected indentation of 2 space characters but found 0.'}
     ]
-  }*/]
+  }]
 });
diff --git a/tests/lib/rules/no-set-state.js b/tests/lib/rules/no-set-state.js
index d3b9fe5e..cc8a25bc 100644
--- a/tests/lib/rules/no-set-state.js
+++ b/tests/lib/rules/no-set-state.js
@@ -107,5 +107,34 @@ ruleTester.run('no-set-state', rule, {
     errors: [{
       message: 'Do not use setState'
     }]
+  }, {
+    code: [
+      'class Hello extends React.Component {',
+      '  someMethod = () => {',
+      '    this.setState({',
+      '      name: this.props.name.toUpperCase()',
+      '    });',
+      '  }',
+      '  render() {',
+      '    return <div onClick={this.someMethod.bind(this)}>Hello {this.state.name}</div>;',
+      '  }',
+      '};'
+    ].join('\n'),
+    parser: 'babel-eslint',
+    errors: [{
+      message: 'Do not use setState'
+    }]
+  }, {
+    code: [
+      'class Hello extends React.Component {',
+      '  render() {',
+      '    return <div onMouseEnter={() => this.setState({dropdownIndex: index})} />;',
+      '  }',
+      '};'
+    ].join('\n'),
+    parser: 'babel-eslint',
+    errors: [{
+      message: 'Do not use setState'
+    }]
   }]
 });