From 5e9817f843dcf28f827e9ae40efd35fa7deb143d Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 21 Oct 2024 20:49:44 -0400 Subject: [PATCH 1/6] feat(eslint-plugin): added related-getter-setter-pairs rule --- .../rules/related-getter-setter-pairs.mdx | 61 +++++++ packages/eslint-plugin/src/configs/all.ts | 1 + .../src/configs/disable-type-checked.ts | 1 + .../src/configs/strict-type-checked-only.ts | 1 + .../src/configs/strict-type-checked.ts | 3 +- packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/related-getter-setter-pairs.ts | 108 ++++++++++++ .../related-getter-setter-pairs.shot | 22 +++ .../rules/related-getter-setter-pairs.test.ts | 165 ++++++++++++++++++ .../related-getter-setter-pairs.shot | 14 ++ packages/typescript-eslint/src/configs/all.ts | 1 + .../src/configs/disable-type-checked.ts | 1 + .../src/configs/strict-type-checked-only.ts | 1 + .../src/configs/strict-type-checked.ts | 1 + 14 files changed, 381 insertions(+), 1 deletion(-) create mode 100644 packages/eslint-plugin/docs/rules/related-getter-setter-pairs.mdx create mode 100644 packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts create mode 100644 packages/eslint-plugin/tests/docs-eslint-output-snapshots/related-getter-setter-pairs.shot create mode 100644 packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts create mode 100644 packages/eslint-plugin/tests/schema-snapshots/related-getter-setter-pairs.shot diff --git a/packages/eslint-plugin/docs/rules/related-getter-setter-pairs.mdx b/packages/eslint-plugin/docs/rules/related-getter-setter-pairs.mdx new file mode 100644 index 000000000000..3bfcf72be361 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/related-getter-setter-pairs.mdx @@ -0,0 +1,61 @@ +--- +description: 'Enforce that `get()` types should be assignable to their equivalent `set()` type.' +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/related-getter-setter-pairs** for documentation. + +TypeScript allows defining different types for a `get` parameter and its corresponding `set` return. +Prior to TypeScript 4.3, the types didn't have to be related. +From TypeScript 4.3 to 5.0, the `get` type had to be a subtype of the `set` type. +As of TypeScript 5.1, the types may be completely unrelated as long as there is an explicit type annotation. + +Defining drastically different types for a `get` and `set` pair can be confusing. +It means that assigning a property to itself would not work: + +```ts +// Assumes box.value's get() return is assignable to its set() parameter +box.value = box.value; +``` + +This rule reports cases where a `get()` and `set()` have the same name, but the `get()`'s type is not assignable to the `set()`'s. + +## Examples + + + + +```ts +interface Box { + get value(): string; + set value(newValue: number); +} +``` + + + + +```ts +interface Box { + get value(): string; + set value(newValue: string); +} +``` + + + + +## When Not To Use It + +If your project needs to model unusual relationships between data, such as older DOM types, this rule may not be useful for you. +You might consider using [ESLint disable comments](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1) for those specific situations instead of completely disabling this rule. + +## Further Reading + +- [MDN documentation on `get`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) +- [MDN documentation on `set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) +- [TypeScript 5.1 Release Notes > Unrelated Types for Getters and Setters](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html#unrelated-types-for-getters-and-setters) diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 107f369260ec..1496d3af0f72 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -142,6 +142,7 @@ export = { '@typescript-eslint/prefer-return-this-type': 'error', '@typescript-eslint/prefer-string-starts-ends-with': 'error', '@typescript-eslint/promise-function-async': 'error', + '@typescript-eslint/related-getter-setter-pairs': 'error', '@typescript-eslint/require-array-sort-compare': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', diff --git a/packages/eslint-plugin/src/configs/disable-type-checked.ts b/packages/eslint-plugin/src/configs/disable-type-checked.ts index 7cf867b382f2..bbbafc868982 100644 --- a/packages/eslint-plugin/src/configs/disable-type-checked.ts +++ b/packages/eslint-plugin/src/configs/disable-type-checked.ts @@ -56,6 +56,7 @@ export = { '@typescript-eslint/prefer-return-this-type': 'off', '@typescript-eslint/prefer-string-starts-ends-with': 'off', '@typescript-eslint/promise-function-async': 'off', + '@typescript-eslint/related-getter-setter-pairs': 'off', '@typescript-eslint/require-array-sort-compare': 'off', '@typescript-eslint/require-await': 'off', '@typescript-eslint/restrict-plus-operands': 'off', diff --git a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts index 8bfba2276c9d..c235c02e7b81 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts @@ -43,6 +43,7 @@ export = { '@typescript-eslint/prefer-promise-reject-errors': 'error', '@typescript-eslint/prefer-reduce-type-parameter': 'error', '@typescript-eslint/prefer-return-this-type': 'error', + '@typescript-eslint/related-getter-setter-pairs': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', '@typescript-eslint/restrict-plus-operands': [ diff --git a/packages/eslint-plugin/src/configs/strict-type-checked.ts b/packages/eslint-plugin/src/configs/strict-type-checked.ts index 4a5c7adfc93e..b00f5eb0fc14 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -76,6 +76,7 @@ export = { '@typescript-eslint/prefer-promise-reject-errors': 'error', '@typescript-eslint/prefer-reduce-type-parameter': 'error', '@typescript-eslint/prefer-return-this-type': 'error', + '@typescript-eslint/related-getter-setter-pairs': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', '@typescript-eslint/restrict-plus-operands': [ @@ -93,10 +94,10 @@ export = { { allowAny: false, allowBoolean: false, + allowNever: false, allowNullish: false, allowNumber: false, allowRegExp: false, - allowNever: false, }, ], 'no-return-await': 'off', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 72c62f3e0122..776e593ec680 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -114,6 +114,7 @@ import preferReturnThisType from './prefer-return-this-type'; import preferStringStartsEndsWith from './prefer-string-starts-ends-with'; import preferTsExpectError from './prefer-ts-expect-error'; import promiseFunctionAsync from './promise-function-async'; +import relatedGetterSetterPairs from './related-getter-setter-pairs'; import requireArraySortCompare from './require-array-sort-compare'; import requireAwait from './require-await'; import restrictPlusOperands from './restrict-plus-operands'; @@ -244,6 +245,7 @@ const rules = { 'prefer-string-starts-ends-with': preferStringStartsEndsWith, 'prefer-ts-expect-error': preferTsExpectError, 'promise-function-async': promiseFunctionAsync, + 'related-getter-setter-pairs': relatedGetterSetterPairs, 'require-array-sort-compare': requireArraySortCompare, 'require-await': requireAwait, 'restrict-plus-operands': restrictPlusOperands, diff --git a/packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts b/packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts new file mode 100644 index 000000000000..51c08c084b47 --- /dev/null +++ b/packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts @@ -0,0 +1,108 @@ +import type { TSESTree } from '@typescript-eslint/utils'; + +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import { createRule, getNameFromMember, getParserServices } from '../util'; + +type Method = TSESTree.MethodDefinition | TSESTree.TSMethodSignature; + +type GetMethod = { + kind: 'get'; + returnType: TSESTree.TSTypeAnnotation | undefined; +} & Method; + +type SetMethod = { kind: 'set'; params: [TSESTree.Node] } & Method; + +interface MethodPair { + get?: GetMethod; + set?: SetMethod; +} + +export default createRule({ + name: 'related-getter-setter-pairs', + meta: { + type: 'problem', + docs: { + description: + 'Enforce that `get()` types should be assignable to their equivalent `set()` type', + recommended: 'strict', + requiresTypeChecking: true, + }, + messages: { + mismatch: + '`get()` type should be assignable to its equivalent `set()` type.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + const services = getParserServices(context); + const checker = services.program.getTypeChecker(); + const methodPairsStack: Map[] = []; + + function addPropertyNode( + member: GetMethod | SetMethod, + inner: TSESTree.Node, + kind: 'get' | 'set', + ): void { + const methodPairs = methodPairsStack[methodPairsStack.length - 1]; + const { name } = getNameFromMember(member, context.sourceCode); + + methodPairs.set(name, { + ...methodPairs.get(name), + [kind]: inner, + }); + } + + return { + ':matches(ClassBody, TSInterfaceBody, TSTypeLiteral):exit'(): void { + const methodPairs = methodPairsStack[methodPairsStack.length - 1]; + + for (const pair of methodPairs.values()) { + if (!pair.get || !pair.set) { + continue; + } + + const getter = pair.get; + + const getType = services.getTypeAtLocation(getter); + const setType = services.getTypeAtLocation(pair.set.params[0]); + + if (!checker.isTypeAssignableTo(getType, setType)) { + context.report({ + node: getter.returnType?.typeAnnotation ?? pair.get.key, + messageId: 'mismatch', + }); + } + } + }, + ':matches(MethodDefinition, TSMethodSignature)[kind=get]'( + node: GetMethod, + ): void { + const getter = getMethodFromNode(node); + + if (getter.returnType) { + addPropertyNode(node, getter, 'get'); + } + }, + ':matches(MethodDefinition, TSMethodSignature)[kind=set]'( + node: SetMethod, + ): void { + const setter = getMethodFromNode(node); + + if (setter.params.length === 1) { + addPropertyNode(node, setter, 'set'); + } + }, + + 'ClassBody, TSInterfaceBody, TSTypeLiteral'(): void { + methodPairsStack.push(new Map()); + }, + }; + }, +}); + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +function getMethodFromNode(node: GetMethod | SetMethod) { + return node.type === AST_NODE_TYPES.TSMethodSignature ? node : node.value; +} diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/related-getter-setter-pairs.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/related-getter-setter-pairs.shot new file mode 100644 index 000000000000..f4d43a5d3fac --- /dev/null +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/related-getter-setter-pairs.shot @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validating rule docs related-getter-setter-pairs.mdx code examples ESLint output 1`] = ` +"Incorrect + +interface Box { + get value(): string; + ~~~~~~ \`get()\` type should be assignable to its equivalent \`set()\` type. + set value(newValue: number); +} +" +`; + +exports[`Validating rule docs related-getter-setter-pairs.mdx code examples ESLint output 2`] = ` +"Correct + +interface Box { + get value(): string; + set value(newValue: string); +} +" +`; diff --git a/packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts b/packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts new file mode 100644 index 000000000000..36df45c0024a --- /dev/null +++ b/packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts @@ -0,0 +1,165 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; + +import rule from '../../src/rules/related-getter-setter-pairs'; +import { getFixturesRootDir } from '../RuleTester'; + +const rootDir = getFixturesRootDir(); +const ruleTester = new RuleTester({ + languageOptions: { + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: rootDir, + }, + }, +}); + +ruleTester.run('related-getter-setter-pairs', rule, { + valid: [ + ` +interface Example { + get value(): string; + set value(newValue: string); +} + `, + ` +interface Example { + get value(): string | undefined; + set value(); +} + `, + ` +interface Example { + get value(): string | undefined; + set value(newValue: string, invalid: string); +} + `, + ` +interface Example { + get value(): string; + set value(newValue: string | undefined); +} + `, + ` +interface Example { + get value(): number; +} + `, + ` +interface Example { + get value(): number; + set value(); +} + `, + ` +interface Example { + set value(newValue: string); +} + `, + ` +interface Example { + set value(); +} + `, + ` +type Example = { + get value(); +}; + `, + ` +type Example = { + set value(); +}; + `, + ` +class Example { + get value() { + return ''; + } +} + `, + ` +class Example { + set value() {} +} + `, + ` +type Example = { + get value(): number; + set value(newValue: number); +}; + `, + ], + + invalid: [ + { + code: ` +interface Example { + get value(): string | undefined; + set value(newValue: string); +} + `, + errors: [ + { + column: 16, + endColumn: 34, + endLine: 3, + line: 3, + messageId: 'mismatch', + }, + ], + }, + { + code: ` +interface Example { + get value(): number; + set value(newValue: string); +} + `, + errors: [ + { + column: 16, + endColumn: 22, + endLine: 3, + line: 3, + messageId: 'mismatch', + }, + ], + }, + { + code: ` +type Example = { + get value(): number; + set value(newValue: string); +}; + `, + errors: [ + { + column: 16, + endColumn: 22, + endLine: 3, + line: 3, + messageId: 'mismatch', + }, + ], + }, + { + code: ` +class Example { + get value(): boolean { + return true; + } + set value(newValue: string) {} +} + `, + errors: [ + { + column: 16, + endColumn: 23, + endLine: 3, + line: 3, + messageId: 'mismatch', + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/schema-snapshots/related-getter-setter-pairs.shot b/packages/eslint-plugin/tests/schema-snapshots/related-getter-setter-pairs.shot new file mode 100644 index 000000000000..2284af742d87 --- /dev/null +++ b/packages/eslint-plugin/tests/schema-snapshots/related-getter-setter-pairs.shot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Rule schemas should be convertible to TS types for documentation purposes related-getter-setter-pairs 1`] = ` +" +# SCHEMA: + +[] + + +# TYPES: + +/** No options declared */ +type Options = [];" +`; diff --git a/packages/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts index 1c177f1943bf..2f5a581dca4c 100644 --- a/packages/typescript-eslint/src/configs/all.ts +++ b/packages/typescript-eslint/src/configs/all.ts @@ -156,6 +156,7 @@ export default ( '@typescript-eslint/prefer-return-this-type': 'error', '@typescript-eslint/prefer-string-starts-ends-with': 'error', '@typescript-eslint/promise-function-async': 'error', + '@typescript-eslint/related-getter-setter-pairs': 'error', '@typescript-eslint/require-array-sort-compare': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', diff --git a/packages/typescript-eslint/src/configs/disable-type-checked.ts b/packages/typescript-eslint/src/configs/disable-type-checked.ts index b4c2afd20ec8..94b694a0fc28 100644 --- a/packages/typescript-eslint/src/configs/disable-type-checked.ts +++ b/packages/typescript-eslint/src/configs/disable-type-checked.ts @@ -63,6 +63,7 @@ export default ( '@typescript-eslint/prefer-return-this-type': 'off', '@typescript-eslint/prefer-string-starts-ends-with': 'off', '@typescript-eslint/promise-function-async': 'off', + '@typescript-eslint/related-getter-setter-pairs': 'off', '@typescript-eslint/require-array-sort-compare': 'off', '@typescript-eslint/require-await': 'off', '@typescript-eslint/restrict-plus-operands': 'off', diff --git a/packages/typescript-eslint/src/configs/strict-type-checked-only.ts b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts index 15a4d035ae3c..c9efc88da6c3 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked-only.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts @@ -56,6 +56,7 @@ export default ( '@typescript-eslint/prefer-promise-reject-errors': 'error', '@typescript-eslint/prefer-reduce-type-parameter': 'error', '@typescript-eslint/prefer-return-this-type': 'error', + '@typescript-eslint/related-getter-setter-pairs': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', '@typescript-eslint/restrict-plus-operands': [ diff --git a/packages/typescript-eslint/src/configs/strict-type-checked.ts b/packages/typescript-eslint/src/configs/strict-type-checked.ts index 99e4b05d303b..4f354baf7e40 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked.ts @@ -89,6 +89,7 @@ export default ( '@typescript-eslint/prefer-promise-reject-errors': 'error', '@typescript-eslint/prefer-reduce-type-parameter': 'error', '@typescript-eslint/prefer-return-this-type': 'error', + '@typescript-eslint/related-getter-setter-pairs': 'error', 'require-await': 'off', '@typescript-eslint/require-await': 'error', '@typescript-eslint/restrict-plus-operands': [ From ac1f0e8f06c6450ca36d68a5446ae3511eed9fa6 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 4 Nov 2024 09:52:35 -0500 Subject: [PATCH 2/6] Fixed stack popping --- .../src/rules/related-getter-setter-pairs.ts | 3 +- .../rules/related-getter-setter-pairs.test.ts | 99 +++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts b/packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts index 51c08c084b47..7361b61f356a 100644 --- a/packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts +++ b/packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts @@ -75,6 +75,8 @@ export default createRule({ }); } } + + methodPairsStack.pop(); }, ':matches(MethodDefinition, TSMethodSignature)[kind=get]'( node: GetMethod, @@ -102,7 +104,6 @@ export default createRule({ }, }); -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type function getMethodFromNode(node: GetMethod | SetMethod) { return node.type === AST_NODE_TYPES.TSMethodSignature ? node : node.value; } diff --git a/packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts b/packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts index 36df45c0024a..4fe0216df0bf 100644 --- a/packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts +++ b/packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts @@ -161,5 +161,104 @@ class Example { }, ], }, + { + code: ` +type GetType = { a: string; b: string }; + +declare class Foo { + get a(): GetType; + + set a(x: { c: string }); +} + `, + errors: [ + { + column: 12, + endColumn: 19, + endLine: 5, + line: 5, + messageId: 'mismatch', + }, + ], + }, + { + code: ` +type GetType = { a: string; b: string }; + +type SetTypeUnused = { c: string }; + +declare class Foo { + get a(): GetType; + + set a(x: { c: string }); +} + `, + errors: [ + { + column: 12, + endColumn: 19, + endLine: 7, + line: 7, + messageId: 'mismatch', + }, + ], + }, + { + code: ` +type GetType = { a: string; b: string }; + +type SetType = { c: string }; + +declare class Foo { + get a(): GetType; + + set a(x: SetType); +} + `, + errors: [ + { + column: 12, + endColumn: 19, + endLine: 7, + line: 7, + messageId: 'mismatch', + }, + ], + }, ], }); + +/* + + ` +type GetType = { a: string; b: string }; + +declare class Foo { + get a(): GetType; + + set a(x: { c: string }); +} + `, + ` +type GetType = { a: string; b: string }; + +type SetTypeUnused = { c: string }; + +declare class Foo { + get a(): GetType; + + set a(x: { c: string }); +} + `, + ` +type GetType = { a: string; b: string }; + +type SetType = { c: string }; + +declare class Foo { + get a(): GetType; + + set a(x: SetType); +} + `, + */ From ab93e7f17f1614e122864d99bd45a3d7212ddf9c Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 4 Nov 2024 09:54:08 -0500 Subject: [PATCH 3/6] Fixed stack popping --- .../rules/related-getter-setter-pairs.test.ts | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts b/packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts index 4fe0216df0bf..d2a3c9f6b51e 100644 --- a/packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts +++ b/packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts @@ -227,38 +227,3 @@ declare class Foo { }, ], }); - -/* - - ` -type GetType = { a: string; b: string }; - -declare class Foo { - get a(): GetType; - - set a(x: { c: string }); -} - `, - ` -type GetType = { a: string; b: string }; - -type SetTypeUnused = { c: string }; - -declare class Foo { - get a(): GetType; - - set a(x: { c: string }); -} - `, - ` -type GetType = { a: string; b: string }; - -type SetType = { c: string }; - -declare class Foo { - get a(): GetType; - - set a(x: SetType); -} - `, - */ From 51971cd64ea4309c383e1ea9545705d802f18b2d Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 4 Nov 2024 10:42:14 -0500 Subject: [PATCH 4/6] Correction: reported getter always has return type annotation --- .../src/rules/related-getter-setter-pairs.ts | 10 +++++--- .../rules/related-getter-setter-pairs.test.ts | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts b/packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts index 7361b61f356a..a757d68af983 100644 --- a/packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts +++ b/packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts @@ -8,9 +8,13 @@ type Method = TSESTree.MethodDefinition | TSESTree.TSMethodSignature; type GetMethod = { kind: 'get'; - returnType: TSESTree.TSTypeAnnotation | undefined; + returnType: TSESTree.TSTypeAnnotation; } & Method; +type GetMethodRaw = { + returnType: TSESTree.TSTypeAnnotation | undefined; +} & GetMethod; + type SetMethod = { kind: 'set'; params: [TSESTree.Node] } & Method; interface MethodPair { @@ -70,7 +74,7 @@ export default createRule({ if (!checker.isTypeAssignableTo(getType, setType)) { context.report({ - node: getter.returnType?.typeAnnotation ?? pair.get.key, + node: getter.returnType.typeAnnotation, messageId: 'mismatch', }); } @@ -104,6 +108,6 @@ export default createRule({ }, }); -function getMethodFromNode(node: GetMethod | SetMethod) { +function getMethodFromNode(node: GetMethodRaw | SetMethod) { return node.type === AST_NODE_TYPES.TSMethodSignature ? node : node.value; } diff --git a/packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts b/packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts index d2a3c9f6b51e..ec7e22385052 100644 --- a/packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts +++ b/packages/eslint-plugin/tests/rules/related-getter-setter-pairs.test.ts @@ -78,6 +78,30 @@ class Example { } `, ` +class Example { + get value() { + return ''; + } + set value() {} +} + `, + ` +class Example { + get value() { + return ''; + } + set value(param) {} +} + `, + ` +class Example { + get value() { + return ''; + } + set value(param: number) {} +} + `, + ` class Example { set value() {} } From 3348863ad9183450bbef45c8df9a639ce6d67353 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 4 Nov 2024 10:42:44 -0500 Subject: [PATCH 5/6] Correction: reported getter always has return type annotation --- packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts b/packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts index a757d68af983..89019b1d0a03 100644 --- a/packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts +++ b/packages/eslint-plugin/src/rules/related-getter-setter-pairs.ts @@ -83,7 +83,7 @@ export default createRule({ methodPairsStack.pop(); }, ':matches(MethodDefinition, TSMethodSignature)[kind=get]'( - node: GetMethod, + node: GetMethodRaw, ): void { const getter = getMethodFromNode(node); From e76d87b906eaf2c6cda61cabc898ac2865a28e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Wed, 6 Nov 2024 09:09:40 -0500 Subject: [PATCH 6/6] Update packages/eslint-plugin/docs/rules/related-getter-setter-pairs.mdx Co-authored-by: Joshua Chen --- .../eslint-plugin/docs/rules/related-getter-setter-pairs.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/related-getter-setter-pairs.mdx b/packages/eslint-plugin/docs/rules/related-getter-setter-pairs.mdx index 3bfcf72be361..d709faf5498e 100644 --- a/packages/eslint-plugin/docs/rules/related-getter-setter-pairs.mdx +++ b/packages/eslint-plugin/docs/rules/related-getter-setter-pairs.mdx @@ -10,7 +10,7 @@ import TabItem from '@theme/TabItem'; > See **https://typescript-eslint.io/rules/related-getter-setter-pairs** for documentation. TypeScript allows defining different types for a `get` parameter and its corresponding `set` return. -Prior to TypeScript 4.3, the types didn't have to be related. +Prior to TypeScript 4.3, the types had to be identical. From TypeScript 4.3 to 5.0, the `get` type had to be a subtype of the `set` type. As of TypeScript 5.1, the types may be completely unrelated as long as there is an explicit type annotation.