diff --git a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts index 9c57c4f40327..488842f12705 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts @@ -1,8 +1,8 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { createRule } from '../util'; +import { createRule, nullThrows, NullThrowsReasons } from '../util'; export default createRule({ name: 'consistent-type-definitions', @@ -54,32 +54,45 @@ export default createRule({ node: node.id, messageId: 'interfaceOverType', fix(fixer) { - const typeNode = node.typeParameters ?? node.id; - const fixes: TSESLint.RuleFix[] = []; + const typeToken = nullThrows( + context.sourceCode.getTokenBefore( + node.id, + token => token.value === 'type', + ), + NullThrowsReasons.MissingToken('type keyword', 'type alias'), + ); - const firstToken = context.sourceCode.getTokenBefore(node.id); - if (firstToken) { - fixes.push(fixer.replaceText(firstToken, 'interface')); - fixes.push( - fixer.replaceTextRange( - [typeNode.range[1], node.typeAnnotation.range[0]], - ' ', - ), - ); - } + const equalsToken = nullThrows( + context.sourceCode.getTokenBefore( + node.typeAnnotation, + token => token.value === '=', + ), + NullThrowsReasons.MissingToken('=', 'type alias'), + ); - const afterToken = context.sourceCode.getTokenAfter( - node.typeAnnotation, + const beforeEqualsToken = nullThrows( + context.sourceCode.getTokenBefore(equalsToken, { + includeComments: true, + }), + NullThrowsReasons.MissingToken('before =', 'type alias'), ); - if ( - afterToken && - afterToken.type === AST_TOKEN_TYPES.Punctuator && - afterToken.value === ';' - ) { - fixes.push(fixer.remove(afterToken)); - } - return fixes; + return [ + // replace 'type' with 'interface'. + fixer.replaceText(typeToken, 'interface'), + + // delete from the = to the { of the type, and put a space to be pretty. + fixer.replaceTextRange( + [beforeEqualsToken.range[1], node.typeAnnotation.range[0]], + ' ', + ), + + // remove from the closing } through the end of the statement. + fixer.removeRange([ + node.typeAnnotation.range[1], + node.range[1], + ]), + ]; }, }); }, diff --git a/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts index 3735fca22ffa..b14fb8976939 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts @@ -96,6 +96,18 @@ export type W = { options: ['interface'], output: `interface T { x: number; }`, }, + { + code: noFormat`type T /* comment */={ x: number; };`, + errors: [ + { + column: 6, + line: 1, + messageId: 'interfaceOverType', + }, + ], + options: ['interface'], + output: `interface T /* comment */ { x: number; }`, + }, { code: ` export type W = { @@ -350,5 +362,104 @@ export declare type Test = { } `, }, + { + code: noFormat` +type Foo = ({ + a: string; +}); + `, + errors: [ + { + line: 2, + messageId: 'interfaceOverType', + }, + ], + output: ` +interface Foo { + a: string; +} + `, + }, + { + code: noFormat` +type Foo = ((((((((({ + a: string; +}))))))))); + `, + errors: [ + { + line: 2, + messageId: 'interfaceOverType', + }, + ], + output: ` +interface Foo { + a: string; +} + `, + }, + { + // no closing semicolon + code: noFormat` +type Foo = { + a: string; +} + `, + errors: [ + { + line: 2, + messageId: 'interfaceOverType', + }, + ], + output: ` +interface Foo { + a: string; +} + `, + }, + { + // no closing semicolon; ensure we don't erase subsequent code. + code: noFormat` +type Foo = { + a: string; +} +type Bar = string; + `, + errors: [ + { + line: 2, + messageId: 'interfaceOverType', + }, + ], + output: ` +interface Foo { + a: string; +} +type Bar = string; + `, + }, + { + // no closing semicolon; ensure we don't erase subsequent code. + code: noFormat` +type Foo = ((({ + a: string; +}))) + +const bar = 1; + `, + errors: [ + { + line: 2, + messageId: 'interfaceOverType', + }, + ], + output: ` +interface Foo { + a: string; +} + +const bar = 1; + `, + }, ], });