no-direct-mutation-state.js 4.3 KB

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