Skip to content

Commit

Permalink
Detect unused class component method
Browse files Browse the repository at this point in the history
  • Loading branch information
pawelnvk committed Apr 13, 2019
1 parent 35de81b commit a537eeb
Show file tree
Hide file tree
Showing 3 changed files with 412 additions and 0 deletions.
31 changes: 31 additions & 0 deletions docs/rules/no-unused-class-component-methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Prevent declaring unused methods of component class (react/no-unused-prop-types)

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;
}
}
});
```
144 changes: 144 additions & 0 deletions lib/rules/no-unused-class-component-methods.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/**
* @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'
);
return isMethod || isArrowFunction;
};
const checkMethods = node => {
if (isNotComponent(node)) return;
const properties = astUtil.getComponentProperties(node);
let methods = properties
.filter(property => (
filterAllMethods(property) &&
!internalMethods.includes(astUtil.getPropertyName(property))
));
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 {
ClassDeclaration: checkMethods,
ClassExpression: checkMethods,
ObjectExpression: checkMethods,
};
})
};
Loading

0 comments on commit a537eeb

Please sign in to comment.