Skip to content

Commit

Permalink
feat: add destructuredArrayIgnorePattern option in no-unused-vars (
Browse files Browse the repository at this point in the history
…#15649)

* feat: add `destructuredArrayIgnorePattern` option in `no-unused-vars`

Fixes #15611

* docs: add `destructuredArrayIgnorePattern` option in `no-unused-vars`

* fix: remove false positives and false negatives

* docs: update

* test: add more cases

* docs: update

* fix: remove false positives

* docs: update

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

* feat: improve error message

* feat: cover more cases

* docs: add more example

* refactor: code

* fix: check for all references

* docs: add more examples

* chore: apply suggestions from code review

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
  • Loading branch information
snitin315 and mdjermanovic committed Mar 11, 2022
1 parent 8933fe7 commit 800bd25
Show file tree
Hide file tree
Showing 3 changed files with 265 additions and 1 deletion.
37 changes: 37 additions & 0 deletions docs/rules/no-unused-vars.md
Expand Up @@ -238,6 +238,43 @@ 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();

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

let _m, n;
foo.forEach(item => {
[_m, n] = item;
console.log(n);
});

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[0];
let additional = "";

if (config.destructuredArrayIgnorePattern && def && def.name.parent.type === "ArrayPattern") {
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.some(ref => ref.identifier.parent.type === "ArrayPattern");

// 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

0 comments on commit 800bd25

Please sign in to comment.