Skip to content

Commit 800bd25

Browse files
feat: add destructuredArrayIgnorePattern option in no-unused-vars (#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>
1 parent 8933fe7 commit 800bd25

File tree

3 files changed

+265
-1
lines changed

3 files changed

+265
-1
lines changed

docs/rules/no-unused-vars.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,43 @@ function foo(x, _y) {
238238
foo();
239239
```
240240

241+
### destructuredArrayIgnorePattern
242+
243+
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.
244+
245+
Examples of **correct** code for the `{ "destructuredArrayIgnorePattern": "^_" }` option:
246+
247+
```js
248+
/*eslint no-unused-vars: ["error", { "destructuredArrayIgnorePattern": "^_" }]*/
249+
250+
const [a, _b, c] = ["a", "b", "c"];
251+
console.log(a+c);
252+
253+
const { x: [_a, foo] } = bar;
254+
console.log(foo);
255+
256+
function baz([_c, x]) {
257+
x;
258+
}
259+
baz();
260+
261+
function test({p: [_q, r]}) {
262+
r;
263+
}
264+
test();
265+
266+
let _m, n;
267+
foo.forEach(item => {
268+
[_m, n] = item;
269+
console.log(n);
270+
});
271+
272+
let _o, p;
273+
_o = 1;
274+
[_o, p] = foo;
275+
p;
276+
```
277+
241278
### caughtErrors
242279

243280
The `caughtErrors` option is used for `catch` block arguments validation.

lib/rules/no-unused-vars.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ module.exports = {
6767
},
6868
caughtErrorsIgnorePattern: {
6969
type: "string"
70+
},
71+
destructuredArrayIgnorePattern: {
72+
type: "string"
7073
}
7174
},
7275
additionalProperties: false
@@ -114,6 +117,10 @@ module.exports = {
114117
if (firstOption.caughtErrorsIgnorePattern) {
115118
config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern, "u");
116119
}
120+
121+
if (firstOption.destructuredArrayIgnorePattern) {
122+
config.destructuredArrayIgnorePattern = new RegExp(firstOption.destructuredArrayIgnorePattern, "u");
123+
}
117124
}
118125
}
119126

@@ -155,7 +162,14 @@ module.exports = {
155162
* @returns {UnusedVarMessageData} The message data to be used with this unused variable.
156163
*/
157164
function getAssignedMessageData(unusedVar) {
158-
const additional = config.varsIgnorePattern ? `. Allowed unused vars must match ${config.varsIgnorePattern.toString()}` : "";
165+
const def = unusedVar.defs[0];
166+
let additional = "";
167+
168+
if (config.destructuredArrayIgnorePattern && def && def.name.parent.type === "ArrayPattern") {
169+
additional = `. Allowed unused elements of array destructuring patterns must match ${config.destructuredArrayIgnorePattern.toString()}`;
170+
} else if (config.varsIgnorePattern) {
171+
additional = `. Allowed unused vars must match ${config.varsIgnorePattern.toString()}`;
172+
}
159173

160174
return {
161175
varName: unusedVar.name,
@@ -584,6 +598,19 @@ module.exports = {
584598

585599
if (def) {
586600
const type = def.type;
601+
const refUsedInArrayPatterns = variable.references.some(ref => ref.identifier.parent.type === "ArrayPattern");
602+
603+
// skip elements of array destructuring patterns
604+
if (
605+
(
606+
def.name.parent.type === "ArrayPattern" ||
607+
refUsedInArrayPatterns
608+
) &&
609+
config.destructuredArrayIgnorePattern &&
610+
config.destructuredArrayIgnorePattern.test(def.name.name)
611+
) {
612+
continue;
613+
}
587614

588615
// skip catch variables
589616
if (type === "CatchClause") {

tests/lib/rules/no-unused-vars.js

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,81 @@ ruleTester.run("no-unused-vars", rule, {
162162
{ code: "function foo(_a) { } foo();", options: [{ args: "all", argsIgnorePattern: "^_" }] },
163163
{ code: "function foo(a, _b) { return a; } foo();", options: [{ args: "after-used", argsIgnorePattern: "^_" }] },
164164
{ code: "var [ firstItemIgnored, secondItem ] = items;\nconsole.log(secondItem);", options: [{ vars: "all", varsIgnorePattern: "[iI]gnored" }], parserOptions: { ecmaVersion: 6 } },
165+
{
166+
code: "const [ a, _b, c ] = items;\nconsole.log(a+c);",
167+
options: [{ destructuredArrayIgnorePattern: "^_" }],
168+
parserOptions: { ecmaVersion: 6 }
169+
},
170+
{
171+
code: "const [ [a, _b, c] ] = items;\nconsole.log(a+c);",
172+
options: [{ destructuredArrayIgnorePattern: "^_" }],
173+
parserOptions: { ecmaVersion: 6 }
174+
},
175+
{
176+
code: "const { x: [_a, foo] } = bar;\nconsole.log(foo);",
177+
options: [{ destructuredArrayIgnorePattern: "^_" }],
178+
parserOptions: { ecmaVersion: 6 }
179+
},
180+
{
181+
code: "function baz([_b, foo]) { foo; };\nbaz()",
182+
options: [{ destructuredArrayIgnorePattern: "^_" }],
183+
parserOptions: { ecmaVersion: 6 }
184+
},
185+
{
186+
code: "function baz({x: [_b, foo]}) {foo};\nbaz()",
187+
options: [{ destructuredArrayIgnorePattern: "^_" }],
188+
parserOptions: { ecmaVersion: 6 }
189+
},
190+
{
191+
code: "function baz([{x: [_b, foo]}]) {foo};\nbaz()",
192+
options: [{ destructuredArrayIgnorePattern: "^_" }],
193+
parserOptions: { ecmaVersion: 6 }
194+
},
195+
{
196+
code: `
197+
let _a, b;
198+
foo.forEach(item => {
199+
[_a, b] = item;
200+
doSomething(b);
201+
});
202+
`,
203+
options: [{ destructuredArrayIgnorePattern: "^_" }],
204+
parserOptions: { ecmaVersion: 6 }
205+
},
206+
{
207+
code: `
208+
// doesn't report _x
209+
let _x, y;
210+
_x = 1;
211+
[_x, y] = foo;
212+
y;
213+
214+
// doesn't report _a
215+
let _a, b;
216+
[_a, b] = foo;
217+
_a = 1;
218+
b;
219+
`,
220+
options: [{ destructuredArrayIgnorePattern: "^_" }],
221+
parserOptions: { ecmaVersion: 2018 }
222+
},
223+
{
224+
code: `
225+
// doesn't report _x
226+
let _x, y;
227+
_x = 1;
228+
[_x, y] = foo;
229+
y;
230+
231+
// doesn't report _a
232+
let _a, b;
233+
_a = 1;
234+
({_a, ...b } = foo);
235+
b;
236+
`,
237+
options: [{ destructuredArrayIgnorePattern: "^_", ignoreRestSiblings: true }],
238+
parserOptions: { ecmaVersion: 2018 }
239+
},
165240

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

541+
// https://github.com/eslint/eslint/issues/15611
542+
{
543+
code: `
544+
const array = ['a', 'b', 'c'];
545+
const [a, _b, c] = array;
546+
const newArray = [a, c];
547+
`,
548+
options: [{ destructuredArrayIgnorePattern: "^_" }],
549+
parserOptions: { ecmaVersion: 2020 },
550+
errors: [
551+
552+
// should report only `newArray`
553+
{ ...assignedError("newArray"), line: 4, column: 19 }
554+
]
555+
},
556+
{
557+
code: `
558+
const array = ['a', 'b', 'c', 'd', 'e'];
559+
const [a, _b, c] = array;
560+
`,
561+
options: [{ destructuredArrayIgnorePattern: "^_" }],
562+
parserOptions: { ecmaVersion: 2020 },
563+
errors: [
564+
{
565+
...assignedError("a", ". Allowed unused elements of array destructuring patterns must match /^_/u"),
566+
line: 3,
567+
column: 20
568+
},
569+
{
570+
...assignedError("c", ". Allowed unused elements of array destructuring patterns must match /^_/u"),
571+
line: 3,
572+
column: 27
573+
}
574+
]
575+
},
576+
{
577+
code: `
578+
const array = ['a', 'b', 'c'];
579+
const [a, _b, c] = array;
580+
const fooArray = ['foo'];
581+
const barArray = ['bar'];
582+
const ignoreArray = ['ignore'];
583+
`,
584+
options: [{ destructuredArrayIgnorePattern: "^_", varsIgnorePattern: "ignore" }],
585+
parserOptions: { ecmaVersion: 2020 },
586+
errors: [
587+
{
588+
...assignedError("a", ". Allowed unused elements of array destructuring patterns must match /^_/u"),
589+
line: 3,
590+
column: 20
591+
},
592+
{
593+
...assignedError("c", ". Allowed unused elements of array destructuring patterns must match /^_/u"),
594+
line: 3,
595+
column: 27
596+
},
597+
{
598+
...assignedError("fooArray", ". Allowed unused vars must match /ignore/u"),
599+
line: 4,
600+
column: 19
601+
},
602+
{
603+
...assignedError("barArray", ". Allowed unused vars must match /ignore/u"),
604+
line: 5,
605+
column: 19
606+
}
607+
]
608+
},
609+
{
610+
code: `
611+
const array = [obj];
612+
const [{_a, foo}] = array;
613+
console.log(foo);
614+
`,
615+
options: [{ destructuredArrayIgnorePattern: "^_" }],
616+
parserOptions: { ecmaVersion: 2020 },
617+
errors: [
618+
{
619+
...assignedError("_a"),
620+
line: 3,
621+
column: 21
622+
}
623+
]
624+
},
625+
{
626+
code: `
627+
function foo([{_a, bar}]) {
628+
bar;
629+
}
630+
foo();
631+
`,
632+
options: [{ destructuredArrayIgnorePattern: "^_" }],
633+
parserOptions: { ecmaVersion: 2020 },
634+
errors: [
635+
{
636+
...definedError("_a"),
637+
line: 2,
638+
column: 28
639+
}
640+
]
641+
},
642+
{
643+
code: `
644+
let _a, b;
645+
646+
foo.forEach(item => {
647+
[a, b] = item;
648+
});
649+
`,
650+
options: [{ destructuredArrayIgnorePattern: "^_" }],
651+
parserOptions: { ecmaVersion: 2020 },
652+
errors: [
653+
{
654+
...definedError("_a"),
655+
line: 2,
656+
column: 17
657+
},
658+
{
659+
...assignedError("b"),
660+
line: 2,
661+
column: 21
662+
}
663+
]
664+
},
665+
466666
// for-in loops (see #2342)
467667
{
468668
code: "(function(obj) { var name; for ( name in obj ) { i(); return; } })({});",

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy