diff --git a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts index 794e380e389..f5b2c48a44f 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts @@ -1,4 +1,5 @@ import { + AST_NODE_TYPES, AST_TOKEN_TYPES, TSESLint, TSESTree, @@ -31,6 +32,21 @@ export default util.createRule({ create(context, [option]) { const sourceCode = context.getSourceCode(); + /** + * Iterates from the highest parent to the currently traversed node + * to determine whether any node in tree is globally declared module declaration + */ + function isCurrentlyTraversedNodeWithinModuleDeclaration(): boolean { + return context + .getAncestors() + .some( + node => + node.type === AST_NODE_TYPES.TSModuleDeclaration && + node.declare && + node.global, + ); + } + return { "TSTypeAliasDeclaration[typeAnnotation.type='TSTypeLiteral']"( node: TSESTree.TSTypeAliasDeclaration, @@ -73,32 +89,41 @@ export default util.createRule({ context.report({ node: node.id, messageId: 'typeOverInterface', - fix(fixer) { - const typeNode = node.typeParameters ?? node.id; - const fixes: TSESLint.RuleFix[] = []; + /** + * remove automatically fix when the interface is within a declare global + * @see {@link https://github.com/typescript-eslint/typescript-eslint/issues/2707} + */ + fix: isCurrentlyTraversedNodeWithinModuleDeclaration() + ? null + : (fixer): TSESLint.RuleFix[] => { + const typeNode = node.typeParameters ?? node.id; + const fixes: TSESLint.RuleFix[] = []; - const firstToken = sourceCode.getFirstToken(node); - if (firstToken) { - fixes.push(fixer.replaceText(firstToken, 'type')); - fixes.push( - fixer.replaceTextRange( - [typeNode.range[1], node.body.range[0]], - ' = ', - ), - ); - } + const firstToken = sourceCode.getFirstToken(node); + if (firstToken) { + fixes.push(fixer.replaceText(firstToken, 'type')); + fixes.push( + fixer.replaceTextRange( + [typeNode.range[1], node.body.range[0]], + ' = ', + ), + ); + } - if (node.extends) { - node.extends.forEach(heritage => { - const typeIdentifier = sourceCode.getText(heritage); - fixes.push( - fixer.insertTextAfter(node.body, ` & ${typeIdentifier}`), - ); - }); - } + if (node.extends) { + node.extends.forEach(heritage => { + const typeIdentifier = sourceCode.getText(heritage); + fixes.push( + fixer.insertTextAfter( + node.body, + ` & ${typeIdentifier}`, + ), + ); + }); + } - return fixes; - }, + return fixes; + }, }); } }, 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 78e5d3ceb01..105c25724c2 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts @@ -197,5 +197,89 @@ export type W = { }, ], }, + { + code: ` +namespace JSX { + interface Array { + foo(x: (x: number) => T): T[]; + } +} + `, + output: noFormat` +namespace JSX { + type Array = { + foo(x: (x: number) => T): T[]; + } +} + `, + options: ['type'], + errors: [ + { + messageId: 'typeOverInterface', + line: 3, + column: 13, + }, + ], + }, + { + code: ` +global { + interface Array { + foo(x: (x: number) => T): T[]; + } +} + `, + output: noFormat` +global { + type Array = { + foo(x: (x: number) => T): T[]; + } +} + `, + options: ['type'], + errors: [ + { + messageId: 'typeOverInterface', + line: 3, + column: 13, + }, + ], + }, + { + code: ` +declare global { + interface Array { + foo(x: (x: number) => T): T[]; + } +} + `, + output: null, + options: ['type'], + errors: [ + { + messageId: 'typeOverInterface', + line: 3, + column: 13, + }, + ], + }, + { + code: ` +declare global { + namespace Foo { + interface Bar {} + } +} + `, + output: null, + options: ['type'], + errors: [ + { + messageId: 'typeOverInterface', + line: 4, + column: 15, + }, + ], + }, ], });