diff --git a/docs/rules/no-param-reassign.md b/docs/rules/no-param-reassign.md index f88d211e22b..2ee312c2b81 100644 --- a/docs/rules/no-param-reassign.md +++ b/docs/rules/no-param-reassign.md @@ -42,7 +42,7 @@ function foo(bar) { ## Options -This rule takes one option, an object, with a boolean property `"props"` and an array `"ignorePropertyModificationsFor"`. `"props"` is `false` by default. If `"props"` is set to `true`, this rule warns against the modification of parameter properties unless they're included in `"ignorePropertyModificationsFor"`, which is an empty array by default. +This rule takes one option, an object, with a boolean property `"props"`, and arrays `"ignorePropertyModificationsFor"` and `"ignorePropertyModificationsForRegex"`. `"props"` is `false` by default. If `"props"` is set to `true`, this rule warns against the modification of parameter properties unless they're included in `"ignorePropertyModificationsFor"` or `"ignorePropertyModificationsForRegex"`, which is an empty array by default. ### props @@ -124,6 +124,24 @@ function foo(bar) { } ``` +Examples of **correct** code for the `{ "props": true }` option with `"ignorePropertyModificationsForRegex"` set: + +```js +/*eslint no-param-reassign: ["error", { "props": true, "ignorePropertyModificationsForRegex": ["^bar"] }]*/ + +function foo(bar) { + barVar.prop = "value"; +} + +function foo(bar) { + delete barrito.aaa; +} + +function foo(bar) { + bar_.aaa++; +} +``` + ## When Not To Use It diff --git a/lib/rules/no-param-reassign.js b/lib/rules/no-param-reassign.js index edaaef537eb..2d873e9e915 100644 --- a/lib/rules/no-param-reassign.js +++ b/lib/rules/no-param-reassign.js @@ -45,6 +45,13 @@ module.exports = { type: "string" }, uniqueItems: true + }, + ignorePropertyModificationsForRegex: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true } }, additionalProperties: false @@ -57,6 +64,7 @@ module.exports = { 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 || []; /** * Checks whether or not the reference modifies properties of its variable. @@ -136,6 +144,19 @@ module.exports = { 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. @@ -157,7 +178,7 @@ module.exports = { ) { if (reference.isWrite()) { context.report({ node: identifier, message: "Assignment to function parameter '{{name}}'.", data: { name: identifier.name } }); - } else if (props && isModifyingProp(reference) && ignoredPropertyAssignmentsFor.indexOf(identifier.name) === -1) { + } else if (props && isModifyingProp(reference) && !isIgnoredPropertyAssignment(identifier.name)) { context.report({ node: identifier, message: "Assignment to property of function parameter '{{name}}'.", data: { name: identifier.name } }); } } diff --git a/tests/lib/rules/no-param-reassign.js b/tests/lib/rules/no-param-reassign.js index fec1e4168f5..5bb70e6aa4c 100644 --- a/tests/lib/rules/no-param-reassign.js +++ b/tests/lib/rules/no-param-reassign.js @@ -45,6 +45,11 @@ ruleTester.run("no-param-reassign", rule, { { code: "function foo(a) { for (a.b of arr); }", options: [{ props: true, ignorePropertyModificationsFor: ["a"] }], parserOptions: { ecmaVersion: 6 } }, { code: "function foo(a, z) { a.b = 0; x.y = 0; }", options: [{ props: true, ignorePropertyModificationsFor: ["a", "x"] }] }, { code: "function foo(a) { a.b.c = 0;}", options: [{ props: true, ignorePropertyModificationsFor: ["a"] }] }, + { code: "function foo(aFoo) { aFoo.b = 0; }", options: [{ props: true, ignorePropertyModificationsForRegex: ["^a.*$"] }] }, + { code: "function foo(aFoo) { ++aFoo.b; }", options: [{ props: true, ignorePropertyModificationsForRegex: ["^a.*$"] }] }, + { code: "function foo(aFoo) { delete aFoo.b; }", options: [{ props: true, ignorePropertyModificationsForRegex: ["^a.*$"] }] }, + { code: "function foo(a, z) { aFoo.b = 0; x.y = 0; }", options: [{ props: true, ignorePropertyModificationsForRegex: ["^a.*$", "^x.*$"] }] }, + { code: "function foo(aFoo) { aFoo.b.c = 0;}", options: [{ props: true, ignorePropertyModificationsForRegex: ["^a.*$"] }] }, { code: "function foo(a) { ({ [a]: variable } = value) }", options: [{ props: true }], @@ -152,6 +157,18 @@ ruleTester.run("no-param-reassign", rule, { parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Assignment to property of function parameter 'bar'." }] }, + { + code: "function foo(bar) { [bar.a] = []; }", + options: [{ props: true, ignorePropertyModificationsForRegex: ["^a.*$"] }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Assignment to property of function parameter 'bar'." }] + }, + { + code: "function foo(bar) { [bar.a] = []; }", + options: [{ props: true, ignorePropertyModificationsForRegex: ["^B.*$"] }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ message: "Assignment to property of function parameter 'bar'." }] + }, { code: "function foo(bar) { ({foo: bar.a} = {}); }", options: [{ props: true }],