no-self-assign.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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 (left.type === "RestElement" && right.type === "SpreadElement") {
  56. eachSelfAssignment(left.argument, right.argument, props, report);
  57. } else if (
  58. left.type === "ObjectPattern" &&
  59. right.type === "ObjectExpression" &&
  60. right.properties.length >= 1
  61. ) {
  62. /*
  63. * Gets the index of the last spread property.
  64. * It's possible to overwrite properties followed by it.
  65. */
  66. let startJ = 0;
  67. for (let i = right.properties.length - 1; i >= 0; --i) {
  68. const propType = right.properties[i].type;
  69. if (
  70. propType === "SpreadElement" ||
  71. propType === "ExperimentalSpreadProperty"
  72. ) {
  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 (
  95. leftName !== null &&
  96. leftName === astUtils.getStaticPropertyName(right)
  97. ) {
  98. eachSelfAssignment(left.value, right.value, props, report);
  99. }
  100. } else if (
  101. props &&
  102. astUtils.skipChainExpression(left).type === "MemberExpression" &&
  103. astUtils.skipChainExpression(right).type === "MemberExpression" &&
  104. astUtils.isSameReference(left, right)
  105. ) {
  106. report(right);
  107. }
  108. }
  109. //------------------------------------------------------------------------------
  110. // Rule Definition
  111. //------------------------------------------------------------------------------
  112. /** @type {import('../types').Rule.RuleModule} */
  113. module.exports = {
  114. meta: {
  115. type: "problem",
  116. defaultOptions: [{ props: true }],
  117. docs: {
  118. description:
  119. "Disallow assignments where both sides are exactly the same",
  120. recommended: true,
  121. url: "https://eslint.org/docs/latest/rules/no-self-assign",
  122. },
  123. schema: [
  124. {
  125. type: "object",
  126. properties: {
  127. props: {
  128. type: "boolean",
  129. },
  130. },
  131. additionalProperties: false,
  132. },
  133. ],
  134. messages: {
  135. selfAssignment: "'{{name}}' is assigned to itself.",
  136. },
  137. },
  138. create(context) {
  139. const sourceCode = context.sourceCode;
  140. const [{ props }] = context.options;
  141. /**
  142. * Reports a given node as self assignments.
  143. * @param {ASTNode} node A node to report. This is an Identifier node.
  144. * @returns {void}
  145. */
  146. function report(node) {
  147. context.report({
  148. node,
  149. messageId: "selfAssignment",
  150. data: {
  151. name: sourceCode.getText(node).replace(SPACES, ""),
  152. },
  153. });
  154. }
  155. return {
  156. AssignmentExpression(node) {
  157. if (["=", "&&=", "||=", "??="].includes(node.operator)) {
  158. eachSelfAssignment(node.left, node.right, props, report);
  159. }
  160. },
  161. };
  162. },
  163. };