no-self-assign.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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. * Checks whether the property of 2 given member expression nodes are the same
  16. * property or not.
  17. * @param {ASTNode} left A member expression node to check.
  18. * @param {ASTNode} right Another member expression node to check.
  19. * @returns {boolean} `true` if the member expressions have the same property.
  20. */
  21. function isSameProperty(left, right) {
  22. if (left.property.type === "Identifier" &&
  23. left.property.type === right.property.type &&
  24. left.property.name === right.property.name &&
  25. left.computed === right.computed
  26. ) {
  27. return true;
  28. }
  29. const lname = astUtils.getStaticPropertyName(left);
  30. const rname = astUtils.getStaticPropertyName(right);
  31. return lname !== null && lname === rname;
  32. }
  33. /**
  34. * Checks whether 2 given member expression nodes are the reference to the same
  35. * property or not.
  36. * @param {ASTNode} left A member expression node to check.
  37. * @param {ASTNode} right Another member expression node to check.
  38. * @returns {boolean} `true` if the member expressions are the reference to the
  39. * same property or not.
  40. */
  41. function isSameMember(left, right) {
  42. if (!isSameProperty(left, right)) {
  43. return false;
  44. }
  45. const lobj = left.object;
  46. const robj = right.object;
  47. if (lobj.type !== robj.type) {
  48. return false;
  49. }
  50. if (lobj.type === "MemberExpression") {
  51. return isSameMember(lobj, robj);
  52. }
  53. if (lobj.type === "ThisExpression") {
  54. return true;
  55. }
  56. return lobj.type === "Identifier" && lobj.name === robj.name;
  57. }
  58. /**
  59. * Traverses 2 Pattern nodes in parallel, then reports self-assignments.
  60. * @param {ASTNode|null} left A left node to traverse. This is a Pattern or
  61. * a Property.
  62. * @param {ASTNode|null} right A right node to traverse. This is a Pattern or
  63. * a Property.
  64. * @param {boolean} props The flag to check member expressions as well.
  65. * @param {Function} report A callback function to report.
  66. * @returns {void}
  67. */
  68. function eachSelfAssignment(left, right, props, report) {
  69. if (!left || !right) {
  70. // do nothing
  71. } else if (
  72. left.type === "Identifier" &&
  73. right.type === "Identifier" &&
  74. left.name === right.name
  75. ) {
  76. report(right);
  77. } else if (
  78. left.type === "ArrayPattern" &&
  79. right.type === "ArrayExpression"
  80. ) {
  81. const end = Math.min(left.elements.length, right.elements.length);
  82. for (let i = 0; i < end; ++i) {
  83. const leftElement = left.elements[i];
  84. const rightElement = right.elements[i];
  85. // Avoid cases such as [...a] = [...a, 1]
  86. if (
  87. leftElement &&
  88. leftElement.type === "RestElement" &&
  89. i < right.elements.length - 1
  90. ) {
  91. break;
  92. }
  93. eachSelfAssignment(leftElement, rightElement, props, report);
  94. // After a spread element, those indices are unknown.
  95. if (rightElement && rightElement.type === "SpreadElement") {
  96. break;
  97. }
  98. }
  99. } else if (
  100. left.type === "RestElement" &&
  101. right.type === "SpreadElement"
  102. ) {
  103. eachSelfAssignment(left.argument, right.argument, props, report);
  104. } else if (
  105. left.type === "ObjectPattern" &&
  106. right.type === "ObjectExpression" &&
  107. right.properties.length >= 1
  108. ) {
  109. /*
  110. * Gets the index of the last spread property.
  111. * It's possible to overwrite properties followed by it.
  112. */
  113. let startJ = 0;
  114. for (let i = right.properties.length - 1; i >= 0; --i) {
  115. const propType = right.properties[i].type;
  116. if (propType === "SpreadElement" || propType === "ExperimentalSpreadProperty") {
  117. startJ = i + 1;
  118. break;
  119. }
  120. }
  121. for (let i = 0; i < left.properties.length; ++i) {
  122. for (let j = startJ; j < right.properties.length; ++j) {
  123. eachSelfAssignment(
  124. left.properties[i],
  125. right.properties[j],
  126. props,
  127. report
  128. );
  129. }
  130. }
  131. } else if (
  132. left.type === "Property" &&
  133. right.type === "Property" &&
  134. right.kind === "init" &&
  135. !right.method
  136. ) {
  137. const leftName = astUtils.getStaticPropertyName(left);
  138. if (leftName !== null && leftName === astUtils.getStaticPropertyName(right)) {
  139. eachSelfAssignment(left.value, right.value, props, report);
  140. }
  141. } else if (
  142. props &&
  143. left.type === "MemberExpression" &&
  144. right.type === "MemberExpression" &&
  145. isSameMember(left, right)
  146. ) {
  147. report(right);
  148. }
  149. }
  150. //------------------------------------------------------------------------------
  151. // Rule Definition
  152. //------------------------------------------------------------------------------
  153. module.exports = {
  154. meta: {
  155. type: "problem",
  156. docs: {
  157. description: "disallow assignments where both sides are exactly the same",
  158. category: "Best Practices",
  159. recommended: true,
  160. url: "https://eslint.org/docs/rules/no-self-assign"
  161. },
  162. schema: [
  163. {
  164. type: "object",
  165. properties: {
  166. props: {
  167. type: "boolean",
  168. default: true
  169. }
  170. },
  171. additionalProperties: false
  172. }
  173. ]
  174. },
  175. create(context) {
  176. const sourceCode = context.getSourceCode();
  177. const [{ props = true } = {}] = context.options;
  178. /**
  179. * Reports a given node as self assignments.
  180. * @param {ASTNode} node A node to report. This is an Identifier node.
  181. * @returns {void}
  182. */
  183. function report(node) {
  184. context.report({
  185. node,
  186. message: "'{{name}}' is assigned to itself.",
  187. data: {
  188. name: sourceCode.getText(node).replace(SPACES, "")
  189. }
  190. });
  191. }
  192. return {
  193. AssignmentExpression(node) {
  194. if (node.operator === "=") {
  195. eachSelfAssignment(node.left, node.right, props, report);
  196. }
  197. }
  198. };
  199. }
  200. };