summaryrefslogtreecommitdiff
path: root/tools/eslint/lib/rules/no-param-reassign.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/eslint/lib/rules/no-param-reassign.js')
-rw-r--r--tools/eslint/lib/rules/no-param-reassign.js147
1 files changed, 98 insertions, 49 deletions
diff --git a/tools/eslint/lib/rules/no-param-reassign.js b/tools/eslint/lib/rules/no-param-reassign.js
index c425246535..6bfa681afb 100644
--- a/tools/eslint/lib/rules/no-param-reassign.js
+++ b/tools/eslint/lib/rules/no-param-reassign.js
@@ -9,79 +9,128 @@
// Rule Definition
//------------------------------------------------------------------------------
-module.exports = function(context) {
+var stopNodePattern = /(?:Statement|Declaration|Function(?:Expression)?|Program)$/;
- //--------------------------------------------------------------------------
- // Helpers
- //--------------------------------------------------------------------------
+module.exports = function(context) {
+ var props = context.options[0] && Boolean(context.options[0].props);
/**
- * Finds the declaration for a given variable by name, searching up the scope tree.
- * @param {Scope} scope The scope in which to search.
- * @param {String} name The name of the variable.
- * @returns {Variable} The declaration information for the given variable, or null if no declaration was found.
+ * Checks whether or not a reference modifies its variable.
+ * If the `props` option is `true`, this checks whether or not the reference modifies properties of its variable also.
+ * @param {Reference} reference - A reference to check.
+ * @returns {boolean} Whether or not the reference modifies its variable.
*/
- function findDeclaration(scope, name) {
- var variables = scope.variables;
+ function isModifying(reference) {
+ if (reference.isWrite()) {
+ return true;
+ }
+
+ // Checks whether its property is modified.
+ if (props) {
+ var node = reference.identifier;
+ var parent = node.parent;
+ while (parent && !stopNodePattern.test(parent.type)) {
+ 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;
+
+ // EXCLUDES: e.g. cache.get(foo.a).b = 0;
+ case "CallExpression":
+ if (parent.callee !== node) {
+ return false;
+ }
+ break;
- for (var i = 0; i < variables.length; i++) {
- if (variables[i].name === name) {
- return variables[i];
+ // EXCLUDES: e.g. cache[foo.a] = 0;
+ case "MemberExpression":
+ if (parent.property === node) {
+ return false;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ node = parent;
+ parent = parent.parent;
}
}
- if (scope.upper) {
- return findDeclaration(scope.upper, name);
- } else {
- return null;
- }
+ return false;
}
/**
- * Determines if a given variable is declared as a function parameter.
- * @param {Variable} variable The variable declaration.
- * @returns {boolean} True if the variable is a function parameter, false otherwise.
+ * Reports a reference if is non initializer and writable.
+ * @param {Reference} reference - A reference to check.
+ * @param {int} index - The index of the reference in the references.
+ * @param {Reference[]} references - The array that the reference belongs to.
+ * @returns {void}
*/
- function isParameter(variable) {
- var defs = variable.defs;
+ function checkReference(reference, index, references) {
+ var identifier = reference.identifier;
- for (var i = 0; i < defs.length; i++) {
- if (defs[i].type === "Parameter") {
- return true;
- }
+ if (identifier &&
+ !reference.init &&
+ isModifying(reference) &&
+ // 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)
+ ) {
+ context.report(
+ identifier,
+ "Assignment to function parameter '{{name}}'.",
+ {name: identifier.name});
}
-
- return false;
}
/**
- * Checks whether a given node is an assignment to a function parameter.
- * If so, a linting error will be reported.
- * @param {ASTNode} node The node to check.
- * @param {String} name The name of the variable being assigned to.
+ * Finds and reports references that are non initializer and writable.
+ * @param {Variable} variable - A variable to check.
* @returns {void}
*/
- function checkParameter(node, name) {
- var declaration = findDeclaration(context.getScope(), name);
-
- if (declaration && isParameter(declaration)) {
- context.report(node, "Assignment to function parameter '{{name}}'.", { name: name });
+ function checkVariable(variable) {
+ if (variable.defs[0].type === "Parameter") {
+ variable.references.forEach(checkReference);
}
}
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
+ /**
+ * Checks parameters of a given function node.
+ * @param {ASTNode} node - A function node to check.
+ * @returns {void}
+ */
+ function checkForFunction(node) {
+ context.getDeclaredVariables(node).forEach(checkVariable);
+ }
return {
- "AssignmentExpression": function(node) {
- checkParameter(node, node.left.name);
- },
-
- "UpdateExpression": function(node) {
- checkParameter(node, node.argument.name);
- }
+ // `:exit` is needed for the `node.parent` property of identifier nodes.
+ "FunctionDeclaration:exit": checkForFunction,
+ "FunctionExpression:exit": checkForFunction,
+ "ArrowFunctionExpression:exit": checkForFunction
};
+
};
-module.exports.schema = [];
+module.exports.schema = [
+ {
+ "type": "object",
+ "properties": {
+ "props": {"type": "boolean"}
+ },
+ "additionalProperties": false
+ }
+];