UNPKG

4.96 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)$/;
12
13module.exports = {
14 meta: {
15 docs: {
16 description: "disallow reassigning `function` parameters",
17 category: "Best Practices",
18 recommended: false
19 },
20
21 schema: [
22 {
23 type: "object",
24 properties: {
25 props: {type: "boolean"}
26 },
27 additionalProperties: false
28 }
29 ]
30 },
31
32 create(context) {
33 const props = context.options[0] && Boolean(context.options[0].props);
34
35 /**
36 * Checks whether or not the reference modifies properties of its variable.
37 * @param {Reference} reference - A reference to check.
38 * @returns {boolean} Whether or not the reference modifies properties of its variable.
39 */
40 function isModifyingProp(reference) {
41 let node = reference.identifier;
42 let parent = node.parent;
43
44 while (parent && !stopNodePattern.test(parent.type)) {
45 switch (parent.type) {
46
47 // e.g. foo.a = 0;
48 case "AssignmentExpression":
49 return parent.left === node;
50
51 // e.g. ++foo.a;
52 case "UpdateExpression":
53 return true;
54
55 // e.g. delete foo.a;
56 case "UnaryExpression":
57 if (parent.operator === "delete") {
58 return true;
59 }
60 break;
61
62 // EXCLUDES: e.g. cache.get(foo.a).b = 0;
63 case "CallExpression":
64 if (parent.callee !== node) {
65 return false;
66 }
67 break;
68
69 // EXCLUDES: e.g. cache[foo.a] = 0;
70 case "MemberExpression":
71 if (parent.property === node) {
72 return false;
73 }
74 break;
75
76 default:
77 break;
78 }
79
80 node = parent;
81 parent = node.parent;
82 }
83
84 return false;
85 }
86
87 /**
88 * Reports a reference if is non initializer and writable.
89 * @param {Reference} reference - A reference to check.
90 * @param {int} index - The index of the reference in the references.
91 * @param {Reference[]} references - The array that the reference belongs to.
92 * @returns {void}
93 */
94 function checkReference(reference, index, references) {
95 const identifier = reference.identifier;
96
97 if (identifier &&
98 !reference.init &&
99
100 // Destructuring assignments can have multiple default value,
101 // so possibly there are multiple writeable references for the same identifier.
102 (index === 0 || references[index - 1].identifier !== identifier)
103 ) {
104 if (reference.isWrite()) {
105 context.report(
106 identifier,
107 "Assignment to function parameter '{{name}}'.",
108 {name: identifier.name});
109 } else if (props && isModifyingProp(reference)) {
110 context.report(
111 identifier,
112 "Assignment to property of function parameter '{{name}}'.",
113 {name: identifier.name});
114 }
115 }
116 }
117
118 /**
119 * Finds and reports references that are non initializer and writable.
120 * @param {Variable} variable - A variable to check.
121 * @returns {void}
122 */
123 function checkVariable(variable) {
124 if (variable.defs[0].type === "Parameter") {
125 variable.references.forEach(checkReference);
126 }
127 }
128
129 /**
130 * Checks parameters of a given function node.
131 * @param {ASTNode} node - A function node to check.
132 * @returns {void}
133 */
134 function checkForFunction(node) {
135 context.getDeclaredVariables(node).forEach(checkVariable);
136 }
137
138 return {
139
140 // `:exit` is needed for the `node.parent` property of identifier nodes.
141 "FunctionDeclaration:exit": checkForFunction,
142 "FunctionExpression:exit": checkForFunction,
143 "ArrowFunctionExpression:exit": checkForFunction
144 };
145
146 }
147};