UNPKG

6.4 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 // Gets the index of the last spread property.
118 // It's possible to overwrite properties followed by it.
119 let startJ = 0;
120
121 for (let i = right.properties.length - 1; i >= 0; --i) {
122 if (right.properties[i].type === "ExperimentalSpreadProperty") {
123 startJ = i + 1;
124 break;
125 }
126 }
127
128 for (let i = 0; i < left.properties.length; ++i) {
129 for (let j = startJ; j < right.properties.length; ++j) {
130 eachSelfAssignment(
131 left.properties[i],
132 right.properties[j],
133 props,
134 report
135 );
136 }
137 }
138 } else if (
139 left.type === "Property" &&
140 right.type === "Property" &&
141 !left.computed &&
142 !right.computed &&
143 right.kind === "init" &&
144 !right.method &&
145 left.key.name === right.key.name
146 ) {
147 eachSelfAssignment(left.value, right.value, props, report);
148 } else if (
149 props &&
150 left.type === "MemberExpression" &&
151 right.type === "MemberExpression" &&
152 isSameMember(left, right)
153 ) {
154 report(right);
155 }
156}
157
158//------------------------------------------------------------------------------
159// Rule Definition
160//------------------------------------------------------------------------------
161
162module.exports = {
163 meta: {
164 docs: {
165 description: "disallow assignments where both sides are exactly the same",
166 category: "Best Practices",
167 recommended: true
168 },
169
170 schema: [
171 {
172 type: "object",
173 properties: {
174 props: {
175 type: "boolean"
176 }
177 },
178 additionalProperties: false
179 }
180 ]
181 },
182
183 create(context) {
184 const sourceCode = context.getSourceCode();
185 const options = context.options[0];
186 const props = Boolean(options && options.props);
187
188 /**
189 * Reports a given node as self assignments.
190 *
191 * @param {ASTNode} node - A node to report. This is an Identifier node.
192 * @returns {void}
193 */
194 function report(node) {
195 context.report({
196 node,
197 message: "'{{name}}' is assigned to itself.",
198 data: {
199 name: sourceCode.getText(node).replace(SPACES, "")
200 }
201 });
202 }
203
204 return {
205 AssignmentExpression(node) {
206 if (node.operator === "=") {
207 eachSelfAssignment(node.left, node.right, props, report);
208 }
209 }
210 };
211 }
212};