Inline a type definition in TypeScript
Asked Answered
S

1

7

I'm writing a type definition file (index.d.ts) for a jQuery library that doesn't come with a type definition.
The methods of that library repeatedly accept parameters of the same multi-type (string | number | []) so I defined it as a CustomType:

export type CustomType = string | number | [];

declare global {
    interface JQuery<TElement = HTMLElement> {
        setFoo(foo: CustomType): this;
        setBar(bar: CustomType): this;
    }
}

When I now want to call setFoo() on a jQuery object, the type hinting (of IntelliJ) shows that a parameter foo: CustomType is expected which doesn't help fellow devs without looking up what that type resembles.
Instead I'd love to see the type hinting to show foo: string | number | [].

For example, in C++ there is this concept of an inline function which basically tells the compiler to put the code of the inlined functions body right into the block where it's called rather than to call / jump to the function. Is there something similar in TypeScript?

How can I force TypeScript to inline this CustomType and make it show as foo: string | number | [] instead of foo: CustomType?

Ugly Solution

declare global {
    interface JQuery<TElement = HTMLElement> {
        setFoo(foo: string | number | []): this;
        setBar(bar: string | number | []): this;
    }
}

One solution would be to eliminate the CustomType and explicitly type parameters with their multi-types, but this becomes rather inconvenient with an increasing number of methods that use the same type as it doesn't benefit from reusability plus it looks ugly to me.

Imaginary Solution

export type CustomType = string | number | [];

declare global {
    interface JQuery<TElement = HTMLElement> {
        setFoo(foo: inline CustomType): this; // <-- note the 'inline' here
        setBar(bar: inline CustomType): this;
    }
}

This would be ideal and in my imagination behave like the 'Ugly Solution', but unfortunately isn't supported. So what would be the proper way to achieve this?

Sherborn answered 17/10, 2020 at 13:57 Comment(5)
Note that the type [] is specifically a zero-length tuple, and not an arbitrary array like any[] or Array<any>. Is that the type you really want?Shortterm
string | number | [] is simple enough that anything I can come up is, in my opinion, uglier. Like, would this be something you'd want to use?Shortterm
Since I can't test it right now, out of curiosity, what happens if CustomType is not exported? Does that change anything?Viscounty
@Shortterm Thanks for the hint regarding []. I meant to use any[]. I'd consider your approach of declaring a const and then using typeof to get the type a little hacky, but given that this CustomType is used in quite a few places in that lib, I'm still willing to accept your code snippet as a valid answer for the sake of reusability and maintenance.Sherborn
@IngoBürk Not exporting CustomType does not change anything in this regard. From my understanding, export just allows import to be used. And it seems to be good practice to export all your types and interfaces.Sherborn
S
5

I think this is not currently possible.

There is an open GitHub issue, microsoft/TypeScript#25784, asking for the ability to "drill down" into the IntelliSense quick info, which, if implemented, may or may not expand unions into their constituents.

There's also microsoft/TypeScript#40780 which asks for an "alias" keyword that works similarly to what you're suggesting: basically a type macro that is eliminated by the time anyone consuming the code looks at it. This issue was closed as a duplicate of a draft pull request for what looks like a slightly different feature. So this line of research seemed to peter out fairly quickly.


So, workaround: create/declare a variable x of the type you'd like to inline away, and refer to this type as typeof x. At the call sites, IntelliSense should, I believe, resolve typeof x to the expanded type. I can't guarantee that this will always happen (the details of how the compiler decides to render type information are a bit murky to me) but it seems to do this in my testing. For example:

const __Custom: string | number | any[];

interface JQuery<TElement = HTMLElement> {
  setFoo(foo: typeof __Custom): this;
  setBar(bar: typeof __Custom): this;
}

And then later:

declare const $: JQuery;
$.setBar(""); // IntelliSense says 
// setBar(bar: string | number | any[]): JQuery<HTMLElement>

This may or may not work for you.

Playground link to code

Shortterm answered 17/10, 2020 at 18:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.