You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
183 lines
5.5 KiB
183 lines
5.5 KiB
/** |
|
* @fileoverview Rule to disallow assignments where both sides are exactly the same |
|
* @author Toru Nagashima |
|
*/ |
|
|
|
"use strict"; |
|
|
|
//------------------------------------------------------------------------------ |
|
// Requirements |
|
//------------------------------------------------------------------------------ |
|
|
|
const astUtils = require("./utils/ast-utils"); |
|
|
|
//------------------------------------------------------------------------------ |
|
// Helpers |
|
//------------------------------------------------------------------------------ |
|
|
|
const SPACES = /\s+/gu; |
|
|
|
/** |
|
* Traverses 2 Pattern nodes in parallel, then reports self-assignments. |
|
* @param {ASTNode|null} left A left node to traverse. This is a Pattern or |
|
* a Property. |
|
* @param {ASTNode|null} right A right node to traverse. This is a Pattern or |
|
* a Property. |
|
* @param {boolean} props The flag to check member expressions as well. |
|
* @param {Function} report A callback function to report. |
|
* @returns {void} |
|
*/ |
|
function eachSelfAssignment(left, right, props, report) { |
|
if (!left || !right) { |
|
|
|
// do nothing |
|
} else if ( |
|
left.type === "Identifier" && |
|
right.type === "Identifier" && |
|
left.name === right.name |
|
) { |
|
report(right); |
|
} else if ( |
|
left.type === "ArrayPattern" && |
|
right.type === "ArrayExpression" |
|
) { |
|
const end = Math.min(left.elements.length, right.elements.length); |
|
|
|
for (let i = 0; i < end; ++i) { |
|
const leftElement = left.elements[i]; |
|
const rightElement = right.elements[i]; |
|
|
|
// Avoid cases such as [...a] = [...a, 1] |
|
if ( |
|
leftElement && |
|
leftElement.type === "RestElement" && |
|
i < right.elements.length - 1 |
|
) { |
|
break; |
|
} |
|
|
|
eachSelfAssignment(leftElement, rightElement, props, report); |
|
|
|
// After a spread element, those indices are unknown. |
|
if (rightElement && rightElement.type === "SpreadElement") { |
|
break; |
|
} |
|
} |
|
} else if ( |
|
left.type === "RestElement" && |
|
right.type === "SpreadElement" |
|
) { |
|
eachSelfAssignment(left.argument, right.argument, props, report); |
|
} else if ( |
|
left.type === "ObjectPattern" && |
|
right.type === "ObjectExpression" && |
|
right.properties.length >= 1 |
|
) { |
|
|
|
/* |
|
* Gets the index of the last spread property. |
|
* It's possible to overwrite properties followed by it. |
|
*/ |
|
let startJ = 0; |
|
|
|
for (let i = right.properties.length - 1; i >= 0; --i) { |
|
const propType = right.properties[i].type; |
|
|
|
if (propType === "SpreadElement" || propType === "ExperimentalSpreadProperty") { |
|
startJ = i + 1; |
|
break; |
|
} |
|
} |
|
|
|
for (let i = 0; i < left.properties.length; ++i) { |
|
for (let j = startJ; j < right.properties.length; ++j) { |
|
eachSelfAssignment( |
|
left.properties[i], |
|
right.properties[j], |
|
props, |
|
report |
|
); |
|
} |
|
} |
|
} else if ( |
|
left.type === "Property" && |
|
right.type === "Property" && |
|
right.kind === "init" && |
|
!right.method |
|
) { |
|
const leftName = astUtils.getStaticPropertyName(left); |
|
|
|
if (leftName !== null && leftName === astUtils.getStaticPropertyName(right)) { |
|
eachSelfAssignment(left.value, right.value, props, report); |
|
} |
|
} else if ( |
|
props && |
|
astUtils.skipChainExpression(left).type === "MemberExpression" && |
|
astUtils.skipChainExpression(right).type === "MemberExpression" && |
|
astUtils.isSameReference(left, right) |
|
) { |
|
report(right); |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Rule Definition |
|
//------------------------------------------------------------------------------ |
|
|
|
/** @type {import('../shared/types').Rule} */ |
|
module.exports = { |
|
meta: { |
|
type: "problem", |
|
|
|
docs: { |
|
description: "Disallow assignments where both sides are exactly the same", |
|
recommended: true, |
|
url: "https://eslint.org/docs/rules/no-self-assign" |
|
}, |
|
|
|
schema: [ |
|
{ |
|
type: "object", |
|
properties: { |
|
props: { |
|
type: "boolean", |
|
default: true |
|
} |
|
}, |
|
additionalProperties: false |
|
} |
|
], |
|
|
|
messages: { |
|
selfAssignment: "'{{name}}' is assigned to itself." |
|
} |
|
}, |
|
|
|
create(context) { |
|
const sourceCode = context.getSourceCode(); |
|
const [{ props = true } = {}] = context.options; |
|
|
|
/** |
|
* Reports a given node as self assignments. |
|
* @param {ASTNode} node A node to report. This is an Identifier node. |
|
* @returns {void} |
|
*/ |
|
function report(node) { |
|
context.report({ |
|
node, |
|
messageId: "selfAssignment", |
|
data: { |
|
name: sourceCode.getText(node).replace(SPACES, "") |
|
} |
|
}); |
|
} |
|
|
|
return { |
|
AssignmentExpression(node) { |
|
if (["=", "&&=", "||=", "??="].includes(node.operator)) { |
|
eachSelfAssignment(node.left, node.right, props, report); |
|
} |
|
} |
|
}; |
|
} |
|
};
|
|
|