Skip to content

Commit

Permalink
Merge pull request #1084 from jomasti/no-useless-scu
Browse files Browse the repository at this point in the history
Add react/no-redundant-should-component-update
  • Loading branch information
ljharb committed May 24, 2017
2 parents a80cf0c + e01a771 commit 7ebcd48
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -96,6 +96,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
* [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-redundant-should-component-update](docs/rules/no-redundant-should-component-update.md): Prevent usage of `shouldComponentUpdate` when extending React.PureComponent
* [react/no-render-return-value](docs/rules/no-render-return-value.md): Prevent usage of the return value of `React.render`
* [react/no-set-state](docs/rules/no-set-state.md): Prevent usage of `setState`
* [react/no-string-refs](docs/rules/no-string-refs.md): Prevent using string references in `ref` attribute.
Expand Down
64 changes: 64 additions & 0 deletions docs/rules/no-redundant-should-component-update.md
@@ -0,0 +1,64 @@
# Prevent usage of shouldComponentUpdate when extending React.PureComponent (react/no-redundant-should-component-update)

Warns if you have `shouldComponentUpdate` defined when defining a component that extends React.PureComponent.
While having `shouldComponentUpdate` will still work, it becomes pointless to extend PureComponent.

## Rule Details

The following patterns are considered warnings:

```jsx
class Foo extends React.PureComponent {
shouldComponentUpdate() {
// do check
}

render() {
return <div>Radical!</div>
}
}

function Bar() {
return class Baz extends React.PureComponent {
shouldComponentUpdate() {
// do check
}

render() {
return <div>Groovy!</div>
}
}
}
```

The following patterns are not considered warnings:

```jsx
class Foo extends React.Component {
shouldComponentUpdate() {
// do check
}

render() {
return <div>Radical!</div>
}
}

function Bar() {
return class Baz extends React.Component {
shouldComponentUpdate() {
// do check
}

render() {
return <div>Groovy!</div>
}
}
}

class Qux extends React.PureComponent {
render() {
return <div>Tubular!</div>
}
}
```
3 changes: 2 additions & 1 deletion index.js
Expand Up @@ -62,7 +62,8 @@ var allRules = {
'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'),
'no-children-prop': require('./lib/rules/no-children-prop'),
'void-dom-elements-no-children': require('./lib/rules/void-dom-elements-no-children'),
'jsx-tag-spacing': require('./lib/rules/jsx-tag-spacing')
'jsx-tag-spacing': require('./lib/rules/jsx-tag-spacing'),
'no-redundant-should-component-update': require('./lib/rules/no-redundant-should-component-update')
};

function filterRules(rules, predicate) {
Expand Down
109 changes: 109 additions & 0 deletions lib/rules/no-redundant-should-component-update.js
@@ -0,0 +1,109 @@
/**
* @fileoverview Flag shouldComponentUpdate when extending PureComponent
*/
'use strict';

var Components = require('../util/Components');

function errorMessage(node) {
return `${node} does not need shouldComponentUpdate when extending React.PureComponent.`;
}

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: 'Flag shouldComponentUpdate when extending PureComponent',
category: 'Possible Errors',
recommended: false
},
schema: []
},

create: Components.detect(function(context, components, utils) {

/**
* Get properties name
* @param {Object} node - Property.
* @returns {String} Property name.
*/
function getPropertyName(node) {
if (node.key) {
return node.key.name;
} else if (node.type === 'ClassProperty') {
// Special case for class properties
// (babel-eslint does not expose property name so we have to rely on tokens)
var tokens = context.getFirstTokens(node, 2);
return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value;
}
return '';
}

/**
* Get properties for a given AST node
* @param {ASTNode} node The AST node being checked.
* @returns {Array} Properties array.
*/
function getComponentProperties(node) {
switch (node.type) {
case 'ClassExpression':
case 'ClassDeclaration':
return node.body.body;
default:
return [];
}
}

/**
* Checks for shouldComponentUpdate property
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} Whether or not the property exists.
*/
function hasShouldComponentUpdate(node) {
var properties = getComponentProperties(node);
return properties.some(function(property) {
var name = getPropertyName(property);
return name === 'shouldComponentUpdate';
});
}

/**
* Get name of node if available
* @param {ASTNode} node The AST node being checked.
* @return {String} The name of the node
*/
function getNodeName(node) {
if (node.id) {
return node.id.name;
} else if (node.parent && node.parent.id) {
return node.parent.id.name;
}
return '';
}

/**
* Checks for violation of rule
* @param {ASTNode} node The AST node being checked.
*/
function checkForViolation(node) {
if (utils.isPureComponent(node)) {
var hasScu = hasShouldComponentUpdate(node);
if (hasScu) {
var className = getNodeName(node);
context.report({
node: node,
message: errorMessage(className)
});
}
}
}

return {
ClassDeclaration: checkForViolation,
ClassExpression: checkForViolation
};
})
};
150 changes: 150 additions & 0 deletions tests/lib/rules/no-redundant-should-component-update.js
@@ -0,0 +1,150 @@
/**
* @fileoverview Tests for no-redundant-should-component-update
*/

'use strict';

// -----------------------------------------------------------------------------
// Requirements
// -----------------------------------------------------------------------------

var rule = require('../../../lib/rules/no-redundant-should-component-update');
var RuleTester = require('eslint').RuleTester;

var parserOptions = {
ecmaVersion: 6,
ecmaFeatures: {
experimentalObjectRestSpread: true,
jsx: true
}
};

function errorMessage(node) {
return `${node} does not need shouldComponentUpdate when extending React.PureComponent.`;
}

// -----------------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------------

var ruleTester = new RuleTester();
ruleTester.run('no-redundant-should-component-update', rule, {
valid: [
{
code: [
'class Foo extends React.Component {',
' shouldComponentUpdate() {',
' return true;',
' }',
'}'
].join('\n'),
parserOptions: parserOptions
},
{
code: [
'class Foo extends React.Component {',
' shouldComponentUpdate = () => {',
' return true;',
' }',
'}'
].join('\n'),
parser: 'babel-eslint',
parserOptions: parserOptions
},
{
code: [
'class Foo extends React.Component {',
' shouldComponentUpdate() {',
' return true;',
' }',
'}'
].join('\n'),
parserOptions: parserOptions
},
{
code: [
'function Foo() {',
' return class Bar extends React.Component {',
' shouldComponentUpdate() {',
' return true;',
' }',
' };',
'}'
].join('\n'),
parserOptions: parserOptions
}
],
invalid: [
{
code: [
'class Foo extends React.PureComponent {',
' shouldComponentUpdate() {',
' return true;',
' }',
'}'
].join('\n'),
errors: [{message: errorMessage('Foo')}],
parserOptions: parserOptions
},
{
code: [
'class Foo extends PureComponent {',
' shouldComponentUpdate() {',
' return true;',
' }',
'}'
].join('\n'),
errors: [{message: errorMessage('Foo')}],
parserOptions: parserOptions
},
{
code: [
'class Foo extends React.PureComponent {',
' shouldComponentUpdate = () => {',
' return true;',
' }',
'}'
].join('\n'),
errors: [{message: errorMessage('Foo')}],
parser: 'babel-eslint',
parserOptions: parserOptions
},
{
code: [
'function Foo() {',
' return class Bar extends React.PureComponent {',
' shouldComponentUpdate() {',
' return true;',
' }',
' };',
'}'
].join('\n'),
errors: [{message: errorMessage('Bar')}],
parserOptions: parserOptions
},
{
code: [
'function Foo() {',
' return class Bar extends PureComponent {',
' shouldComponentUpdate() {',
' return true;',
' }',
' };',
'}'
].join('\n'),
errors: [{message: errorMessage('Bar')}],
parserOptions: parserOptions
},
{
code: [
'var Foo = class extends PureComponent {',
' shouldComponentUpdate() {',
' return true;',
' }',
'}'
].join('\n'),
errors: [{message: errorMessage('Foo')}],
parserOptions: parserOptions
}
]
});

0 comments on commit 7ebcd48

Please sign in to comment.