no-self-assign.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. /**
  2. * @fileoverview Rule to disallow assignments where both sides are exactly the same
  3. * @author Toru Nagashima
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. const SPACES = /\s+/gu;
  14. /**
  15. * Traverses 2 Pattern nodes in parallel, then reports self-assignments.
  16. * @param {ASTNode|null} left A left node to traverse. This is a Pattern or
  17. * a Property.
  18. * @param {ASTNode|null} right A right node to traverse. This is a Pattern or
  19. * a Property.
  20. * @param {boolean} props The flag to check member expressions as well.
  21. * @param {Function} report A callback function to report.
  22. * @returns {void}
  23. */
  24. function eachSelfAssignment(left, right, props, report) {
  25. if (!left || !right) {
  26. // do nothing
  27. } else if (
  28. left.type === "Identifier" &&
  29. right.type === "Identifier" &&
  30. left.name === right.name
  31. ) {
  32. report(right);
  33. } else if (
  34. left.type === "ArrayPattern" &&
  35. right.type === "ArrayExpression"
  36. ) {
  37. const end = Math.min(left.elements.length, right.elements.length);
  38. for (let i = 0; i < end; ++i) {
  39. const leftElement = left.elements[i];
  40. const rightElement = right.elements[i];
  41. // Avoid cases such as [...a] = [...a, 1]
  42. if (
  43. leftElement &&
  44. leftElement.type === "RestElement" &&
  45. i < right.elements.length - 1
  46. ) {
  47. break;
  48. }
  49. eachSelfAssignment(leftElement, rightElement, props, report);
  50. // After a spread element, those indices are unknown.
  51. if (rightElement && rightElement.type === "SpreadElement") {
  52. break;
  53. }
  54. }
  55. } else if (
  56. left.type === "RestElement" &&
  57. right.type === "SpreadElement"
  58. ) {
  59. eachSelfAssignment(left.argument, right.argument, props, report);
  60. } else if (
  61. left.type === "ObjectPattern" &&
  62. right.type === "ObjectExpression" &&
  63. right.properties.length >= 1
  64. ) {
  65. /*
  66. * Gets the index of the last spread property.
  67. * It's possible to overwrite properties followed by it.
  68. */
  69. let startJ = 0;
  70. for (let i = right.properties.length - 1; i >= 0; --i) {
  71. const propType = right.properties[i].type;
  72. if (propType === "SpreadElement" || propType === "ExperimentalSpreadProperty") {
  73. startJ = i + 1;
  74. break;
  75. }
  76. }
  77. for (let i = 0; i < left.properties.length; ++i) {
  78. for (let j = startJ; j < right.properties.length; ++j) {
  79. eachSelfAssignment(
  80. left.properties[i],
  81. right.properties[j],
  82. props,
  83. report
  84. );
  85. }
  86. }
  87. } else if (
  88. left.type === "Property" &&
  89. right.type === "Property" &&
  90. right.kind === "init" &&
  91. !right.method
  92. ) {
  93. const leftName = astUtils.getStaticPropertyName(left);
  94. if (leftName !== null && leftName === astUtils.getStaticPropertyName(right)) {
  95. eachSelfAssignment(left.value, right.value, props, report);
  96. }
  97. } else if (
  98. props &&
  99. astUtils.skipChainExpression(left).type === "MemberExpression" &&
  100. astUtils.skipChainExpression(right).type === "MemberExpression" &&
  101. astUtils.isSameReference(left, right)
  102. ) {
  103. report(right);
  104. }
  105. }
  106. //------------------------------------------------------------------------------
  107. // Rule Definition
  108. //------------------------------------------------------------------------------
  109. /** @type {import('../shared/types').Rule} */
  110. module.exports = {
  111. meta: {
  112. type: "problem",
  113. docs: {
  114. description: "Disallow assignments where both sides are exactly the same",
  115. recommended: true,
  116. url: "https://eslint.org/docs/rules/no-self-assign"
  117. },
  118. schema: [
  119. {
  120. type: "object",
  121. properties: {
  122. props: {
  123. type: "boolean",
  124. default: true
  125. }
  126. },
  127. additionalProperties: false
  128. }
  129. ],
  130. messages: {
  131. selfAssignment: "'{{name}}' is assigned to itself."
  132. }
  133. },
  134. create(context) {
  135. const sourceCode = context.getSourceCode();
  136. const [{ props = true } = {}] = context.options;
  137. /**
  138. * Reports a given node as self assignments.
  139. * @param {ASTNode} node A node to report. This is an Identifier node.
  140. * @returns {void}
  141. */
  142. function report(node) {
  143. context.report({
  144. node,
  145. messageId: "selfAssignment",
  146. data: {
  147. name: sourceCode.getText(node).replace(SPACES, "")
  148. }
  149. });
  150. }
  151. return {
  152. AssignmentExpression(node) {
  153. if (["=", "&&=", "||=", "??="].includes(node.operator)) {
  154. eachSelfAssignment(node.left, node.right, props, report);
  155. }
  156. }
  157. };
  158. }
  159. };