diff --git a/packages/eslint-plugin/src/rules/method-signature-style.ts b/packages/eslint-plugin/src/rules/method-signature-style.ts index 6ef8db5a0c9..2d6366bbda1 100644 --- a/packages/eslint-plugin/src/rules/method-signature-style.ts +++ b/packages/eslint-plugin/src/rules/method-signature-style.ts @@ -4,11 +4,10 @@ import { } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; -export type Options = ['property' | 'method']; +export type Options = [('property' | 'method')?]; +export type MessageIds = 'errorMethod' | 'errorProperty'; -export type MessageId = 'errorMethod' | 'errorProperty'; - -export default util.createRule({ +export default util.createRule({ name: 'method-signature-style', meta: { type: 'suggestion', @@ -56,10 +55,21 @@ export default util.createRule({ ): string { let params = '()'; if (node.params.length > 0) { + const openingParen = util.nullThrows( + sourceCode.getTokenBefore(node.params[0], util.isOpeningParenToken), + 'Missing opening paren before first parameter', + ); + const closingParen = util.nullThrows( + sourceCode.getTokenAfter( + node.params[node.params.length - 1], + util.isClosingParenToken, + ), + 'Missing closing paren after last parameter', + ); + params = sourceCode.text.substring( - sourceCode.getTokenBefore(node.params[0])!.range[0], - sourceCode.getTokenAfter(node.params[node.params.length - 1])! - .range[1], + openingParen.range[0], + closingParen.range[1], ); } if (node.typeParameters != null) { @@ -75,6 +85,18 @@ export default util.createRule({ return sourceCode.getText(node.returnType!.typeAnnotation); } + function getDelimiter(node: TSESTree.Node): string { + const lastToken = sourceCode.getLastToken(node); + if ( + lastToken && + (util.isSemicolonToken(lastToken) || util.isCommaToken(lastToken)) + ) { + return lastToken.value; + } + + return ''; + } + return { TSMethodSignature(methodNode): void { if (mode === 'method') { @@ -88,9 +110,10 @@ export default util.createRule({ const key = getMethodKey(methodNode); const params = getMethodParams(methodNode); const returnType = getMethodReturnType(methodNode); + const delimiter = getDelimiter(methodNode); return fixer.replaceText( methodNode, - `${key}: ${params} => ${returnType}`, + `${key}: ${params} => ${returnType}${delimiter}`, ); }, }); @@ -112,9 +135,10 @@ export default util.createRule({ const key = getMethodKey(propertyNode); const params = getMethodParams(typeNode); const returnType = getMethodReturnType(typeNode); + const delimiter = getDelimiter(propertyNode); return fixer.replaceText( propertyNode, - `${key}${params}: ${returnType}`, + `${key}${params}: ${returnType}${delimiter}`, ); }, }); 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 76003e67d84..dc938ff8cb9 100644 --- a/packages/eslint-plugin/tests/rules/method-signature-style.test.ts +++ b/packages/eslint-plugin/tests/rules/method-signature-style.test.ts @@ -7,19 +7,36 @@ const ruleTester = new RuleTester({ ruleTester.run('method-signature-style', rule, { valid: [ - ...batchedSingleLineTests({ - code: noFormat` - interface Test { f: (a: string) => number } - interface Test { ['f']: (a: boolean) => void } - interface Test { f: (a: T) => T } - interface Test { ['f']: (a: T, b: T) => T } - interface Test { 'f!': (/* b */ x: any /* c */) => void } - type Test = { readonly f: (a: string) => number } - type Test = { ['f']?: (a: boolean) => void } - type Test = { readonly f?: (a?: T) => T } - type Test = { readonly ['f']?: (a: T, b: T) => T } - `, - }), + ` +interface Test { + f: (a: string) => number; +} + `, + ` +interface Test { + ['f']: (a: boolean) => void; +} + `, + ` +interface Test { + f: (a: T) => T; +} + `, + ` +interface Test { + ['f']: (a: T, b: T) => T; +} + `, + // TODO - requires prettier2 to format correctly + noFormat` +interface Test { + 'f!': (/* b */ x: any /* c */) => void; +} + `, + 'type Test = { readonly f: (a: string) => number };', + "type Test = { ['f']?: (a: boolean) => void };", + 'type Test = { readonly f?: (a?: T) => T };', + "type Test = { readonly ['f']?: (a: T, b: T) => T };", ...batchedSingleLineTests({ options: ['method'], code: noFormat` @@ -107,5 +124,107 @@ ruleTester.run('method-signature-style', rule, { type Test = { readonly ['f']?(a: T, b: T): T } `, }), + { + code: noFormat` +interface Foo { + semi(arg: string): void; + comma(arg: string): void, + none(arg: string): void +} + `, + output: noFormat` +interface Foo { + semi: (arg: string) => void; + comma: (arg: string) => void, + none: (arg: string) => void +} + `, + errors: [ + { + messageId: 'errorMethod', + line: 3, + }, + { + messageId: 'errorMethod', + line: 4, + }, + { + messageId: 'errorMethod', + line: 5, + }, + ], + }, + { + code: noFormat` +interface Foo { + semi: (arg: string) => void; + comma: (arg: string) => void, + none: (arg: string) => void +} + `, + output: noFormat` +interface Foo { + semi(arg: string): void; + comma(arg: string): void, + none(arg: string): void +} + `, + options: ['method'], + errors: [ + { + messageId: 'errorProperty', + line: 3, + }, + { + messageId: 'errorProperty', + line: 4, + }, + { + messageId: 'errorProperty', + line: 5, + }, + ], + }, + // https://github.com/typescript-eslint/typescript-eslint/issues/1857 + { + code: noFormat` +interface Foo { + x( + args: Pick< + Bar, + 'one' | 'two' | 'three' + >, + ): Baz; + y( + foo: string, + bar: number, + ): void; +} + `, + output: noFormat` +interface Foo { + x: ( + args: Pick< + Bar, + 'one' | 'two' | 'three' + >, + ) => Baz; + y: ( + foo: string, + bar: number, + ) => void; +} + `, + errors: [ + { + messageId: 'errorMethod', + line: 3, + }, + { + messageId: 'errorMethod', + line: 9, + }, + ], + }, ], });