Writing more descriptive intellisense docs for Typescript Union Types

1.2k views Asked by At

Given the following code, when we call the baz function, the typeahead will show 'a' and 'b' as possible values.

enter image description here

However, if I want to provide additional documentation for each of those values, how would I do it? For example, if something like this is the desired behavior:

enter image description here

I thought I should give more context about what I'm trying to do and why:

Consider the following example:

const paintColor = {
  /** This color is good fo X */
  Waltz: "#d9e5d7",
  /** This color is good fo Y */
  "Indiana Clay": "#ed7c4b",
  /** This color is good fo Z */
  Odyssey: "#575b6a"
};

const locations = {
  /** There are no good school at this location*/
  City: "123 city center road",
  /** There are lots of parking at this location but it is very far*/
  West: "245 some other road"
};

interface HouseProp {
  color: keyof typeof paintColor; //"Waltz" | "Indiana Clay" | "Odyssey"
  location: keyof typeof locations; //"City" | "West"
}

const House = ({ color, location }: HouseProp) => {
  ...
};

Where House is a react component that renders a house based on the color and location props.

And this House component is used everywhere throughout the project.

With the current setup, I could use House like this:

<House color="Indiana Clay" location="City" />

The problem is, the IntelliSense can't pick up on the docs I've written as part of the code:

enter image description here

What I would like is this: enter image description here

P.S. I know that I could turn paintColor and locations into enums, and use things like this:

import House, {HouseColor, HouseLocation} from './House';
<House color={HouseColor["Indiana Clay"]} location={HouseLocation.City} />

But that component interface just isn't as nice as my original proposal.

2

There are 2 answers

1
Karol Majewski On BEST ANSWER

You can't really annotate union members. You can, however, express your union differently — by using overloads or by choosing to use an enum instead.

Solution #1: Function overloads

/**
 * a is good fo X
 */
function baz(param: 'a'): number;
/**
 * b is an excellent choice for Y
 */
function baz(param: 'b'): number;
function baz(param: 'a' | 'b'): number {
  return 1;
}

Screenshot

Solution #2: Overload as an interface

interface Baz {
  /**
   * a is good fo X
   */
  (param: 'a'): number;
  /**
   * b is an excellent choice for Y
   */
  (param: 'b'): number;
}

const baz: Baz = (param: 'a' | 'b') => {
  return 1;
}

Screenshot

Solution #3: Using an enum instead

enum Foo {
  /**
   * a is good fo X
   */
  A = 'a',
  /**
   * b is an excellent choice for Y
   */
  B = 'b',
}

function baz(param: Foo) {
  return 1;
}

Screenshot

I know that's not exactly what you'd like, but that's your second best option.

0
starball On

This is a current behavioural limitation of TypeScript (not sure if it is in the backlog). For example, see this issue ticket in the TypeScript repo, which is about string literals in union types: Support parsing TSDoc string comments #38106. Give that issue ticket a thumbs up and subscribe to it to get notifications about discussion and progress (but please avoid noisy comments like "+1" / "bump" / "me too").

See also this related issue ticket in the TSDoc repo: RFC: Specify that doc comments are allowed on simple union types #164, and this issue ticket in the typedoc repo: Support for enum-like use of string literal types #1710.

If you want hover info for the enumeration values, then you could workaround it by switching to using TypeScript's enum feature (which the asker of this question has specified that they don't want to do, but I find still worth mentioning). Ex.

/** FOO */
enum Foo {
   /** AAA */
   a = "a",
   /** BBB */
   b = "b",
   /** CCC */
   c = "c",
};
type FooArray = Foo[];

const foo = Foo.a;
const fooArray: FooArray = [Foo.a, Foo.b, Foo.c];

Then when you hover your mouse over the "a" in "Foo.a", you'll get a hover widget appear with "AAA" in it.