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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add destructuredArrayIgnorePattern option in no-unused-vars #15649

Merged
merged 15 commits into from Mar 11, 2022
38 changes: 38 additions & 0 deletions docs/rules/no-unused-vars.md
Expand Up @@ -238,6 +238,44 @@ function foo(x, _y) {
foo();
```

### destructuredArrayIgnorePattern

The `destructuredArrayIgnorePattern` option specifies exceptions not to check for usage: elements of array destructuring patterns whose names match a regexp pattern. For example, variables whose names begin with an underscore.

Examples of **correct** code for the `{ "destructuredArrayIgnorePattern": "^_" }` option:

```js
/*eslint no-unused-vars: ["error", { "destructuredArrayIgnorePattern": "^_" }]*/

const [a, _b, c] = ["a", "b", "c"];
console.log(a+c);

const { x: [_a, foo] } = bar;
console.log(foo);

function baz([_c, x]) {
x;
}
baz();

Comment on lines +250 to +260
Copy link
Sponsor Contributor

@ljharb ljharb Mar 10, 2022

Choose a reason for hiding this comment

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

i'm confused. in an array pattern, you never need an unused variable, you just omit the binding - ie, [, x] etc. Why would you ever want an exception here?

It's only in object destructuring that omitting the binding has a use case.

Copy link
Member

Choose a reason for hiding this comment

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

The rationale for this option is in Additional Comments of the original post in #15611.

This is optional behavior, unused variables in array patterns are not ignored by default. Users who prefer elisions can simply not enable this option.

Copy link
Sponsor Contributor

Choose a reason for hiding this comment

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

So, this is a style option, added after the prohibition on such things?

function test({p: [_q, r]}) {
r;
}
test();

let _m, n;

foo.forEach(item => {
[_m, n] = item;
console.log(n);
});
snitin315 marked this conversation as resolved.
Show resolved Hide resolved

let _o, p;
_o = 1;
[_o, p] = foo;
p;
```

### caughtErrors

The `caughtErrors` option is used for `catch` block arguments validation.
Expand Down
29 changes: 28 additions & 1 deletion lib/rules/no-unused-vars.js
Expand Up @@ -67,6 +67,9 @@ module.exports = {
},
caughtErrorsIgnorePattern: {
type: "string"
},
destructuredArrayIgnorePattern: {
type: "string"
}
},
additionalProperties: false
Expand Down Expand Up @@ -114,6 +117,10 @@ module.exports = {
if (firstOption.caughtErrorsIgnorePattern) {
config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern, "u");
}

if (firstOption.destructuredArrayIgnorePattern) {
config.destructuredArrayIgnorePattern = new RegExp(firstOption.destructuredArrayIgnorePattern, "u");
}
}
}

Expand Down Expand Up @@ -155,7 +162,14 @@ module.exports = {
* @returns {UnusedVarMessageData} The message data to be used with this unused variable.
*/
function getAssignedMessageData(unusedVar) {
const additional = config.varsIgnorePattern ? `. Allowed unused vars must match ${config.varsIgnorePattern.toString()}` : "";
const def = unusedVar.defs && unusedVar.defs[0];
snitin315 marked this conversation as resolved.
Show resolved Hide resolved
let additional = "";

if (config.destructuredArrayIgnorePattern && def.name.parent.type === "ArrayPattern") {
snitin315 marked this conversation as resolved.
Show resolved Hide resolved
additional = `. Allowed unused elements of array destructuring patterns must match ${config.destructuredArrayIgnorePattern.toString()}`;
} else if (config.varsIgnorePattern) {
additional = `. Allowed unused vars must match ${config.varsIgnorePattern.toString()}`;
}

return {
varName: unusedVar.name,
Expand Down Expand Up @@ -584,6 +598,19 @@ module.exports = {

if (def) {
const type = def.type;
const refUsedInArrayPatterns = variable.references && variable.references.some(ref => ref.identifier.parent.type === "ArrayPattern");
snitin315 marked this conversation as resolved.
Show resolved Hide resolved

// skip elements of array destructuring patterns
if (
(
def.name.parent.type === "ArrayPattern" ||
refUsedInArrayPatterns
) &&
config.destructuredArrayIgnorePattern &&
config.destructuredArrayIgnorePattern.test(def.name.name)
) {
continue;
}

// skip catch variables
if (type === "CatchClause") {
Expand Down
200 changes: 200 additions & 0 deletions tests/lib/rules/no-unused-vars.js
Expand Up @@ -162,6 +162,81 @@ ruleTester.run("no-unused-vars", rule, {
{ code: "function foo(_a) { } foo();", options: [{ args: "all", argsIgnorePattern: "^_" }] },
{ code: "function foo(a, _b) { return a; } foo();", options: [{ args: "after-used", argsIgnorePattern: "^_" }] },
{ code: "var [ firstItemIgnored, secondItem ] = items;\nconsole.log(secondItem);", options: [{ vars: "all", varsIgnorePattern: "[iI]gnored" }], parserOptions: { ecmaVersion: 6 } },
{
code: "const [ a, _b, c ] = items;\nconsole.log(a+c);",
options: [{ destructuredArrayIgnorePattern: "^_" }],
parserOptions: { ecmaVersion: 6 }
},
{
code: "const [ [a, _b, c] ] = items;\nconsole.log(a+c);",
options: [{ destructuredArrayIgnorePattern: "^_" }],
parserOptions: { ecmaVersion: 6 }
},
{
code: "const { x: [_a, foo] } = bar;\nconsole.log(foo);",
options: [{ destructuredArrayIgnorePattern: "^_" }],
parserOptions: { ecmaVersion: 6 }
},
{
code: "function baz([_b, foo]) { foo; };\nbaz()",
options: [{ destructuredArrayIgnorePattern: "^_" }],
parserOptions: { ecmaVersion: 6 }
},
{
code: "function baz({x: [_b, foo]}) {foo};\nbaz()",
options: [{ destructuredArrayIgnorePattern: "^_" }],
parserOptions: { ecmaVersion: 6 }
},
{
code: "function baz([{x: [_b, foo]}]) {foo};\nbaz()",
options: [{ destructuredArrayIgnorePattern: "^_" }],
parserOptions: { ecmaVersion: 6 }
},
{
code: `
let _a, b;
foo.forEach(item => {
[_a, b] = item;
doSomething(b);
});
`,
options: [{ destructuredArrayIgnorePattern: "^_" }],
parserOptions: { ecmaVersion: 6 }
},
{
code: `
// doesn't report _x
let _x, y;
_x = 1;
[_x, y] = foo;
y;

// doesn't report _a
let _a, b;
[_a, b] = foo;
_a = 1;
b;
`,
options: [{ destructuredArrayIgnorePattern: "^_" }],
parserOptions: { ecmaVersion: 2018 }
},
{
code: `
// doesn't report _x
let _x, y;
_x = 1;
[_x, y] = foo;
y;

// doesn't report _a
let _a, b;
_a = 1;
({_a, ...b } = foo);
b;
`,
options: [{ destructuredArrayIgnorePattern: "^_", ignoreRestSiblings: true }],
parserOptions: { ecmaVersion: 2018 }
},

// for-in loops (see #2342)
"(function(obj) { var name; for ( name in obj ) return; })({});",
Expand Down Expand Up @@ -463,6 +538,131 @@ ruleTester.run("no-unused-vars", rule, {
}]
},

// https://github.com/eslint/eslint/issues/15611
{
code: `
const array = ['a', 'b', 'c'];
const [a, _b, c] = array;
const newArray = [a, c];
`,
options: [{ destructuredArrayIgnorePattern: "^_" }],
parserOptions: { ecmaVersion: 2020 },
errors: [

// should report only `newArray`
{ ...assignedError("newArray"), line: 4, column: 19 }
]
},
{
code: `
const array = ['a', 'b', 'c', 'd', 'e'];
const [a, _b, c] = array;
`,
options: [{ destructuredArrayIgnorePattern: "^_" }],
parserOptions: { ecmaVersion: 2020 },
errors: [
{
...assignedError("a", ". Allowed unused elements of array destructuring patterns must match /^_/u"),
line: 3,
column: 20
},
{
...assignedError("c", ". Allowed unused elements of array destructuring patterns must match /^_/u"),
line: 3,
column: 27
}
]
},
{
code: `
const array = ['a', 'b', 'c'];
const [a, _b, c] = array;
const fooArray = ['foo'];
const barArray = ['bar'];
const ignoreArray = ['ignore'];
`,
options: [{ destructuredArrayIgnorePattern: "^_", varsIgnorePattern: "ignore" }],
parserOptions: { ecmaVersion: 2020 },
errors: [
{
...assignedError("a", ". Allowed unused elements of array destructuring patterns must match /^_/u"),
line: 3,
column: 20
},
{
...assignedError("c", ". Allowed unused elements of array destructuring patterns must match /^_/u"),
line: 3,
column: 27
},
{
...assignedError("fooArray", ". Allowed unused vars must match /ignore/u"),
line: 4,
column: 19
},
{
...assignedError("barArray", ". Allowed unused vars must match /ignore/u"),
line: 5,
column: 19
}
]
},
{
code: `
const array = [obj];
const [{_a, foo}] = array;
console.log(foo);
`,
options: [{ destructuredArrayIgnorePattern: "^_" }],
parserOptions: { ecmaVersion: 2020 },
errors: [
{
...assignedError("_a"),
line: 3,
column: 21
}
]
},
{
code: `
function foo([{_a, bar}]) {
bar;
}
foo();
`,
options: [{ destructuredArrayIgnorePattern: "^_" }],
parserOptions: { ecmaVersion: 2020 },
errors: [
{
...definedError("_a"),
line: 2,
column: 28
}
]
},
{
code: `
let _a, b;

foo.forEach(item => {
[a, b] = item;
});
`,
options: [{ destructuredArrayIgnorePattern: "^_" }],
parserOptions: { ecmaVersion: 2020 },
errors: [
{
...definedError("_a"),
line: 2,
column: 17
},
{
...assignedError("b"),
line: 2,
column: 21
}
]
},

// for-in loops (see #2342)
{
code: "(function(obj) { var name; for ( name in obj ) { i(); return; } })({});",
Expand Down