[d565449] | 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 | };
|
---|