Skip to content

Commit e84c99e

Browse files
Paweł Nowakljharb
Paweł Nowak
authored andcommitted
[New] add no-unused-class-component-methods`
1 parent d3b5d71 commit e84c99e

File tree

5 files changed

+583
-0
lines changed

5 files changed

+583
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2754,3 +2754,4 @@ If you're still not using React 15 you can keep the old behavior by setting the
27542754
[`static-property-placement`]: docs/rules/static-property-placement.md
27552755
[`jsx-curly-newline`]: docs/rules/jsx-curly-newline.md
27562756
[`jsx-no-useless-fragment`]: docs/rules/jsx-no-useless-fragment.md
2757+
[`no-unused-class-component-methods`]: docs/rules/no-unused-class-component-methods.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Prevent declaring unused methods of component class (react/no-unused-class-component-methods)
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+
```

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ const allRules = {
7171
'no-unescaped-entities': require('./lib/rules/no-unescaped-entities'),
7272
'no-unknown-property': require('./lib/rules/no-unknown-property'),
7373
'no-unsafe': require('./lib/rules/no-unsafe'),
74+
'no-unused-class-component-methods': require('./lib/rules/no-unused-class-component-methods'),
7475
'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'),
7576
'no-unused-state': require('./lib/rules/no-unused-state'),
7677
'no-will-update-set-state': require('./lib/rules/no-will-update-set-state'),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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+
const isFunctionExpression = (
62+
node.type === 'ClassProperty' &&
63+
node.value.type === 'FunctionExpression'
64+
);
65+
66+
return isMethod || isArrowFunction || isFunctionExpression;
67+
};
68+
const checkMethods = node => {
69+
const properties = astUtil.getComponentProperties(node);
70+
let methods = properties
71+
.filter(property => (
72+
filterAllMethods(property) &&
73+
internalMethods.indexOf(astUtil.getPropertyName(property)) === -1
74+
));
75+
const getThisExpressions = subnode => {
76+
if (!methods.length) {
77+
return;
78+
}
79+
80+
switch (subnode.type) {
81+
case 'ClassProperty':
82+
case 'JSXAttribute':
83+
case 'MethodDefinition':
84+
getThisExpressions(subnode.value);
85+
break;
86+
case 'ArrowFunctionExpression':
87+
case 'FunctionExpression':
88+
getThisExpressions(subnode.body);
89+
break;
90+
case 'BlockStatement':
91+
subnode.body.forEach(getThisExpressions);
92+
break;
93+
case 'ReturnStatement':
94+
getThisExpressions(subnode.argument);
95+
break;
96+
case 'JSXElement':
97+
getThisExpressions(subnode.openingElement);
98+
subnode.children.forEach(getThisExpressions);
99+
break;
100+
case 'JSXOpeningElement':
101+
subnode.attributes.forEach(getThisExpressions);
102+
break;
103+
case 'JSXExpressionContainer':
104+
case 'ExpressionStatement':
105+
getThisExpressions(subnode.expression);
106+
break;
107+
case 'CallExpression':
108+
getThisExpressions(subnode.callee);
109+
break;
110+
case 'VariableDeclaration':
111+
subnode.declarations.forEach(getThisExpressions);
112+
break;
113+
case 'VariableDeclarator':
114+
getThisExpressions(subnode.init);
115+
break;
116+
case 'MemberExpression':
117+
if (subnode.object.type !== 'ThisExpression') {
118+
return;
119+
}
120+
121+
methods = methods.filter(method =>
122+
subnode.property.name !== astUtil.getPropertyName(method)
123+
);
124+
break;
125+
default:
126+
break;
127+
}
128+
};
129+
130+
properties.forEach(getThisExpressions);
131+
132+
if (!methods.length) {
133+
return;
134+
}
135+
136+
methods.forEach(method => {
137+
context.report({
138+
node: method,
139+
message: 'Unused method "{{method}}" of class "{{class}}"',
140+
data: {
141+
class: node.id.name,
142+
method: astUtil.getPropertyName(method)
143+
}
144+
});
145+
});
146+
};
147+
148+
return {
149+
'Program:exit': () => {
150+
const list = components.list();
151+
152+
Object.values(list).forEach(({ node }) => {
153+
if (isNotComponent(node)) {
154+
return;
155+
}
156+
157+
checkMethods(node);
158+
});
159+
}
160+
};
161+
})
162+
};

0 commit comments

Comments
 (0)