Repository: yannickcr/eslint-plugin-react
Pull request #1394 (2017-08-23 05:54:48)

[no-unused-state] Ignore computed property keys for no unused state rule
For https://github.com/yannickcr/eslint-plugin-react/issues/1361.

This sets the no-unused-state rule to ignore computed property keys when the contents aren't literal.

I think it should cover all kinds of literals for checking unused state properties because it could happen in types other than string and could be checked. If that is undesirable, I could change it to only checking string literals. 

Other than that, let me know what other improvements you have in mind.


Patch:
diff --git a/README.md b/README.md
index 502a8d8e..6bb08367 100644
--- a/README.md
+++ b/README.md
@@ -116,7 +116,7 @@ Finally, enable all of the rules that you would like to use.  Use [our preset](#
 * [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 (fixable)
-* [react/sort-comp](docs/rules/sort-comp.md): Enforce component methods order (fixable)
+* [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/style-prop-object](docs/rules/style-prop-object.md): Enforce style prop value being an object
 * [react/void-dom-elements-no-children](docs/rules/void-dom-elements-no-children.md): Prevent void DOM elements (e.g. `<img />`, `<br />`) from receiving children
diff --git a/docs/rules/jsx-sort-props.md b/docs/rules/jsx-sort-props.md
index 5b770514..c0a14019 100644
--- a/docs/rules/jsx-sort-props.md
+++ b/docs/rules/jsx-sort-props.md
@@ -2,8 +2,6 @@
 
 Some developers prefer to sort props names alphabetically to be able to find necessary props easier at the later time. Others feel that it adds complexity and becomes burden to maintain.
 
-**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line.
-
 ## Rule Details
 
 This rule checks all JSX components and verifies that all props are sorted alphabetically. A spread attribute resets the verification. The default configuration of the rule is case-sensitive.
diff --git a/docs/rules/jsx-tag-spacing.md b/docs/rules/jsx-tag-spacing.md
index 75ed07ee..0e168242 100644
--- a/docs/rules/jsx-tag-spacing.md
+++ b/docs/rules/jsx-tag-spacing.md
@@ -2,8 +2,6 @@
 
 Enforce or forbid spaces after the opening bracket, before the closing bracket of self-closing elements, and between the angle bracket and slash of JSX closing or self-closing elements.
 
-**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line.
-
 ## Rule Details
 
 This rule checks the whitespace inside and surrounding the JSX syntactic elements.
diff --git a/lib/rules/no-unused-state.js b/lib/rules/no-unused-state.js
index 41f333af..d4bef8c2 100644
--- a/lib/rules/no-unused-state.js
+++ b/lib/rules/no-unused-state.js
@@ -25,10 +25,14 @@ function uncast(node) {
 // method definitions, ObjectExpression property keys).
 function getName(node) {
   node = uncast(node);
-  if (node.type === 'Identifier') {
+  const type = node.type;
+
+  if (type === 'Identifier') {
     return node.name;
-  } else if (node.type === 'Literal') {
+  } else if (type === 'Literal') {
     return String(node.value);
+  } else if (type === 'TemplateLiteral' && node.expressions.length === 0) {
+    return node.quasis[0].value.raw;
   }
   return null;
 }
@@ -92,7 +96,15 @@ module.exports = {
     // current set of state fields.
     function addStateFields(node) {
       for (const prop of node.properties) {
-        if (prop.type === 'Property' && getName(prop.key) !== null) {
+        const key = prop.key;
+
+        if (
+          prop.type === 'Property' &&
+          (key.type === 'Literal' ||
+          (key.type === 'TemplateLiteral' && key.expressions.length === 0) ||
+          (prop.computed === false && key.type === 'Identifier')) &&
+          getName(prop.key) !== null
+        ) {
           classInfo.stateFields.add(prop);
         }
       }


Test patch:
diff --git a/tests/lib/rules/no-unused-state.js b/tests/lib/rules/no-unused-state.js
index c5e60d89..4fbb8a4f 100644
--- a/tests/lib/rules/no-unused-state.js
+++ b/tests/lib/rules/no-unused-state.js
@@ -46,6 +46,70 @@ eslintTester.run('no-unused-state', rule, {
         return <SomeComponent foo={this.state.foo} />;
       }
     });`,
+    `var ComputedKeyFromVariableTest = createReactClass({
+      getInitialState: function() {
+        return { [foo]: 0 };
+      },
+      render: function() {
+        return <SomeComponent />;
+      }
+    });`,
+    `var ComputedKeyFromBooleanLiteralTest = createReactClass({
+      getInitialState: function() {
+        return { [true]: 0 };
+      },
+      render: function() {
+        return <SomeComponent foo={this.state[true]} />;
+      }
+    });`,
+    `var ComputedKeyFromNumberLiteralTest = createReactClass({
+      getInitialState: function() {
+        return { [123]: 0 };
+      },
+      render: function() {
+        return <SomeComponent foo={this.state[123]} />;
+      }
+    });`,
+    `var ComputedKeyFromExpressionTest = createReactClass({
+      getInitialState: function() {
+        return { [foo + bar]: 0 };
+      },
+      render: function() {
+        return <SomeComponent />;
+      }
+    });`,
+    `var ComputedKeyFromBinaryExpressionTest = createReactClass({
+      getInitialState: function() {
+        return { ['foo' + 'bar' * 8]: 0 };
+      },
+      render: function() {
+        return <SomeComponent />;
+      }
+    });`,
+    `var ComputedKeyFromStringLiteralTest = createReactClass({
+      getInitialState: function() {
+        return { ['foo']: 0 };
+      },
+      render: function() {
+        return <SomeComponent foo={this.state.foo} />;
+      }
+    });`,
+    `var ComputedKeyFromTemplateLiteralTest = createReactClass({
+      getInitialState: function() {
+        return { [\`foo\${bar}\`]: 0 };
+      },
+      render: function() {
+        return <SomeComponent />;
+      }
+    });`,
+    `var ComputedKeyFromTemplateLiteralTest = createReactClass({
+      getInitialState: function() {
+        return { [\`foo\`]: 0 };
+      },
+      render: function() {
+        return <SomeComponent foo={this.state['foo']} />;
+      }
+    });`,
     `var GetInitialStateMethodTest = createReactClass({
       getInitialState() {
         return { foo: 0 };
@@ -86,6 +150,70 @@ eslintTester.run('no-unused-state', rule, {
         return <SomeComponent foo={this.state.foo} />;
       }
     }`,
+    `class ComputedKeyFromVariableTest extends React.Component {
+      constructor() {
+        this.state = { [foo]: 0 };
+      }
+      render() {
+        return <SomeComponent />;
+      }
+    }`,
+    `class ComputedKeyFromBooleanLiteralTest extends React.Component {
+      constructor() {
+        this.state = { [false]: 0 };
+      }
+      render() {
+        return <SomeComponent foo={this.state['false']} />;
+      }
+    }`,
+    `class ComputedKeyFromNumberLiteralTest extends React.Component {
+      constructor() {
+        this.state = { [345]: 0 };
+      }
+      render() {
+        return <SomeComponent foo={this.state[345]} />;
+      }
+    }`,
+    `class ComputedKeyFromExpressionTest extends React.Component {
+      constructor() {
+        this.state = { [foo + bar]: 0 };
+      }
+      render() {
+        return <SomeComponent />;
+      }
+    }`,
+    `class ComputedKeyFromBinaryExpressionTest extends React.Component {
+      constructor() {
+        this.state = { [1 + 2 * 8]: 0 };
+      }
+      render() {
+        return <SomeComponent />;
+      }
+    }`,
+    `class ComputedKeyFromStringLiteralTest extends React.Component {
+      constructor() {
+        this.state = { ['foo']: 0 };
+      }
+      render() {
+        return <SomeComponent foo={this.state.foo} />;
+      }
+    }`,
+    `class ComputedKeyFromTemplateLiteralTest extends React.Component {
+      constructor() {
+        this.state = { [\`foo\${bar}\`]: 0 };
+      }
+      render() {
+        return <SomeComponent />;
+      }
+    }`,
+    `class ComputedKeyFromTemplateLiteralTest extends React.Component {
+      constructor() {
+        this.state = { [\`foo\`]: 0 };
+      }
+      render() {
+        return <SomeComponent foo={this.state.foo} />;
+      }
+    }`,
     `class SetStateTest extends React.Component {
         onFooChange(newFoo) {
           this.setState({ foo: newFoo });
@@ -284,6 +412,50 @@ eslintTester.run('no-unused-state', rule, {
         })`,
       errors: getErrorMessages(['foo'])
     },
+    {
+      code: `var UnusedComputedStringLiteralKeyStateTest = createReactClass({
+          getInitialState: function() {
+            return { ['foo']: 0 };
+          },
+          render: function() {
+            return <SomeComponent />;
+          }
+        })`,
+      errors: getErrorMessages(['foo'])
+    },
+    {
+      code: `var UnusedComputedTemplateLiteralKeyStateTest = createReactClass({
+          getInitialState: function() {
+            return { [\`foo\`]: 0 };
+          },
+          render: function() {
+            return <SomeComponent />;
+          }
+        })`,
+      errors: getErrorMessages(['foo'])
+    },
+    {
+      code: `var UnusedComputedNumberLiteralKeyStateTest = createReactClass({
+          getInitialState: function() {
+            return { [123]: 0 };
+          },
+          render: function() {
+            return <SomeComponent />;
+          }
+        })`,
+      errors: getErrorMessages(['123'])
+    },
+    {
+      code: `var UnusedComputedBooleanLiteralKeyStateTest = createReactClass({
+          getInitialState: function() {
+            return { [true]: 0 };
+          },
+          render: function() {
+            return <SomeComponent />;
+          }
+        })`,
+      errors: getErrorMessages(['true'])
+    },
     {
       code: `var UnusedGetInitialStateMethodTest = createReactClass({
           getInitialState() {
@@ -338,6 +510,66 @@ eslintTester.run('no-unused-state', rule, {
       errors: getErrorMessages(['foo']),
       parser: 'babel-eslint'
     },
+    {
+      code: `class UnusedComputedStringLiteralKeyStateTest extends React.Component {
+          state = { ['foo']: 0 };
+          render() {
+            return <SomeComponent />;
+          }
+        }`,
+      errors: getErrorMessages(['foo']),
+      parser: 'babel-eslint'
+    },
+    {
+      code: `class UnusedComputedTemplateLiteralKeyStateTest extends React.Component {
+          state = { [\`foo\`]: 0 };
+          render() {
+            return <SomeComponent />;
+          }
+        }`,
+      errors: getErrorMessages(['foo']),
+      parser: 'babel-eslint'
+    },
+    {
+      code: `class UnusedComputedTemplateLiteralKeyStateTest extends React.Component {
+          state = { [\`foo \\n bar\`]: 0 };
+          render() {
+            return <SomeComponent />;
+          }
+        }`,
+      errors: getErrorMessages(['foo \\n bar']),
+      parser: 'babel-eslint'
+    },
+    {
+      code: `class UnusedComputedBooleanLiteralKeyStateTest extends React.Component {
+          state = { [true]: 0 };
+          render() {
+            return <SomeComponent />;
+          }
+        }`,
+      errors: getErrorMessages(['true']),
+      parser: 'babel-eslint'
+    },
+    {
+      code: `class UnusedComputedNumberLiteralKeyStateTest extends React.Component {
+          state = { [123]: 0 };
+          render() {
+            return <SomeComponent />;
+          }
+        }`,
+      errors: getErrorMessages(['123']),
+      parser: 'babel-eslint'
+    },
+    {
+      code: `class UnusedComputedFloatLiteralKeyStateTest extends React.Component {
+          state = { [123.12]: 0 };
+          render() {
+            return <SomeComponent />;
+          }
+        }`,
+      errors: getErrorMessages(['123.12']),
+      parser: 'babel-eslint'
+    },
     {
       code: `class UnusedStateWhenPropsAreSpreadTest extends React.Component {
           constructor() {