UNPKG

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