1 | /**
|
2 | * @fileoverview Disallow reassignment of function parameters.
|
3 | * @author Nat Burns
|
4 | * @copyright 2014 Nat Burns. All rights reserved.
|
5 | */
|
6 | ;
|
7 |
|
8 | //------------------------------------------------------------------------------
|
9 | // Rule Definition
|
10 | //------------------------------------------------------------------------------
|
11 |
|
12 | var stopNodePattern = /(?:Statement|Declaration|Function(?:Expression)?|Program)$/;
|
13 |
|
14 | module.exports = function(context) {
|
15 | var props = context.options[0] && Boolean(context.options[0].props);
|
16 |
|
17 | /**
|
18 | * Checks whether or not a reference modifies its variable.
|
19 | * If the `props` option is `true`, this checks whether or not the reference modifies properties of its variable also.
|
20 | * @param {Reference} reference - A reference to check.
|
21 | * @returns {boolean} Whether or not the reference modifies its variable.
|
22 | */
|
23 | function isModifying(reference) {
|
24 | if (reference.isWrite()) {
|
25 | return true;
|
26 | }
|
27 |
|
28 | // Checks whether its property is modified.
|
29 | if (props) {
|
30 | var node = reference.identifier;
|
31 | var parent = node.parent;
|
32 | while (parent && !stopNodePattern.test(parent.type)) {
|
33 | switch (parent.type) {
|
34 | // e.g. foo.a = 0;
|
35 | case "AssignmentExpression":
|
36 | return parent.left === node;
|
37 |
|
38 | // e.g. ++foo.a;
|
39 | case "UpdateExpression":
|
40 | return true;
|
41 |
|
42 | // e.g. delete foo.a;
|
43 | case "UnaryExpression":
|
44 | if (parent.operator === "delete") {
|
45 | return true;
|
46 | }
|
47 | break;
|
48 |
|
49 | // EXCLUDES: e.g. cache.get(foo.a).b = 0;
|
50 | case "CallExpression":
|
51 | if (parent.callee !== node) {
|
52 | return false;
|
53 | }
|
54 | break;
|
55 |
|
56 | // EXCLUDES: e.g. cache[foo.a] = 0;
|
57 | case "MemberExpression":
|
58 | if (parent.property === node) {
|
59 | return false;
|
60 | }
|
61 | break;
|
62 |
|
63 | default:
|
64 | break;
|
65 | }
|
66 |
|
67 | node = parent;
|
68 | parent = parent.parent;
|
69 | }
|
70 | }
|
71 |
|
72 | return false;
|
73 | }
|
74 |
|
75 | /**
|
76 | * Reports a reference if is non initializer and writable.
|
77 | * @param {Reference} reference - A reference to check.
|
78 | * @param {int} index - The index of the reference in the references.
|
79 | * @param {Reference[]} references - The array that the reference belongs to.
|
80 | * @returns {void}
|
81 | */
|
82 | function checkReference(reference, index, references) {
|
83 | var identifier = reference.identifier;
|
84 |
|
85 | if (identifier &&
|
86 | !reference.init &&
|
87 | isModifying(reference) &&
|
88 | // Destructuring assignments can have multiple default value,
|
89 | // so possibly there are multiple writeable references for the same identifier.
|
90 | (index === 0 || references[index - 1].identifier !== identifier)
|
91 | ) {
|
92 | context.report(
|
93 | identifier,
|
94 | "Assignment to function parameter '{{name}}'.",
|
95 | {name: identifier.name});
|
96 | }
|
97 | }
|
98 |
|
99 | /**
|
100 | * Finds and reports references that are non initializer and writable.
|
101 | * @param {Variable} variable - A variable to check.
|
102 | * @returns {void}
|
103 | */
|
104 | function checkVariable(variable) {
|
105 | if (variable.defs[0].type === "Parameter") {
|
106 | variable.references.forEach(checkReference);
|
107 | }
|
108 | }
|
109 |
|
110 | /**
|
111 | * Checks parameters of a given function node.
|
112 | * @param {ASTNode} node - A function node to check.
|
113 | * @returns {void}
|
114 | */
|
115 | function checkForFunction(node) {
|
116 | context.getDeclaredVariables(node).forEach(checkVariable);
|
117 | }
|
118 |
|
119 | return {
|
120 | // `:exit` is needed for the `node.parent` property of identifier nodes.
|
121 | "FunctionDeclaration:exit": checkForFunction,
|
122 | "FunctionExpression:exit": checkForFunction,
|
123 | "ArrowFunctionExpression:exit": checkForFunction
|
124 | };
|
125 |
|
126 | };
|
127 |
|
128 | module.exports.schema = [
|
129 | {
|
130 | "type": "object",
|
131 | "properties": {
|
132 | "props": {"type": "boolean"}
|
133 | },
|
134 | "additionalProperties": false
|
135 | }
|
136 | ];
|