source: imaps-frontend/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.js@ d565449

main
Last change on this file since d565449 was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

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