Skip to content

fix(eslint-plugin): [unified-signatures] exempt this from optional parameter overload check #11005

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
37 changes: 37 additions & 0 deletions packages/eslint-plugin/src/rules/unified-signatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,14 @@
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,
Expand Down Expand Up @@ -294,6 +302,22 @@
: undefined;
}

function isThisParam(param: TSESTree.Parameter | undefined): boolean {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI/PSA - I changed this not to be a type predicate since it's only valid in the true branch, not the false branch (the function returning false does not imply that param is not a TSESTree.Identifier).

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.
Expand All @@ -310,6 +334,19 @@
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;

Check warning on line 347 in packages/eslint-plugin/src/rules/unified-signatures.ts

View check run for this annotation

Codecov / codecov/patch

packages/eslint-plugin/src/rules/unified-signatures.ts#L347

Added line #L347 was not covered by tests
}

// 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)
Expand Down
83 changes: 83 additions & 0 deletions packages/eslint-plugin/tests/rules/unified-signatures.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
{
Expand Down Expand Up @@ -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',
},
],
},
],
});