1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | 'use strict';
|
8 |
|
9 | const Components = require('../util/Components');
|
10 | const docsUrl = require('../util/docsUrl');
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | module.exports = {
|
17 | meta: {
|
18 | docs: {
|
19 | description: 'Prevent direct mutation of this.state',
|
20 | category: 'Possible Errors',
|
21 | recommended: true,
|
22 | url: docsUrl('no-direct-mutation-state')
|
23 | }
|
24 | },
|
25 |
|
26 | create: Components.detect((context, components, utils) => {
|
27 | |
28 |
|
29 |
|
30 |
|
31 |
|
32 | function isValid(component) {
|
33 | return Boolean(component && !component.mutateSetState);
|
34 | }
|
35 |
|
36 | |
37 |
|
38 |
|
39 |
|
40 | function reportMutations(component) {
|
41 | let mutation;
|
42 | for (let i = 0, j = component.mutations.length; i < j; i++) {
|
43 | mutation = component.mutations[i];
|
44 | context.report({
|
45 | node: mutation,
|
46 | message: 'Do not mutate state directly. Use setState().'
|
47 | });
|
48 | }
|
49 | }
|
50 |
|
51 | |
52 |
|
53 |
|
54 |
|
55 |
|
56 | function getOuterMemberExpression(node) {
|
57 | while (node.object && node.object.property) {
|
58 | node = node.object;
|
59 | }
|
60 | return node;
|
61 | }
|
62 |
|
63 | |
64 |
|
65 |
|
66 |
|
67 |
|
68 | function shouldIgnoreComponent(component) {
|
69 | return !component || (component.inConstructor && !component.inCallExpression);
|
70 | }
|
71 |
|
72 |
|
73 |
|
74 |
|
75 | return {
|
76 | MethodDefinition(node) {
|
77 | if (node.kind === 'constructor') {
|
78 | components.set(node, {
|
79 | inConstructor: true
|
80 | });
|
81 | }
|
82 | },
|
83 |
|
84 | CallExpression(node) {
|
85 | components.set(node, {
|
86 | inCallExpression: true
|
87 | });
|
88 | },
|
89 |
|
90 | AssignmentExpression(node) {
|
91 | const component = components.get(utils.getParentComponent());
|
92 | if (shouldIgnoreComponent(component) || !node.left || !node.left.object) {
|
93 | return;
|
94 | }
|
95 | const item = getOuterMemberExpression(node.left);
|
96 | if (utils.isStateMemberExpression(item)) {
|
97 | const mutations = (component && component.mutations) || [];
|
98 | mutations.push(node.left.object);
|
99 | components.set(node, {
|
100 | mutateSetState: true,
|
101 | mutations
|
102 | });
|
103 | }
|
104 | },
|
105 |
|
106 | UpdateExpression(node) {
|
107 | const component = components.get(utils.getParentComponent());
|
108 | if (shouldIgnoreComponent(component) || node.argument.type !== 'MemberExpression') {
|
109 | return;
|
110 | }
|
111 | const item = getOuterMemberExpression(node.argument);
|
112 | if (utils.isStateMemberExpression(item)) {
|
113 | const mutations = (component && component.mutations) || [];
|
114 | mutations.push(item);
|
115 | components.set(node, {
|
116 | mutateSetState: true,
|
117 | mutations
|
118 | });
|
119 | }
|
120 | },
|
121 |
|
122 | 'CallExpression:exit'(node) {
|
123 | components.set(node, {
|
124 | inCallExpression: false
|
125 | });
|
126 | },
|
127 |
|
128 | 'MethodDefinition:exit'(node) {
|
129 | if (node.kind === 'constructor') {
|
130 | components.set(node, {
|
131 | inConstructor: false
|
132 | });
|
133 | }
|
134 | },
|
135 |
|
136 | 'Program:exit'() {
|
137 | const list = components.list();
|
138 |
|
139 | Object.keys(list).forEach((key) => {
|
140 | if (!isValid(list[key])) {
|
141 | reportMutations(list[key]);
|
142 | }
|
143 | });
|
144 | }
|
145 | };
|
146 | })
|
147 | };
|