typescriptvisual-studio-code

VSCode Intellisense Unwrap TypeScript Types


Say I have:

type X = Blob | File;

function printX(x: X) {
    console.log(x);
}

with VSCode intellisense, I get the following prompt when I start calling the function:

printX(x: X): void

The problem is that I don't necessarily know what X is, so I have to jump to definition. What I want is to signal to VSCode that I want the type "unwrapped" so that it displays Blob | File for the x parameter when I'm filling it in.

Is this even possible?


Solution

  • TypeScript doesn't give any explicit controls over how types are displayed, and whether they are represented as written (like X) or expanded to their definitions (like Blob | File) or how far to expand them (imagine you had type Y = {x: X}, should it display as Y, or as {x: X}, or as {x: Blob | File}?). There is an open feature request at microsoft/TypeScript#45954 asking for something better, but for now it's not part of the language.

    That means the best you can do is try to take advantage of the heuristics TypeScript uses for displaying types. The most straightforward approach is just to write the desired type yourself:

    function printX(x: Blob | File) {
      console.log(x);
    }
    

    Of course then it could diverge from X, but you could write some code that would produce an error if that happens:

    declare var __xTest: X;
    declare var __xTest: Blob | File;
    //          ^^^^^^^
    // if there is an error here, you need to fix printX
    

    Or, you could try to use generics to prompt TypeScript to evaluate the type more fully when you call printX():

    function printX<Y extends X & {}>(x: Y) {
      console.log(x);
    }
    
    printX(
    //     ^ printX(x: Blob | File): void
    

    Note that I needed to intersect X with the empty object type {} in order for this not to produce just X again. Since neither Blob nor File accept undefined or null, then X & {} is equivalent to X (it's the same as NonNullable<X>, but if you use that utility type then you'll see that, and you don't want it.)

    But largely you need to just make do with what TypeScript gives you. If you've created a type alias because it's useful in your code, then ideally you'd give it a useful name so that no matter how it's displayed, there's some hope it will be something meaningful:

    type BlobOrFile = Blob | File;
    function printX(x: BlobOrFile) {
      console.log(x);
    }
    
    printX(
    //     ^ printX(x: BlobOrFile): void
    

    Playground link to code