is-same-reference.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. 'use strict';
  2. const {getStaticValue} = require('@eslint-community/eslint-utils');
  3. // Copied from https://github.com/eslint/eslint/blob/94ba68d76a6940f68ff82eea7332c6505f93df76/lib/rules/utils/ast-utils.js#L392
  4. /**
  5. Gets the property name of a given node.
  6. The node can be a MemberExpression, a Property, or a MethodDefinition.
  7. If the name is dynamic, this returns `null`.
  8. For examples:
  9. a.b // => "b"
  10. a["b"] // => "b"
  11. a['b'] // => "b"
  12. a[`b`] // => "b"
  13. a[100] // => "100"
  14. a[b] // => null
  15. a["a" + "b"] // => null
  16. a[tag`b`] // => null
  17. a[`${b}`] // => null
  18. let a = {b: 1} // => "b"
  19. let a = {["b"]: 1} // => "b"
  20. let a = {['b']: 1} // => "b"
  21. let a = {[`b`]: 1} // => "b"
  22. let a = {[100]: 1} // => "100"
  23. let a = {[b]: 1} // => null
  24. let a = {["a" + "b"]: 1} // => null
  25. let a = {[tag`b`]: 1} // => null
  26. let a = {[`${b}`]: 1} // => null
  27. @param {ASTNode} node The node to get.
  28. @returns {string|undefined} The property name if static. Otherwise, undefined.
  29. */
  30. function getStaticPropertyName(node) {
  31. let property;
  32. switch (node?.type) {
  33. case 'MemberExpression': {
  34. property = node.property;
  35. break;
  36. }
  37. /* c8 ignore next 2 */
  38. case 'ChainExpression': {
  39. return getStaticPropertyName(node.expression);
  40. }
  41. // Only reachable when use this to get class/object member key
  42. /* c8 ignore next */
  43. case 'Property':
  44. case 'MethodDefinition': {
  45. /* c8 ignore next 2 */
  46. property = node.key;
  47. break;
  48. }
  49. // No default
  50. }
  51. if (property) {
  52. if (property.type === 'Identifier' && !node.computed) {
  53. return property.name;
  54. }
  55. const staticResult = getStaticValue(property);
  56. if (!staticResult) {
  57. return;
  58. }
  59. return String(staticResult.value);
  60. }
  61. }
  62. /**
  63. Check if two literal nodes are the same value.
  64. @param {ASTNode} left The Literal node to compare.
  65. @param {ASTNode} right The other Literal node to compare.
  66. @returns {boolean} `true` if the two literal nodes are the same value.
  67. */
  68. function equalLiteralValue(left, right) {
  69. // RegExp literal.
  70. if (left.regex || right.regex) {
  71. return Boolean(
  72. left.regex
  73. && right.regex
  74. && left.regex.pattern === right.regex.pattern
  75. && left.regex.flags === right.regex.flags,
  76. );
  77. }
  78. // BigInt literal.
  79. if (left.bigint || right.bigint) {
  80. return left.bigint === right.bigint;
  81. }
  82. return left.value === right.value;
  83. }
  84. /**
  85. Check if two expressions reference the same value. For example:
  86. a = a
  87. a.b = a.b
  88. a[0] = a[0]
  89. a['b'] = a['b']
  90. @param {ASTNode} left The left side of the comparison.
  91. @param {ASTNode} right The right side of the comparison.
  92. @returns {boolean} `true` if both sides match and reference the same value.
  93. */
  94. function isSameReference(left, right) {
  95. if (left.type !== right.type) {
  96. // Handle `a.b` and `a?.b` are samely.
  97. if (left.type === 'ChainExpression') {
  98. return isSameReference(left.expression, right);
  99. }
  100. if (right.type === 'ChainExpression') {
  101. return isSameReference(left, right.expression);
  102. }
  103. return false;
  104. }
  105. switch (left.type) {
  106. case 'Super':
  107. case 'ThisExpression': {
  108. return true;
  109. }
  110. case 'Identifier':
  111. case 'PrivateIdentifier': {
  112. return left.name === right.name;
  113. }
  114. case 'Literal': {
  115. return equalLiteralValue(left, right);
  116. }
  117. case 'ChainExpression': {
  118. return isSameReference(left.expression, right.expression);
  119. }
  120. case 'MemberExpression': {
  121. const nameA = getStaticPropertyName(left);
  122. // `x.y = x["y"]`
  123. if (nameA !== undefined) {
  124. return (
  125. isSameReference(left.object, right.object)
  126. && nameA === getStaticPropertyName(right)
  127. );
  128. }
  129. /*
  130. `x[0] = x[0]`
  131. `x[y] = x[y]`
  132. `x.y = x.y`
  133. */
  134. return (
  135. left.computed === right.computed
  136. && isSameReference(left.object, right.object)
  137. && isSameReference(left.property, right.property)
  138. );
  139. }
  140. default: {
  141. return false;
  142. }
  143. }
  144. }
  145. module.exports = isSameReference;