no-direct-mutation-state.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. /**
  2. * @fileoverview Prevent direct mutation of this.state
  3. * @author David Petersen
  4. * @author Nicolas Fernandez <@burabure>
  5. */
  6. 'use strict';
  7. const Components = require('../util/Components');
  8. const componentUtil = require('../util/componentUtil');
  9. const docsUrl = require('../util/docsUrl');
  10. const report = require('../util/report');
  11. // ------------------------------------------------------------------------------
  12. // Rule Definition
  13. // ------------------------------------------------------------------------------
  14. const messages = {
  15. noDirectMutation: 'Do not mutate state directly. Use setState().',
  16. };
  17. module.exports = {
  18. meta: {
  19. docs: {
  20. description: 'Disallow direct mutation of this.state',
  21. category: 'Possible Errors',
  22. recommended: true,
  23. url: docsUrl('no-direct-mutation-state'),
  24. },
  25. messages,
  26. },
  27. create: Components.detect((context, components, utils) => {
  28. /**
  29. * Checks if the component is valid
  30. * @param {Object} component The component to process
  31. * @returns {Boolean} True if the component is valid, false if not.
  32. */
  33. function isValid(component) {
  34. return Boolean(component && !component.mutateSetState);
  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. report(context, messages.noDirectMutation, 'noDirectMutation', {
  45. node: mutation,
  46. });
  47. }
  48. }
  49. /**
  50. * Walks through the MemberExpression to the top-most property.
  51. * @param {Object} node The node to process
  52. * @returns {Object} The outer-most MemberExpression
  53. */
  54. function getOuterMemberExpression(node) {
  55. while (node.object && node.object.property) {
  56. node = node.object;
  57. }
  58. return node;
  59. }
  60. /**
  61. * Determine if we should currently ignore assignments in this component.
  62. * @param {?Object} component The component to process
  63. * @returns {Boolean} True if we should skip assignment checks.
  64. */
  65. function shouldIgnoreComponent(component) {
  66. return !component || (component.inConstructor && !component.inCallExpression);
  67. }
  68. // --------------------------------------------------------------------------
  69. // Public
  70. // --------------------------------------------------------------------------
  71. return {
  72. MethodDefinition(node) {
  73. if (node.kind === 'constructor') {
  74. components.set(node, {
  75. inConstructor: true,
  76. });
  77. }
  78. },
  79. CallExpression(node) {
  80. components.set(node, {
  81. inCallExpression: true,
  82. });
  83. },
  84. AssignmentExpression(node) {
  85. const component = components.get(utils.getParentComponent());
  86. if (shouldIgnoreComponent(component) || !node.left || !node.left.object) {
  87. return;
  88. }
  89. const item = getOuterMemberExpression(node.left);
  90. if (componentUtil.isStateMemberExpression(item)) {
  91. const mutations = (component && component.mutations) || [];
  92. mutations.push(node.left.object);
  93. components.set(node, {
  94. mutateSetState: true,
  95. mutations,
  96. });
  97. }
  98. },
  99. UpdateExpression(node) {
  100. const component = components.get(utils.getParentComponent());
  101. if (shouldIgnoreComponent(component) || node.argument.type !== 'MemberExpression') {
  102. return;
  103. }
  104. const item = getOuterMemberExpression(node.argument);
  105. if (componentUtil.isStateMemberExpression(item)) {
  106. const mutations = (component && component.mutations) || [];
  107. mutations.push(item);
  108. components.set(node, {
  109. mutateSetState: true,
  110. mutations,
  111. });
  112. }
  113. },
  114. 'CallExpression:exit'(node) {
  115. components.set(node, {
  116. inCallExpression: false,
  117. });
  118. },
  119. 'MethodDefinition:exit'(node) {
  120. if (node.kind === 'constructor') {
  121. components.set(node, {
  122. inConstructor: false,
  123. });
  124. }
  125. },
  126. 'Program:exit'() {
  127. const list = components.list();
  128. Object.keys(list).forEach((key) => {
  129. if (!isValid(list[key])) {
  130. reportMutations(list[key]);
  131. }
  132. });
  133. },
  134. };
  135. }),
  136. };