2 * @fileoverview Disallow reassignment of function parameters.
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
11 const stopNodePattern = /(?:Statement|Declaration|Function(?:Expression)?|Program)$/u;
18 description: "disallow reassigning `function` parameters",
19 category: "Best Practices",
21 url: "https://eslint.org/docs/rules/no-param-reassign"
34 additionalProperties: false
42 ignorePropertyModificationsFor: {
49 ignorePropertyModificationsForRegex: {
57 additionalProperties: false
65 const props = context.options[0] && context.options[0].props;
66 const ignoredPropertyAssignmentsFor = context.options[0] && context.options[0].ignorePropertyModificationsFor || [];
67 const ignoredPropertyAssignmentsForRegex = context.options[0] && context.options[0].ignorePropertyModificationsForRegex || [];
70 * Checks whether or not the reference modifies properties of its variable.
71 * @param {Reference} reference A reference to check.
72 * @returns {boolean} Whether or not the reference modifies properties of its variable.
74 function isModifyingProp(reference) {
75 let node = reference.identifier;
76 let parent = node.parent;
78 while (parent && (!stopNodePattern.test(parent.type) ||
79 parent.type === "ForInStatement" || parent.type === "ForOfStatement")) {
80 switch (parent.type) {
83 case "AssignmentExpression":
84 return parent.left === node;
87 case "UpdateExpression":
91 case "UnaryExpression":
92 if (parent.operator === "delete") {
97 // e.g. for (foo.a in b) {}
98 case "ForInStatement":
99 case "ForOfStatement":
100 if (parent.left === node) {
104 // this is a stop node for parent.right and parent.body
107 // EXCLUDES: e.g. cache.get(foo.a).b = 0;
108 case "CallExpression":
109 if (parent.callee !== node) {
114 // EXCLUDES: e.g. cache[foo.a] = 0;
115 case "MemberExpression":
116 if (parent.property === node) {
121 // EXCLUDES: e.g. ({ [foo]: a }) = bar;
123 if (parent.key === node) {
129 // EXCLUDES: e.g. (foo ? a : b).c = bar;
130 case "ConditionalExpression":
131 if (parent.test === node) {
141 parent = node.parent;
148 * Tests that an identifier name matches any of the ignored property assignments.
149 * First we test strings in ignoredPropertyAssignmentsFor.
150 * Then we instantiate and test RegExp objects from ignoredPropertyAssignmentsForRegex strings.
151 * @param {string} identifierName A string that describes the name of an identifier to
152 * ignore property assignments for.
153 * @returns {boolean} Whether the string matches an ignored property assignment regular expression or not.
155 function isIgnoredPropertyAssignment(identifierName) {
156 return ignoredPropertyAssignmentsFor.includes(identifierName) ||
157 ignoredPropertyAssignmentsForRegex.some(ignored => new RegExp(ignored, "u").test(identifierName));
161 * Reports a reference if is non initializer and writable.
162 * @param {Reference} reference A reference to check.
163 * @param {int} index The index of the reference in the references.
164 * @param {Reference[]} references The array that the reference belongs to.
167 function checkReference(reference, index, references) {
168 const identifier = reference.identifier;
174 * Destructuring assignments can have multiple default value,
175 * so possibly there are multiple writeable references for the same identifier.
177 (index === 0 || references[index - 1].identifier !== identifier)
179 if (reference.isWrite()) {
180 context.report({ node: identifier, message: "Assignment to function parameter '{{name}}'.", data: { name: identifier.name } });
181 } else if (props && isModifyingProp(reference) && !isIgnoredPropertyAssignment(identifier.name)) {
182 context.report({ node: identifier, message: "Assignment to property of function parameter '{{name}}'.", data: { name: identifier.name } });
188 * Finds and reports references that are non initializer and writable.
189 * @param {Variable} variable A variable to check.
192 function checkVariable(variable) {
193 if (variable.defs[0].type === "Parameter") {
194 variable.references.forEach(checkReference);
199 * Checks parameters of a given function node.
200 * @param {ASTNode} node A function node to check.
203 function checkForFunction(node) {
204 context.getDeclaredVariables(node).forEach(checkVariable);
209 // `:exit` is needed for the `node.parent` property of identifier nodes.
210 "FunctionDeclaration:exit": checkForFunction,
211 "FunctionExpression:exit": checkForFunction,
212 "ArrowFunctionExpression:exit": checkForFunction