Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update: Allow regex in no-param-reassign ignore option array #11275

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 19 additions & 1 deletion docs/rules/no-param-reassign.md
Expand Up @@ -34,7 +34,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

Expand Down Expand Up @@ -92,6 +92,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++;
}
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we please also add for-in and for-of examples? Just to make this example consistent with other examples from the actual version.



## When Not To Use It

Expand Down
23 changes: 22 additions & 1 deletion lib/rules/no-param-reassign.js
Expand Up @@ -45,6 +45,13 @@ module.exports = {
type: "string"
},
uniqueItems: true
},
ignorePropertyModificationsForRegex: {
type: "array",
items: {
type: "string"
},
uniqueItems: true
}
},
additionalProperties: false
Expand All @@ -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.
Expand Down Expand Up @@ -125,6 +133,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.
Expand All @@ -146,7 +167,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 } });
}
}
Expand Down
17 changes: 17 additions & 0 deletions tests/lib/rules/no-param-reassign.js
Expand Up @@ -39,6 +39,11 @@ ruleTester.run("no-param-reassign", rule, {
{ code: "function foo(a) { delete a.b; }", options: [{ props: true, ignorePropertyModificationsFor: ["a"] }] },
{ 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.*$"] }] },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to have an additional valid test to show that the two options do not intentionally (or unintentionally) disable each other.

For example, a test where props from two different params are modified, one modification is allowed because of ignorePropertyModificationsFor, the other because of ignorePropertyModificationsForRegex.

{
code: "function foo(a) { ({ [a]: variable } = value) }",
options: [{ props: true }],
Expand Down Expand Up @@ -106,6 +111,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 }],
Expand Down