From 7f3fba348d432d7637e1c737df943ee1f9105062 Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Mon, 4 May 2020 13:50:27 +0900 Subject: [PATCH] fix(eslint-plugin): [method-signature-style] fix overloaded methods to an intersection type (#1966) --- .../docs/rules/method-signature-style.md | 11 ++ .../src/rules/method-signature-style.ts | 50 +++++++ .../rules/method-signature-style.test.ts | 123 ++++++++++++++++++ 3 files changed, 184 insertions(+) diff --git a/packages/eslint-plugin/docs/rules/method-signature-style.md b/packages/eslint-plugin/docs/rules/method-signature-style.md index d760c8d0193..4358ee36ca0 100644 --- a/packages/eslint-plugin/docs/rules/method-signature-style.md +++ b/packages/eslint-plugin/docs/rules/method-signature-style.md @@ -43,6 +43,11 @@ interface T1 { type T2 = { func(arg: boolean): void; }; +interface T3 { + func(arg: number): void; + func(arg: string): void; + func(arg: boolean): void; +} ``` Examples of **correct** code with `property` option. @@ -54,6 +59,12 @@ interface T1 { type T2 = { func: (arg: boolean) => void; }; +// this is equivalent to the overload +interface T3 { + func: ((arg: number) => void) & + ((arg: string) => void) & + ((arg: boolean) => void); +} ``` Examples of **incorrect** code with `method` option. diff --git a/packages/eslint-plugin/src/rules/method-signature-style.ts b/packages/eslint-plugin/src/rules/method-signature-style.ts index 2d6366bbda1..45f7fdbd711 100644 --- a/packages/eslint-plugin/src/rules/method-signature-style.ts +++ b/packages/eslint-plugin/src/rules/method-signature-style.ts @@ -103,6 +103,56 @@ export default util.createRule({ return; } + const duplicatedKeyMethodNodes: TSESTree.TSMethodSignature[] = + methodNode.parent?.type === AST_NODE_TYPES.TSInterfaceBody + ? methodNode.parent.body.filter( + (element): element is TSESTree.TSMethodSignature => + element.type === AST_NODE_TYPES.TSMethodSignature && + element !== methodNode && + getMethodKey(element) === getMethodKey(methodNode), + ) + : []; + + if (duplicatedKeyMethodNodes.length > 0) { + context.report({ + node: methodNode, + messageId: 'errorMethod', + *fix(fixer) { + const methodNodes = [ + methodNode, + ...duplicatedKeyMethodNodes, + ].sort((a, b) => (a.range[0] < b.range[0] ? -1 : 1)); + const typeString = methodNodes.reduce((str, node, idx, nodes) => { + const params = getMethodParams(node); + const returnType = getMethodReturnType(node); + return `${str}(${params} => ${returnType})${ + idx !== nodes.length - 1 ? ' & ' : '' + }`; + }, ''); + const key = getMethodKey(methodNode); + const delimiter = getDelimiter(methodNode); + yield fixer.replaceText( + methodNode, + `${key}: ${typeString}${delimiter}`, + ); + for (const node of duplicatedKeyMethodNodes) { + const lastToken = sourceCode.getLastToken(node); + if (lastToken) { + const nextToken = sourceCode.getTokenAfter(lastToken); + if (nextToken) { + yield fixer.remove(node); + yield fixer.replaceTextRange( + [lastToken.range[1], nextToken.range[0]], + '', + ); + } + } + } + }, + }); + return; + } + context.report({ node: methodNode, messageId: 'errorMethod', diff --git a/packages/eslint-plugin/tests/rules/method-signature-style.test.ts b/packages/eslint-plugin/tests/rules/method-signature-style.test.ts index dc938ff8cb9..62383014082 100644 --- a/packages/eslint-plugin/tests/rules/method-signature-style.test.ts +++ b/packages/eslint-plugin/tests/rules/method-signature-style.test.ts @@ -226,5 +226,128 @@ interface Foo { }, ], }, + { + code: noFormat` +interface Foo { + foo(): one; + foo(): two; + foo(): three; +} + `, + output: noFormat` +interface Foo { + foo: (() => one) & (() => two) & (() => three); +} + `, + errors: [ + { + messageId: 'errorMethod', + line: 3, + }, + { + messageId: 'errorMethod', + line: 4, + }, + { + messageId: 'errorMethod', + line: 5, + }, + ], + }, + { + code: noFormat` +interface Foo { + foo(bar: string): one; + foo(bar: number, baz: string): two; + foo(): three; +} + `, + output: noFormat` +interface Foo { + foo: ((bar: string) => one) & ((bar: number, baz: string) => two) & (() => three); +} + `, + errors: [ + { + messageId: 'errorMethod', + line: 3, + }, + { + messageId: 'errorMethod', + line: 4, + }, + { + messageId: 'errorMethod', + line: 5, + }, + ], + }, + { + code: noFormat` +interface Foo { + [foo](bar: string): one; + [foo](bar: number, baz: string): two; + [foo](): three; +} + `, + output: noFormat` +interface Foo { + [foo]: ((bar: string) => one) & ((bar: number, baz: string) => two) & (() => three); +} + `, + errors: [ + { + messageId: 'errorMethod', + line: 3, + }, + { + messageId: 'errorMethod', + line: 4, + }, + { + messageId: 'errorMethod', + line: 5, + }, + ], + }, + { + code: noFormat` +interface Foo { + [foo](bar: string): one; + [foo](bar: number, baz: string): two; + [foo](): three; + bar(arg: string): void; + bar(baz: number): Foo; +} + `, + output: noFormat` +interface Foo { + [foo]: ((bar: string) => one) & ((bar: number, baz: string) => two) & (() => three); + bar: ((arg: string) => void) & ((baz: number) => Foo); +} + `, + errors: [ + { + messageId: 'errorMethod', + line: 3, + }, + { + messageId: 'errorMethod', + line: 4, + }, + { + messageId: 'errorMethod', + line: 5, + }, + { + messageId: 'errorMethod', + line: 6, + }, + { + messageId: 'errorMethod', + line: 7, + }, + ], + }, ], });