Skip to content

Commit 1371f71

Browse files
jakeleventhalmeowtec
authored andcommitted
[New] add no-unused-class-component-methods
Co-authored-by: meowtec <[email protected]> Co-authored-by: Jake Leventhal <[email protected]>
1 parent 119a7f3 commit 1371f71

File tree

6 files changed

+1090
-0
lines changed

6 files changed

+1090
-0
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
55

66
## Unreleased
77

8+
### Added
9+
* [`no-unused-class-component-methods`]: Handle unused class component methods ([#2166][] @jakeleventhal @pawelnvk)
10+
11+
[#2166]: https://github.com/yannickcr/eslint-plugin-react/pull/2166
12+
813
## [7.26.1] - 2021.09.29
914

1015
### Fixed
@@ -3482,6 +3487,7 @@ If you're still not using React 15 you can keep the old behavior by setting the
34823487
[`no-unknown-property`]: docs/rules/no-unknown-property.md
34833488
[`no-unsafe`]: docs/rules/no-unsafe.md
34843489
[`no-unstable-nested-components`]: docs/rules/no-unstable-nested-components.md
3490+
[`no-unused-class-component-methods`]: docs/rules/no-unused-class-component-methods.md
34853491
[`no-unused-prop-types`]: docs/rules/no-unused-prop-types.md
34863492
[`no-unused-state`]: docs/rules/no-unused-state.md
34873493
[`no-will-update-set-state`]: docs/rules/no-will-update-set-state.md

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ Enable the rules that you would like to use.
156156
|| 🔧 | [react/no-unknown-property](docs/rules/no-unknown-property.md) | Prevent usage of unknown DOM property |
157157
| | | [react/no-unsafe](docs/rules/no-unsafe.md) | Prevent usage of unsafe lifecycle methods |
158158
| | | [react/no-unstable-nested-components](docs/rules/no-unstable-nested-components.md) | Prevent creating unstable components inside components |
159+
| | | [react/no-unused-class-component-methods](docs/rules/no-unused-class-component-methods.md) | Prevent declaring unused methods of component class |
159160
| | | [react/no-unused-prop-types](docs/rules/no-unused-prop-types.md) | Prevent definitions of unused prop types |
160161
| | | [react/no-unused-state](docs/rules/no-unused-state.md) | Prevent definition of unused state fields |
161162
| | | [react/no-will-update-set-state](docs/rules/no-will-update-set-state.md) | Prevent usage of setState in componentWillUpdate |
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Prevent declaring unused methods of component class (react/no-unused-class-component-methods)
2+
3+
Warns you if you have defined a method or property 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+
static getDerivedStateFromError(error) {
23+
return { hasError: true };
24+
}
25+
action() {}
26+
componentDidMount() {
27+
this.action();
28+
}
29+
render() {
30+
return null;
31+
}
32+
}
33+
});
34+
```

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ const allRules = {
7878
'no-unknown-property': require('./lib/rules/no-unknown-property'),
7979
'no-unsafe': require('./lib/rules/no-unsafe'),
8080
'no-unstable-nested-components': require('./lib/rules/no-unstable-nested-components'),
81+
'no-unused-class-component-methods': require('./lib/rules/no-unused-class-component-methods'),
8182
'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'),
8283
'no-unused-state': require('./lib/rules/no-unused-state'),
8384
'no-will-update-set-state': require('./lib/rules/no-will-update-set-state'),
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
/**
2+
* @fileoverview Prevent declaring unused methods and properties of component class
3+
* @author Paweł Nowak, Berton Zhu
4+
*/
5+
6+
'use strict';
7+
8+
const Components = require('../util/Components');
9+
const docsUrl = require('../util/docsUrl');
10+
11+
// ------------------------------------------------------------------------------
12+
// Rule Definition
13+
// ------------------------------------------------------------------------------
14+
15+
const LIFECYCLE_METHODS = new Set([
16+
'constructor',
17+
'componentDidCatch',
18+
'componentDidMount',
19+
'componentDidUpdate',
20+
'componentWillMount',
21+
'componentWillReceiveProps',
22+
'componentWillUnmount',
23+
'componentWillUpdate',
24+
'getSnapshotBeforeUpdate',
25+
'render',
26+
'shouldComponentUpdate',
27+
'UNSAFE_componentWillMount',
28+
'UNSAFE_componentWillReceiveProps',
29+
'UNSAFE_componentWillUpdate'
30+
]);
31+
32+
const ES6_LIFECYCLE = new Set([
33+
'state'
34+
]);
35+
36+
const ES5_LIFECYCLE = new Set([
37+
'getInitialState',
38+
'getDefaultProps',
39+
'mixins'
40+
]);
41+
42+
function isKeyLiteralLike(node, property) {
43+
return property.type === 'Literal'
44+
|| (property.type === 'TemplateLiteral' && property.expressions.length === 0)
45+
|| (node.computed === false && property.type === 'Identifier');
46+
}
47+
48+
// Descend through all wrapping TypeCastExpressions and return the expression
49+
// that was cast.
50+
function uncast(node) {
51+
while (node.type === 'TypeCastExpression') {
52+
node = node.expression;
53+
}
54+
return node;
55+
}
56+
57+
// Return the name of an identifier or the string value of a literal. Useful
58+
// anywhere that a literal may be used as a key (e.g., member expressions,
59+
// method definitions, ObjectExpression property keys).
60+
function getName(node) {
61+
node = uncast(node);
62+
const type = node.type;
63+
64+
if (type === 'Identifier') {
65+
return node.name;
66+
}
67+
if (type === 'Literal') {
68+
return String(node.value);
69+
}
70+
if (type === 'TemplateLiteral' && node.expressions.length === 0) {
71+
return node.quasis[0].value.raw;
72+
}
73+
return null;
74+
}
75+
76+
function isThisExpression(node) {
77+
return uncast(node).type === 'ThisExpression';
78+
}
79+
80+
function getInitialClassInfo(node, isClass) {
81+
return {
82+
classNode: node,
83+
isClass,
84+
// Set of nodes where properties were defined.
85+
properties: new Set(),
86+
87+
// Set of names of properties that we've seen used.
88+
usedProperties: new Set(),
89+
90+
inStatic: false
91+
};
92+
}
93+
94+
module.exports = {
95+
meta: {
96+
docs: {
97+
description: 'Prevent declaring unused methods of component class',
98+
category: 'Best Practices',
99+
recommended: false,
100+
url: docsUrl('no-unused-class-component-methods')
101+
},
102+
schema: [
103+
{
104+
type: 'object',
105+
additionalProperties: false
106+
}
107+
]
108+
},
109+
110+
create: Components.detect((context, components, utils) => {
111+
let classInfo = null;
112+
113+
// Takes an ObjectExpression node and adds all named Property nodes to the
114+
// current set of properties.
115+
function addProperty(node) {
116+
classInfo.properties.add(node);
117+
}
118+
119+
// Adds the name of the given node as a used property if the node is an
120+
// Identifier or a Literal. Other node types are ignored.
121+
function addUsedProperty(node) {
122+
const name = getName(node);
123+
if (name) {
124+
classInfo.usedProperties.add(name);
125+
}
126+
}
127+
128+
function reportUnusedProperties() {
129+
// Report all unused properties.
130+
for (const node of classInfo.properties) { // eslint-disable-line no-restricted-syntax
131+
const name = getName(node);
132+
if (
133+
!classInfo.usedProperties.has(name)
134+
&& !LIFECYCLE_METHODS.has(name)
135+
&& (classInfo.isClass ? !ES6_LIFECYCLE.has(name) : !ES5_LIFECYCLE.has(name))
136+
) {
137+
const className = (classInfo.classNode.id && classInfo.classNode.id.name) || '';
138+
139+
context.report({
140+
node,
141+
message: `Unused method or property "${name}"${className ? ` of class "${className}"` : ''}`
142+
});
143+
}
144+
}
145+
}
146+
147+
function exitMethod() {
148+
if (!classInfo || !classInfo.inStatic) {
149+
return;
150+
}
151+
152+
classInfo.inStatic = false;
153+
}
154+
155+
return {
156+
ClassDeclaration(node) {
157+
if (utils.isES6Component(node)) {
158+
classInfo = getInitialClassInfo(node, true);
159+
}
160+
},
161+
162+
ObjectExpression(node) {
163+
if (utils.isES5Component(node)) {
164+
classInfo = getInitialClassInfo(node, false);
165+
}
166+
},
167+
168+
'ClassDeclaration:exit'() {
169+
if (!classInfo) {
170+
return;
171+
}
172+
reportUnusedProperties();
173+
classInfo = null;
174+
},
175+
176+
'ObjectExpression:exit'(node) {
177+
if (!classInfo || classInfo.classNode !== node) {
178+
return;
179+
}
180+
reportUnusedProperties();
181+
classInfo = null;
182+
},
183+
184+
Property(node) {
185+
if (!classInfo || classInfo.classNode !== node.parent) {
186+
return;
187+
}
188+
189+
if (isKeyLiteralLike(node, node.key)) {
190+
addProperty(node.key);
191+
}
192+
},
193+
194+
'ClassProperty, MethodDefinition'(node) {
195+
if (!classInfo) {
196+
return;
197+
}
198+
199+
if (node.static) {
200+
classInfo.inStatic = true;
201+
return;
202+
}
203+
204+
if (isKeyLiteralLike(node, node.key)) {
205+
addProperty(node.key);
206+
}
207+
},
208+
209+
'ClassProperty:exit': exitMethod,
210+
'MethodDefinition:exit': exitMethod,
211+
212+
MemberExpression(node) {
213+
if (!classInfo || classInfo.inStatic) {
214+
return;
215+
}
216+
217+
if (isThisExpression(node.object) && isKeyLiteralLike(node, node.property)) {
218+
if (node.parent.type === 'AssignmentExpression' && node.parent.left === node) {
219+
// detect `this.property = xxx`
220+
addProperty(node.property);
221+
} else {
222+
// detect `this.property()`, `x = this.property`, etc.
223+
addUsedProperty(node.property);
224+
}
225+
}
226+
},
227+
228+
VariableDeclarator(node) {
229+
if (!classInfo || classInfo.inStatic) {
230+
return;
231+
}
232+
233+
// detect `{ foo, bar: baz } = this`
234+
if (node.init && isThisExpression(node.init) && node.id.type === 'ObjectPattern') {
235+
node.id.properties.forEach((prop) => {
236+
if (prop.type === 'Property' && isKeyLiteralLike(prop, prop.key)) {
237+
addUsedProperty(prop.key);
238+
}
239+
});
240+
}
241+
}
242+
};
243+
})
244+
};

0 commit comments

Comments
 (0)