From 8e328f67120e6b738837e0824f3221746fc4b434 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Sun, 11 Jun 2023 07:58:47 +0900 Subject: [PATCH 01/35] copy prefer-destructuring from ESLint --- .../src/rules/prefer-destructuring.ts | 49 + .../src/util/getESLintCoreRule.ts | 1 + .../tests/rules/prefer-destructuring.test.ts | 840 ++++++++++++++++++ .../eslint-plugin/typings/eslint-rules.d.ts | 30 + 4 files changed, 920 insertions(+) create mode 100644 packages/eslint-plugin/src/rules/prefer-destructuring.ts create mode 100644 packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts new file mode 100644 index 00000000000..63533d5e7e4 --- /dev/null +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -0,0 +1,49 @@ +import type { + InferMessageIdsTypeFromRule, + InferOptionsTypeFromRule, +} from '../util'; +import { createRule } from '../util'; +import { getESLintCoreRule } from '../util/getESLintCoreRule'; + +const baseRule = getESLintCoreRule('prefer-destructuring'); + +type Options = InferOptionsTypeFromRule; +type MessageIds = InferMessageIdsTypeFromRule; + +export default createRule({ + name: 'prefer-destructuring', + meta: { + type: 'suggestion', + docs: { + description: 'Require destructuring from arrays and/or objects', + recommended: false, + extendsBaseRule: true, + requiresTypeChecking: false, + }, + schema: baseRule.meta?.schema, + fixable: baseRule.meta?.fixable, + hasSuggestions: baseRule.meta?.hasSuggestions, + messages: baseRule.meta?.messages, + }, + defaultOptions: [ + { + VariableDeclarator: { + array: true, + object: true, + }, + AssignmentExpression: { + array: true, + object: true, + }, + }, + { + enforceForRenamedProperties: false, + }, + ], + create(context) { + const rules = baseRule.create(context); + return { + ...rules, + }; + }, +}); diff --git a/packages/eslint-plugin/src/util/getESLintCoreRule.ts b/packages/eslint-plugin/src/util/getESLintCoreRule.ts index 96785ad6f15..416b45042e1 100644 --- a/packages/eslint-plugin/src/util/getESLintCoreRule.ts +++ b/packages/eslint-plugin/src/util/getESLintCoreRule.ts @@ -35,6 +35,7 @@ interface RuleMap { 'no-restricted-globals': typeof import('eslint/lib/rules/no-restricted-globals'); 'object-curly-spacing': typeof import('eslint/lib/rules/object-curly-spacing'); 'prefer-const': typeof import('eslint/lib/rules/prefer-const'); + 'prefer-destructuring': typeof import('eslint/lib/rules/prefer-destructuring'); quotes: typeof import('eslint/lib/rules/quotes'); semi: typeof import('eslint/lib/rules/semi'); 'space-before-blocks': typeof import('eslint/lib/rules/space-before-blocks'); diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts new file mode 100644 index 00000000000..ea6177583b4 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -0,0 +1,840 @@ +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import rule from '../../src/rules/prefer-destructuring'; +import type { RunTests } from '../RuleTester'; +import { getFixturesRootDir, RuleTester } from '../RuleTester'; + +const rootPath = getFixturesRootDir(); + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + sourceType: 'module', + tsconfigRootDir: rootPath, + project: './tsconfig.json', + }, +}); + +const baseTests: RunTests<'preferDestructuring', unknown[]> = { + /* + * This test code is based on code from the ESLint project (https://github.com/eslint/eslint). + * ESLint is licensed under the MIT License. + * Copyright (c) 2023 OpenJS Foundation and other contributors, . + */ + /* eslint-disable @typescript-eslint/internal/plugin-test-formatting */ + valid: [ + 'var [foo] = array;', + 'var { foo } = object;', + 'var foo;', + { + // Ensure that the default behavior does not require destructuring when renaming + code: 'var foo = object.bar;', + options: [{ VariableDeclarator: { object: true } }], + }, + { + // Ensure that the default behavior does not require destructuring when renaming + code: 'var foo = object.bar;', + options: [{ object: true }], + }, + { + code: 'var foo = object.bar;', + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'var foo = object.bar;', + options: [{ object: true }, { enforceForRenamedProperties: false }], + }, + { + code: "var foo = object['bar'];", + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'var foo = object[bar];', + options: [{ object: true }, { enforceForRenamedProperties: false }], + }, + { + code: 'var { bar: foo } = object;', + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'var { bar: foo } = object;', + options: [{ object: true }, { enforceForRenamedProperties: true }], + }, + { + code: 'var { [bar]: foo } = object;', + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'var { [bar]: foo } = object;', + options: [{ object: true }, { enforceForRenamedProperties: true }], + }, + { + code: 'var foo = array[0];', + options: [{ VariableDeclarator: { array: false } }], + }, + { + code: 'var foo = array[0];', + options: [{ array: false }], + }, + { + code: 'var foo = object.foo;', + options: [{ VariableDeclarator: { object: false } }], + }, + { + code: "var foo = object['foo'];", + options: [{ VariableDeclarator: { object: false } }], + }, + '({ foo } = object);', + { + // Fix #8654 + code: 'var foo = array[0];', + options: [ + { VariableDeclarator: { array: false } }, + { enforceForRenamedProperties: true }, + ], + }, + { + // Fix #8654 + code: 'var foo = array[0];', + options: [{ array: false }, { enforceForRenamedProperties: true }], + }, + '[foo] = array;', + 'foo += array[0]', + { + code: 'foo &&= array[0]', + parserOptions: { ecmaVersion: 2021 }, + }, + 'foo += bar.foo', + { + code: 'foo ||= bar.foo', + parserOptions: { ecmaVersion: 2021 }, + }, + { + code: "foo ??= bar['foo']", + parserOptions: { ecmaVersion: 2021 }, + }, + { + code: 'foo = object.foo;', + options: [ + { AssignmentExpression: { object: false } }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'foo = object.foo;', + options: [ + { AssignmentExpression: { object: false } }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'foo = array[0];', + options: [ + { AssignmentExpression: { array: false } }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'foo = array[0];', + options: [ + { AssignmentExpression: { array: false } }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'foo = array[0];', + options: [ + { + VariableDeclarator: { array: true }, + AssignmentExpression: { array: false }, + }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'var foo = array[0];', + options: [ + { + VariableDeclarator: { array: false }, + AssignmentExpression: { array: true }, + }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'foo = object.foo;', + options: [ + { + VariableDeclarator: { object: true }, + AssignmentExpression: { object: false }, + }, + ], + }, + { + code: 'var foo = object.foo;', + options: [ + { + VariableDeclarator: { object: false }, + AssignmentExpression: { object: true }, + }, + ], + }, + 'class Foo extends Bar { static foo() {var foo = super.foo} }', + 'foo = bar[foo];', + 'var foo = bar[foo];', + { + code: 'var {foo: {bar}} = object;', + options: [{ object: true }], + }, + { + code: 'var {bar} = object.foo;', + options: [{ object: true }], + }, + + // Optional chaining + 'var foo = array?.[0];', // because the fixed code can throw TypeError. + 'var foo = object?.foo;', + + // Private identifiers + 'class C { #x; foo() { const x = this.#x; } }', + 'class C { #x; foo() { x = this.#x; } }', + 'class C { #x; foo(a) { x = a.#x; } }', + { + code: 'class C { #x; foo() { const x = this.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo() { const y = this.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo() { x = this.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo() { y = this.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo(a) { x = a.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo(a) { y = a.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo() { x = this.a.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + ], + invalid: [ + { + code: 'var foo = array[0];', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'foo = array[0];', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: 'var foo = object.foo;', + output: 'var {foo} = object;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (a, b).foo;', + output: 'var {foo} = (a, b);', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var length = (() => {}).length;', + output: 'var {length} = () => {};', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (a = b).foo;', + output: 'var {foo} = a = b;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (a || b).foo;', + output: 'var {foo} = a || b;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (f()).foo;', + output: 'var {foo} = f();', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.bar.foo;', + output: 'var {foo} = object.bar;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foobar = object.bar;', + output: null, + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foobar = object.bar;', + output: null, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object[bar];', + output: null, + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object[bar];', + output: null, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object[foo];', + output: null, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: "var foo = object['foo'];", + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'foo = object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: "foo = object['foo'];", + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: 'var foo = array[0];', + output: null, + options: [ + { VariableDeclarator: { array: true } }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'foo = array[0];', + output: null, + options: [{ AssignmentExpression: { array: true } }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: 'var foo = array[0];', + output: null, + options: [ + { + VariableDeclarator: { array: true }, + AssignmentExpression: { array: false }, + }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = array[0];', + output: null, + options: [ + { + VariableDeclarator: { array: true }, + AssignmentExpression: { array: false }, + }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'foo = array[0];', + output: null, + options: [ + { + VariableDeclarator: { array: false }, + AssignmentExpression: { array: true }, + }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: 'foo = object.foo;', + output: null, + options: [ + { + VariableDeclarator: { array: true, object: false }, + AssignmentExpression: { object: true }, + }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: 'class Foo extends Bar { static foo() {var bar = super.foo.bar} }', + output: 'class Foo extends Bar { static foo() {var {bar} = super.foo} }', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + + // comments + { + code: 'var /* comment */ foo = object.foo;', + output: 'var /* comment */ {foo} = object;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var a, /* comment */foo = object.foo;', + output: 'var a, /* comment */{foo} = object;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo /* comment */ = object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var a, foo /* comment */ = object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo /* comment */ = object.foo, a;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo // comment\n = object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = /* comment */ object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = // comment\n object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (/* comment */ object).foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (object /* comment */).foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = bar(/* comment */).foo;', + output: 'var {foo} = bar(/* comment */);', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = bar/* comment */.baz.foo;', + output: 'var {foo} = bar/* comment */.baz;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = bar[// comment\nbaz].foo;', + output: 'var {foo} = bar[// comment\nbaz];', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo // comment\n = bar(/* comment */).foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = bar/* comment */.baz/* comment */.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object// comment\n.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object./* comment */foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (/* comment */ object.foo);', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (object.foo /* comment */);', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.foo/* comment */;', + output: 'var {foo} = object/* comment */;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.foo// comment', + output: 'var {foo} = object// comment', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.foo/* comment */, a;', + output: 'var {foo} = object/* comment */, a;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.foo// comment\n, a;', + output: 'var {foo} = object// comment\n, a;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.foo, /* comment */ a;', + output: 'var {foo} = object, /* comment */ a;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + ], + /* eslint-enable @typescript-eslint/internal/plugin-test-formatting */ +}; + +ruleTester.run('prefer-desctructuring', rule, baseTests); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index 1726745df3f..cd54c71c6e8 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -992,6 +992,36 @@ declare module 'eslint/lib/rules/prefer-const' { export = rule; } +declare module 'eslint/lib/rules/prefer-destructuring' { + import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; + + interface DestructuringTypeConfig { + object: boolean; + array: boolean; + } + + const rule: TSESLint.RuleModule< + 'preferDestructuring', + [ + ( + | DestructuringTypeConfig + | { + VariableDeclarator?: DestructuringTypeConfig; + AssignmentExpression?: DestructuringTypeConfig; + } + ), + { + enforceForRenamedProperties?: boolean; + }, + ], + { + VariableDeclarator(node: TSESTree.VariableDeclarator): void; + AssignmentExpression(node: TSESTree.AssignmentExpression): void; + } + >; + export = rule; +} + declare module 'eslint/lib/rules/object-curly-spacing' { import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; From 392f1326417cb30cf2e9d5f7317ec6aafe3d5680 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Mon, 12 Jun 2023 19:04:59 +0900 Subject: [PATCH 02/35] rewrite schema by hand --- .../src/rules/prefer-destructuring.ts | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index 63533d5e7e4..e4aaf7ab36d 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -1,3 +1,5 @@ +import type { JSONSchema4 } from 'json-schema'; + import type { InferMessageIdsTypeFromRule, InferOptionsTypeFromRule, @@ -10,6 +12,43 @@ const baseRule = getESLintCoreRule('prefer-destructuring'); type Options = InferOptionsTypeFromRule; type MessageIds = InferMessageIdsTypeFromRule; +const destructuringTypeConfig: JSONSchema4 = { + type: 'object', + properties: { + array: { + type: 'boolean', + }, + object: { + type: 'boolean', + }, + }, + additionalProperties: false, +}; + +const schema: readonly JSONSchema4[] = [ + { + oneOf: [ + { + type: 'object', + properties: { + VariableDeclarator: destructuringTypeConfig, + AssignmentExpression: destructuringTypeConfig, + }, + additionalProperties: false, + }, + destructuringTypeConfig, + ], + }, + { + type: 'object', + properties: { + enforceForRenamedProperties: { + type: 'boolean', + }, + }, + }, +]; + export default createRule({ name: 'prefer-destructuring', meta: { @@ -20,7 +59,7 @@ export default createRule({ extendsBaseRule: true, requiresTypeChecking: false, }, - schema: baseRule.meta?.schema, + schema, fixable: baseRule.meta?.fixable, hasSuggestions: baseRule.meta?.hasSuggestions, messages: baseRule.meta?.messages, From 4435c44a8dac2e99692f8ea132cffebf73712da4 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Mon, 12 Jun 2023 22:21:57 +0900 Subject: [PATCH 03/35] add enforceForTypeAnnotatedProperties option autofix is still wrong. --- .../src/rules/prefer-destructuring.ts | 27 ++++++++++++-- .../tests/rules/prefer-destructuring.test.ts | 36 ++++++++++++++++++- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index e4aaf7ab36d..1526e0f1fd5 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -9,7 +9,13 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('prefer-destructuring'); -type Options = InferOptionsTypeFromRule; +type BaseOptions = InferOptionsTypeFromRule; +type Options = [ + BaseOptions[0], + BaseOptions[1] & { + enforceForTypeAnnotatedProperties?: boolean; + }, +]; type MessageIds = InferMessageIdsTypeFromRule; const destructuringTypeConfig: JSONSchema4 = { @@ -45,6 +51,9 @@ const schema: readonly JSONSchema4[] = [ enforceForRenamedProperties: { type: 'boolean', }, + enforceForTypeAnnotatedProperties: { + type: 'boolean', + }, }, }, ]; @@ -77,12 +86,24 @@ export default createRule({ }, { enforceForRenamedProperties: false, + enforceForTypeAnnotatedProperties: false, }, ], - create(context) { + create(context, [, { enforceForTypeAnnotatedProperties = false }]) { const rules = baseRule.create(context); return { - ...rules, + VariableDeclarator(node): void { + if ( + node.id.typeAnnotation !== undefined && + !enforceForTypeAnnotatedProperties + ) { + return; + } + rules.VariableDeclarator(node); + }, + AssignmentExpression(node): void { + rules.AssignmentExpression(node); + }, }; }, }); diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index ea6177583b4..26c0da36e70 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -837,4 +837,38 @@ const baseTests: RunTests<'preferDestructuring', unknown[]> = { /* eslint-enable @typescript-eslint/internal/plugin-test-formatting */ }; -ruleTester.run('prefer-desctructuring', rule, baseTests); +ruleTester.run('prefer-desctructuring', rule, { + valid: [ + ...baseTests.valid, + // type annotated + 'var foo: string = object.foo;', + 'const bar: number = array[0];', + ], + invalid: [ + ...baseTests.invalid, + { + code: 'var foo: string = object.foo;', + options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + output: 'var {foo} = object;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo: string = array[0];', + options: [{ array: true }, { enforceForTypeAnnotatedProperties: true }], + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + ], +}); From e5ab24f84fdd01fa09ca6f855c60195892bcdcda Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Mon, 12 Jun 2023 22:58:31 +0900 Subject: [PATCH 04/35] add tests --- .../tests/rules/prefer-destructuring.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index 26c0da36e70..bca5a27e121 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -843,6 +843,22 @@ ruleTester.run('prefer-desctructuring', rule, { // type annotated 'var foo: string = object.foo;', 'const bar: number = array[0];', + { + code: 'var { foo } = object;', + options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + }, + { + code: 'var { foo }: { foo: number } = object;', + options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + }, + { + code: 'var [foo] = array;', + options: [{ array: true }, { enforceForTypeAnnotatedProperties: true }], + }, + { + code: 'var [foo]: [foo: number] = array;', + options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + }, ], invalid: [ ...baseTests.invalid, From 90e094f4cd909f528f6dff84806d50af4964b891 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Tue, 13 Jun 2023 08:53:27 +0900 Subject: [PATCH 05/35] prevent fixing type-annotated declaration --- .../src/rules/prefer-destructuring.ts | 40 ++++++++++++++++--- .../tests/rules/prefer-destructuring.test.ts | 2 +- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index 1526e0f1fd5..97bb70efdb8 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -1,3 +1,4 @@ +import type { TSESLint } from '@typescript-eslint/utils'; import type { JSONSchema4 } from 'json-schema'; import type { @@ -93,13 +94,15 @@ export default createRule({ const rules = baseRule.create(context); return { VariableDeclarator(node): void { - if ( - node.id.typeAnnotation !== undefined && - !enforceForTypeAnnotatedProperties - ) { + if (node.id.typeAnnotation === undefined) { + rules.VariableDeclarator(node); return; } - rules.VariableDeclarator(node); + if (!enforceForTypeAnnotatedProperties) { + return; + } + const noFixRules = baseRule.create(noFixContext(context)); + noFixRules.VariableDeclarator(node); }, AssignmentExpression(node): void { rules.AssignmentExpression(node); @@ -107,3 +110,30 @@ export default createRule({ }; }, }); + +type Context = TSESLint.RuleContext; + +function noFixContext(context: Context): Context { + const customContext: { + report: Context['report']; + } = { + report: (descriptor): void => { + context.report({ + ...descriptor, + fix: undefined, + }); + }, + }; + + // we can't directly proxy `context` because its `report` property is non-configurable + // and non-writable. So we proxy `customContext` and redirect all + // property access to the original context except for `report` + return new Proxy(customContext as typeof context, { + get(target, path, receiver): unknown { + if (path !== 'report') { + return Reflect.get(context, path, receiver); + } + return Reflect.get(target, path, receiver); + }, + }); +} diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index bca5a27e121..8d42f219e8a 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -865,7 +865,7 @@ ruleTester.run('prefer-desctructuring', rule, { { code: 'var foo: string = object.foo;', options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], - output: 'var {foo} = object;', + output: null, errors: [ { messageId: 'preferDestructuring', From 1619def54380933903c319142c404624760d9d5a Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Tue, 13 Jun 2023 09:03:09 +0900 Subject: [PATCH 06/35] add tests --- .../tests/rules/prefer-destructuring.test.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index 8d42f219e8a..fb476120a45 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -843,6 +843,7 @@ ruleTester.run('prefer-desctructuring', rule, { // type annotated 'var foo: string = object.foo;', 'const bar: number = array[0];', + // enforceForTypeAnnotatedProperties: true { code: 'var { foo } = object;', options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], @@ -859,9 +860,22 @@ ruleTester.run('prefer-desctructuring', rule, { code: 'var [foo]: [foo: number] = array;', options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], }, + { + code: 'var foo: unknown = object.bar;', + options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + }, + { + code: 'var { foo: bar } = object;', + options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + }, + { + code: 'var { foo: bar }: { foo: boolean } = object;', + options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + }, ], invalid: [ ...baseTests.invalid, + // enforceForTypeAnnotatedProperties: true { code: 'var foo: string = object.foo;', options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], @@ -886,5 +900,23 @@ ruleTester.run('prefer-desctructuring', rule, { }, ], }, + { + code: 'var foo: unknown = object.bar;', + options: [ + { object: true }, + { + enforceForTypeAnnotatedProperties: true, + enforceForRenamedProperties: true, + }, + ], + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, ], }); From 8bcb9215cd3f9e7f19ec2aedfee0969c291280f6 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Tue, 13 Jun 2023 12:54:01 +0900 Subject: [PATCH 07/35] move baseTests into end of file --- .../tests/rules/prefer-destructuring.test.ts | 1645 +++++++++-------- 1 file changed, 824 insertions(+), 821 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index fb476120a45..696e1c8feb3 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -15,827 +15,7 @@ const ruleTester = new RuleTester({ }, }); -const baseTests: RunTests<'preferDestructuring', unknown[]> = { - /* - * This test code is based on code from the ESLint project (https://github.com/eslint/eslint). - * ESLint is licensed under the MIT License. - * Copyright (c) 2023 OpenJS Foundation and other contributors, . - */ - /* eslint-disable @typescript-eslint/internal/plugin-test-formatting */ - valid: [ - 'var [foo] = array;', - 'var { foo } = object;', - 'var foo;', - { - // Ensure that the default behavior does not require destructuring when renaming - code: 'var foo = object.bar;', - options: [{ VariableDeclarator: { object: true } }], - }, - { - // Ensure that the default behavior does not require destructuring when renaming - code: 'var foo = object.bar;', - options: [{ object: true }], - }, - { - code: 'var foo = object.bar;', - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'var foo = object.bar;', - options: [{ object: true }, { enforceForRenamedProperties: false }], - }, - { - code: "var foo = object['bar'];", - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'var foo = object[bar];', - options: [{ object: true }, { enforceForRenamedProperties: false }], - }, - { - code: 'var { bar: foo } = object;', - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'var { bar: foo } = object;', - options: [{ object: true }, { enforceForRenamedProperties: true }], - }, - { - code: 'var { [bar]: foo } = object;', - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'var { [bar]: foo } = object;', - options: [{ object: true }, { enforceForRenamedProperties: true }], - }, - { - code: 'var foo = array[0];', - options: [{ VariableDeclarator: { array: false } }], - }, - { - code: 'var foo = array[0];', - options: [{ array: false }], - }, - { - code: 'var foo = object.foo;', - options: [{ VariableDeclarator: { object: false } }], - }, - { - code: "var foo = object['foo'];", - options: [{ VariableDeclarator: { object: false } }], - }, - '({ foo } = object);', - { - // Fix #8654 - code: 'var foo = array[0];', - options: [ - { VariableDeclarator: { array: false } }, - { enforceForRenamedProperties: true }, - ], - }, - { - // Fix #8654 - code: 'var foo = array[0];', - options: [{ array: false }, { enforceForRenamedProperties: true }], - }, - '[foo] = array;', - 'foo += array[0]', - { - code: 'foo &&= array[0]', - parserOptions: { ecmaVersion: 2021 }, - }, - 'foo += bar.foo', - { - code: 'foo ||= bar.foo', - parserOptions: { ecmaVersion: 2021 }, - }, - { - code: "foo ??= bar['foo']", - parserOptions: { ecmaVersion: 2021 }, - }, - { - code: 'foo = object.foo;', - options: [ - { AssignmentExpression: { object: false } }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'foo = object.foo;', - options: [ - { AssignmentExpression: { object: false } }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'foo = array[0];', - options: [ - { AssignmentExpression: { array: false } }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'foo = array[0];', - options: [ - { AssignmentExpression: { array: false } }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'foo = array[0];', - options: [ - { - VariableDeclarator: { array: true }, - AssignmentExpression: { array: false }, - }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'var foo = array[0];', - options: [ - { - VariableDeclarator: { array: false }, - AssignmentExpression: { array: true }, - }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'foo = object.foo;', - options: [ - { - VariableDeclarator: { object: true }, - AssignmentExpression: { object: false }, - }, - ], - }, - { - code: 'var foo = object.foo;', - options: [ - { - VariableDeclarator: { object: false }, - AssignmentExpression: { object: true }, - }, - ], - }, - 'class Foo extends Bar { static foo() {var foo = super.foo} }', - 'foo = bar[foo];', - 'var foo = bar[foo];', - { - code: 'var {foo: {bar}} = object;', - options: [{ object: true }], - }, - { - code: 'var {bar} = object.foo;', - options: [{ object: true }], - }, - - // Optional chaining - 'var foo = array?.[0];', // because the fixed code can throw TypeError. - 'var foo = object?.foo;', - - // Private identifiers - 'class C { #x; foo() { const x = this.#x; } }', - 'class C { #x; foo() { x = this.#x; } }', - 'class C { #x; foo(a) { x = a.#x; } }', - { - code: 'class C { #x; foo() { const x = this.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo() { const y = this.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo() { x = this.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo() { y = this.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo(a) { x = a.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo(a) { y = a.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo() { x = this.a.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - ], - invalid: [ - { - code: 'var foo = array[0];', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'foo = array[0];', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: 'var foo = object.foo;', - output: 'var {foo} = object;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (a, b).foo;', - output: 'var {foo} = (a, b);', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var length = (() => {}).length;', - output: 'var {length} = () => {};', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (a = b).foo;', - output: 'var {foo} = a = b;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (a || b).foo;', - output: 'var {foo} = a || b;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (f()).foo;', - output: 'var {foo} = f();', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.bar.foo;', - output: 'var {foo} = object.bar;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foobar = object.bar;', - output: null, - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: true }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foobar = object.bar;', - output: null, - options: [{ object: true }, { enforceForRenamedProperties: true }], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object[bar];', - output: null, - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: true }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object[bar];', - output: null, - options: [{ object: true }, { enforceForRenamedProperties: true }], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object[foo];', - output: null, - options: [{ object: true }, { enforceForRenamedProperties: true }], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: "var foo = object['foo'];", - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'foo = object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: "foo = object['foo'];", - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: 'var foo = array[0];', - output: null, - options: [ - { VariableDeclarator: { array: true } }, - { enforceForRenamedProperties: true }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'foo = array[0];', - output: null, - options: [{ AssignmentExpression: { array: true } }], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: 'var foo = array[0];', - output: null, - options: [ - { - VariableDeclarator: { array: true }, - AssignmentExpression: { array: false }, - }, - { enforceForRenamedProperties: true }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = array[0];', - output: null, - options: [ - { - VariableDeclarator: { array: true }, - AssignmentExpression: { array: false }, - }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'foo = array[0];', - output: null, - options: [ - { - VariableDeclarator: { array: false }, - AssignmentExpression: { array: true }, - }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: 'foo = object.foo;', - output: null, - options: [ - { - VariableDeclarator: { array: true, object: false }, - AssignmentExpression: { object: true }, - }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: 'class Foo extends Bar { static foo() {var bar = super.foo.bar} }', - output: 'class Foo extends Bar { static foo() {var {bar} = super.foo} }', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - - // comments - { - code: 'var /* comment */ foo = object.foo;', - output: 'var /* comment */ {foo} = object;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var a, /* comment */foo = object.foo;', - output: 'var a, /* comment */{foo} = object;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo /* comment */ = object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var a, foo /* comment */ = object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo /* comment */ = object.foo, a;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo // comment\n = object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = /* comment */ object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = // comment\n object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (/* comment */ object).foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (object /* comment */).foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = bar(/* comment */).foo;', - output: 'var {foo} = bar(/* comment */);', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = bar/* comment */.baz.foo;', - output: 'var {foo} = bar/* comment */.baz;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = bar[// comment\nbaz].foo;', - output: 'var {foo} = bar[// comment\nbaz];', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo // comment\n = bar(/* comment */).foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = bar/* comment */.baz/* comment */.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object// comment\n.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object./* comment */foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (/* comment */ object.foo);', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (object.foo /* comment */);', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.foo/* comment */;', - output: 'var {foo} = object/* comment */;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.foo// comment', - output: 'var {foo} = object// comment', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.foo/* comment */, a;', - output: 'var {foo} = object/* comment */, a;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.foo// comment\n, a;', - output: 'var {foo} = object// comment\n, a;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.foo, /* comment */ a;', - output: 'var {foo} = object, /* comment */ a;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - ], - /* eslint-enable @typescript-eslint/internal/plugin-test-formatting */ -}; +const baseTests = getBaseTests(); ruleTester.run('prefer-desctructuring', rule, { valid: [ @@ -920,3 +100,826 @@ ruleTester.run('prefer-desctructuring', rule, { }, ], }); + +function getBaseTests(): RunTests<'preferDestructuring', unknown[]> { + /* + * These test cases are based on code from the ESLint project (https://github.com/eslint/eslint). + * ESLint is licensed under the MIT License. + * Copyright (c) 2023 OpenJS Foundation and other contributors, . + */ + return { + valid: [ + 'var [foo] = array;', + 'var { foo } = object;', + 'var foo;', + { + // Ensure that the default behavior does not require destructuring when renaming + code: 'var foo = object.bar;', + options: [{ VariableDeclarator: { object: true } }], + }, + { + // Ensure that the default behavior does not require destructuring when renaming + code: 'var foo = object.bar;', + options: [{ object: true }], + }, + { + code: 'var foo = object.bar;', + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'var foo = object.bar;', + options: [{ object: true }, { enforceForRenamedProperties: false }], + }, + { + code: "var foo = object['bar'];", + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'var foo = object[bar];', + options: [{ object: true }, { enforceForRenamedProperties: false }], + }, + { + code: 'var { bar: foo } = object;', + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'var { bar: foo } = object;', + options: [{ object: true }, { enforceForRenamedProperties: true }], + }, + { + code: 'var { [bar]: foo } = object;', + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'var { [bar]: foo } = object;', + options: [{ object: true }, { enforceForRenamedProperties: true }], + }, + { + code: 'var foo = array[0];', + options: [{ VariableDeclarator: { array: false } }], + }, + { + code: 'var foo = array[0];', + options: [{ array: false }], + }, + { + code: 'var foo = object.foo;', + options: [{ VariableDeclarator: { object: false } }], + }, + { + code: "var foo = object['foo'];", + options: [{ VariableDeclarator: { object: false } }], + }, + '({ foo } = object);', + { + // Fix #8654 + code: 'var foo = array[0];', + options: [ + { VariableDeclarator: { array: false } }, + { enforceForRenamedProperties: true }, + ], + }, + { + // Fix #8654 + code: 'var foo = array[0];', + options: [{ array: false }, { enforceForRenamedProperties: true }], + }, + '[foo] = array;', + 'foo += array[0]', + { + code: 'foo &&= array[0]', + parserOptions: { ecmaVersion: 2021 }, + }, + 'foo += bar.foo', + { + code: 'foo ||= bar.foo', + parserOptions: { ecmaVersion: 2021 }, + }, + { + code: "foo ??= bar['foo']", + parserOptions: { ecmaVersion: 2021 }, + }, + { + code: 'foo = object.foo;', + options: [ + { AssignmentExpression: { object: false } }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'foo = object.foo;', + options: [ + { AssignmentExpression: { object: false } }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'foo = array[0];', + options: [ + { AssignmentExpression: { array: false } }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'foo = array[0];', + options: [ + { AssignmentExpression: { array: false } }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'foo = array[0];', + options: [ + { + VariableDeclarator: { array: true }, + AssignmentExpression: { array: false }, + }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'var foo = array[0];', + options: [ + { + VariableDeclarator: { array: false }, + AssignmentExpression: { array: true }, + }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'foo = object.foo;', + options: [ + { + VariableDeclarator: { object: true }, + AssignmentExpression: { object: false }, + }, + ], + }, + { + code: 'var foo = object.foo;', + options: [ + { + VariableDeclarator: { object: false }, + AssignmentExpression: { object: true }, + }, + ], + }, + 'class Foo extends Bar { static foo() {var foo = super.foo} }', + 'foo = bar[foo];', + 'var foo = bar[foo];', + { + code: 'var {foo: {bar}} = object;', + options: [{ object: true }], + }, + { + code: 'var {bar} = object.foo;', + options: [{ object: true }], + }, + + // Optional chaining + 'var foo = array?.[0];', // because the fixed code can throw TypeError. + 'var foo = object?.foo;', + + // Private identifiers + 'class C { #x; foo() { const x = this.#x; } }', + 'class C { #x; foo() { x = this.#x; } }', + 'class C { #x; foo(a) { x = a.#x; } }', + { + code: 'class C { #x; foo() { const x = this.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo() { const y = this.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo() { x = this.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo() { y = this.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo(a) { x = a.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo(a) { y = a.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo() { x = this.a.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + ], + invalid: [ + { + code: 'var foo = array[0];', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'foo = array[0];', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: 'var foo = object.foo;', + output: 'var {foo} = object;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (a, b).foo;', + output: 'var {foo} = (a, b);', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var length = (() => {}).length;', + output: 'var {length} = () => {};', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (a = b).foo;', + output: 'var {foo} = a = b;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (a || b).foo;', + output: 'var {foo} = a || b;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (f()).foo;', + output: 'var {foo} = f();', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.bar.foo;', + output: 'var {foo} = object.bar;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foobar = object.bar;', + output: null, + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foobar = object.bar;', + output: null, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object[bar];', + output: null, + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object[bar];', + output: null, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object[foo];', + output: null, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: "var foo = object['foo'];", + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'foo = object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: "foo = object['foo'];", + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: 'var foo = array[0];', + output: null, + options: [ + { VariableDeclarator: { array: true } }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'foo = array[0];', + output: null, + options: [{ AssignmentExpression: { array: true } }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: 'var foo = array[0];', + output: null, + options: [ + { + VariableDeclarator: { array: true }, + AssignmentExpression: { array: false }, + }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = array[0];', + output: null, + options: [ + { + VariableDeclarator: { array: true }, + AssignmentExpression: { array: false }, + }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'foo = array[0];', + output: null, + options: [ + { + VariableDeclarator: { array: false }, + AssignmentExpression: { array: true }, + }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: 'foo = object.foo;', + output: null, + options: [ + { + VariableDeclarator: { array: true, object: false }, + AssignmentExpression: { object: true }, + }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: 'class Foo extends Bar { static foo() {var bar = super.foo.bar} }', + output: + 'class Foo extends Bar { static foo() {var {bar} = super.foo} }', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + + // comments + { + code: 'var /* comment */ foo = object.foo;', + output: 'var /* comment */ {foo} = object;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var a, /* comment */foo = object.foo;', + output: 'var a, /* comment */{foo} = object;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo /* comment */ = object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var a, foo /* comment */ = object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo /* comment */ = object.foo, a;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo // comment\n = object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = /* comment */ object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = // comment\n object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (/* comment */ object).foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (object /* comment */).foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = bar(/* comment */).foo;', + output: 'var {foo} = bar(/* comment */);', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = bar/* comment */.baz.foo;', + output: 'var {foo} = bar/* comment */.baz;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = bar[// comment\nbaz].foo;', + output: 'var {foo} = bar[// comment\nbaz];', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo // comment\n = bar(/* comment */).foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = bar/* comment */.baz/* comment */.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object// comment\n.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object./* comment */foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (/* comment */ object.foo);', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (object.foo /* comment */);', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.foo/* comment */;', + output: 'var {foo} = object/* comment */;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.foo// comment', + output: 'var {foo} = object// comment', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.foo/* comment */, a;', + output: 'var {foo} = object/* comment */, a;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.foo// comment\n, a;', + output: 'var {foo} = object// comment\n, a;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.foo, /* comment */ a;', + output: 'var {foo} = object, /* comment */ a;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + ], + }; +} From 47b9e6c85fb3ac5b7c142ecf7347f95540a632fa Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Thu, 15 Jun 2023 22:38:39 +0900 Subject: [PATCH 08/35] refactor --- .../src/rules/prefer-destructuring.ts | 15 +- .../tests/rules/prefer-destructuring.test.ts | 1644 ++++++++--------- .../eslint-plugin/typings/eslint-rules.d.ts | 27 +- 3 files changed, 839 insertions(+), 847 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index 97bb70efdb8..9c95dd2f682 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -11,12 +11,13 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('prefer-destructuring'); type BaseOptions = InferOptionsTypeFromRule; -type Options = [ - BaseOptions[0], - BaseOptions[1] & { - enforceForTypeAnnotatedProperties?: boolean; - }, -]; +type FullBaseOptions = BaseOptions & [unknown, unknown]; +type Options0 = FullBaseOptions[0]; +type Options1 = FullBaseOptions[1] & { + enforceForTypeAnnotatedProperties?: boolean; +}; +type Options = [] | [Options0] | [Options0, Options1]; + type MessageIds = InferMessageIdsTypeFromRule; const destructuringTypeConfig: JSONSchema4 = { @@ -90,7 +91,7 @@ export default createRule({ enforceForTypeAnnotatedProperties: false, }, ], - create(context, [, { enforceForTypeAnnotatedProperties = false }]) { + create(context, [, { enforceForTypeAnnotatedProperties = false } = {}]) { const rules = baseRule.create(context); return { VariableDeclarator(node): void { diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index 696e1c8feb3..a349f243e8d 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -1,7 +1,6 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import rule from '../../src/rules/prefer-destructuring'; -import type { RunTests } from '../RuleTester'; import { getFixturesRootDir, RuleTester } from '../RuleTester'; const rootPath = getFixturesRootDir(); @@ -15,11 +14,8 @@ const ruleTester = new RuleTester({ }, }); -const baseTests = getBaseTests(); - ruleTester.run('prefer-desctructuring', rule, { valid: [ - ...baseTests.valid, // type annotated 'var foo: string = object.foo;', 'const bar: number = array[0];', @@ -54,7 +50,6 @@ ruleTester.run('prefer-desctructuring', rule, { }, ], invalid: [ - ...baseTests.invalid, // enforceForTypeAnnotatedProperties: true { code: 'var foo: string = object.foo;', @@ -101,825 +96,824 @@ ruleTester.run('prefer-desctructuring', rule, { ], }); -function getBaseTests(): RunTests<'preferDestructuring', unknown[]> { - /* - * These test cases are based on code from the ESLint project (https://github.com/eslint/eslint). - * ESLint is licensed under the MIT License. - * Copyright (c) 2023 OpenJS Foundation and other contributors, . - */ - return { - valid: [ - 'var [foo] = array;', - 'var { foo } = object;', - 'var foo;', - { - // Ensure that the default behavior does not require destructuring when renaming - code: 'var foo = object.bar;', - options: [{ VariableDeclarator: { object: true } }], - }, - { - // Ensure that the default behavior does not require destructuring when renaming - code: 'var foo = object.bar;', - options: [{ object: true }], - }, - { - code: 'var foo = object.bar;', - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'var foo = object.bar;', - options: [{ object: true }, { enforceForRenamedProperties: false }], - }, - { - code: "var foo = object['bar'];", - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'var foo = object[bar];', - options: [{ object: true }, { enforceForRenamedProperties: false }], - }, - { - code: 'var { bar: foo } = object;', - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'var { bar: foo } = object;', - options: [{ object: true }, { enforceForRenamedProperties: true }], - }, - { - code: 'var { [bar]: foo } = object;', - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'var { [bar]: foo } = object;', - options: [{ object: true }, { enforceForRenamedProperties: true }], - }, - { - code: 'var foo = array[0];', - options: [{ VariableDeclarator: { array: false } }], - }, - { - code: 'var foo = array[0];', - options: [{ array: false }], - }, - { - code: 'var foo = object.foo;', - options: [{ VariableDeclarator: { object: false } }], - }, - { - code: "var foo = object['foo'];", - options: [{ VariableDeclarator: { object: false } }], - }, - '({ foo } = object);', - { - // Fix #8654 - code: 'var foo = array[0];', - options: [ - { VariableDeclarator: { array: false } }, - { enforceForRenamedProperties: true }, - ], - }, - { - // Fix #8654 - code: 'var foo = array[0];', - options: [{ array: false }, { enforceForRenamedProperties: true }], - }, - '[foo] = array;', - 'foo += array[0]', - { - code: 'foo &&= array[0]', - parserOptions: { ecmaVersion: 2021 }, - }, - 'foo += bar.foo', - { - code: 'foo ||= bar.foo', - parserOptions: { ecmaVersion: 2021 }, - }, - { - code: "foo ??= bar['foo']", - parserOptions: { ecmaVersion: 2021 }, - }, - { - code: 'foo = object.foo;', - options: [ - { AssignmentExpression: { object: false } }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'foo = object.foo;', - options: [ - { AssignmentExpression: { object: false } }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'foo = array[0];', - options: [ - { AssignmentExpression: { array: false } }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'foo = array[0];', - options: [ - { AssignmentExpression: { array: false } }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'foo = array[0];', - options: [ - { - VariableDeclarator: { array: true }, - AssignmentExpression: { array: false }, - }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'var foo = array[0];', - options: [ - { - VariableDeclarator: { array: false }, - AssignmentExpression: { array: true }, - }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'foo = object.foo;', - options: [ - { - VariableDeclarator: { object: true }, - AssignmentExpression: { object: false }, - }, - ], - }, - { - code: 'var foo = object.foo;', - options: [ - { - VariableDeclarator: { object: false }, - AssignmentExpression: { object: true }, - }, - ], - }, - 'class Foo extends Bar { static foo() {var foo = super.foo} }', - 'foo = bar[foo];', - 'var foo = bar[foo];', - { - code: 'var {foo: {bar}} = object;', - options: [{ object: true }], - }, - { - code: 'var {bar} = object.foo;', - options: [{ object: true }], - }, - - // Optional chaining - 'var foo = array?.[0];', // because the fixed code can throw TypeError. - 'var foo = object?.foo;', +/* + * These test cases are based on code from the ESLint project (https://github.com/eslint/eslint). + * ESLint is licensed under the MIT License. + * Copyright (c) 2023 OpenJS Foundation and other contributors, . + */ +ruleTester.run('prefer-desctructuring', rule, { + /* eslint-disable @typescript-eslint/internal/plugin-test-formatting */ + valid: [ + 'var [foo] = array;', + 'var { foo } = object;', + 'var foo;', + { + // Ensure that the default behavior does not require destructuring when renaming + code: 'var foo = object.bar;', + options: [{ VariableDeclarator: { object: true } }, {}], + }, + { + // Ensure that the default behavior does not require destructuring when renaming + code: 'var foo = object.bar;', + options: [{ object: true }, {}], + }, + { + code: 'var foo = object.bar;', + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'var foo = object.bar;', + options: [{ object: true }, { enforceForRenamedProperties: false }], + }, + { + code: "var foo = object['bar'];", + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'var foo = object[bar];', + options: [{ object: true }, { enforceForRenamedProperties: false }], + }, + { + code: 'var { bar: foo } = object;', + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'var { bar: foo } = object;', + options: [{ object: true }, { enforceForRenamedProperties: true }], + }, + { + code: 'var { [bar]: foo } = object;', + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'var { [bar]: foo } = object;', + options: [{ object: true }, { enforceForRenamedProperties: true }], + }, + { + code: 'var foo = array[0];', + options: [{ VariableDeclarator: { array: false } }], + }, + { + code: 'var foo = array[0];', + options: [{ array: false }], + }, + { + code: 'var foo = object.foo;', + options: [{ VariableDeclarator: { object: false } }], + }, + { + code: "var foo = object['foo'];", + options: [{ VariableDeclarator: { object: false } }], + }, + '({ foo } = object);', + { + // Fix #8654 + code: 'var foo = array[0];', + options: [ + { VariableDeclarator: { array: false } }, + { enforceForRenamedProperties: true }, + ], + }, + { + // Fix #8654 + code: 'var foo = array[0];', + options: [{ array: false }, { enforceForRenamedProperties: true }], + }, + '[foo] = array;', + 'foo += array[0]', + { + code: 'foo &&= array[0]', + parserOptions: { ecmaVersion: 2021 }, + }, + 'foo += bar.foo', + { + code: 'foo ||= bar.foo', + parserOptions: { ecmaVersion: 2021 }, + }, + { + code: "foo ??= bar['foo']", + parserOptions: { ecmaVersion: 2021 }, + }, + { + code: 'foo = object.foo;', + options: [ + { AssignmentExpression: { object: false } }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'foo = object.foo;', + options: [ + { AssignmentExpression: { object: false } }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'foo = array[0];', + options: [ + { AssignmentExpression: { array: false } }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'foo = array[0];', + options: [ + { AssignmentExpression: { array: false } }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'foo = array[0];', + options: [ + { + VariableDeclarator: { array: true }, + AssignmentExpression: { array: false }, + }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'var foo = array[0];', + options: [ + { + VariableDeclarator: { array: false }, + AssignmentExpression: { array: true }, + }, + { enforceForRenamedProperties: false }, + ], + }, + { + code: 'foo = object.foo;', + options: [ + { + VariableDeclarator: { object: true }, + AssignmentExpression: { object: false }, + }, + ], + }, + { + code: 'var foo = object.foo;', + options: [ + { + VariableDeclarator: { object: false }, + AssignmentExpression: { object: true }, + }, + ], + }, + 'class Foo extends Bar { static foo() {var foo = super.foo} }', + 'foo = bar[foo];', + 'var foo = bar[foo];', + { + code: 'var {foo: {bar}} = object;', + options: [{ object: true }], + }, + { + code: 'var {bar} = object.foo;', + options: [{ object: true }], + }, - // Private identifiers - 'class C { #x; foo() { const x = this.#x; } }', - 'class C { #x; foo() { x = this.#x; } }', - 'class C { #x; foo(a) { x = a.#x; } }', - { - code: 'class C { #x; foo() { const x = this.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo() { const y = this.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo() { x = this.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo() { y = this.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo(a) { x = a.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo(a) { y = a.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo() { x = this.a.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - ], - invalid: [ - { - code: 'var foo = array[0];', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'foo = array[0];', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: 'var foo = object.foo;', - output: 'var {foo} = object;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (a, b).foo;', - output: 'var {foo} = (a, b);', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var length = (() => {}).length;', - output: 'var {length} = () => {};', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (a = b).foo;', - output: 'var {foo} = a = b;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (a || b).foo;', - output: 'var {foo} = a || b;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (f()).foo;', - output: 'var {foo} = f();', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.bar.foo;', - output: 'var {foo} = object.bar;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foobar = object.bar;', - output: null, - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: true }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foobar = object.bar;', - output: null, - options: [{ object: true }, { enforceForRenamedProperties: true }], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object[bar];', - output: null, - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: true }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object[bar];', - output: null, - options: [{ object: true }, { enforceForRenamedProperties: true }], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object[foo];', - output: null, - options: [{ object: true }, { enforceForRenamedProperties: true }], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: "var foo = object['foo'];", - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'foo = object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: "foo = object['foo'];", - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: 'var foo = array[0];', - output: null, - options: [ - { VariableDeclarator: { array: true } }, - { enforceForRenamedProperties: true }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'foo = array[0];', - output: null, - options: [{ AssignmentExpression: { array: true } }], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: 'var foo = array[0];', - output: null, - options: [ - { - VariableDeclarator: { array: true }, - AssignmentExpression: { array: false }, - }, - { enforceForRenamedProperties: true }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = array[0];', - output: null, - options: [ - { - VariableDeclarator: { array: true }, - AssignmentExpression: { array: false }, - }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'foo = array[0];', - output: null, - options: [ - { - VariableDeclarator: { array: false }, - AssignmentExpression: { array: true }, - }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: 'foo = object.foo;', - output: null, - options: [ - { - VariableDeclarator: { array: true, object: false }, - AssignmentExpression: { object: true }, - }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: 'class Foo extends Bar { static foo() {var bar = super.foo.bar} }', - output: - 'class Foo extends Bar { static foo() {var {bar} = super.foo} }', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, + // Optional chaining + 'var foo = array?.[0];', // because the fixed code can throw TypeError. + 'var foo = object?.foo;', - // comments - { - code: 'var /* comment */ foo = object.foo;', - output: 'var /* comment */ {foo} = object;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var a, /* comment */foo = object.foo;', - output: 'var a, /* comment */{foo} = object;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo /* comment */ = object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var a, foo /* comment */ = object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo /* comment */ = object.foo, a;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo // comment\n = object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = /* comment */ object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = // comment\n object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (/* comment */ object).foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (object /* comment */).foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = bar(/* comment */).foo;', - output: 'var {foo} = bar(/* comment */);', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = bar/* comment */.baz.foo;', - output: 'var {foo} = bar/* comment */.baz;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = bar[// comment\nbaz].foo;', - output: 'var {foo} = bar[// comment\nbaz];', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo // comment\n = bar(/* comment */).foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = bar/* comment */.baz/* comment */.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object// comment\n.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object./* comment */foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (/* comment */ object.foo);', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (object.foo /* comment */);', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.foo/* comment */;', - output: 'var {foo} = object/* comment */;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.foo// comment', - output: 'var {foo} = object// comment', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.foo/* comment */, a;', - output: 'var {foo} = object/* comment */, a;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.foo// comment\n, a;', - output: 'var {foo} = object// comment\n, a;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.foo, /* comment */ a;', - output: 'var {foo} = object, /* comment */ a;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - ], - }; -} + // Private identifiers + 'class C { #x; foo() { const x = this.#x; } }', + 'class C { #x; foo() { x = this.#x; } }', + 'class C { #x; foo(a) { x = a.#x; } }', + { + code: 'class C { #x; foo() { const x = this.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo() { const y = this.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo() { x = this.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo() { y = this.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo(a) { x = a.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo(a) { y = a.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: 'class C { #x; foo() { x = this.a.#x; } }', + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + ], + invalid: [ + { + code: 'var foo = array[0];', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'foo = array[0];', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: 'var foo = object.foo;', + output: 'var {foo} = object;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (a, b).foo;', + output: 'var {foo} = (a, b);', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var length = (() => {}).length;', + output: 'var {length} = () => {};', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (a = b).foo;', + output: 'var {foo} = a = b;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (a || b).foo;', + output: 'var {foo} = a || b;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (f()).foo;', + output: 'var {foo} = f();', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.bar.foo;', + output: 'var {foo} = object.bar;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foobar = object.bar;', + output: null, + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foobar = object.bar;', + output: null, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object[bar];', + output: null, + options: [ + { VariableDeclarator: { object: true } }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object[bar];', + output: null, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object[foo];', + output: null, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: "var foo = object['foo'];", + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'foo = object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: "foo = object['foo'];", + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: 'var foo = array[0];', + output: null, + options: [ + { VariableDeclarator: { array: true } }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'foo = array[0];', + output: null, + options: [{ AssignmentExpression: { array: true } }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: 'var foo = array[0];', + output: null, + options: [ + { + VariableDeclarator: { array: true }, + AssignmentExpression: { array: false }, + }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = array[0];', + output: null, + options: [ + { + VariableDeclarator: { array: true }, + AssignmentExpression: { array: false }, + }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'foo = array[0];', + output: null, + options: [ + { + VariableDeclarator: { array: false }, + AssignmentExpression: { array: true }, + }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: 'foo = object.foo;', + output: null, + options: [ + { + VariableDeclarator: { array: true, object: false }, + AssignmentExpression: { object: true }, + }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: 'class Foo extends Bar { static foo() {var bar = super.foo.bar} }', + output: 'class Foo extends Bar { static foo() {var {bar} = super.foo} }', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + + // comments + { + code: 'var /* comment */ foo = object.foo;', + output: 'var /* comment */ {foo} = object;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var a, /* comment */foo = object.foo;', + output: 'var a, /* comment */{foo} = object;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo /* comment */ = object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var a, foo /* comment */ = object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo /* comment */ = object.foo, a;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo // comment\n = object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = /* comment */ object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = // comment\n object.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (/* comment */ object).foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (object /* comment */).foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = bar(/* comment */).foo;', + output: 'var {foo} = bar(/* comment */);', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = bar/* comment */.baz.foo;', + output: 'var {foo} = bar/* comment */.baz;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = bar[// comment\nbaz].foo;', + output: 'var {foo} = bar[// comment\nbaz];', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo // comment\n = bar(/* comment */).foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = bar/* comment */.baz/* comment */.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object// comment\n.foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object./* comment */foo;', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (/* comment */ object.foo);', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (object.foo /* comment */);', + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.foo/* comment */;', + output: 'var {foo} = object/* comment */;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.foo// comment', + output: 'var {foo} = object// comment', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.foo/* comment */, a;', + output: 'var {foo} = object/* comment */, a;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.foo// comment\n, a;', + output: 'var {foo} = object// comment\n, a;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = object.foo, /* comment */ a;', + output: 'var {foo} = object, /* comment */ a;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + ], + /* eslint-enable @typescript-eslint/internal/plugin-test-formatting */ +}); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index cd54c71c6e8..e1d5a0cd4c8 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -996,24 +996,21 @@ declare module 'eslint/lib/rules/prefer-destructuring' { import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; interface DestructuringTypeConfig { - object: boolean; - array: boolean; + object?: boolean; + array?: boolean; + } + type Option0 = + | DestructuringTypeConfig + | { + VariableDeclarator?: DestructuringTypeConfig; + AssignmentExpression?: DestructuringTypeConfig; + }; + interface Option1 { + enforceForRenamedProperties?: boolean; } - const rule: TSESLint.RuleModule< 'preferDestructuring', - [ - ( - | DestructuringTypeConfig - | { - VariableDeclarator?: DestructuringTypeConfig; - AssignmentExpression?: DestructuringTypeConfig; - } - ), - { - enforceForRenamedProperties?: boolean; - }, - ], + [] | [Option0] | [Option0, Option1], { VariableDeclarator(node: TSESTree.VariableDeclarator): void; AssignmentExpression(node: TSESTree.AssignmentExpression): void; From e997503d7a229c1d430e6363205d2a13940d1664 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Sat, 17 Jun 2023 13:10:04 +0900 Subject: [PATCH 09/35] consider numeric properties of non-itreable objects for VariableDeclarator --- .../src/rules/prefer-destructuring.ts | 102 ++++++++++-- .../tests/rules/prefer-destructuring.test.ts | 150 ++++++++++++++++++ 2 files changed, 242 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index 9c95dd2f682..a1e56e5cb6d 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -1,11 +1,14 @@ -import type { TSESLint } from '@typescript-eslint/utils'; +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import type { JSONSchema4 } from 'json-schema'; +import * as tsutils from 'tsutils'; +import type * as ts from 'typescript'; import type { InferMessageIdsTypeFromRule, InferOptionsTypeFromRule, } from '../util'; -import { createRule } from '../util'; +import { createRule, getParserServices, isTypeAnyType } from '../util'; import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('prefer-destructuring'); @@ -91,24 +94,76 @@ export default createRule({ enforceForTypeAnnotatedProperties: false, }, ], - create(context, [, { enforceForTypeAnnotatedProperties = false } = {}]) { - const rules = baseRule.create(context); + create( + context, + [ + enabledTypes, + { + enforceForRenamedProperties = false, + enforceForTypeAnnotatedProperties = false, + } = {}, + ], + ) { + const { program, esTreeNodeToTSNodeMap } = getParserServices(context); + const typeChecker = program.getTypeChecker(); + const baseRules = baseRule.create(context); return { VariableDeclarator(node): void { - if (node.id.typeAnnotation === undefined) { - rules.VariableDeclarator(node); + const rules = + node.id.typeAnnotation === undefined + ? baseRules + : baseRule.create(noFixContext(context)); + if ( + node.id.typeAnnotation !== undefined && + !enforceForTypeAnnotatedProperties + ) { return; } - if (!enforceForTypeAnnotatedProperties) { - return; + if ( + node.init != null && + node.init.type === AST_NODE_TYPES.MemberExpression && + isArrayLiteralIntegerIndexAccess(node.init) + ) { + const tsObj = esTreeNodeToTSNodeMap.get(node.init.object); + const objType = typeChecker.getTypeAtLocation(tsObj); + if (!isTypeIterableType(objType, typeChecker)) { + if ( + !enforceForRenamedProperties || + !getNormalizedEnabledType(node.type, 'object') + ) { + return; + } + context.report({ + node, + messageId: 'preferDestructuring', + data: { type: 'object' }, + }); + return; + } } - const noFixRules = baseRule.create(noFixContext(context)); - noFixRules.VariableDeclarator(node); + rules.VariableDeclarator(node); }, AssignmentExpression(node): void { + const rules = baseRules; rules.AssignmentExpression(node); }, }; + function getNormalizedEnabledType( + nodeType: + | AST_NODE_TYPES.VariableDeclarator + | AST_NODE_TYPES.AssignmentExpression, + destructuringType: 'array' | 'object', + ): boolean | undefined { + if (enabledTypes === undefined) { + return true; + } + if ('object' in enabledTypes || 'array' in enabledTypes) { + return enabledTypes[destructuringType]; + } + return enabledTypes[nodeType as keyof typeof enabledTypes]?.[ + destructuringType as keyof typeof enabledTypes[keyof typeof enabledTypes] + ]; + } }, }); @@ -138,3 +193,30 @@ function noFixContext(context: Context): Context { }, }); } + +function isTypeIterableType( + type: ts.Type, + typeChecker: ts.TypeChecker, +): boolean { + if (isTypeAnyType(type)) { + return true; + } + if (!type.isUnion()) { + const iterator = tsutils.getWellKnownSymbolPropertyOfType( + type, + 'iterator', + typeChecker, + ); + return iterator !== undefined; + } + return type.types.every(t => isTypeIterableType(t, typeChecker)); +} + +function isArrayLiteralIntegerIndexAccess( + node: TSESTree.MemberExpression, +): boolean { + if (node.property.type !== AST_NODE_TYPES.Literal) { + return false; + } + return Number.isInteger(node.property.value); +} diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index a349f243e8d..54da6e0f6ae 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -48,6 +48,45 @@ ruleTester.run('prefer-desctructuring', rule, { code: 'var { foo: bar }: { foo: boolean } = object;', options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], }, + + // numeric property for iterable / non-iterable + ` + let x: { 0: unknown }; + let y = x[0]; + `, + ` + let x: unknown; + let y = x[0]; + `, + ` + let x: { 0: unknown } | unknown[]; + let y = x[0]; + `, + ` + let x: { 0: unknown } & (() => void); + let y = x[0]; + `, + ` + let x: Record; + let y = x[0]; + `, + { + code: ` + let x: { 0: unknown }; + let { 0: y } = x; + `, + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: ` + let x: { 0: unknown }; + let y = x[0]; + `, + options: [{ array: true }, { enforceForRenamedProperties: true }], + }, ], invalid: [ // enforceForTypeAnnotatedProperties: true @@ -93,6 +132,117 @@ ruleTester.run('prefer-desctructuring', rule, { }, ], }, + + // numeric property for iterable / non-iterable + { + code: ` + let x: { [Symbol.iterator]: unknown }; + let y = x[0]; + `, + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` + let x: [1, 2, 3]; + let y = x[0]; + `, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` + function* it() { + yield 1; + } + let y = it()[0]; + `, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` + let x: any; + let y = x[0]; + `, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` + let x: string[] | { [Symbol.iterator]: unknown }; + let y = x[0]; + `, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` + let x: object & unknown[]; + let y = x[0]; + `, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` + let x: { 0: string }; + let y = x[0]; + `, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` + let x: { 0: unknown } | unknown[]; + let y = x[0]; + `, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, ], }); From 375f99cbab4a4f4fb349d52518b1b4de965e7043 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Sun, 18 Jun 2023 22:41:50 +0900 Subject: [PATCH 10/35] consider numeric properties of non-itreable objects for AssignmentExpression --- .../src/rules/prefer-destructuring.ts | 95 +++++++----- .../tests/rules/prefer-destructuring.test.ts | 146 ++++++++++++++++++ 2 files changed, 201 insertions(+), 40 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index a1e56e5cb6d..0a087041fe1 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -104,50 +104,62 @@ export default createRule({ } = {}, ], ) { - const { program, esTreeNodeToTSNodeMap } = getParserServices(context); - const typeChecker = program.getTypeChecker(); - const baseRules = baseRule.create(context); return { VariableDeclarator(node): void { - const rules = - node.id.typeAnnotation === undefined - ? baseRules - : baseRule.create(noFixContext(context)); - if ( - node.id.typeAnnotation !== undefined && - !enforceForTypeAnnotatedProperties - ) { - return; - } - if ( - node.init != null && - node.init.type === AST_NODE_TYPES.MemberExpression && - isArrayLiteralIntegerIndexAccess(node.init) - ) { - const tsObj = esTreeNodeToTSNodeMap.get(node.init.object); - const objType = typeChecker.getTypeAtLocation(tsObj); - if (!isTypeIterableType(objType, typeChecker)) { - if ( - !enforceForRenamedProperties || - !getNormalizedEnabledType(node.type, 'object') - ) { - return; - } - context.report({ - node, - messageId: 'preferDestructuring', - data: { type: 'object' }, - }); - return; - } - } - rules.VariableDeclarator(node); + performCheck(node.id, node.init, node); }, AssignmentExpression(node): void { - const rules = baseRules; - rules.AssignmentExpression(node); + performCheck(node.left, node.right, node); }, }; + + function performCheck( + leftNode: TSESTree.BindingName | TSESTree.Expression, + rightNode: TSESTree.Expression | null, + reportNode: TSESTree.VariableDeclarator | TSESTree.AssignmentExpression, + ): void { + const { program, esTreeNodeToTSNodeMap } = getParserServices(context); + const typeChecker = program.getTypeChecker(); + const baseRules = baseRule.create(context); + const rules = + leftNode.type === AST_NODE_TYPES.Identifier && + leftNode.typeAnnotation === undefined + ? baseRules + : baseRule.create(noFixContext(context)); + if ( + 'typeAnnotation' in leftNode && + leftNode.typeAnnotation !== undefined && + !enforceForTypeAnnotatedProperties + ) { + return; + } + + if (rightNode != null && isArrayLiteralIntegerIndexAccess(rightNode)) { + const tsObj = esTreeNodeToTSNodeMap.get(rightNode.object); + const objType = typeChecker.getTypeAtLocation(tsObj); + if (!isTypeIterableType(objType, typeChecker)) { + if ( + !enforceForRenamedProperties || + !getNormalizedEnabledType(reportNode.type, 'object') + ) { + return; + } + context.report({ + node: reportNode, + messageId: 'preferDestructuring', + data: { type: 'object' }, + }); + return; + } + } + + if (reportNode.type === AST_NODE_TYPES.AssignmentExpression) { + rules.AssignmentExpression(reportNode); + } else { + rules.VariableDeclarator(reportNode); + } + } + function getNormalizedEnabledType( nodeType: | AST_NODE_TYPES.VariableDeclarator @@ -213,8 +225,11 @@ function isTypeIterableType( } function isArrayLiteralIntegerIndexAccess( - node: TSESTree.MemberExpression, -): boolean { + node: TSESTree.Expression, +): node is TSESTree.MemberExpression { + if (node.type !== AST_NODE_TYPES.MemberExpression) { + return false; + } if (node.property.type !== AST_NODE_TYPES.Literal) { return false; } diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index 54da6e0f6ae..7170040f0bf 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -54,22 +54,42 @@ ruleTester.run('prefer-desctructuring', rule, { let x: { 0: unknown }; let y = x[0]; `, + ` + let x: { 0: unknown }; + y = x[0]; + `, ` let x: unknown; let y = x[0]; `, + ` + let x: unknown; + y = x[0]; + `, ` let x: { 0: unknown } | unknown[]; let y = x[0]; `, + ` + let x: { 0: unknown } | unknown[]; + y = x[0]; + `, ` let x: { 0: unknown } & (() => void); let y = x[0]; `, + ` + let x: { 0: unknown } & (() => void); + y = x[0]; + `, ` let x: Record; let y = x[0]; `, + ` + let x: Record; + y = x[0]; + `, { code: ` let x: { 0: unknown }; @@ -80,6 +100,16 @@ ruleTester.run('prefer-desctructuring', rule, { { enforceForRenamedProperties: true }, ], }, + { + code: ` + let x: { 0: unknown }; + ({ 0: y } = x); + `, + options: [ + { array: true, object: true }, + { enforceForRenamedProperties: true }, + ], + }, { code: ` let x: { 0: unknown }; @@ -87,6 +117,13 @@ ruleTester.run('prefer-desctructuring', rule, { `, options: [{ array: true }, { enforceForRenamedProperties: true }], }, + { + code: ` + let x: { 0: unknown }; + y = x[0]; + `, + options: [{ array: true }, { enforceForRenamedProperties: true }], + }, ], invalid: [ // enforceForTypeAnnotatedProperties: true @@ -148,6 +185,20 @@ ruleTester.run('prefer-desctructuring', rule, { }, ], }, + { + code: ` + let x: { [Symbol.iterator]: unknown }; + y = x[0]; + `, + output: null, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, { code: ` let x: [1, 2, 3]; @@ -161,6 +212,19 @@ ruleTester.run('prefer-desctructuring', rule, { }, ], }, + { + code: ` + let x: [1, 2, 3]; + y = x[0]; + `, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, { code: ` function* it() { @@ -176,6 +240,21 @@ ruleTester.run('prefer-desctructuring', rule, { }, ], }, + { + code: ` + function* it() { + yield 1; + } + y = it()[0]; + `, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, { code: ` let x: any; @@ -189,6 +268,19 @@ ruleTester.run('prefer-desctructuring', rule, { }, ], }, + { + code: ` + let x: any; + y = x[0]; + `, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, { code: ` let x: string[] | { [Symbol.iterator]: unknown }; @@ -202,6 +294,19 @@ ruleTester.run('prefer-desctructuring', rule, { }, ], }, + { + code: ` + let x: string[] | { [Symbol.iterator]: unknown }; + y = x[0]; + `, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, { code: ` let x: object & unknown[]; @@ -215,6 +320,19 @@ ruleTester.run('prefer-desctructuring', rule, { }, ], }, + { + code: ` + let x: object & unknown[]; + y = x[0]; + `, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'array' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, { code: ` let x: { 0: string }; @@ -229,6 +347,20 @@ ruleTester.run('prefer-desctructuring', rule, { }, ], }, + { + code: ` + let x: { 0: string }; + y = x[0]; + `, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, { code: ` let x: { 0: unknown } | unknown[]; @@ -243,6 +375,20 @@ ruleTester.run('prefer-desctructuring', rule, { }, ], }, + { + code: ` + let x: { 0: unknown } | unknown[]; + y = x[0]; + `, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, ], }); From 05680aacf016c8043b87bc3ed455ab6aded5ade3 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Sun, 18 Jun 2023 22:45:06 +0900 Subject: [PATCH 11/35] fix a bug --- .../eslint-plugin/src/rules/prefer-destructuring.ts | 3 +++ .../tests/rules/prefer-destructuring.test.ts | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index 0a087041fe1..e80aede2eff 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -109,6 +109,9 @@ export default createRule({ performCheck(node.id, node.init, node); }, AssignmentExpression(node): void { + if (node.operator !== '=') { + return; + } performCheck(node.left, node.right, node); }, }; diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index 7170040f0bf..2d1f33550c9 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -124,6 +124,16 @@ ruleTester.run('prefer-desctructuring', rule, { `, options: [{ array: true }, { enforceForRenamedProperties: true }], }, + { + code: ` + let x: { 0: unknown }; + y += x[0]; + `, + options: [ + { object: true, array: true }, + { enforceForRenamedProperties: true }, + ], + }, ], invalid: [ // enforceForTypeAnnotatedProperties: true From a6879935f15efd8db0afc80801b74387ae1232a2 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Sun, 18 Jun 2023 23:06:50 +0900 Subject: [PATCH 12/35] fix a bug --- .../src/rules/prefer-destructuring.ts | 6 ++- .../tests/rules/prefer-destructuring.test.ts | 42 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index e80aede2eff..204a58bb827 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -137,7 +137,11 @@ export default createRule({ return; } - if (rightNode != null && isArrayLiteralIntegerIndexAccess(rightNode)) { + if ( + rightNode != null && + isArrayLiteralIntegerIndexAccess(rightNode) && + rightNode.object.type !== AST_NODE_TYPES.Super + ) { const tsObj = esTreeNodeToTSNodeMap.get(rightNode.object); const objType = typeChecker.getTypeAtLocation(tsObj); if (!isTypeIterableType(objType, typeChecker)) { diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index 2d1f33550c9..e0caef618c6 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -48,6 +48,16 @@ ruleTester.run('prefer-desctructuring', rule, { code: 'var { foo: bar }: { foo: boolean } = object;', options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], }, + { + code: ` + class Bar extends Foo { + static foo() { + var foo: any = super.foo; + } + } + `, + options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + }, // numeric property for iterable / non-iterable ` @@ -134,6 +144,38 @@ ruleTester.run('prefer-desctructuring', rule, { { enforceForRenamedProperties: true }, ], }, + { + code: ` + class Bar { + public [0]: unknown; + } + class Foo extends Bar { + static foo() { + let y = super[0]; + } + } + `, + options: [ + { object: true, array: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: ` + class Bar { + public [0]: unknown; + } + class Foo extends Bar { + static foo() { + y = super[0]; + } + } + `, + options: [ + { object: true, array: true }, + { enforceForRenamedProperties: true }, + ], + }, ], invalid: [ // enforceForTypeAnnotatedProperties: true From 0fbc758a085dd0eb495dec0d2162d6104e606a36 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Sun, 18 Jun 2023 23:20:55 +0900 Subject: [PATCH 13/35] add "openjsf" into the dictionary --- .cspell.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.cspell.json b/.cspell.json index b8e07af4864..b1bca8f9af8 100644 --- a/.cspell.json +++ b/.cspell.json @@ -88,6 +88,7 @@ "nullish", "OOM", "OOMs", + "openjsf", "parameterised", "performant", "pluggable", From 7951154fe3da48bc6c944de5115191c65a4671f4 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Mon, 19 Jun 2023 13:01:58 +0900 Subject: [PATCH 14/35] add prefer-destructuring into all --- packages/eslint-plugin/src/configs/all.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 777cf069827..6478c316b9b 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -137,6 +137,8 @@ export = { '@typescript-eslint/parameter-properties': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-enum-initializers': 'error', + 'prefer-destructuring': 'off', + '@typescript-eslint/prefer-destructuring': 'error', '@typescript-eslint/prefer-for-of': 'error', '@typescript-eslint/prefer-function-type': 'error', '@typescript-eslint/prefer-includes': 'error', From d3626c625baf618a20d0a24fd2d538f2119191b7 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Tue, 20 Jun 2023 18:55:16 +0900 Subject: [PATCH 15/35] add doc and minor tweak --- .../docs/rules/prefer-destructuring.md | 91 +++++++++++++++++++ packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/prefer-destructuring.ts | 12 +-- .../tests/rules/prefer-destructuring.test.ts | 56 +++++++++--- 4 files changed, 142 insertions(+), 19 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/prefer-destructuring.md diff --git a/packages/eslint-plugin/docs/rules/prefer-destructuring.md b/packages/eslint-plugin/docs/rules/prefer-destructuring.md new file mode 100644 index 00000000000..abbbf2efc99 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/prefer-destructuring.md @@ -0,0 +1,91 @@ +--- +description: 'Require destructuring from arrays and/or objects.' +--- + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/prefer-destructuring** for documentation. + +## Examples + +This rule extends the base [`eslint/prefer-destructuring`](https://eslint.org/docs/rules/prfer-destructuring) rule. +It adds support for TypeScript's type annotations in variable declarations. + + + +### `eslint/prefer-destructuring` + +```ts +const x: string = obj.x; // This is incorrect and the auto fixer provides following untyped fix. +// const { x } = obj; +``` + +### `@typescript-eslint/prefer-destructuring` + +```ts +const x: string = obj.x; // This is correct by default. You can also forbid this by an option. +``` + + + +And it infers binding patterns more accurately thanks to the type checker. + + + +### ❌ Incorrect + +```ts +const x = ['a']; +const y = x[0]; +``` + +### ✅ Correct + +```ts +const x: = { 0: 'a' }; +const y = x[0]; +``` + +It is correct when `enforceForRenamedProperties` is not true. +Valid destructuring syntax is renamed style like `{ 0: y } = x` rather than `[y] = x` because `x` is not iterable. + +## Options + +This rule adds the following options: + +```ts +type Options = [ + BasePreferDestructuringOptions[0], + BasePreferDestructuringOptions[1] & { + enforceForDeclarationWithTypeAnnotation?: boolean; + }, +]; + +const defaultOptions: Options = [ + basePreferDestructuringDefaultOptions[0], + { + ...basePreferDestructuringDefaultOptions[1], + enforceForDeclarationWithTypeAnnotation: false, + }, +]; +``` + +### `enforceForDeclarationWithTypeAnnotation` + +When set to `true`, type annotated variable declarations are enforced to use destructuring assignment. + +Examples with `{ enforceForDeclarationWithTypeAnnotation: true }`: + + + +### ❌ Incorrect + +```ts +const x: string = obj.x; +``` + +### ✅ Correct + +```ts +const { x }: { x: string } = obj; +``` diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 405514edd1f..7ab603d2db7 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -99,6 +99,7 @@ import objectCurlySpacing from './object-curly-spacing'; import paddingLineBetweenStatements from './padding-line-between-statements'; import parameterProperties from './parameter-properties'; import preferAsConst from './prefer-as-const'; +import preferDestructuring from './prefer-destructuring'; import preferEnumInitializers from './prefer-enum-initializers'; import preferForOf from './prefer-for-of'; import preferFunctionType from './prefer-function-type'; @@ -237,6 +238,7 @@ export default { 'padding-line-between-statements': paddingLineBetweenStatements, 'parameter-properties': parameterProperties, 'prefer-as-const': preferAsConst, + 'prefer-destructuring': preferDestructuring, 'prefer-enum-initializers': preferEnumInitializers, 'prefer-for-of': preferForOf, 'prefer-function-type': preferFunctionType, diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index 204a58bb827..468eec2b050 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -17,7 +17,7 @@ type BaseOptions = InferOptionsTypeFromRule; type FullBaseOptions = BaseOptions & [unknown, unknown]; type Options0 = FullBaseOptions[0]; type Options1 = FullBaseOptions[1] & { - enforceForTypeAnnotatedProperties?: boolean; + enforceForDeclarationWithTypeAnnotation?: boolean; }; type Options = [] | [Options0] | [Options0, Options1]; @@ -56,7 +56,7 @@ const schema: readonly JSONSchema4[] = [ enforceForRenamedProperties: { type: 'boolean', }, - enforceForTypeAnnotatedProperties: { + enforceForDeclarationWithTypeAnnotation: { type: 'boolean', }, }, @@ -71,7 +71,7 @@ export default createRule({ description: 'Require destructuring from arrays and/or objects', recommended: false, extendsBaseRule: true, - requiresTypeChecking: false, + requiresTypeChecking: true, }, schema, fixable: baseRule.meta?.fixable, @@ -91,7 +91,7 @@ export default createRule({ }, { enforceForRenamedProperties: false, - enforceForTypeAnnotatedProperties: false, + enforceForDeclarationWithTypeAnnotation: false, }, ], create( @@ -100,7 +100,7 @@ export default createRule({ enabledTypes, { enforceForRenamedProperties = false, - enforceForTypeAnnotatedProperties = false, + enforceForDeclarationWithTypeAnnotation = false, } = {}, ], ) { @@ -132,7 +132,7 @@ export default createRule({ if ( 'typeAnnotation' in leftNode && leftNode.typeAnnotation !== undefined && - !enforceForTypeAnnotatedProperties + !enforceForDeclarationWithTypeAnnotation ) { return; } diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index e0caef618c6..a052842f02f 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -19,34 +19,55 @@ ruleTester.run('prefer-desctructuring', rule, { // type annotated 'var foo: string = object.foo;', 'const bar: number = array[0];', - // enforceForTypeAnnotatedProperties: true + // enforceForDeclarationWithTypeAnnotation: true { code: 'var { foo } = object;', - options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + options: [ + { object: true }, + { enforceForDeclarationWithTypeAnnotation: true }, + ], }, { code: 'var { foo }: { foo: number } = object;', - options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + options: [ + { object: true }, + { enforceForDeclarationWithTypeAnnotation: true }, + ], }, { code: 'var [foo] = array;', - options: [{ array: true }, { enforceForTypeAnnotatedProperties: true }], + options: [ + { array: true }, + { enforceForDeclarationWithTypeAnnotation: true }, + ], }, { code: 'var [foo]: [foo: number] = array;', - options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + options: [ + { object: true }, + { enforceForDeclarationWithTypeAnnotation: true }, + ], }, { code: 'var foo: unknown = object.bar;', - options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + options: [ + { object: true }, + { enforceForDeclarationWithTypeAnnotation: true }, + ], }, { code: 'var { foo: bar } = object;', - options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + options: [ + { object: true }, + { enforceForDeclarationWithTypeAnnotation: true }, + ], }, { code: 'var { foo: bar }: { foo: boolean } = object;', - options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + options: [ + { object: true }, + { enforceForDeclarationWithTypeAnnotation: true }, + ], }, { code: ` @@ -56,7 +77,10 @@ ruleTester.run('prefer-desctructuring', rule, { } } `, - options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + options: [ + { object: true }, + { enforceForDeclarationWithTypeAnnotation: true }, + ], }, // numeric property for iterable / non-iterable @@ -178,10 +202,13 @@ ruleTester.run('prefer-desctructuring', rule, { }, ], invalid: [ - // enforceForTypeAnnotatedProperties: true + // enforceForDeclarationWithTypeAnnotation: true { code: 'var foo: string = object.foo;', - options: [{ object: true }, { enforceForTypeAnnotatedProperties: true }], + options: [ + { object: true }, + { enforceForDeclarationWithTypeAnnotation: true }, + ], output: null, errors: [ { @@ -193,7 +220,10 @@ ruleTester.run('prefer-desctructuring', rule, { }, { code: 'var foo: string = array[0];', - options: [{ array: true }, { enforceForTypeAnnotatedProperties: true }], + options: [ + { array: true }, + { enforceForDeclarationWithTypeAnnotation: true }, + ], output: null, errors: [ { @@ -208,7 +238,7 @@ ruleTester.run('prefer-desctructuring', rule, { options: [ { object: true }, { - enforceForTypeAnnotatedProperties: true, + enforceForDeclarationWithTypeAnnotation: true, enforceForRenamedProperties: true, }, ], From 93c179cf4c7d26a6e7a3dbbeaada785edc6a68d6 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Wed, 21 Jun 2023 04:56:25 +0900 Subject: [PATCH 16/35] fix typo --- packages/eslint-plugin/docs/rules/prefer-destructuring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-destructuring.md b/packages/eslint-plugin/docs/rules/prefer-destructuring.md index abbbf2efc99..42a742da06e 100644 --- a/packages/eslint-plugin/docs/rules/prefer-destructuring.md +++ b/packages/eslint-plugin/docs/rules/prefer-destructuring.md @@ -8,7 +8,7 @@ description: 'Require destructuring from arrays and/or objects.' ## Examples -This rule extends the base [`eslint/prefer-destructuring`](https://eslint.org/docs/rules/prfer-destructuring) rule. +This rule extends the base [`eslint/prefer-destructuring`](https://eslint.org/docs/latest/rules/prefer-destructuring) rule. It adds support for TypeScript's type annotations in variable declarations. From 5ebadbf89a53feb526b08ab966ce86672615f9e3 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Wed, 21 Jun 2023 04:57:46 +0900 Subject: [PATCH 17/35] fix incorrect "correct" case --- packages/eslint-plugin/docs/rules/prefer-destructuring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-destructuring.md b/packages/eslint-plugin/docs/rules/prefer-destructuring.md index 42a742da06e..5980b81dbae 100644 --- a/packages/eslint-plugin/docs/rules/prefer-destructuring.md +++ b/packages/eslint-plugin/docs/rules/prefer-destructuring.md @@ -42,7 +42,7 @@ const y = x[0]; ### ✅ Correct ```ts -const x: = { 0: 'a' }; +const x = { 0: 'a' }; const y = x[0]; ``` From 5d1e5be0ac511d015b5312873c4213e31858859c Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Wed, 21 Jun 2023 19:06:06 +0900 Subject: [PATCH 18/35] improve test coverage --- .../src/rules/prefer-destructuring.ts | 5 +- .../tests/rules/prefer-destructuring.test.ts | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index 468eec2b050..3982abae865 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -97,7 +97,7 @@ export default createRule({ create( context, [ - enabledTypes, + enabledTypes = { object: true, array: true }, { enforceForRenamedProperties = false, enforceForDeclarationWithTypeAnnotation = false, @@ -173,9 +173,6 @@ export default createRule({ | AST_NODE_TYPES.AssignmentExpression, destructuringType: 'array' | 'object', ): boolean | undefined { - if (enabledTypes === undefined) { - return true; - } if ('object' in enabledTypes || 'array' in enabledTypes) { return enabledTypes[destructuringType]; } diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index a052842f02f..2f395cba22a 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -158,6 +158,32 @@ ruleTester.run('prefer-desctructuring', rule, { `, options: [{ array: true }, { enforceForRenamedProperties: true }], }, + { + code: ` + let x: { 0: unknown }; + let y = x[0]; + `, + options: [ + { + VariableDeclarator: { array: true, object: false }, + AssignmentExpression: { array: true, object: true }, + }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: ` + let x: { 0: unknown }; + y = x[0]; + `, + options: [ + { + VariableDeclarator: { array: true, object: true }, + AssignmentExpression: { array: true, object: false }, + }, + { enforceForRenamedProperties: true }, + ], + }, { code: ` let x: { 0: unknown }; @@ -443,6 +469,46 @@ ruleTester.run('prefer-desctructuring', rule, { }, ], }, + { + code: ` + let x: { 0: string }; + let y = x[0]; + `, + options: [ + { + VariableDeclarator: { object: true, array: false }, + AssignmentExpression: { object: false, array: false }, + }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` + let x: { 0: string }; + y = x[0]; + `, + options: [ + { + VariableDeclarator: { object: false, array: false }, + AssignmentExpression: { object: true, array: false }, + }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, { code: ` let x: { 0: unknown } | unknown[]; From 2d7e2af149606df041ad0a776d7a4b56fc69e358 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Thu, 22 Jun 2023 07:39:14 +0900 Subject: [PATCH 19/35] improve test coverage --- .../src/rules/prefer-destructuring.ts | 30 +++++++------------ .../eslint-plugin/typings/eslint-rules.d.ts | 2 +- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index 3982abae865..e163e2e4229 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -19,7 +19,7 @@ type Options0 = FullBaseOptions[0]; type Options1 = FullBaseOptions[1] & { enforceForDeclarationWithTypeAnnotation?: boolean; }; -type Options = [] | [Options0] | [Options0, Options1]; +type Options = [Options0, Options1?]; type MessageIds = InferMessageIdsTypeFromRule; @@ -74,9 +74,9 @@ export default createRule({ requiresTypeChecking: true, }, schema, - fixable: baseRule.meta?.fixable, - hasSuggestions: baseRule.meta?.hasSuggestions, - messages: baseRule.meta?.messages, + fixable: baseRule.meta.fixable, + hasSuggestions: baseRule.meta.hasSuggestions, + messages: baseRule.meta.messages, }, defaultOptions: [ { @@ -89,21 +89,13 @@ export default createRule({ object: true, }, }, - { - enforceForRenamedProperties: false, - enforceForDeclarationWithTypeAnnotation: false, - }, + {}, ], - create( - context, - [ - enabledTypes = { object: true, array: true }, - { - enforceForRenamedProperties = false, - enforceForDeclarationWithTypeAnnotation = false, - } = {}, - ], - ) { + create(context, [enabledTypes, options1]) { + const { + enforceForRenamedProperties = false, + enforceForDeclarationWithTypeAnnotation = false, + } = options1!; return { VariableDeclarator(node): void { performCheck(node.id, node.init, node); @@ -176,7 +168,7 @@ export default createRule({ if ('object' in enabledTypes || 'array' in enabledTypes) { return enabledTypes[destructuringType]; } - return enabledTypes[nodeType as keyof typeof enabledTypes]?.[ + return enabledTypes[nodeType as keyof typeof enabledTypes][ destructuringType as keyof typeof enabledTypes[keyof typeof enabledTypes] ]; } diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index e1d5a0cd4c8..c92242a1259 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -1010,7 +1010,7 @@ declare module 'eslint/lib/rules/prefer-destructuring' { } const rule: TSESLint.RuleModule< 'preferDestructuring', - [] | [Option0] | [Option0, Option1], + [Option0, Option1?], { VariableDeclarator(node: TSESTree.VariableDeclarator): void; AssignmentExpression(node: TSESTree.AssignmentExpression): void; From 521506c2fecdbd6ac652879b9debedb2854107a3 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 5 Aug 2023 14:48:33 -0400 Subject: [PATCH 20/35] fix: bring in adjustments from main branch --- .../eslint-plugin/src/rules/prefer-destructuring.ts | 11 +++++------ .../tests/rules/prefer-destructuring.test.ts | 3 ++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index e163e2e4229..e784dabbfc9 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -1,7 +1,7 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import type { JSONSchema4 } from 'json-schema'; -import * as tsutils from 'tsutils'; +import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema'; +import * as tsutils from 'ts-api-utils'; import type * as ts from 'typescript'; import type { @@ -69,7 +69,6 @@ export default createRule({ type: 'suggestion', docs: { description: 'Require destructuring from arrays and/or objects', - recommended: false, extendsBaseRule: true, requiresTypeChecking: true, }, @@ -91,11 +90,11 @@ export default createRule({ }, {}, ], - create(context, [enabledTypes, options1]) { + create(context, [enabledTypes, options1 = {}]) { const { enforceForRenamedProperties = false, enforceForDeclarationWithTypeAnnotation = false, - } = options1!; + } = options1; return { VariableDeclarator(node): void { performCheck(node.id, node.init, node); @@ -169,7 +168,7 @@ export default createRule({ return enabledTypes[destructuringType]; } return enabledTypes[nodeType as keyof typeof enabledTypes][ - destructuringType as keyof typeof enabledTypes[keyof typeof enabledTypes] + destructuringType as keyof (typeof enabledTypes)[keyof typeof enabledTypes] ]; } }, diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index 2f395cba22a..f335a1b21af 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -1,7 +1,8 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import rule from '../../src/rules/prefer-destructuring'; -import { getFixturesRootDir, RuleTester } from '../RuleTester'; +import { getFixturesRootDir } from '../RuleTester'; const rootPath = getFixturesRootDir(); From 044d5037b74228faa8de95b0e136bcff97e7c620 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 5 Aug 2023 15:12:15 -0400 Subject: [PATCH 21/35] Updated snapshot --- .../prefer-destructuring.shot | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 packages/eslint-plugin/tests/schema-snapshots/prefer-destructuring.shot diff --git a/packages/eslint-plugin/tests/schema-snapshots/prefer-destructuring.shot b/packages/eslint-plugin/tests/schema-snapshots/prefer-destructuring.shot new file mode 100644 index 00000000000..ca0e2597bf5 --- /dev/null +++ b/packages/eslint-plugin/tests/schema-snapshots/prefer-destructuring.shot @@ -0,0 +1,94 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Rule schemas should be convertible to TS types for documentation purposes prefer-destructuring 1`] = ` +" +# SCHEMA: + +[ + { + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "AssignmentExpression": { + "additionalProperties": false, + "properties": { + "array": { + "type": "boolean" + }, + "object": { + "type": "boolean" + } + }, + "type": "object" + }, + "VariableDeclarator": { + "additionalProperties": false, + "properties": { + "array": { + "type": "boolean" + }, + "object": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "array": { + "type": "boolean" + }, + "object": { + "type": "boolean" + } + }, + "type": "object" + } + ] + }, + { + "properties": { + "enforceForDeclarationWithTypeAnnotation": { + "type": "boolean" + }, + "enforceForRenamedProperties": { + "type": "boolean" + } + }, + "type": "object" + } +] + + +# TYPES: + +type Options = [ + ( + | { + AssignmentExpression?: { + array?: boolean; + object?: boolean; + }; + VariableDeclarator?: { + array?: boolean; + object?: boolean; + }; + } + | { + array?: boolean; + object?: boolean; + } + ), + { + enforceForDeclarationWithTypeAnnotation?: boolean; + enforceForRenamedProperties?: boolean; + [k: string]: unknown; + }, +]; +" +`; From 0102f80a1644ef5b6d497d41a117f408b0b16247 Mon Sep 17 00:00:00 2001 From: Seiya <20365512+seiyab@users.noreply.github.com> Date: Mon, 7 Aug 2023 22:15:47 +0900 Subject: [PATCH 22/35] Update packages/eslint-plugin/src/rules/prefer-destructuring.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Josh Goldberg ✨ --- packages/eslint-plugin/src/rules/prefer-destructuring.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index e784dabbfc9..f3399b079d2 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -14,12 +14,10 @@ import { getESLintCoreRule } from '../util/getESLintCoreRule'; const baseRule = getESLintCoreRule('prefer-destructuring'); type BaseOptions = InferOptionsTypeFromRule; -type FullBaseOptions = BaseOptions & [unknown, unknown]; -type Options0 = FullBaseOptions[0]; -type Options1 = FullBaseOptions[1] & { +type EnforcementOptions = BaseOptions[1] & { enforceForDeclarationWithTypeAnnotation?: boolean; }; -type Options = [Options0, Options1?]; +type Options = [BaseOptions[0], EnforcementOptions?]; type MessageIds = InferMessageIdsTypeFromRule; From b6a1840e3e6a4b203f5aa12af0271193b4c8c81c Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Mon, 7 Aug 2023 22:33:38 +0900 Subject: [PATCH 23/35] rename a function --- packages/eslint-plugin/src/rules/prefer-destructuring.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index f3399b079d2..0297059cd74 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -133,7 +133,7 @@ export default createRule({ ) { const tsObj = esTreeNodeToTSNodeMap.get(rightNode.object); const objType = typeChecker.getTypeAtLocation(tsObj); - if (!isTypeIterableType(objType, typeChecker)) { + if (!isTypeAnyOrIterableType(objType, typeChecker)) { if ( !enforceForRenamedProperties || !getNormalizedEnabledType(reportNode.type, 'object') @@ -199,7 +199,7 @@ function noFixContext(context: Context): Context { }); } -function isTypeIterableType( +function isTypeAnyOrIterableType( type: ts.Type, typeChecker: ts.TypeChecker, ): boolean { @@ -214,7 +214,7 @@ function isTypeIterableType( ); return iterator !== undefined; } - return type.types.every(t => isTypeIterableType(t, typeChecker)); + return type.types.every(t => isTypeAnyOrIterableType(t, typeChecker)); } function isArrayLiteralIntegerIndexAccess( From 4d69749905db386f343bce1ab1f96105fbfc728c Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Tue, 8 Aug 2023 19:02:32 +0900 Subject: [PATCH 24/35] fix typo --- .../eslint-plugin/tests/rules/prefer-destructuring.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index f335a1b21af..879eb01f0fb 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -15,7 +15,7 @@ const ruleTester = new RuleTester({ }, }); -ruleTester.run('prefer-desctructuring', rule, { +ruleTester.run('prefer-destructuring', rule, { valid: [ // type annotated 'var foo: string = object.foo;', @@ -546,7 +546,7 @@ ruleTester.run('prefer-desctructuring', rule, { * ESLint is licensed under the MIT License. * Copyright (c) 2023 OpenJS Foundation and other contributors, . */ -ruleTester.run('prefer-desctructuring', rule, { +ruleTester.run('prefer-destructuring', rule, { /* eslint-disable @typescript-eslint/internal/plugin-test-formatting */ valid: [ 'var [foo] = array;', From b2de45bce5c1fa61979b1d393e61ac17e712dd17 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Tue, 8 Aug 2023 21:59:35 +0900 Subject: [PATCH 25/35] reduce baseRule.create() calls --- .../eslint-plugin/src/rules/prefer-destructuring.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index 0297059cd74..06f2e2dd8fb 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -93,6 +93,11 @@ export default createRule({ enforceForRenamedProperties = false, enforceForDeclarationWithTypeAnnotation = false, } = options1; + const { program, esTreeNodeToTSNodeMap } = getParserServices(context); + const typeChecker = program.getTypeChecker(); + const baseRules = baseRule.create(context); + const baseRulesWithoutFix = baseRule.create(noFixContext(context)); + return { VariableDeclarator(node): void { performCheck(node.id, node.init, node); @@ -110,14 +115,11 @@ export default createRule({ rightNode: TSESTree.Expression | null, reportNode: TSESTree.VariableDeclarator | TSESTree.AssignmentExpression, ): void { - const { program, esTreeNodeToTSNodeMap } = getParserServices(context); - const typeChecker = program.getTypeChecker(); - const baseRules = baseRule.create(context); const rules = leftNode.type === AST_NODE_TYPES.Identifier && leftNode.typeAnnotation === undefined ? baseRules - : baseRule.create(noFixContext(context)); + : baseRulesWithoutFix; if ( 'typeAnnotation' in leftNode && leftNode.typeAnnotation !== undefined && From c0dbe8f5fe91fa09a38bc0f7a469d7693bc9d898 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Tue, 8 Aug 2023 22:03:08 +0900 Subject: [PATCH 26/35] lazily run baseRule.create(noFixContext(context)) --- .../eslint-plugin/src/rules/prefer-destructuring.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index 06f2e2dd8fb..cf0cb45a48b 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -96,7 +96,7 @@ export default createRule({ const { program, esTreeNodeToTSNodeMap } = getParserServices(context); const typeChecker = program.getTypeChecker(); const baseRules = baseRule.create(context); - const baseRulesWithoutFix = baseRule.create(noFixContext(context)); + let baseRulesWithoutFixCache: typeof baseRules | null = null; return { VariableDeclarator(node): void { @@ -119,7 +119,7 @@ export default createRule({ leftNode.type === AST_NODE_TYPES.Identifier && leftNode.typeAnnotation === undefined ? baseRules - : baseRulesWithoutFix; + : baseRulesWithoutFix(); if ( 'typeAnnotation' in leftNode && leftNode.typeAnnotation !== undefined && @@ -171,6 +171,13 @@ export default createRule({ destructuringType as keyof (typeof enabledTypes)[keyof typeof enabledTypes] ]; } + + function baseRulesWithoutFix() { + if (baseRulesWithoutFixCache === null) { + baseRulesWithoutFixCache = baseRule.create(noFixContext(context)); + } + return baseRulesWithoutFixCache; + } }, }); From 7e0d976210c55ad7ca5ecaf5fc4895a2d4988389 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Wed, 9 Aug 2023 19:05:46 +0900 Subject: [PATCH 27/35] lint --- packages/eslint-plugin/src/rules/prefer-destructuring.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index cf0cb45a48b..f93f0f638c0 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -172,8 +172,8 @@ export default createRule({ ]; } - function baseRulesWithoutFix() { - if (baseRulesWithoutFixCache === null) { + function baseRulesWithoutFix(): ReturnType { + if (baseRulesWithoutFixCache == null) { baseRulesWithoutFixCache = baseRule.create(noFixContext(context)); } return baseRulesWithoutFixCache; From a1e24e489e49b36e972ed97218a8064422b917d4 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Wed, 9 Aug 2023 19:08:41 +0900 Subject: [PATCH 28/35] add test cases --- .../tests/rules/prefer-destructuring.test.ts | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index 879eb01f0fb..567782b9b82 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -185,6 +185,39 @@ ruleTester.run('prefer-destructuring', rule, { { enforceForRenamedProperties: true }, ], }, + { + code: ` + let x: Record; + let i: number = 0; + y = x[i]; + `, + options: [ + { object: false, array: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: ` + let x: Record; + let i: 0 = 0; + y = x[i]; + `, + options: [ + { object: false, array: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: ` + let x: Record; + let i: 0 | 1 | 2 = 0; + y = x[i]; + `, + options: [ + { object: false, array: true }, + { enforceForRenamedProperties: true }, + ], + }, { code: ` let x: { 0: unknown }; @@ -510,6 +543,60 @@ ruleTester.run('prefer-destructuring', rule, { }, ], }, + { + code: ` + let x: Record; + let i: number = 0; + y = x[i]; + `, + options: [ + { object: true, array: true }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: ` + let x: Record; + let i: 0 = 0; + y = x[i]; + `, + options: [ + { object: true, array: true }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: ` + let x: Record; + let i: 0 | 1 | 2 = 0; + y = x[i]; + `, + options: [ + { object: true, array: true }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, { code: ` let x: { 0: unknown } | unknown[]; From 67a18bc4893cf22654f13a7c912c0a0a11ed52e1 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Wed, 9 Aug 2023 22:22:23 +0900 Subject: [PATCH 29/35] add test cases --- .../tests/rules/prefer-destructuring.test.ts | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index 567782b9b82..f4c38d99a75 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -218,6 +218,50 @@ ruleTester.run('prefer-destructuring', rule, { { enforceForRenamedProperties: true }, ], }, + { + code: ` + let x: unknown[]; + let i: number = 0; + y = x[i]; + `, + options: [ + { object: false, array: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: ` + let x: unknown[]; + let i: 0 = 0; + y = x[i]; + `, + options: [ + { object: false, array: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: ` + let x: unknown[]; + let i: 0 | 1 | 2 = 0; + y = x[i]; + `, + options: [ + { object: false, array: true }, + { enforceForRenamedProperties: true }, + ], + }, + { + code: ` + let x: unknown[]; + let i: number = 0; + y = x[i]; + `, + options: [ + { object: true, array: true }, + { enforceForRenamedProperties: false }, + ], + }, { code: ` let x: { 0: unknown }; @@ -597,6 +641,60 @@ ruleTester.run('prefer-destructuring', rule, { }, ], }, + { + code: ` + let x: unknown[]; + let i: number = 0; + y = x[i]; + `, + options: [ + { object: true, array: true }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: ` + let x: unknown[]; + let i: 0 = 0; + y = x[i]; + `, + options: [ + { object: true, array: true }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: ` + let x: unknown[]; + let i: 0 | 1 | 2 = 0; + y = x[i]; + `, + options: [ + { object: true, array: true }, + { enforceForRenamedProperties: true }, + ], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, { code: ` let x: { 0: unknown } | unknown[]; From 96cbada28c28e578b1cbca1a38381d271fce137d Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Thu, 10 Aug 2023 22:38:50 +0900 Subject: [PATCH 30/35] remove tests copied from base rule --- .cspell.json | 1 - .../tests/rules/prefer-destructuring.test.ts | 822 ------------------ 2 files changed, 823 deletions(-) diff --git a/.cspell.json b/.cspell.json index 9b58a49e4c4..6f036d77516 100644 --- a/.cspell.json +++ b/.cspell.json @@ -97,7 +97,6 @@ "nullish", "OOM", "OOMs", - "openjsf", "parameterised", "performant", "pluggable", diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index f4c38d99a75..c1f946ea58c 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -725,825 +725,3 @@ ruleTester.run('prefer-destructuring', rule, { }, ], }); - -/* - * These test cases are based on code from the ESLint project (https://github.com/eslint/eslint). - * ESLint is licensed under the MIT License. - * Copyright (c) 2023 OpenJS Foundation and other contributors, . - */ -ruleTester.run('prefer-destructuring', rule, { - /* eslint-disable @typescript-eslint/internal/plugin-test-formatting */ - valid: [ - 'var [foo] = array;', - 'var { foo } = object;', - 'var foo;', - { - // Ensure that the default behavior does not require destructuring when renaming - code: 'var foo = object.bar;', - options: [{ VariableDeclarator: { object: true } }, {}], - }, - { - // Ensure that the default behavior does not require destructuring when renaming - code: 'var foo = object.bar;', - options: [{ object: true }, {}], - }, - { - code: 'var foo = object.bar;', - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'var foo = object.bar;', - options: [{ object: true }, { enforceForRenamedProperties: false }], - }, - { - code: "var foo = object['bar'];", - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'var foo = object[bar];', - options: [{ object: true }, { enforceForRenamedProperties: false }], - }, - { - code: 'var { bar: foo } = object;', - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'var { bar: foo } = object;', - options: [{ object: true }, { enforceForRenamedProperties: true }], - }, - { - code: 'var { [bar]: foo } = object;', - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'var { [bar]: foo } = object;', - options: [{ object: true }, { enforceForRenamedProperties: true }], - }, - { - code: 'var foo = array[0];', - options: [{ VariableDeclarator: { array: false } }], - }, - { - code: 'var foo = array[0];', - options: [{ array: false }], - }, - { - code: 'var foo = object.foo;', - options: [{ VariableDeclarator: { object: false } }], - }, - { - code: "var foo = object['foo'];", - options: [{ VariableDeclarator: { object: false } }], - }, - '({ foo } = object);', - { - // Fix #8654 - code: 'var foo = array[0];', - options: [ - { VariableDeclarator: { array: false } }, - { enforceForRenamedProperties: true }, - ], - }, - { - // Fix #8654 - code: 'var foo = array[0];', - options: [{ array: false }, { enforceForRenamedProperties: true }], - }, - '[foo] = array;', - 'foo += array[0]', - { - code: 'foo &&= array[0]', - parserOptions: { ecmaVersion: 2021 }, - }, - 'foo += bar.foo', - { - code: 'foo ||= bar.foo', - parserOptions: { ecmaVersion: 2021 }, - }, - { - code: "foo ??= bar['foo']", - parserOptions: { ecmaVersion: 2021 }, - }, - { - code: 'foo = object.foo;', - options: [ - { AssignmentExpression: { object: false } }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'foo = object.foo;', - options: [ - { AssignmentExpression: { object: false } }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'foo = array[0];', - options: [ - { AssignmentExpression: { array: false } }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'foo = array[0];', - options: [ - { AssignmentExpression: { array: false } }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'foo = array[0];', - options: [ - { - VariableDeclarator: { array: true }, - AssignmentExpression: { array: false }, - }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'var foo = array[0];', - options: [ - { - VariableDeclarator: { array: false }, - AssignmentExpression: { array: true }, - }, - { enforceForRenamedProperties: false }, - ], - }, - { - code: 'foo = object.foo;', - options: [ - { - VariableDeclarator: { object: true }, - AssignmentExpression: { object: false }, - }, - ], - }, - { - code: 'var foo = object.foo;', - options: [ - { - VariableDeclarator: { object: false }, - AssignmentExpression: { object: true }, - }, - ], - }, - 'class Foo extends Bar { static foo() {var foo = super.foo} }', - 'foo = bar[foo];', - 'var foo = bar[foo];', - { - code: 'var {foo: {bar}} = object;', - options: [{ object: true }], - }, - { - code: 'var {bar} = object.foo;', - options: [{ object: true }], - }, - - // Optional chaining - 'var foo = array?.[0];', // because the fixed code can throw TypeError. - 'var foo = object?.foo;', - - // Private identifiers - 'class C { #x; foo() { const x = this.#x; } }', - 'class C { #x; foo() { x = this.#x; } }', - 'class C { #x; foo(a) { x = a.#x; } }', - { - code: 'class C { #x; foo() { const x = this.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo() { const y = this.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo() { x = this.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo() { y = this.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo(a) { x = a.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo(a) { y = a.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - { - code: 'class C { #x; foo() { x = this.a.#x; } }', - options: [ - { array: true, object: true }, - { enforceForRenamedProperties: true }, - ], - }, - ], - invalid: [ - { - code: 'var foo = array[0];', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'foo = array[0];', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: 'var foo = object.foo;', - output: 'var {foo} = object;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (a, b).foo;', - output: 'var {foo} = (a, b);', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var length = (() => {}).length;', - output: 'var {length} = () => {};', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (a = b).foo;', - output: 'var {foo} = a = b;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (a || b).foo;', - output: 'var {foo} = a || b;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (f()).foo;', - output: 'var {foo} = f();', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.bar.foo;', - output: 'var {foo} = object.bar;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foobar = object.bar;', - output: null, - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: true }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foobar = object.bar;', - output: null, - options: [{ object: true }, { enforceForRenamedProperties: true }], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object[bar];', - output: null, - options: [ - { VariableDeclarator: { object: true } }, - { enforceForRenamedProperties: true }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object[bar];', - output: null, - options: [{ object: true }, { enforceForRenamedProperties: true }], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object[foo];', - output: null, - options: [{ object: true }, { enforceForRenamedProperties: true }], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: "var foo = object['foo'];", - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'foo = object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: "foo = object['foo'];", - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: 'var foo = array[0];', - output: null, - options: [ - { VariableDeclarator: { array: true } }, - { enforceForRenamedProperties: true }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'foo = array[0];', - output: null, - options: [{ AssignmentExpression: { array: true } }], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: 'var foo = array[0];', - output: null, - options: [ - { - VariableDeclarator: { array: true }, - AssignmentExpression: { array: false }, - }, - { enforceForRenamedProperties: true }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = array[0];', - output: null, - options: [ - { - VariableDeclarator: { array: true }, - AssignmentExpression: { array: false }, - }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'foo = array[0];', - output: null, - options: [ - { - VariableDeclarator: { array: false }, - AssignmentExpression: { array: true }, - }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'array' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: 'foo = object.foo;', - output: null, - options: [ - { - VariableDeclarator: { array: true, object: false }, - AssignmentExpression: { object: true }, - }, - ], - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.AssignmentExpression, - }, - ], - }, - { - code: 'class Foo extends Bar { static foo() {var bar = super.foo.bar} }', - output: 'class Foo extends Bar { static foo() {var {bar} = super.foo} }', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - - // comments - { - code: 'var /* comment */ foo = object.foo;', - output: 'var /* comment */ {foo} = object;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var a, /* comment */foo = object.foo;', - output: 'var a, /* comment */{foo} = object;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo /* comment */ = object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var a, foo /* comment */ = object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo /* comment */ = object.foo, a;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo // comment\n = object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = /* comment */ object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = // comment\n object.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (/* comment */ object).foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (object /* comment */).foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = bar(/* comment */).foo;', - output: 'var {foo} = bar(/* comment */);', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = bar/* comment */.baz.foo;', - output: 'var {foo} = bar/* comment */.baz;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = bar[// comment\nbaz].foo;', - output: 'var {foo} = bar[// comment\nbaz];', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo // comment\n = bar(/* comment */).foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = bar/* comment */.baz/* comment */.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object// comment\n.foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object./* comment */foo;', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (/* comment */ object.foo);', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = (object.foo /* comment */);', - output: null, - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.foo/* comment */;', - output: 'var {foo} = object/* comment */;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.foo// comment', - output: 'var {foo} = object// comment', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.foo/* comment */, a;', - output: 'var {foo} = object/* comment */, a;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.foo// comment\n, a;', - output: 'var {foo} = object// comment\n, a;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - { - code: 'var foo = object.foo, /* comment */ a;', - output: 'var {foo} = object, /* comment */ a;', - errors: [ - { - messageId: 'preferDestructuring', - data: { type: 'object' }, - type: AST_NODE_TYPES.VariableDeclarator, - }, - ], - }, - ], - /* eslint-enable @typescript-eslint/internal/plugin-test-formatting */ -}); From 4ca0e2af86e04f24d018575f2da81a2fe69cd508 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Thu, 10 Aug 2023 23:27:34 +0900 Subject: [PATCH 31/35] add tests --- .../tests/rules/prefer-destructuring.test.ts | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index c1f946ea58c..a6a20b39ce5 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -304,6 +304,77 @@ ruleTester.run('prefer-destructuring', rule, { { enforceForRenamedProperties: true }, ], }, + + // already destructured + ` + let xs: unknown[] = [1]; + let [x] = xs; + `, + ` + const obj: { x: unknown } = { x: 1 }; + const { x } = obj; + `, + ` + var obj: { x: unknown } = { x: 1 }; + var { x: y } = obj; + `, + ` + let obj: { x: unknown } = { x: 1 }; + let key: 'x' = 'x'; + let { [key]: foo } = obj; + `, + ` + const obj: { x: unknown } = { x: 1 }; + let x: unknown; + ({ x } = obj); + `, + + // valid unless enforceForRenamedProperties is true + ` + let obj: { x: unknown } = { x: 1 }; + let y = obj.x; + `, + ` + var obj: { x: unknown } = { x: 1 }; + var y: unknown; + y = obj.x; + `, + ` + const obj: { x: unknown } = { x: 1 }; + const y = obj['x']; + `, + ` + let obj: Record = {}; + let key = 'abc'; + var y = obj[key]; + `, + + // shorthand operators shouldn't be reported; + ` + let obj: { x: number } = { x: 1 }; + let x = 10; + x += obj.x; + `, + ` + let obj: { x: boolean } = { x: false }; + let x = true; + x ||= obj.x; + `, + ` + const xs: number[] = [1]; + let x = 3; + x *= xs[0]; + `, + + // optional chaining shouldn't be reported + ` + let xs: unknown[] | undefined; + let x = xs?.[0]; + `, + ` + let obj: Record | undefined; + let x = obj?.x; + `, ], invalid: [ // enforceForDeclarationWithTypeAnnotation: true From 25f780da1ea95d46c44e293b438ec6cee3295e4e Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Fri, 11 Aug 2023 00:12:54 +0900 Subject: [PATCH 32/35] add tests --- .../tests/rules/prefer-destructuring.test.ts | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index a6a20b39ce5..cac81e877d0 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -375,6 +375,73 @@ ruleTester.run('prefer-destructuring', rule, { let obj: Record | undefined; let x = obj?.x; `, + + // private identifiers + ` + class C { + #foo: string; + + method() { + const foo: unknown = this.#foo; + } + } + `, + ` + class C { + #foo: string; + + method() { + let foo: unknown; + foo = this.#foo; + } + } + `, + { + code: ` + class C { + #foo: string; + + method() { + const bar: unknown = this.#foo; + } + } + `, + options: [ + { object: true, array: true }, + { enforceForDeclarationWithTypeAnnotation: true }, + ], + }, + { + code: ` + class C { + #foo: string; + + method(another: C) { + let bar: unknown; + bar: unknown = another.#foo; + } + } + `, + options: [ + { object: true, array: true }, + { enforceForDeclarationWithTypeAnnotation: true }, + ], + }, + { + code: ` + class C { + #foo: string; + + method() { + const foo: unknown = this.#foo; + } + } + `, + options: [ + { object: true, array: true }, + { enforceForDeclarationWithTypeAnnotation: true }, + ], + }, ], invalid: [ // enforceForDeclarationWithTypeAnnotation: true @@ -794,5 +861,173 @@ ruleTester.run('prefer-destructuring', rule, { }, ], }, + + // auto fixes + { + code: ` + let obj = { foo: 'bar' }; + const foo = obj.foo; + `, + output: ` + let obj = { foo: 'bar' }; + const {foo} = obj; + `, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` + let obj = { foo: 'bar' }; + var x: null = null; + const foo = (x, obj).foo; + `, + output: ` + let obj = { foo: 'bar' }; + var x: null = null; + const {foo} = (x, obj); + `, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'const call = (() => null).call;', + output: 'const {call} = () => null;', + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` + const obj = { foo: 'bar' }; + let a: any; + var foo = (a = obj).foo; + `, + output: ` + const obj = { foo: 'bar' }; + let a: any; + var {foo} = a = obj; + `, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` + const obj = { asdf: { qwer: null } }; + const qwer = obj.asdf.qwer; + `, + output: ` + const obj = { asdf: { qwer: null } }; + const {qwer} = obj.asdf; + `, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` + const obj = { foo: 100 }; + const /* comment */ foo = obj.foo; + `, + output: ` + const obj = { foo: 100 }; + const /* comment */ {foo} = obj; + `, + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + + // enforceForRenamedProperties: true + { + code: ` + let obj = { foo: 'bar' }; + const x = obj.foo; + `, + output: null, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` + let obj = { foo: 'bar' }; + let x: unknown; + x = obj.foo; + `, + output: null, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, + { + code: ` + let obj: Record; + let key = 'abc'; + const x = obj[key]; + `, + output: null, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` + let obj: Record; + let key = 'abc'; + let x: unknown; + x = obj[key]; + `, + output: null, + options: [{ object: true }, { enforceForRenamedProperties: true }], + errors: [ + { + messageId: 'preferDestructuring', + data: { type: 'object' }, + type: AST_NODE_TYPES.AssignmentExpression, + }, + ], + }, ], }); From 7451956e0fd95c07146041ea12a6c9033238a3c0 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:30:49 +0900 Subject: [PATCH 33/35] declare variables --- .../tests/rules/prefer-destructuring.test.ts | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts index cac81e877d0..812d317b18c 100644 --- a/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-destructuring.test.ts @@ -18,53 +18,80 @@ const ruleTester = new RuleTester({ ruleTester.run('prefer-destructuring', rule, { valid: [ // type annotated - 'var foo: string = object.foo;', - 'const bar: number = array[0];', + ` + declare const object: { foo: string }; + var foo: string = object.foo; + `, + ` + declare const array: number[]; + const bar: number = array[0]; + `, // enforceForDeclarationWithTypeAnnotation: true { - code: 'var { foo } = object;', + code: ` + declare const object: { foo: string }; + var { foo } = object; + `, options: [ { object: true }, { enforceForDeclarationWithTypeAnnotation: true }, ], }, { - code: 'var { foo }: { foo: number } = object;', + code: ` + declare const object: { foo: string }; + var { foo }: { foo: number } = object; + `, options: [ { object: true }, { enforceForDeclarationWithTypeAnnotation: true }, ], }, { - code: 'var [foo] = array;', + code: ` + declare const array: number[]; + var [foo] = array; + `, options: [ { array: true }, { enforceForDeclarationWithTypeAnnotation: true }, ], }, { - code: 'var [foo]: [foo: number] = array;', + code: ` + declare const array: number[]; + var [foo]: [foo: number] = array; + `, options: [ { object: true }, { enforceForDeclarationWithTypeAnnotation: true }, ], }, { - code: 'var foo: unknown = object.bar;', + code: ` + declare const object: { bar: string }; + var foo: unknown = object.bar; + `, options: [ { object: true }, { enforceForDeclarationWithTypeAnnotation: true }, ], }, { - code: 'var { foo: bar } = object;', + code: ` + declare const object: { foo: string }; + var { foo: bar } = object; + `, options: [ { object: true }, { enforceForDeclarationWithTypeAnnotation: true }, ], }, { - code: 'var { foo: bar }: { foo: boolean } = object;', + code: ` + declare const object: { foo: boolean }; + var { foo: bar }: { foo: boolean } = object; + `, options: [ { object: true }, { enforceForDeclarationWithTypeAnnotation: true }, @@ -72,6 +99,10 @@ ruleTester.run('prefer-destructuring', rule, { }, { code: ` + declare class Foo { + foo: string; + } + class Bar extends Foo { static foo() { var foo: any = super.foo; From 6ca18e1bfb00b2586fbc01d51a4952c7033be39c Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Mon, 14 Aug 2023 21:55:09 +0900 Subject: [PATCH 34/35] minor improvements - naming of options - using nullish coalescing assignment --- packages/eslint-plugin/src/rules/prefer-destructuring.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index f93f0f638c0..4649bd57a05 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -88,11 +88,11 @@ export default createRule({ }, {}, ], - create(context, [enabledTypes, options1 = {}]) { + create(context, [enabledTypes, options = {}]) { const { enforceForRenamedProperties = false, enforceForDeclarationWithTypeAnnotation = false, - } = options1; + } = options; const { program, esTreeNodeToTSNodeMap } = getParserServices(context); const typeChecker = program.getTypeChecker(); const baseRules = baseRule.create(context); @@ -173,9 +173,7 @@ export default createRule({ } function baseRulesWithoutFix(): ReturnType { - if (baseRulesWithoutFixCache == null) { - baseRulesWithoutFixCache = baseRule.create(noFixContext(context)); - } + baseRulesWithoutFixCache ??= baseRule.create(noFixContext(context)); return baseRulesWithoutFixCache; } }, From 3c23be8103bde96eb7a946d11d65b690cce80546 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Mon, 14 Aug 2023 22:49:20 +0900 Subject: [PATCH 35/35] improve type and coverage --- packages/eslint-plugin/src/rules/prefer-destructuring.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-destructuring.ts b/packages/eslint-plugin/src/rules/prefer-destructuring.ts index 4649bd57a05..41a4db15254 100644 --- a/packages/eslint-plugin/src/rules/prefer-destructuring.ts +++ b/packages/eslint-plugin/src/rules/prefer-destructuring.ts @@ -17,7 +17,7 @@ type BaseOptions = InferOptionsTypeFromRule; type EnforcementOptions = BaseOptions[1] & { enforceForDeclarationWithTypeAnnotation?: boolean; }; -type Options = [BaseOptions[0], EnforcementOptions?]; +type Options = [BaseOptions[0], EnforcementOptions]; type MessageIds = InferMessageIdsTypeFromRule; @@ -88,7 +88,7 @@ export default createRule({ }, {}, ], - create(context, [enabledTypes, options = {}]) { + create(context, [enabledTypes, options]) { const { enforceForRenamedProperties = false, enforceForDeclarationWithTypeAnnotation = false,