Skip to content

Commit c59a0ba

Browse files
zackargylenot-an-aardvark
authored andcommitted
Update: add ignoreRestSiblings option to no-unused-vars (#7968)
Update: no-unused-vars to account for rest property omissions Update docs Update: use RestProperty and only check last property Temp temp temp Use parent type
1 parent 5cdfa99 commit c59a0ba

File tree

3 files changed

+106
-2
lines changed

3 files changed

+106
-2
lines changed

docs/rules/no-unused-vars.md

+13-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ By default this rule is enabled with `all` option for variables and `after-used`
9999
```json
100100
{
101101
"rules": {
102-
"no-unused-vars": ["error", { "vars": "all", "args": "after-used" }]
102+
"no-unused-vars": ["error", { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }]
103103
}
104104
}
105105
```
@@ -195,6 +195,18 @@ Examples of **correct** code for the `{ "args": "none" }` option:
195195
})();
196196
```
197197

198+
### ignoreRestSiblings
199+
200+
The `ignoreRestSiblings` option is a boolean (default: `false`). Using a [Rest Property](https://github.com/sebmarkbage/ecmascript-rest-spread) it is possible to "omit" properties from an object, but by default the sibling properties are marked as "unused". With this option enabled the rest property's siblings are ignored.
201+
202+
Examples of **correct** code for the `{ "ignoreRestSiblings": true }` option:
203+
204+
```js
205+
/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/
206+
// 'type' is ignored because it has a rest property sibling.
207+
var { type, ...coords } = data;
208+
```
209+
198210
### argsIgnorePattern
199211

200212
The `argsIgnorePattern` option specifies exceptions not to check for usage: arguments whose names match a regexp pattern. For example, variables whose names begin with an underscore.

lib/rules/no-unused-vars.js

+30-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ module.exports = {
4242
args: {
4343
enum: ["all", "after-used", "none"]
4444
},
45+
ignoreRestSiblings: {
46+
type: "boolean"
47+
},
4548
argsIgnorePattern: {
4649
type: "string"
4750
},
@@ -66,6 +69,7 @@ module.exports = {
6669
const config = {
6770
vars: "all",
6871
args: "after-used",
72+
ignoreRestSiblings: false,
6973
caughtErrors: "none"
7074
};
7175

@@ -77,6 +81,7 @@ module.exports = {
7781
} else {
7882
config.vars = firstOption.vars || config.vars;
7983
config.args = firstOption.args || config.args;
84+
config.ignoreRestSiblings = firstOption.ignoreRestSiblings || config.ignoreRestSiblings;
8085
config.caughtErrors = firstOption.caughtErrors || config.caughtErrors;
8186

8287
if (firstOption.varsIgnorePattern) {
@@ -125,6 +130,30 @@ module.exports = {
125130
}
126131
}
127132

133+
/**
134+
* Determines if a variable has a sibling rest property
135+
* @param {Variable} variable - EScope variable object.
136+
* @returns {boolean} True if the variable is exported, false if not.
137+
* @private
138+
*/
139+
function hasRestSpreadSibling(variable) {
140+
if (config.ignoreRestSiblings) {
141+
const restProperties = new Set(["ExperimentalRestProperty", "RestProperty"]);
142+
143+
return variable.defs
144+
.filter(def => def.name.type === "Identifier")
145+
.some(def => (
146+
def.node.id &&
147+
def.node.id.type === "ObjectPattern" &&
148+
def.node.id.properties.length &&
149+
restProperties.has(def.node.id.properties[def.node.id.properties.length - 1].type) && // last property is a rest property
150+
!restProperties.has(def.name.parent.type) // variable is sibling of the rest property
151+
));
152+
} else {
153+
return false;
154+
}
155+
}
156+
128157
/**
129158
* Determines if a reference is a read operation.
130159
* @param {Reference} ref - An escope Reference
@@ -495,7 +524,7 @@ module.exports = {
495524
}
496525
}
497526

498-
if (!isUsedVariable(variable) && !isExported(variable)) {
527+
if (!isUsedVariable(variable) && !isExported(variable) && !hasRestSpreadSibling(variable)) {
499528
unusedVars.push(variable);
500529
}
501530
}

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

+63
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,22 @@ ruleTester.defineRule("use-every-a", context => {
3434
};
3535
});
3636

37+
/**
38+
* Returns an extended test that includes es2017 parser options.
39+
* @param {Object} test The test to extend
40+
* @returns {Object} A parser-extended test case
41+
*/
42+
function includeRestPropertyParser(test) {
43+
return Object.assign({
44+
parserOptions: {
45+
ecmaVersion: 2017,
46+
ecmaFeatures: {
47+
experimentalObjectRestSpread: true
48+
}
49+
}
50+
}, test);
51+
}
52+
3753
/**
3854
* Returns an expected error for defined-but-not-used variables.
3955
* @param {string} varName The name of the variable
@@ -179,6 +195,12 @@ ruleTester.run("no-unused-vars", rule, {
179195
options: [{ vars: "all", args: "all" }]
180196
},
181197

198+
// Using object rest for variable omission
199+
includeRestPropertyParser({
200+
code: "const data = { type: 'coords', x: 1, y: 2 };\nconst { type, ...coords } = data;\n console.log(coords);",
201+
options: [{ ignoreRestSiblings: true }]
202+
}),
203+
182204
// https://github.com/eslint/eslint/issues/6348
183205
{ code: "var a = 0, b; b = a = a + 1; foo(b);" },
184206
{ code: "var a = 0, b; b = a += a + 1; foo(b);" },
@@ -333,6 +355,47 @@ ruleTester.run("no-unused-vars", rule, {
333355
]
334356
},
335357

358+
// Rest property sibling without ignoreRestSiblings
359+
includeRestPropertyParser({
360+
code: "const data = { type: 'coords', x: 1, y: 2 };\nconst { type, ...coords } = data;\n console.log(coords);",
361+
errors: [
362+
{ line: 2, column: 9, message: "'type' is assigned a value but never used." }
363+
]
364+
}),
365+
366+
// Unused rest property with ignoreRestSiblings
367+
includeRestPropertyParser({
368+
code: "const data = { type: 'coords', x: 1, y: 2 };\nconst { type, ...coords } = data;\n console.log(type)",
369+
options: [{ ignoreRestSiblings: true }],
370+
errors: [
371+
{ line: 2, column: 18, message: "'coords' is assigned a value but never used." }
372+
]
373+
}),
374+
375+
// Unused rest property without ignoreRestSiblings
376+
includeRestPropertyParser({
377+
code: "const data = { type: 'coords', x: 1, y: 2 };\nconst { type, ...coords } = data;\n console.log(type)",
378+
errors: [
379+
{ line: 2, column: 18, message: "'coords' is assigned a value but never used." }
380+
]
381+
}),
382+
383+
// Nested array destructuring with rest property
384+
includeRestPropertyParser({
385+
code: "const data = { vars: ['x','y'], x: 1, y: 2 };\nconst { vars: [x], ...coords } = data;\n console.log(coords)",
386+
errors: [
387+
{ line: 2, column: 16, message: "'x' is assigned a value but never used." }
388+
]
389+
}),
390+
391+
// Nested object destructuring with rest property
392+
includeRestPropertyParser({
393+
code: "const data = { defaults: { x: 0 }, x: 1, y: 2 };\nconst { defaults: { x }, ...coords } = data;\n console.log(coords)",
394+
errors: [
395+
{ line: 2, column: 21, message: "'x' is assigned a value but never used." }
396+
]
397+
}),
398+
336399
// https://github.com/eslint/eslint/issues/3714
337400
{
338401
code: "/* global a$fooz,$foo */\na$fooz;",

0 commit comments

Comments
 (0)