UNPKG

4.3 kBJavaScriptView Raw
1/**
2 * @fileoverview Prevent direct mutation of this.state
3 * @author David Petersen
4 * @author Nicolas Fernandez <@burabure>
5 */
6
7'use strict';
8
9const Components = require('../util/Components');
10const docsUrl = require('../util/docsUrl');
11
12// ------------------------------------------------------------------------------
13// Rule Definition
14// ------------------------------------------------------------------------------
15
16module.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 * Checks if the component is valid
29 * @param {Object} component The component to process
30 * @returns {Boolean} True if the component is valid, false if not.
31 */
32 function isValid(component) {
33 return Boolean(component && !component.mutateSetState);
34 }
35
36 /**
37 * Reports undeclared proptypes for a given component
38 * @param {Object} component The component to process
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 * Walks throughs the MemberExpression to the top-most property.
53 * @param {Object} node The node to process
54 * @returns {Object} The outer-most MemberExpression
55 */
56 function getOuterMemberExpression(node) {
57 while (node.object && node.object.property) {
58 node = node.object;
59 }
60 return node;
61 }
62
63 /**
64 * Determine if we should currently ignore assignments in this component.
65 * @param {?Object} component The component to process
66 * @returns {Boolean} True if we should skip assignment checks.
67 */
68 function shouldIgnoreComponent(component) {
69 return !component || (component.inConstructor && !component.inCallExpression);
70 }
71
72 // --------------------------------------------------------------------------
73 // Public
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};