249 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			249 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
/**
 | 
						|
 * @fileoverview Disallow reassigning function parameters.
 | 
						|
 * @author Nat Burns
 | 
						|
 */
 | 
						|
"use strict";
 | 
						|
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
// Rule Definition
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
 | 
						|
const stopNodePattern =
 | 
						|
	/(?:Statement|Declaration|Function(?:Expression)?|Program)$/u;
 | 
						|
 | 
						|
/** @type {import('../types').Rule.RuleModule} */
 | 
						|
module.exports = {
 | 
						|
	meta: {
 | 
						|
		type: "suggestion",
 | 
						|
 | 
						|
		docs: {
 | 
						|
			description: "Disallow reassigning function parameters",
 | 
						|
			recommended: false,
 | 
						|
			url: "https://eslint.org/docs/latest/rules/no-param-reassign",
 | 
						|
		},
 | 
						|
 | 
						|
		schema: [
 | 
						|
			{
 | 
						|
				oneOf: [
 | 
						|
					{
 | 
						|
						type: "object",
 | 
						|
						properties: {
 | 
						|
							props: {
 | 
						|
								enum: [false],
 | 
						|
							},
 | 
						|
						},
 | 
						|
						additionalProperties: false,
 | 
						|
					},
 | 
						|
					{
 | 
						|
						type: "object",
 | 
						|
						properties: {
 | 
						|
							props: {
 | 
						|
								enum: [true],
 | 
						|
							},
 | 
						|
							ignorePropertyModificationsFor: {
 | 
						|
								type: "array",
 | 
						|
								items: {
 | 
						|
									type: "string",
 | 
						|
								},
 | 
						|
								uniqueItems: true,
 | 
						|
							},
 | 
						|
							ignorePropertyModificationsForRegex: {
 | 
						|
								type: "array",
 | 
						|
								items: {
 | 
						|
									type: "string",
 | 
						|
								},
 | 
						|
								uniqueItems: true,
 | 
						|
							},
 | 
						|
						},
 | 
						|
						additionalProperties: false,
 | 
						|
					},
 | 
						|
				],
 | 
						|
			},
 | 
						|
		],
 | 
						|
 | 
						|
		messages: {
 | 
						|
			assignmentToFunctionParam:
 | 
						|
				"Assignment to function parameter '{{name}}'.",
 | 
						|
			assignmentToFunctionParamProp:
 | 
						|
				"Assignment to property of function parameter '{{name}}'.",
 | 
						|
		},
 | 
						|
	},
 | 
						|
 | 
						|
	create(context) {
 | 
						|
		const props = context.options[0] && context.options[0].props;
 | 
						|
		const ignoredPropertyAssignmentsFor =
 | 
						|
			(context.options[0] &&
 | 
						|
				context.options[0].ignorePropertyModificationsFor) ||
 | 
						|
			[];
 | 
						|
		const ignoredPropertyAssignmentsForRegex =
 | 
						|
			(context.options[0] &&
 | 
						|
				context.options[0].ignorePropertyModificationsForRegex) ||
 | 
						|
			[];
 | 
						|
		const sourceCode = context.sourceCode;
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Checks whether or not the reference modifies properties of its variable.
 | 
						|
		 * @param {Reference} reference A reference to check.
 | 
						|
		 * @returns {boolean} Whether or not the reference modifies properties of its variable.
 | 
						|
		 */
 | 
						|
		function isModifyingProp(reference) {
 | 
						|
			let node = reference.identifier;
 | 
						|
			let parent = node.parent;
 | 
						|
 | 
						|
			while (
 | 
						|
				parent &&
 | 
						|
				(!stopNodePattern.test(parent.type) ||
 | 
						|
					parent.type === "ForInStatement" ||
 | 
						|
					parent.type === "ForOfStatement")
 | 
						|
			) {
 | 
						|
				switch (parent.type) {
 | 
						|
					// e.g. foo.a = 0;
 | 
						|
					case "AssignmentExpression":
 | 
						|
						return parent.left === node;
 | 
						|
 | 
						|
					// e.g. ++foo.a;
 | 
						|
					case "UpdateExpression":
 | 
						|
						return true;
 | 
						|
 | 
						|
					// e.g. delete foo.a;
 | 
						|
					case "UnaryExpression":
 | 
						|
						if (parent.operator === "delete") {
 | 
						|
							return true;
 | 
						|
						}
 | 
						|
						break;
 | 
						|
 | 
						|
					// e.g. for (foo.a in b) {}
 | 
						|
					case "ForInStatement":
 | 
						|
					case "ForOfStatement":
 | 
						|
						if (parent.left === node) {
 | 
						|
							return true;
 | 
						|
						}
 | 
						|
 | 
						|
						// this is a stop node for parent.right and parent.body
 | 
						|
						return false;
 | 
						|
 | 
						|
					// EXCLUDES: e.g. cache.get(foo.a).b = 0;
 | 
						|
					case "CallExpression":
 | 
						|
						if (parent.callee !== node) {
 | 
						|
							return false;
 | 
						|
						}
 | 
						|
						break;
 | 
						|
 | 
						|
					// EXCLUDES: e.g. cache[foo.a] = 0;
 | 
						|
					case "MemberExpression":
 | 
						|
						if (parent.property === node) {
 | 
						|
							return false;
 | 
						|
						}
 | 
						|
						break;
 | 
						|
 | 
						|
					// EXCLUDES: e.g. ({ [foo]: a }) = bar;
 | 
						|
					case "Property":
 | 
						|
						if (parent.key === node) {
 | 
						|
							return false;
 | 
						|
						}
 | 
						|
 | 
						|
						break;
 | 
						|
 | 
						|
					// EXCLUDES: e.g. (foo ? a : b).c = bar;
 | 
						|
					case "ConditionalExpression":
 | 
						|
						if (parent.test === node) {
 | 
						|
							return false;
 | 
						|
						}
 | 
						|
 | 
						|
						break;
 | 
						|
 | 
						|
					// no default
 | 
						|
				}
 | 
						|
 | 
						|
				node = parent;
 | 
						|
				parent = node.parent;
 | 
						|
			}
 | 
						|
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Tests that an identifier name matches any of the ignored property assignments.
 | 
						|
		 * First we test strings in ignoredPropertyAssignmentsFor.
 | 
						|
		 * Then we instantiate and test RegExp objects from ignoredPropertyAssignmentsForRegex strings.
 | 
						|
		 * @param {string} identifierName A string that describes the name of an identifier to
 | 
						|
		 * ignore property assignments for.
 | 
						|
		 * @returns {boolean} Whether the string matches an ignored property assignment regular expression or not.
 | 
						|
		 */
 | 
						|
		function isIgnoredPropertyAssignment(identifierName) {
 | 
						|
			return (
 | 
						|
				ignoredPropertyAssignmentsFor.includes(identifierName) ||
 | 
						|
				ignoredPropertyAssignmentsForRegex.some(ignored =>
 | 
						|
					new RegExp(ignored, "u").test(identifierName),
 | 
						|
				)
 | 
						|
			);
 | 
						|
		}
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Reports a reference if is non initializer and writable.
 | 
						|
		 * @param {Reference} reference A reference to check.
 | 
						|
		 * @param {number} index The index of the reference in the references.
 | 
						|
		 * @param {Reference[]} references The array that the reference belongs to.
 | 
						|
		 * @returns {void}
 | 
						|
		 */
 | 
						|
		function checkReference(reference, index, references) {
 | 
						|
			const identifier = reference.identifier;
 | 
						|
 | 
						|
			if (
 | 
						|
				identifier &&
 | 
						|
				!reference.init &&
 | 
						|
				/*
 | 
						|
				 * Destructuring assignments can have multiple default value,
 | 
						|
				 * so possibly there are multiple writeable references for the same identifier.
 | 
						|
				 */
 | 
						|
				(index === 0 || references[index - 1].identifier !== identifier)
 | 
						|
			) {
 | 
						|
				if (reference.isWrite()) {
 | 
						|
					context.report({
 | 
						|
						node: identifier,
 | 
						|
						messageId: "assignmentToFunctionParam",
 | 
						|
						data: { name: identifier.name },
 | 
						|
					});
 | 
						|
				} else if (
 | 
						|
					props &&
 | 
						|
					isModifyingProp(reference) &&
 | 
						|
					!isIgnoredPropertyAssignment(identifier.name)
 | 
						|
				) {
 | 
						|
					context.report({
 | 
						|
						node: identifier,
 | 
						|
						messageId: "assignmentToFunctionParamProp",
 | 
						|
						data: { name: identifier.name },
 | 
						|
					});
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Finds and reports references that are non initializer and writable.
 | 
						|
		 * @param {Variable} variable A variable to check.
 | 
						|
		 * @returns {void}
 | 
						|
		 */
 | 
						|
		function checkVariable(variable) {
 | 
						|
			if (variable.defs[0].type === "Parameter") {
 | 
						|
				variable.references.forEach(checkReference);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Checks parameters of a given function node.
 | 
						|
		 * @param {ASTNode} node A function node to check.
 | 
						|
		 * @returns {void}
 | 
						|
		 */
 | 
						|
		function checkForFunction(node) {
 | 
						|
			sourceCode.getDeclaredVariables(node).forEach(checkVariable);
 | 
						|
		}
 | 
						|
 | 
						|
		return {
 | 
						|
			// `:exit` is needed for the `node.parent` property of identifier nodes.
 | 
						|
			"FunctionDeclaration:exit": checkForFunction,
 | 
						|
			"FunctionExpression:exit": checkForFunction,
 | 
						|
			"ArrowFunctionExpression:exit": checkForFunction,
 | 
						|
		};
 | 
						|
	},
 | 
						|
};
 |