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