diff --git a/packages/eslint-plugin/src/rules/unified-signatures.ts b/packages/eslint-plugin/src/rules/unified-signatures.ts index c58e3308572..809b19dd1aa 100644 --- a/packages/eslint-plugin/src/rules/unified-signatures.ts +++ b/packages/eslint-plugin/src/rules/unified-signatures.ts @@ -264,6 +264,14 @@ export default createRule({ types1: readonly TSESTree.Parameter[], types2: readonly TSESTree.Parameter[], ): Unify | undefined { + const firstParam1 = types1[0]; + const firstParam2 = types2[0]; + + // exempt signatures with `this: void` from the rule + if (isThisVoidParam(firstParam1) || isThisVoidParam(firstParam2)) { + return undefined; + } + const index = getIndexOfFirstDifference( types1, types2, @@ -294,6 +302,22 @@ export default createRule({ : undefined; } + function isThisParam(param: TSESTree.Parameter | undefined): boolean { + return ( + param != null && + param.type === AST_NODE_TYPES.Identifier && + param.name === 'this' + ); + } + + function isThisVoidParam(param: TSESTree.Parameter | undefined) { + return ( + isThisParam(param) && + (param as TSESTree.Identifier).typeAnnotation?.typeAnnotation.type === + AST_NODE_TYPES.TSVoidKeyword + ); + } + /** * Detect `a(): void` and `a(x: number): void`. * Returns the parameter declaration (`x: number` in this example) that should be optional/rest, and overload it's a part of. @@ -310,6 +334,19 @@ export default createRule({ const shorter = sig1.length < sig2.length ? sig1 : sig2; const shorterSig = sig1.length < sig2.length ? a : b; + const firstParam1 = sig1.at(0); + const firstParam2 = sig2.at(0); + // If one signature has explicit this type and another doesn't, they can't + // be unified. + if (isThisParam(firstParam1) !== isThisParam(firstParam2)) { + return undefined; + } + + // exempt signatures with `this: void` from the rule + if (isThisVoidParam(firstParam1) || isThisVoidParam(firstParam2)) { + return undefined; + } + // If one is has 2+ parameters more than the other, they must all be optional/rest. // Differ by optional parameters: f() and f(x), f() and f(x, ?y, ...z) // Not allowed: f() and f(x, y) diff --git a/packages/eslint-plugin/tests/rules/unified-signatures.test.ts b/packages/eslint-plugin/tests/rules/unified-signatures.test.ts index dd8412b53ef..30952ddebb0 100644 --- a/packages/eslint-plugin/tests/rules/unified-signatures.test.ts +++ b/packages/eslint-plugin/tests/rules/unified-signatures.test.ts @@ -380,6 +380,21 @@ declare function f(x: boolean): unknown; `, options: [{ ignoreOverloadsWithDifferentJSDoc: true }], }, + ` +function f(): void; +function f(this: {}): void; +function f(this: void | {}): void {} + `, + ` +function f(a: boolean): void; +function f(this: {}, a: boolean): void; +function f(this: void | {}, a: boolean): void {} + `, + ` +function f(this: void, a: boolean): void; +function f(this: {}, a: boolean): void; +function f(this: void | {}, a: boolean): void {} + `, ], invalid: [ { @@ -1136,5 +1151,73 @@ declare function f(x: boolean): unknown; ], options: [{ ignoreOverloadsWithDifferentJSDoc: true }], }, + { + code: ` +function f(this: {}, a: boolean): void; +function f(this: {}, a: string): void; +function f(this: {}, a: boolean | string): void {} + `, + errors: [ + { + column: 22, + line: 3, + messageId: 'singleParameterDifference', + }, + ], + }, + { + code: ` +function f(this: {}): void; +function f(this: {}, a: string): void; +function f(this: {}, a?: string): void {} + `, + errors: [ + { + column: 22, + line: 3, + messageId: 'omittingSingleParameter', + }, + ], + }, + { + code: ` +function f(this: string): void; +function f(this: number): void; +function f(this: string | number): void {} + `, + errors: [ + { + column: 12, + data: { + failureStringStart: + 'These overloads can be combined into one signature', + type1: 'string', + type2: 'number', + }, + line: 3, + messageId: 'singleParameterDifference', + }, + ], + }, + { + code: ` +function f(this: string, a: boolean): void; +function f(this: number, a: boolean): void; +function f(this: string | number, a: boolean): void {} + `, + errors: [ + { + column: 12, + data: { + failureStringStart: + 'These overloads can be combined into one signature', + type1: 'string', + type2: 'number', + }, + line: 3, + messageId: 'singleParameterDifference', + }, + ], + }, ], });