diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts index 139089a74486..4fb54dda7473 100644 --- a/packages/eslint-plugin/src/rules/no-unused-vars.ts +++ b/packages/eslint-plugin/src/rules/no-unused-vars.ts @@ -14,6 +14,7 @@ export type Options = [ argsIgnorePattern?: string; caughtErrors?: 'all' | 'none'; caughtErrorsIgnorePattern?: string; + destructuredArrayIgnorePattern?: string; }, ]; @@ -25,6 +26,7 @@ interface TranslatedOptions { argsIgnorePattern?: RegExp; caughtErrors: 'all' | 'none'; caughtErrorsIgnorePattern?: RegExp; + destructuredArrayIgnorePattern?: RegExp; } export default util.createRule({ @@ -66,6 +68,9 @@ export default util.createRule({ caughtErrorsIgnorePattern: { type: 'string', }, + destructuredArrayIgnorePattern: { + type: 'string', + }, }, additionalProperties: false, }, @@ -123,12 +128,33 @@ export default util.createRule({ 'u', ); } + + if (firstOption.destructuredArrayIgnorePattern) { + options.destructuredArrayIgnorePattern = new RegExp( + firstOption.destructuredArrayIgnorePattern, + 'u', + ); + } } } return options; })(); function collectUnusedVariables(): TSESLint.Scope.Variable[] { + /** + * Checks whether a node is a sibling of the rest property or not. + * @param {ASTNode} node a node to check + * @returns {boolean} True if the node is a sibling of the rest property, otherwise false. + */ + function hasRestSibling(node: TSESTree.Node): boolean { + return ( + node.type === AST_NODE_TYPES.Property && + node.parent?.type === AST_NODE_TYPES.ObjectPattern && + node.parent.properties[node.parent.properties.length - 1].type === + AST_NODE_TYPES.RestElement + ); + } + /** * Determines if a variable has a sibling rest property * @param variable eslint-scope variable object. @@ -138,17 +164,14 @@ export default util.createRule({ variable: TSESLint.Scope.Variable, ): boolean { if (options.ignoreRestSiblings) { - return variable.defs.some(def => { - const propertyNode = def.name.parent!; - const patternNode = propertyNode.parent!; - - return ( - propertyNode.type === AST_NODE_TYPES.Property && - patternNode.type === AST_NODE_TYPES.ObjectPattern && - patternNode.properties[patternNode.properties.length - 1].type === - AST_NODE_TYPES.RestElement - ); - }); + const hasRestSiblingDefinition = variable.defs.some(def => + hasRestSibling(def.name.parent!), + ); + const hasRestSiblingReference = variable.references.some(ref => + hasRestSibling(ref.identifier.parent!), + ); + + return hasRestSiblingDefinition || hasRestSiblingReference; } return false; @@ -188,6 +211,20 @@ export default util.createRule({ continue; } + const refUsedInArrayPatterns = variable.references.some( + ref => ref.identifier.parent?.type === AST_NODE_TYPES.ArrayPattern, + ); + + // skip elements of array destructuring patterns + if ( + (def.name.parent?.type === AST_NODE_TYPES.ArrayPattern || + refUsedInArrayPatterns) && + 'name' in def.name && + options.destructuredArrayIgnorePattern?.test(def.name.name) + ) { + continue; + } + // skip catch variables if (def.type === TSESLint.Scope.DefinitionType.CatchClause) { if (options.caughtErrors === 'none') { @@ -361,9 +398,17 @@ export default util.createRule({ function getAssignedMessageData( unusedVar: TSESLint.Scope.Variable, ): Record { - const additional = options.varsIgnorePattern - ? `. Allowed unused vars must match ${options.varsIgnorePattern.toString()}` - : ''; + const def = unusedVar.defs[0]; + let additional = ''; + + if ( + options.destructuredArrayIgnorePattern && + def?.name.parent?.type === AST_NODE_TYPES.ArrayPattern + ) { + additional = `. Allowed unused elements of array destructuring patterns must match ${options.destructuredArrayIgnorePattern.toString()}`; + } else if (options.varsIgnorePattern) { + additional = `. Allowed unused vars must match ${options.varsIgnorePattern.toString()}`; + } return { varName: unusedVar.name, diff --git a/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars-eslint.test.ts b/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars-eslint.test.ts index 9ddde4e9f24e..6f26085bfd7b 100644 --- a/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars-eslint.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars-eslint.test.ts @@ -701,6 +701,111 @@ console.log(secondItem); options: [{ vars: 'all', varsIgnorePattern: '[iI]gnored' }], parserOptions: { ecmaVersion: 6 }, }, + { + code: ` +const [a, _b, c] = items; +console.log(a + c); + `, + options: [{ destructuredArrayIgnorePattern: '^_' }], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` +const [[a, _b, c]] = items; +console.log(a + c); + `, + options: [{ destructuredArrayIgnorePattern: '^_' }], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` +const { + x: [_a, foo], +} = bar; +console.log(foo); + `, + options: [{ destructuredArrayIgnorePattern: '^_' }], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` +function baz([_b, foo]) { + foo; +} +baz(); + `, + options: [{ destructuredArrayIgnorePattern: '^_' }], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` +function baz({ x: [_b, foo] }) { + foo; +} +baz(); + `, + options: [{ destructuredArrayIgnorePattern: '^_' }], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` +function baz([ + { + x: [_b, foo], + }, +]) { + foo; +} +baz(); + `, + 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) ` @@ -1017,6 +1122,13 @@ console.log(Foo); parserOptions: { ecmaVersion: 2018 }, }, + // https://github.com/eslint/eslint/issues/14163 + { + code: 'let foo, rest;\n({ foo, ...rest } = something);\nconsole.log(rest);', + options: [{ ignoreRestSiblings: true }], + parserOptions: { ecmaVersion: 2020 }, + }, + // https://github.com/eslint/eslint/issues/10952 ` /*eslint use-every-a:1*/ !function (b, a) { @@ -1504,6 +1616,148 @@ foo(); }, ], }, + // 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: 7 }, + ], + }, + { + 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: 8, + }, + { + ...assignedError( + 'c', + '. Allowed unused elements of array destructuring patterns must match /^_/u', + ), + line: 3, + column: 15, + }, + ], + }, + { + 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: 8, + }, + { + ...assignedError( + 'c', + '. Allowed unused elements of array destructuring patterns must match /^_/u', + ), + line: 3, + column: 15, + }, + { + ...assignedError( + 'fooArray', + '. Allowed unused vars must match /ignore/u', + ), + line: 4, + column: 7, + }, + { + ...assignedError( + 'barArray', + '. Allowed unused vars must match /ignore/u', + ), + line: 5, + column: 7, + }, + ], + }, + { + code: ` +const array = [obj]; +const [{ _a, foo }] = array; +console.log(foo); + `, + options: [{ destructuredArrayIgnorePattern: '^_' }], + parserOptions: { ecmaVersion: 2020 }, + errors: [ + { + ...assignedError('_a'), + line: 3, + column: 10, + }, + ], + }, + { + code: ` +function foo([{ _a, bar }]) { + bar; +} +foo(); + `, + options: [{ destructuredArrayIgnorePattern: '^_' }], + parserOptions: { ecmaVersion: 2020 }, + errors: [ + { + ...definedError('_a'), + line: 2, + column: 17, + }, + ], + }, + { + code: ` +let _a, b; +foo.forEach(item => { + [a, b] = item; +}); + `, + options: [{ destructuredArrayIgnorePattern: '^_' }], + parserOptions: { ecmaVersion: 2020 }, + errors: [ + { + ...definedError('_a'), + line: 2, + column: 5, + }, + { + ...assignedError('b'), + line: 4, + column: 7, + }, + ], + }, // for-in loops (see #2342) { @@ -1684,6 +1938,27 @@ console.log(type); }, ], }, + { + code: ` +let type, coords; +({ type, ...coords } = data); +console.log(type); + `, + options: [{ ignoreRestSiblings: true }], + parserOptions: { ecmaVersion: 2018 }, + errors: [ + { + line: 3, + column: 13, + messageId: 'unusedVar', + data: { + varName: 'coords', + action: 'assigned a value', + additional: '', + }, + }, + ], + }, // Unused rest property without ignoreRestSiblings { diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index fa7d5934b263..7e3b9db43e36 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -416,6 +416,7 @@ declare module 'eslint/lib/rules/no-unused-vars' { argsIgnorePattern?: string; caughtErrors?: 'all' | 'none'; caughtErrorsIgnorePattern?: string; + destructuredArrayIgnorePattern?: string; }, ], { 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