UNPKG

5.86 kBJavaScriptView Raw
1/**
2 * @fileoverview Prevent usage of this.state within setState
3 * @author Rolf Erik Lekang, Jørgen Aaberg
4 */
5
6'use strict';
7
8const docsUrl = require('../util/docsUrl');
9const Components = require('../util/Components');
10
11// ------------------------------------------------------------------------------
12// Rule Definition
13// ------------------------------------------------------------------------------
14
15module.exports = {
16 meta: {
17 docs: {
18 description: 'Reports when this.state is accessed within setState',
19 category: 'Possible Errors',
20 recommended: false,
21 url: docsUrl('no-access-state-in-setstate')
22 }
23 },
24
25 create: Components.detect((context, components, utils) => {
26 function isSetStateCall(node) {
27 return node.type === 'CallExpression'
28 && node.callee.property
29 && node.callee.property.name === 'setState'
30 && node.callee.object.type === 'ThisExpression';
31 }
32
33 function isFirstArgumentInSetStateCall(current, node) {
34 if (!isSetStateCall(current)) {
35 return false;
36 }
37 while (node && node.parent !== current) {
38 node = node.parent;
39 }
40 return current.arguments[0] === node;
41 }
42
43 function isClassComponent() {
44 return !!(utils.getParentES6Component() || utils.getParentES5Component());
45 }
46
47 // The methods array contains all methods or functions that are using this.state
48 // or that are calling another method or function using this.state
49 const methods = [];
50 // The vars array contains all variables that contains this.state
51 const vars = [];
52 return {
53 CallExpression(node) {
54 if (!isClassComponent()) {
55 return;
56 }
57 // Appends all the methods that are calling another
58 // method containing this.state to the methods array
59 methods.forEach((method) => {
60 if (node.callee.name === method.methodName) {
61 let current = node.parent;
62 while (current.type !== 'Program') {
63 if (current.type === 'MethodDefinition') {
64 methods.push({
65 methodName: current.key.name,
66 node: method.node
67 });
68 break;
69 }
70 current = current.parent;
71 }
72 }
73 });
74
75 // Finding all CallExpressions that is inside a setState
76 // to further check if they contains this.state
77 let current = node.parent;
78 while (current.type !== 'Program') {
79 if (isFirstArgumentInSetStateCall(current, node)) {
80 const methodName = node.callee.name;
81 methods.forEach((method) => {
82 if (method.methodName === methodName) {
83 context.report({
84 node: method.node,
85 message: 'Use callback in setState when referencing the previous state.'
86 });
87 }
88 });
89
90 break;
91 }
92 current = current.parent;
93 }
94 },
95
96 MemberExpression(node) {
97 if (
98 node.property.name === 'state'
99 && node.object.type === 'ThisExpression'
100 && isClassComponent()
101 ) {
102 let current = node;
103 while (current.type !== 'Program') {
104 // Reporting if this.state is directly within this.setState
105 if (isFirstArgumentInSetStateCall(current, node)) {
106 context.report({
107 node,
108 message: 'Use callback in setState when referencing the previous state.'
109 });
110 break;
111 }
112
113 // Storing all functions and methods that contains this.state
114 if (current.type === 'MethodDefinition') {
115 methods.push({
116 methodName: current.key.name,
117 node
118 });
119 break;
120 } else if (current.type === 'FunctionExpression' && current.parent.key) {
121 methods.push({
122 methodName: current.parent.key.name,
123 node
124 });
125 break;
126 }
127
128 // Storing all variables containg this.state
129 if (current.type === 'VariableDeclarator') {
130 vars.push({
131 node,
132 scope: context.getScope(),
133 variableName: current.id.name
134 });
135 break;
136 }
137
138 current = current.parent;
139 }
140 }
141 },
142
143 Identifier(node) {
144 // Checks if the identifier is a variable within an object
145 let current = node;
146 while (current.parent.type === 'BinaryExpression') {
147 current = current.parent;
148 }
149 if (
150 current.parent.value === current
151 || current.parent.object === current
152 ) {
153 while (current.type !== 'Program') {
154 if (isFirstArgumentInSetStateCall(current, node)) {
155 vars
156 .filter((v) => v.scope === context.getScope() && v.variableName === node.name)
157 .forEach((v) => {
158 context.report({
159 node: v.node,
160 message: 'Use callback in setState when referencing the previous state.'
161 });
162 });
163 }
164 current = current.parent;
165 }
166 }
167 },
168
169 ObjectPattern(node) {
170 const isDerivedFromThis = node.parent.init && node.parent.init.type === 'ThisExpression';
171 node.properties.forEach((property) => {
172 if (property && property.key && property.key.name === 'state' && isDerivedFromThis) {
173 vars.push({
174 node: property.key,
175 scope: context.getScope(),
176 variableName: property.key.name
177 });
178 }
179 });
180 }
181 };
182 })
183};