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