Skip to content

Commit a537eeb

Browse files
author
Paweł Nowak
committed
Detect unused class component method
1 parent 35de81b commit a537eeb

File tree

3 files changed

+412
-0
lines changed

3 files changed

+412
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Prevent declaring unused methods of component class (react/no-unused-prop-types)
2+
3+
Warns you if you have defined a method but it is never being used anywhere.
4+
5+
## Rule Details
6+
7+
The following patterns are considered warnings:
8+
9+
```jsx
10+
class Foo extends React.Component {
11+
handleClick() {}
12+
render() {
13+
return null;
14+
}
15+
}
16+
```
17+
18+
The following patterns are **not** considered warnings:
19+
20+
```jsx
21+
class Foo extends React.Component {
22+
action() {}
23+
componentDidMount() {
24+
this.action();
25+
}
26+
render() {
27+
return null;
28+
}
29+
}
30+
});
31+
```
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/**
2+
* @fileoverview Prevent declaring unused methods of component class
3+
* @author Paweł Nowak
4+
*/
5+
6+
'use strict';
7+
8+
const Components = require('../util/Components');
9+
const astUtil = require('../util/ast');
10+
const docsUrl = require('../util/docsUrl');
11+
12+
// ------------------------------------------------------------------------------
13+
// Rule Definition
14+
// ------------------------------------------------------------------------------
15+
16+
const internalMethods = [
17+
'constructor',
18+
'componentDidCatch',
19+
'componentDidMount',
20+
'componentDidUpdate',
21+
'componentWillMount',
22+
'componentWillReceiveProps',
23+
'componentWillUnmount',
24+
'componentWillUpdate',
25+
'getSnapshotBeforeUpdate',
26+
'render',
27+
'shouldComponentUpdate',
28+
'UNSAFE_componentWillMount',
29+
'UNSAFE_componentWillReceiveProps',
30+
'UNSAFE_componentWillUpdate',
31+
]
32+
33+
module.exports = {
34+
meta: {
35+
docs: {
36+
description: 'Prevent declaring unused methods of component class',
37+
category: 'Best Practices',
38+
recommended: false,
39+
url: docsUrl('no-unused-class-component-methods')
40+
},
41+
schema: [
42+
{
43+
type: 'object',
44+
additionalProperties: false
45+
}
46+
]
47+
},
48+
49+
create: Components.detect((context, components, utils) => {
50+
const isNotComponent = node => (
51+
!utils.isES5Component(node) &&
52+
!utils.isES6Component(node) &&
53+
!utils.isCreateElement(node)
54+
);
55+
const filterAllMethods = node => {
56+
const isMethod = node.type === 'MethodDefinition';
57+
const isArrowFunction = (
58+
node.type === 'ClassProperty' &&
59+
node.value.type === 'ArrowFunctionExpression'
60+
);
61+
return isMethod || isArrowFunction;
62+
};
63+
const checkMethods = node => {
64+
if (isNotComponent(node)) return;
65+
const properties = astUtil.getComponentProperties(node);
66+
let methods = properties
67+
.filter(property => (
68+
filterAllMethods(property) &&
69+
!internalMethods.includes(astUtil.getPropertyName(property))
70+
));
71+
const getThisExpressions = subnode => {
72+
if (!methods.length) return;
73+
74+
switch(subnode.type) {
75+
case 'ClassProperty':
76+
case 'JSXAttribute':
77+
case 'MethodDefinition':
78+
getThisExpressions(subnode.value);
79+
break;
80+
case 'ArrowFunctionExpression':
81+
case 'FunctionExpression':
82+
getThisExpressions(subnode.body);
83+
break;
84+
case 'BlockStatement':
85+
subnode.body.forEach(getThisExpressions);
86+
break;
87+
case 'ReturnStatement':
88+
getThisExpressions(subnode.argument);
89+
break;
90+
case 'JSXElement':
91+
getThisExpressions(subnode.openingElement);
92+
subnode.children.forEach(getThisExpressions);
93+
break;
94+
case 'JSXOpeningElement':
95+
subnode.attributes.forEach(getThisExpressions);
96+
break;
97+
case 'JSXExpressionContainer':
98+
case 'ExpressionStatement':
99+
getThisExpressions(subnode.expression);
100+
break;
101+
case 'CallExpression':
102+
getThisExpressions(subnode.callee);
103+
break;
104+
case 'VariableDeclaration':
105+
subnode.declarations.forEach(getThisExpressions);
106+
break;
107+
case 'VariableDeclarator':
108+
getThisExpressions(subnode.init);
109+
break;
110+
case 'MemberExpression':
111+
if (subnode.object.type !== 'ThisExpression') return;
112+
113+
methods = methods.filter(method =>
114+
subnode.property.name !== astUtil.getPropertyName(method)
115+
);
116+
break;
117+
default:
118+
break;
119+
}
120+
};
121+
122+
properties.forEach(getThisExpressions);
123+
124+
if (!methods.length) return;
125+
126+
methods.forEach(method => {
127+
context.report({
128+
node: method,
129+
message: 'Unused method "{{method}}" of class "{{class}}"',
130+
data: {
131+
class: node.id.name,
132+
method: astUtil.getPropertyName(method),
133+
}
134+
});
135+
})
136+
}
137+
138+
return {
139+
ClassDeclaration: checkMethods,
140+
ClassExpression: checkMethods,
141+
ObjectExpression: checkMethods,
142+
};
143+
})
144+
};

0 commit comments

Comments
 (0)