Skip to content

Commit 2d53aa7

Browse files
committed
Complete rule no-access-state-in-setstate
1 parent 9bec9e9 commit 2d53aa7

File tree

2 files changed

+122
-15
lines changed

2 files changed

+122
-15
lines changed

lib/rules/no-access-state-in-setstate.js

Lines changed: 104 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* @fileoverview Prevent usage of this.state within setState
3-
* @author Rolf Erik Lekang
3+
* @author Rolf Erik Lekang, Jørgen Aaberg
44
*/
55

66
'use strict';
@@ -21,23 +21,117 @@ module.exports = {
2121
create: function(context) {
2222
function isSetStateCall(node) {
2323
return node.type === 'CallExpression' &&
24-
node.callee &&
2524
node.callee.property &&
26-
node.callee.property.name === 'setState';
25+
node.callee.property.name === 'setState' &&
26+
node.callee.object.type === 'ThisExpression';
2727
}
2828

29+
// The methods array contains all methods or functions that are using this.state
30+
// or that are calling another method or function using this.state
31+
const methods = [];
32+
// The vars array contains all variables that contains this.state
33+
const vars = [];
2934
return {
30-
ThisExpression: function(node) {
31-
var memberExpression = node.parent;
32-
if (memberExpression.property.name === 'state') {
33-
var current = memberExpression;
35+
CallExpression(node) {
36+
// Appends all the methods that are calling another
37+
// method containg this.state to the methods array
38+
methods.map(method => {
39+
if (node.callee.name === method.methodName) {
40+
let current = node.parent;
41+
while (current.type !== 'Program') {
42+
if (current.type === 'MethodDefinition') {
43+
methods.push({
44+
methodName: current.key.name,
45+
node: method.node
46+
});
47+
break;
48+
}
49+
current = current.parent;
50+
}
51+
}
52+
});
53+
54+
// Finding all CallExpressions that is inside a setState
55+
// to further check if they contains this.state
56+
let current = node.parent;
57+
while (current.type !== 'Program') {
58+
if (isSetStateCall(current)) {
59+
const methodName = node.callee.name;
60+
methods.map(method => {
61+
if (method.methodName === methodName) {
62+
context.report(
63+
method.node,
64+
'Use callback in setState when referencing the previous state.'
65+
);
66+
}
67+
});
68+
69+
break;
70+
}
71+
current = current.parent;
72+
}
73+
},
74+
75+
MemberExpression(node) {
76+
if (
77+
node.property.name === 'state' &&
78+
node.object.type === 'ThisExpression'
79+
) {
80+
let current = node;
3481
while (current.type !== 'Program') {
82+
// Reporting if this.state is directly within this.setState
3583
if (isSetStateCall(current)) {
36-
context.report({
37-
node: memberExpression,
38-
message: 'Use callback in setState when referencing the previous state.'
84+
context.report(
85+
node,
86+
'Use callback in setState when referencing the previous state.'
87+
);
88+
break;
89+
}
90+
91+
// Storing all functions and methods that contains this.state
92+
if (current.type === 'MethodDefinition') {
93+
methods.push({
94+
methodName: current.key.name,
95+
node: node
3996
});
4097
break;
98+
} else if (current.type === 'FunctionExpression') {
99+
methods.push({
100+
methodName: current.parent.key.name,
101+
node: node
102+
});
103+
break;
104+
}
105+
106+
// Storing all variables containg this.state
107+
if (current.type === 'VariableDeclarator') {
108+
vars.push({
109+
node: node,
110+
scope: context.getScope()
111+
});
112+
break;
113+
}
114+
115+
current = current.parent;
116+
}
117+
}
118+
},
119+
120+
Identifier(node) {
121+
// Checks if the identifier is a variable within an object
122+
let current = node;
123+
while (current.parent.type === 'BinaryExpression') {
124+
current = current.parent;
125+
}
126+
if (current.parent.value === current) {
127+
while (current.type !== 'Program') {
128+
if (isSetStateCall(current)) {
129+
vars
130+
.filter(v => v.scope === context.getScope())
131+
.map(v => context.report(
132+
v.node,
133+
'Use callback in setState when referencing the previous state.'
134+
));
41135
}
42136
current = current.parent;
43137
}

tests/lib/rules/no-access-state-in-setstate.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
/**
22
* @fileoverview Prevent usage of this.state within setState
3-
* @author Rolf Erik Lekang
3+
* @author Rolf Erik Lekang, Jørgen Aaberg
44
*/
55
'use strict';
66

77
// ------------------------------------------------------------------------------
88
// Requirements
99
// ------------------------------------------------------------------------------
1010

11-
var rule = require('../../../lib/rules/no-access-state-in-setstate');
12-
var RuleTester = require('eslint').RuleTester;
11+
const rule = require('../../../lib/rules/no-access-state-in-setstate');
12+
const RuleTester = require('eslint').RuleTester;
1313

14-
var parserOptions = {
14+
const parserOptions = {
1515
ecmaVersion: 6,
1616
ecmaFeatures: {
1717
jsx: true
@@ -22,7 +22,7 @@ var parserOptions = {
2222
// Tests
2323
// ------------------------------------------------------------------------------
2424

25-
var ruleTester = new RuleTester();
25+
const ruleTester = new RuleTester();
2626
ruleTester.run('no-access-state-in-setstate', rule, {
2727
valid: [{
2828
code: [
@@ -33,6 +33,19 @@ ruleTester.run('no-access-state-in-setstate', rule, {
3333
'});'
3434
].join('\n'),
3535
parserOptions: parserOptions
36+
}, {
37+
code: [
38+
'var Hello = React.createClass({',
39+
' multiplyValue: function(obj) {',
40+
' return obj.value*2',
41+
' },',
42+
' onClick: function() {',
43+
' var value = this.state.value',
44+
' this.multiplyValue({ value: value })',
45+
' }',
46+
'});'
47+
].join('\n'),
48+
parserOptions: parserOptions
3649
}],
3750

3851
invalid: [{

0 commit comments

Comments
 (0)