'"one" | "two" | string' autocomplete TypeScript trick
Have you ever wanted to suggest some strings but still allow any string? Like "GET" | "POST" | string? But then it just doesn't work like you want it to?
// Note how '"GET" | "POST" | string' gets reduced to just "string" since it's the
// broadest type of the intersection? That's expected behaviour.
type HTTPMethod = "GET" | "POST" | string
// ^?
// ^ This should be "string"
function doHTTP(method: HTTPMethod): void {
console.log(method)
// ^?
// ^ This should be "(parameter) method: string".
}
doHTTP("")
// ^ Press CTRL+SPACE here to try and get autocomplete. You should get nothing.
From the compiler's point of view, this is just a very fancy way of writing
string. By the time we're looking for suggestions at [HTTPMethod], all the literals are lost.
The trick is to fool the type checker into thinking that the | string part of the union type is magically (somehow?) different than the type of the literals -- that way it doesn't get reduced to just type T = string. The magic is string & {}.
// It works!
type HTTPMethod = "GET" | "POST" | (string & {})
// ^?
// ^ This should be '(string & {}) | "GET" | "POST"'
function doHTTP(method: HTTPMethod): void {
console.log(method)
// ^?
// ^ This should be "(parameter) method: HTTPMethod".
}
doHTTP("")
// ^ Press CTRL+SPACE here to try and get autocomplete. You should get string literals!
// ...but you can also just type any string you want. This is valid.
doHTTP("not-an-http-method")
The type-fest npm package has a type for this exact use-case: LiteralUnion.
import type {LiteralUnion} from 'type-fest';
// Before
type Pet = 'dog' | 'cat' | string;
const petWithoutAutocomplete: Pet = '';
// Start typing in your TypeScript-enabled IDE.
// You **will not** get auto-completion for `dog` and `cat` literals.
// After
type Pet2 = LiteralUnion<'dog' | 'cat', string>;
const petWithAutoComplete: Pet2 = '';
// You **will** get auto-completion for `dog` and `cat` literals.
There was a TypeScript GitHub issue opened about having this "one" | "two" | string literal stuff work out of the box, but it didn't go anywhere. Microsoft/TypeScript#29729