-3

I'm using fetch-mock library to build my own utils.

When defining a new function and passing args to fetchMock.mock function, I get this error:

A spread argument must either have a tuple type or be passed to a rest parameter

export function mockRequest(...args: any) {
  return fetchMock.mock(...args);
}

I could strictly define the amount of arguments, but would want to avoid it. Any suggestions how to keep ...args and make TypeScript happy?

I've tried various solutions such as using .apply and doing some casts as well but to no avail.

EugenSunic
  • 13,162
  • 13
  • 64
  • 86
  • 3
    What's even the point of using Typescript if everything is any? – jperl Aug 13 '22 at 23:16
  • @jperl how do you mean everything is any? I've just put any here since it covers everything but still won't work when passing the args to the fetchMock function. You're implying that my entire project uses any whereas only a pecentage of the code tries to avoid TS – EugenSunic Aug 13 '22 at 23:18
  • https://www.typescriptlang.org/play?#code/KYDwDg9gTgLgBAMwK4DsDGMCWEVwLYRoDWASsAI5LADOMAFAHRMCGUA5tQFxzMoCeAbQC6ASjgBvAFBw4UYDCRRcePgDFUGbCkYt21EZIC+kyWhy18ajVhxwAvHB0NWHbr0Gj7APgmz5i3BgoKjhDIA – jperl Aug 13 '22 at 23:31
  • 1
    Is fetchMock.mock typed at all? – jperl Aug 13 '22 at 23:38
  • 1
    Say the function is accepting exactly two parameters and they are not optional, then Typescript won't let you do it and rightfully so. It doesn't make sense to use spread arguments if your other function expects two arguments because with spread arguments, you can pass 0 or more. – jperl Aug 13 '22 at 23:43
  • 1
    It makes sense to use spread arguments only if the other function is also accepting rest arguments. – jperl Aug 13 '22 at 23:44
  • Of course, you can always tell TS off by using `// @ts-ignore` where it gets in your way, if you are sure you want to be unsafe. – Amadan Aug 13 '22 at 23:48
  • 1
    Yes or by not using typescript at all – jperl Aug 13 '22 at 23:49
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax – jperl Aug 13 '22 at 23:49
  • without typescript the fetchMock accepts rest, I think I ll have to use ignore. tnx – EugenSunic Aug 14 '22 at 00:02
  • @MatinKajabadi article doesn't help, I said I checked everything on the .net already. – EugenSunic Aug 14 '22 at 11:02

2 Answers2

1

Problem

fetchMock.mock is typed to accept 0-3 arguments:

mock(matcher: MockMatcher | MockOptions, response: MockResponse | MockResponseFunction, options?: MockOptions): this;
mock(options: MockOptions): this;
mock(): this;

wheresrhys/fetch-mock/types/index.d.ts:L270-L294

An attempt is made to spread an array of any into it. any will not stop TypeScript from checking the potential length of the spread – it will only not check the type of the items in the tuple are acceptable for the function parameters. An example:

function fn(a: number, b: number) {}

const foo: any[] = [];
const bar: [any, any] = ["one", "two"];

fn(...foo); // Not OK ❌
// ~~~~~~
// A spread argument must either have a tuple type or be passed to a rest parameter.

fn(...bar); // OK, even though we're passing in strings ✅

TypeScript Playground

Solution

The error message tells you the solution (emphasis mine):

A spread argument must either have a tuple type or be passed to a rest parameter.

Let's explore both of them.

Passing to a rest parameter

If the function is typed to accept a rest parameter it works because rest parameters are essentially an infinite list of optional parameters. However, in your case, you don't have control of the interface of the library. Consequently, you'll have to use the other option.

Type the argument as a tuple

Tuples are a list that have two notable properties. They are:

  • Finite. This guarantees the length will match the length of the arguments to the function you're attempting to use.
  • Ordered. This guarantees the type of a value at each index will match with the function's argument at the same index.

Using Parameters?

tl;dr this doesn't work, see the linked GitHub issues

TypeScript provides a utility function named Parameters which constructs a tuple type from the types used in the parameters of the function passed to it, so in theory you could do this:

function mockRequest(...args: Parameters<typeof fetchMock.mock>) {
  return fetchMock.mock(...args);
}

TypeScript Playground

However at the time of writing, Parameters only returns the parameters from the last overload (see TypeScript issue 14107 and TypeScript issue 32164). In the above example, mockRequest would be typed to accept no parameters because the last overload of fetchMock.mock has no parameters.

Manual types

As lovely as it would've been to use Parameters. It looks like we'll have to manually add types. Here's a working example:

type Overload1 = [matcherOrOptions:  MockMatcher | MockOptions, response: MockResponse | MockResponseFunction, options?: MockOptions];
type Overload2 = [matcherOrOptions: MockOptions];
type Overload3 = [];

type Args = Overload1 | Overload2 | Overload3;

function mockRequest(...args: Args) {
  return args.length === 1 ?
    fetchMock.mock(...args) :
    args.length >= 2 ?
      fetchMock.mock(...args as Overload1) :
      fetchMock.mock();
}

TypeScript Playground

For an underdetermined reason, TypeScript isn't able to narrow the type of args to Overload1 with the condition args.length >= 2 so a cast is necessary.

Wing
  • 8,438
  • 4
  • 37
  • 46
  • finally a decent answer with decent understanding of what I want. – EugenSunic Aug 14 '22 at 15:45
  • @EugenSunic: I've just noticed a problem that actually means the proposed solution doesn't work. `fetchMock.mock`'s overloads means `Parameters` actually returns `[]`. This means you can't pass anything to `mockRequest`. I'm working on a resolution. The theory in the answer still stands though. – Wing Aug 14 '22 at 15:52
  • yea noticed as well, but I appreciate the effort you put into. I believe I can workout from your answer – EugenSunic Aug 14 '22 at 15:53
  • 1
    @EugenSunic: I've proposed a workaround. It's not as pretty but it works ‍♀️ – Wing Aug 14 '22 at 16:44
-1

Try making args an array type.

export function mockRequest(...args: any[]) {
    return fetchMock.mock(...args);
}
noodlejs
  • 301
  • 2
  • 8