forked from jsx-eslint/eslint-plugin-react
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmakeNoMethodSetStateRule.js
130 lines (112 loc) · 3.66 KB
/
makeNoMethodSetStateRule.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**
* @fileoverview Prevent usage of setState in lifecycle methods
* @author Yannick Croissant
*/
'use strict';
const findLast = require('array.prototype.findlast');
const docsUrl = require('./docsUrl');
const report = require('./report');
const getAncestors = require('./eslint').getAncestors;
const testReactVersion = require('./version').testReactVersion;
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
function mapTitle(methodName) {
const map = {
componentDidMount: 'did-mount',
componentDidUpdate: 'did-update',
componentWillUpdate: 'will-update',
};
const title = map[methodName];
if (!title) {
throw Error(`No docsUrl for '${methodName}'`);
}
return `no-${title}-set-state`;
}
const messages = {
noSetState: 'Do not use setState in {{name}}',
};
const methodNoopsAsOf = {
componentDidMount: '>= 16.3.0',
componentDidUpdate: '>= 16.3.0',
};
function shouldBeNoop(context, methodName) {
return methodName in methodNoopsAsOf
&& testReactVersion(context, methodNoopsAsOf[methodName])
&& !testReactVersion(context, '999.999.999'); // for when the version is not specified
}
// eslint-disable-next-line valid-jsdoc
/**
* @param {string} methodName
* @param {(context: import('eslint').Rule.RuleContext) => boolean} [shouldCheckUnsafeCb]
* @returns {import('eslint').Rule.RuleModule}
*/
module.exports = function makeNoMethodSetStateRule(methodName, shouldCheckUnsafeCb) {
return {
meta: {
docs: {
description: `Disallow usage of setState in ${methodName}`,
category: 'Best Practices',
recommended: false,
url: docsUrl(mapTitle(methodName)),
},
messages,
schema: [{
enum: ['disallow-in-func'],
}],
},
create(context) {
const mode = context.options[0] || 'allow-in-func';
function nameMatches(name) {
if (name === methodName) {
return true;
}
if (typeof shouldCheckUnsafeCb === 'function' && shouldCheckUnsafeCb(context)) {
return name === `UNSAFE_${methodName}`;
}
return false;
}
if (shouldBeNoop(context, methodName)) {
return {};
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
CallExpression(node) {
const callee = node.callee;
if (
callee.type !== 'MemberExpression'
|| callee.object.type !== 'ThisExpression'
|| !('name' in callee.property)
|| callee.property.name !== 'setState'
) {
return;
}
const ancestors = getAncestors(context, node);
let depth = 0;
findLast(ancestors, (ancestor) => {
// ancestors.some((ancestor) => {
if (/Function(Expression|Declaration)$/.test(ancestor.type)) {
depth += 1;
}
if (
(ancestor.type !== 'Property' && ancestor.type !== 'MethodDefinition' && ancestor.type !== 'ClassProperty' && ancestor.type !== 'PropertyDefinition')
|| !nameMatches(ancestor.key.name)
|| (mode !== 'disallow-in-func' && depth > 1)
) {
return false;
}
report(context, messages.noSetState, 'noSetState', {
node: callee,
data: {
name: ancestor.key.name,
},
});
return true;
});
},
};
},
};
};