174 lines
3.8 KiB
JavaScript
174 lines
3.8 KiB
JavaScript
|
'use strict';
|
||
|
const {getStaticValue} = require('@eslint-community/eslint-utils');
|
||
|
|
||
|
// Copied from https://github.com/eslint/eslint/blob/94ba68d76a6940f68ff82eea7332c6505f93df76/lib/rules/utils/ast-utils.js#L392
|
||
|
|
||
|
/**
|
||
|
Gets the property name of a given node.
|
||
|
The node can be a MemberExpression, a Property, or a MethodDefinition.
|
||
|
|
||
|
If the name is dynamic, this returns `null`.
|
||
|
|
||
|
For examples:
|
||
|
|
||
|
a.b // => "b"
|
||
|
a["b"] // => "b"
|
||
|
a['b'] // => "b"
|
||
|
a[`b`] // => "b"
|
||
|
a[100] // => "100"
|
||
|
a[b] // => null
|
||
|
a["a" + "b"] // => null
|
||
|
a[tag`b`] // => null
|
||
|
a[`${b}`] // => null
|
||
|
|
||
|
let a = {b: 1} // => "b"
|
||
|
let a = {["b"]: 1} // => "b"
|
||
|
let a = {['b']: 1} // => "b"
|
||
|
let a = {[`b`]: 1} // => "b"
|
||
|
let a = {[100]: 1} // => "100"
|
||
|
let a = {[b]: 1} // => null
|
||
|
let a = {["a" + "b"]: 1} // => null
|
||
|
let a = {[tag`b`]: 1} // => null
|
||
|
let a = {[`${b}`]: 1} // => null
|
||
|
@param {ASTNode} node The node to get.
|
||
|
@returns {string|undefined} The property name if static. Otherwise, undefined.
|
||
|
*/
|
||
|
function getStaticPropertyName(node) {
|
||
|
let property;
|
||
|
|
||
|
switch (node?.type) {
|
||
|
case 'MemberExpression': {
|
||
|
property = node.property;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* c8 ignore next 2 */
|
||
|
case 'ChainExpression': {
|
||
|
return getStaticPropertyName(node.expression);
|
||
|
}
|
||
|
|
||
|
// Only reachable when use this to get class/object member key
|
||
|
/* c8 ignore next */
|
||
|
case 'Property':
|
||
|
case 'MethodDefinition': {
|
||
|
/* c8 ignore next 2 */
|
||
|
property = node.key;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// No default
|
||
|
}
|
||
|
|
||
|
if (property) {
|
||
|
if (property.type === 'Identifier' && !node.computed) {
|
||
|
return property.name;
|
||
|
}
|
||
|
|
||
|
const staticResult = getStaticValue(property);
|
||
|
if (!staticResult) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
return String(staticResult.value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Check if two literal nodes are the same value.
|
||
|
@param {ASTNode} left The Literal node to compare.
|
||
|
@param {ASTNode} right The other Literal node to compare.
|
||
|
@returns {boolean} `true` if the two literal nodes are the same value.
|
||
|
*/
|
||
|
function equalLiteralValue(left, right) {
|
||
|
// RegExp literal.
|
||
|
if (left.regex || right.regex) {
|
||
|
return Boolean(
|
||
|
left.regex
|
||
|
&& right.regex
|
||
|
&& left.regex.pattern === right.regex.pattern
|
||
|
&& left.regex.flags === right.regex.flags,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// BigInt literal.
|
||
|
if (left.bigint || right.bigint) {
|
||
|
return left.bigint === right.bigint;
|
||
|
}
|
||
|
|
||
|
return left.value === right.value;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Check if two expressions reference the same value. For example:
|
||
|
a = a
|
||
|
a.b = a.b
|
||
|
a[0] = a[0]
|
||
|
a['b'] = a['b']
|
||
|
@param {ASTNode} left The left side of the comparison.
|
||
|
@param {ASTNode} right The right side of the comparison.
|
||
|
@returns {boolean} `true` if both sides match and reference the same value.
|
||
|
*/
|
||
|
function isSameReference(left, right) {
|
||
|
if (left.type !== right.type) {
|
||
|
// Handle `a.b` and `a?.b` are samely.
|
||
|
if (left.type === 'ChainExpression') {
|
||
|
return isSameReference(left.expression, right);
|
||
|
}
|
||
|
|
||
|
if (right.type === 'ChainExpression') {
|
||
|
return isSameReference(left, right.expression);
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
switch (left.type) {
|
||
|
case 'Super':
|
||
|
case 'ThisExpression': {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
case 'Identifier':
|
||
|
case 'PrivateIdentifier': {
|
||
|
return left.name === right.name;
|
||
|
}
|
||
|
|
||
|
case 'Literal': {
|
||
|
return equalLiteralValue(left, right);
|
||
|
}
|
||
|
|
||
|
case 'ChainExpression': {
|
||
|
return isSameReference(left.expression, right.expression);
|
||
|
}
|
||
|
|
||
|
case 'MemberExpression': {
|
||
|
const nameA = getStaticPropertyName(left);
|
||
|
|
||
|
// `x.y = x["y"]`
|
||
|
if (nameA !== undefined) {
|
||
|
return (
|
||
|
isSameReference(left.object, right.object)
|
||
|
&& nameA === getStaticPropertyName(right)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
`x[0] = x[0]`
|
||
|
`x[y] = x[y]`
|
||
|
`x.y = x.y`
|
||
|
*/
|
||
|
return (
|
||
|
left.computed === right.computed
|
||
|
&& isSameReference(left.object, right.object)
|
||
|
&& isSameReference(left.property, right.property)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
default: {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = isSameReference;
|