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
21 changes: 21 additions & 0 deletions docs/rules/no-unused-vars.md
Expand Up @@ -238,6 +238,27 @@ function foo(x, _y) {
foo();
```

### destructuredArrayIgnorePattern

The `destructuredArrayIgnorePattern` option specifies exceptions not to check for usage: destructuring assignments from an array whose names match a regexp pattern. For example, variables whose names begin with an underscore.
snitin315 marked this conversation as resolved.
Show resolved Hide resolved

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();
```

### caughtErrors

The `caughtErrors` option is used for `catch` block arguments validation.
Expand Down
36 changes: 35 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,16 @@ 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 patternToMatch;

if (config.destructuredArrayIgnorePattern && def.type === "Variable" && def.node.id.type === "ArrayPattern" && def.name.parent.parent.type !== "ObjectPattern") {
patternToMatch = config.destructuredArrayIgnorePattern;
} else if (config.varsIgnorePattern) {
patternToMatch = config.varsIgnorePattern;
}

const additional = patternToMatch ? `. Allowed unused vars must match ${patternToMatch.toString()}` : "";
snitin315 marked this conversation as resolved.
Show resolved Hide resolved

return {
varName: unusedVar.name,
Expand Down Expand Up @@ -614,6 +630,11 @@ module.exports = {
continue;
}

// Allow cases like - `function baz([_b, foo]) { foo}`, `function baz({x: [_b, foo]}) {foo}`
if (def.name.parent.type === "ArrayPattern" && config.destructuredArrayIgnorePattern && config.destructuredArrayIgnorePattern.test(def.name.name)) {
continue;
}
snitin315 marked this conversation as resolved.
Show resolved Hide resolved

// if "args" option is "after-used", skip used variables
if (config.args === "after-used" && astUtils.isFunction(def.name.parent) && !isAfterLastUsedArg(variable)) {
continue;
Expand All @@ -624,6 +645,19 @@ module.exports = {
if (config.varsIgnorePattern && config.varsIgnorePattern.test(def.name.name)) {
continue;
}

if (type === "Variable" && config.destructuredArrayIgnorePattern && config.destructuredArrayIgnorePattern.test(def.name.name)) {

// Allow - `const [a, _b, c] = arr`, Disallow - `const [{ _a, foo }] = [obj];`
if (def.node.id.type === "ArrayPattern" && def.name.parent.parent.type !== "ObjectPattern") {
continue;
}

// For cases like - `var { x: [_a, foo] } = obj;`
if (def.node.id.type === "ObjectPattern" && def.name.parent.type === "ArrayPattern") {
continue;
}
}
snitin315 marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
107 changes: 107 additions & 0 deletions tests/lib/rules/no-unused-vars.js
Expand Up @@ -162,6 +162,11 @@ 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 { 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 } },

// for-in loops (see #2342)
"(function(obj) { var name; for ( name in obj ) return; })({});",
Expand Down Expand Up @@ -463,6 +468,108 @@ 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 vars must match /^_/u"),
line: 3,
column: 20
},
{
...assignedError("c", ". Allowed unused vars 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 vars must match /^_/u"),
line: 3,
column: 20
},
{
...assignedError("c", ". Allowed unused vars 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
}
]
},

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