Skip to content

Commit

Permalink
[New] add no-unused-class-component-methods`
Browse files Browse the repository at this point in the history
  • Loading branch information
pawelnvk authored and ljharb committed Apr 13, 2019
1 parent d3b5d71 commit e84c99e
Show file tree
Hide file tree
Showing 5 changed files with 583 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2754,3 +2754,4 @@ If you're still not using React 15 you can keep the old behavior by setting the
[`static-property-placement`]: docs/rules/static-property-placement.md
[`jsx-curly-newline`]: docs/rules/jsx-curly-newline.md
[`jsx-no-useless-fragment`]: docs/rules/jsx-no-useless-fragment.md
[`no-unused-class-component-methods`]: docs/rules/no-unused-class-component-methods.md
31 changes: 31 additions & 0 deletions docs/rules/no-unused-class-component-methods.md
@@ -0,0 +1,31 @@
# Prevent declaring unused methods of component class (react/no-unused-class-component-methods)

Warns you if you have defined a method but it is never being used anywhere.

## Rule Details

The following patterns are considered warnings:

```jsx
class Foo extends React.Component {
handleClick() {}
render() {
return null;
}
}
```

The following patterns are **not** considered warnings:

```jsx
class Foo extends React.Component {
action() {}
componentDidMount() {
this.action();
}
render() {
return null;
}
}
});
```
1 change: 1 addition & 0 deletions index.js
Expand Up @@ -71,6 +71,7 @@ const allRules = {
'no-unescaped-entities': require('./lib/rules/no-unescaped-entities'),
'no-unknown-property': require('./lib/rules/no-unknown-property'),
'no-unsafe': require('./lib/rules/no-unsafe'),
'no-unused-class-component-methods': require('./lib/rules/no-unused-class-component-methods'),
'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'),
'no-unused-state': require('./lib/rules/no-unused-state'),
'no-will-update-set-state': require('./lib/rules/no-will-update-set-state'),
Expand Down
162 changes: 162 additions & 0 deletions lib/rules/no-unused-class-component-methods.js
@@ -0,0 +1,162 @@
/**
* @fileoverview Prevent declaring unused methods of component class
* @author Paweł Nowak
*/

'use strict';

const Components = require('../util/Components');
const astUtil = require('../util/ast');
const docsUrl = require('../util/docsUrl');

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

const internalMethods = [
'constructor',
'componentDidCatch',
'componentDidMount',
'componentDidUpdate',
'componentWillMount',
'componentWillReceiveProps',
'componentWillUnmount',
'componentWillUpdate',
'getSnapshotBeforeUpdate',
'render',
'shouldComponentUpdate',
'UNSAFE_componentWillMount',
'UNSAFE_componentWillReceiveProps',
'UNSAFE_componentWillUpdate'
];

module.exports = {
meta: {
docs: {
description: 'Prevent declaring unused methods of component class',
category: 'Best Practices',
recommended: false,
url: docsUrl('no-unused-class-component-methods')
},
schema: [
{
type: 'object',
additionalProperties: false
}
]
},

create: Components.detect((context, components, utils) => {
const isNotComponent = node => (
!utils.isES5Component(node) &&
!utils.isES6Component(node) &&
!utils.isCreateElement(node)
);
const filterAllMethods = node => {
const isMethod = node.type === 'MethodDefinition';
const isArrowFunction = (
node.type === 'ClassProperty' &&
node.value.type === 'ArrowFunctionExpression'
);
const isFunctionExpression = (
node.type === 'ClassProperty' &&
node.value.type === 'FunctionExpression'
);

return isMethod || isArrowFunction || isFunctionExpression;
};
const checkMethods = node => {
const properties = astUtil.getComponentProperties(node);
let methods = properties
.filter(property => (
filterAllMethods(property) &&
internalMethods.indexOf(astUtil.getPropertyName(property)) === -1
));
const getThisExpressions = subnode => {
if (!methods.length) {
return;
}

switch (subnode.type) {
case 'ClassProperty':
case 'JSXAttribute':
case 'MethodDefinition':
getThisExpressions(subnode.value);
break;
case 'ArrowFunctionExpression':
case 'FunctionExpression':
getThisExpressions(subnode.body);
break;
case 'BlockStatement':
subnode.body.forEach(getThisExpressions);
break;
case 'ReturnStatement':
getThisExpressions(subnode.argument);
break;
case 'JSXElement':
getThisExpressions(subnode.openingElement);
subnode.children.forEach(getThisExpressions);
break;
case 'JSXOpeningElement':
subnode.attributes.forEach(getThisExpressions);
break;
case 'JSXExpressionContainer':
case 'ExpressionStatement':
getThisExpressions(subnode.expression);
break;
case 'CallExpression':
getThisExpressions(subnode.callee);
break;
case 'VariableDeclaration':
subnode.declarations.forEach(getThisExpressions);
break;
case 'VariableDeclarator':
getThisExpressions(subnode.init);
break;
case 'MemberExpression':
if (subnode.object.type !== 'ThisExpression') {
return;
}

methods = methods.filter(method =>
subnode.property.name !== astUtil.getPropertyName(method)
);
break;
default:
break;
}
};

properties.forEach(getThisExpressions);

if (!methods.length) {
return;
}

methods.forEach(method => {
context.report({
node: method,
message: 'Unused method "{{method}}" of class "{{class}}"',
data: {
class: node.id.name,
method: astUtil.getPropertyName(method)
}
});
});
};

return {
'Program:exit': () => {
const list = components.list();

Object.values(list).forEach(({ node }) => {
if (isNotComponent(node)) {
return;
}

checkMethods(node);
});
}
};
})
};

0 comments on commit e84c99e

Please sign in to comment.