UNPKG

8.56 kBJavaScriptView Raw
1/**
2 * @fileoverview Disallow reassignment of function parameters.
3 * @author Nat Burns
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Rule Definition
9//------------------------------------------------------------------------------
10
11const stopNodePattern = /(?:Statement|Declaration|Function(?:Expression)?|Program)$/u;
12
13module.exports = {
14 meta: {
15 type: "suggestion",
16
17 docs: {
18 description: "disallow reassigning `function` parameters",
19 category: "Best Practices",
20 recommended: false,
21 url: "https://eslint.org/docs/rules/no-param-reassign"
22 },
23
24 schema: [
25 {
26 oneOf: [
27 {
28 type: "object",
29 properties: {
30 props: {
31 enum: [false]
32 }
33 },
34 additionalProperties: false
35 },
36 {
37 type: "object",
38 properties: {
39 props: {
40 enum: [true]
41 },
42 ignorePropertyModificationsFor: {
43 type: "array",
44 items: {
45 type: "string"
46 },
47 uniqueItems: true
48 },
49 ignorePropertyModificationsForRegex: {
50 type: "array",
51 items: {
52 type: "string"
53 },
54 uniqueItems: true
55 }
56 },
57 additionalProperties: false
58 }
59 ]
60 }
61 ],
62
63 messages: {
64 assignmentToFunctionParam: "Assignment to function parameter '{{name}}'.",
65 assignmentToFunctionParamProp: "Assignment to property of function parameter '{{name}}'."
66 }
67 },
68
69 create(context) {
70 const props = context.options[0] && context.options[0].props;
71 const ignoredPropertyAssignmentsFor = context.options[0] && context.options[0].ignorePropertyModificationsFor || [];
72 const ignoredPropertyAssignmentsForRegex = context.options[0] && context.options[0].ignorePropertyModificationsForRegex || [];
73
74 /**
75 * Checks whether or not the reference modifies properties of its variable.
76 * @param {Reference} reference A reference to check.
77 * @returns {boolean} Whether or not the reference modifies properties of its variable.
78 */
79 function isModifyingProp(reference) {
80 let node = reference.identifier;
81 let parent = node.parent;
82
83 while (parent && (!stopNodePattern.test(parent.type) ||
84 parent.type === "ForInStatement" || parent.type === "ForOfStatement")) {
85 switch (parent.type) {
86
87 // e.g. foo.a = 0;
88 case "AssignmentExpression":
89 return parent.left === node;
90
91 // e.g. ++foo.a;
92 case "UpdateExpression":
93 return true;
94
95 // e.g. delete foo.a;
96 case "UnaryExpression":
97 if (parent.operator === "delete") {
98 return true;
99 }
100 break;
101
102 // e.g. for (foo.a in b) {}
103 case "ForInStatement":
104 case "ForOfStatement":
105 if (parent.left === node) {
106 return true;
107 }
108
109 // this is a stop node for parent.right and parent.body
110 return false;
111
112 // EXCLUDES: e.g. cache.get(foo.a).b = 0;
113 case "CallExpression":
114 if (parent.callee !== node) {
115 return false;
116 }
117 break;
118
119 // EXCLUDES: e.g. cache[foo.a] = 0;
120 case "MemberExpression":
121 if (parent.property === node) {
122 return false;
123 }
124 break;
125
126 // EXCLUDES: e.g. ({ [foo]: a }) = bar;
127 case "Property":
128 if (parent.key === node) {
129 return false;
130 }
131
132 break;
133
134 // EXCLUDES: e.g. (foo ? a : b).c = bar;
135 case "ConditionalExpression":
136 if (parent.test === node) {
137 return false;
138 }
139
140 break;
141
142 // no default
143 }
144
145 node = parent;
146 parent = node.parent;
147 }
148
149 return false;
150 }
151
152 /**
153 * Tests that an identifier name matches any of the ignored property assignments.
154 * First we test strings in ignoredPropertyAssignmentsFor.
155 * Then we instantiate and test RegExp objects from ignoredPropertyAssignmentsForRegex strings.
156 * @param {string} identifierName A string that describes the name of an identifier to
157 * ignore property assignments for.
158 * @returns {boolean} Whether the string matches an ignored property assignment regular expression or not.
159 */
160 function isIgnoredPropertyAssignment(identifierName) {
161 return ignoredPropertyAssignmentsFor.includes(identifierName) ||
162 ignoredPropertyAssignmentsForRegex.some(ignored => new RegExp(ignored, "u").test(identifierName));
163 }
164
165 /**
166 * Reports a reference if is non initializer and writable.
167 * @param {Reference} reference A reference to check.
168 * @param {int} index The index of the reference in the references.
169 * @param {Reference[]} references The array that the reference belongs to.
170 * @returns {void}
171 */
172 function checkReference(reference, index, references) {
173 const identifier = reference.identifier;
174
175 if (identifier &&
176 !reference.init &&
177
178 /*
179 * Destructuring assignments can have multiple default value,
180 * so possibly there are multiple writeable references for the same identifier.
181 */
182 (index === 0 || references[index - 1].identifier !== identifier)
183 ) {
184 if (reference.isWrite()) {
185 context.report({
186 node: identifier,
187 messageId: "assignmentToFunctionParam",
188 data: { name: identifier.name }
189 });
190 } else if (props && isModifyingProp(reference) && !isIgnoredPropertyAssignment(identifier.name)) {
191 context.report({
192 node: identifier,
193 messageId: "assignmentToFunctionParamProp",
194 data: { name: identifier.name }
195 });
196 }
197 }
198 }
199
200 /**
201 * Finds and reports references that are non initializer and writable.
202 * @param {Variable} variable A variable to check.
203 * @returns {void}
204 */
205 function checkVariable(variable) {
206 if (variable.defs[0].type === "Parameter") {
207 variable.references.forEach(checkReference);
208 }
209 }
210
211 /**
212 * Checks parameters of a given function node.
213 * @param {ASTNode} node A function node to check.
214 * @returns {void}
215 */
216 function checkForFunction(node) {
217 context.getDeclaredVariables(node).forEach(checkVariable);
218 }
219
220 return {
221
222 // `:exit` is needed for the `node.parent` property of identifier nodes.
223 "FunctionDeclaration:exit": checkForFunction,
224 "FunctionExpression:exit": checkForFunction,
225 "ArrowFunctionExpression:exit": checkForFunction
226 };
227
228 }
229};