From 816b1bb40c713b04eaf8c16248c1fa912b223be7 Mon Sep 17 00:00:00 2001 From: ryym Date: Sun, 22 Nov 2020 17:31:08 +0900 Subject: [PATCH 1/4] fix(eslint-plugin): [consistent-indexed-object-style] Skip readonly index signature --- .../src/rules/consistent-indexed-object-style.ts | 4 ++++ .../rules/consistent-indexed-object-style.test.ts | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts b/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts index c1b86bf8c61..1791a959178 100644 --- a/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts +++ b/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts @@ -76,6 +76,10 @@ export default createRule({ return; } + if (member.readonly) { + return; + } + const [parameter] = member.parameters; if (!parameter) { diff --git a/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts b/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts index ebedccf424a..77a2cd3874e 100644 --- a/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts @@ -51,6 +51,18 @@ type Foo = { }; `, + // Readonly + ` +interface Foo { + readonly [key: string]: any; +} + `, + ` +type Foo = { + readonly [key: string]: any; +}; + `, + // Generic ` type Foo = Generic<{ From 3212edb49ab5ad5a6bbd661d397617a6dbc3a89c Mon Sep 17 00:00:00 2001 From: ryym Date: Sun, 22 Nov 2020 17:40:01 +0900 Subject: [PATCH 2/4] docs(eslint-plugin): [consistent-indexed-object-style] Add readonly type to correct code examples --- .../docs/rules/consistent-indexed-object-style.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.md b/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.md index c98997c5e0a..a62ebaf214c 100644 --- a/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.md +++ b/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.md @@ -46,6 +46,14 @@ Examples of **correct** code with `record` option. ```ts type Foo = Record; + +interface Foo { + readonly [key: string]: unknown; +} + +type Foo = { + readonly [key: string]: unknown; +}; ``` Examples of **incorrect** code with `index-signature` option. From caf36b753d75b735649f781b74ebee68b152bc04 Mon Sep 17 00:00:00 2001 From: ryym Date: Mon, 23 Nov 2020 08:10:10 +0900 Subject: [PATCH 3/4] Revert "docs(eslint-plugin): [consistent-indexed-object-style] Add readonly type to correct code examples" This reverts commit 3212edb49ab5ad5a6bbd661d397617a6dbc3a89c. The rule should replace a readonly index signature with `Readonly` instead of ignoring it. --- .../docs/rules/consistent-indexed-object-style.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.md b/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.md index a62ebaf214c..c98997c5e0a 100644 --- a/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.md +++ b/packages/eslint-plugin/docs/rules/consistent-indexed-object-style.md @@ -46,14 +46,6 @@ Examples of **correct** code with `record` option. ```ts type Foo = Record; - -interface Foo { - readonly [key: string]: unknown; -} - -type Foo = { - readonly [key: string]: unknown; -}; ``` Examples of **incorrect** code with `index-signature` option. From ddd60119e38bcf3e061b324da313dd23235aa5a9 Mon Sep 17 00:00:00 2001 From: ryym Date: Mon, 23 Nov 2020 08:13:40 +0900 Subject: [PATCH 4/4] feat(eslint-plugin): [consistent-indexed-object-style] Convert readonly index signature to Readonly Record type This replaces the fix of 816b1bb4. Instead of just ignoring the readonly index signature, replace it with `Readonly>` type. --- .../rules/consistent-indexed-object-style.ts | 12 +-- .../consistent-indexed-object-style.test.ts | 77 ++++++++++++++++--- 2 files changed, 69 insertions(+), 20 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts b/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts index 1791a959178..bd48718c68b 100644 --- a/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts +++ b/packages/eslint-plugin/src/rules/consistent-indexed-object-style.ts @@ -76,10 +76,6 @@ export default createRule({ return; } - if (member.readonly) { - return; - } - const [parameter] = member.parameters; if (!parameter) { @@ -105,10 +101,10 @@ export default createRule({ fix(fixer) { const key = sourceCode.getText(keyType.typeAnnotation); const value = sourceCode.getText(valueType.typeAnnotation); - return fixer.replaceText( - node, - `${prefix}Record<${key}, ${value}>${postfix}`, - ); + const record = member.readonly + ? `Readonly>` + : `Record<${key}, ${value}>`; + return fixer.replaceText(node, `${prefix}${record}${postfix}`); }, }); } diff --git a/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts b/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts index 77a2cd3874e..31077fcf620 100644 --- a/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-indexed-object-style.test.ts @@ -51,18 +51,6 @@ type Foo = { }; `, - // Readonly - ` -interface Foo { - readonly [key: string]: any; -} - `, - ` -type Foo = { - readonly [key: string]: any; -}; - `, - // Generic ` type Foo = Generic<{ @@ -152,6 +140,19 @@ type Foo = Record; errors: [{ messageId: 'preferRecord', line: 2, column: 1 }], }, + // Readonly interface + { + code: ` +interface Foo { + readonly [key: string]: any; +} + `, + output: ` +type Foo = Readonly>; + `, + errors: [{ messageId: 'preferRecord', line: 2, column: 1 }], + }, + // Interface with generic parameter { code: ` @@ -165,6 +166,19 @@ type Foo = Record; errors: [{ messageId: 'preferRecord', line: 2, column: 1 }], }, + // Readonly interface with generic parameter + { + code: ` +interface Foo { + readonly [key: string]: A; +} + `, + output: ` +type Foo = Readonly>; + `, + errors: [{ messageId: 'preferRecord', line: 2, column: 1 }], + }, + // Interface with multiple generic parameters { code: ` @@ -178,6 +192,19 @@ type Foo = Record; errors: [{ messageId: 'preferRecord', line: 2, column: 1 }], }, + // Readonly interface with multiple generic parameters + { + code: ` +interface Foo { + readonly [key: A]: B; +} + `, + output: ` +type Foo = Readonly>; + `, + errors: [{ messageId: 'preferRecord', line: 2, column: 1 }], + }, + // Type literal { code: 'type Foo = { [key: string]: any };', @@ -185,6 +212,13 @@ type Foo = Record; errors: [{ messageId: 'preferRecord', line: 1, column: 12 }], }, + // Readonly type literal + { + code: 'type Foo = { readonly [key: string]: any };', + output: 'type Foo = Readonly>;', + errors: [{ messageId: 'preferRecord', line: 1, column: 12 }], + }, + // Generic { code: 'type Foo = Generic<{ [key: string]: any }>;', @@ -192,6 +226,13 @@ type Foo = Record; errors: [{ messageId: 'preferRecord', line: 1, column: 20 }], }, + // Readonly Generic + { + code: 'type Foo = Generic<{ readonly [key: string]: any }>;', + output: 'type Foo = Generic>>;', + errors: [{ messageId: 'preferRecord', line: 1, column: 20 }], + }, + // Function types { code: 'function foo(arg: { [key: string]: any }) {}', @@ -204,6 +245,18 @@ type Foo = Record; errors: [{ messageId: 'preferRecord', line: 1, column: 17 }], }, + // Readonly function types + { + code: 'function foo(arg: { readonly [key: string]: any }) {}', + output: 'function foo(arg: Readonly>) {}', + errors: [{ messageId: 'preferRecord', line: 1, column: 19 }], + }, + { + code: 'function foo(): { readonly [key: string]: any } {}', + output: 'function foo(): Readonly> {}', + errors: [{ messageId: 'preferRecord', line: 1, column: 17 }], + }, + // Never // Type literal {