Description
Authors often want to specify that a parameter may be passed any string, but has a set of preferred values. Right now, Typescript has no concept of 'preferred type', even though people try to indicate by unioning it with the declared type: string | 'a' | 'b' | 'c'
. However, the compiler reduces this to string
early on. People have used workarounds to fool the subtype reduction machinery, like string & {} | 'a'
, but PRs like #49119 improve reduction and unintentionally break these workarounds from time to time.
Based on discussion from the May 20 2022 Design Meeting and suggestions from Discord [1] I propose an explicit jsdoc tag to specify the preferred type:
/**
* @suggest {'foo' | 'bar'} e
*/
function f(e: string) {
}
This will instruct the language service to offer completions for e
that wouldn't be possible given just its declared type. Documentation generators can also display the preferred type along with the declared type.
The syntax is similar to @param
:
@suggest
{
type }
identifier
But it can be used with any declaration:
/**
* @suggest {'foo' | 'bar'} T
*/
function f<T>(p: T): T {
}
/** @suggest {'a' | 'b'} x */
declare var x: string
interface I {
/** @suggest {'a' | 'b'} p */
p: string
}
const o: I = {
/**
* @suggest {'a' | 'b' | 'c'} p Suggestions can conflict
*/
p: 'a'
}
declare class C {
/**
* @suggest {HTMLAnchorElement | HTMLDivElement} p
*/
p: HTMLElement
}
In Javascript, usage is more redundant:
/**
* @param {string} p
* @suggest {'a' | 'b'} p
*/
function f(p) {
}
In Typescript and checkJs files, the compiler should issue an error if the suggested type is not a subtype of the declared type:
/**
* @suggest {'a' | 'b' | 0} p
// ^^^^^^^^^^^^^ Suggested type must be a subtype of the declared type
*/
function f(p: string) {
}
And if the suggested type is never
, then nothing should be added to the suggestion list. Maybe documentation generators should treat this as a signal that the property is deprecated (although -- why not use @deprecated
in that case?).
Other options:
- Do nothing. Make sure at least one workaround remains to make the compiler leave reducible unions unreduced.
- Directly support
string | 'a' | 'b' | 'c'
: have the compiler create a new string type for every string used in a literal union. Proposed in Support Intellisense for string/number literals in a widened union #33471.
Other design questions
- Should the tag specify values instead of types?
This seems clunky for the common case, a list of values:
/**
* @suggest {['a', 'b', 'c']} p
*/
- Could the tag be simplified by allowing it only after another tag for a declaration?
The syntax could be simplified to @suggest
{
type }
in that case. That is,
/**
* @param p @suggest {'a' | 'b'}
*/
But this is awkward in Typescript where people aren't used to writing @template
for type parameters, for example. And few other JSDoc tags are context-sensitive like this -- @typedef
/@property
is the main one, and it's notoriously hard to use.
- Should the suggestion be integrated on the end of existing tags?
That is,
/**
* @param {string} p {'a' | 'b'}
*/
I can't think of a good syntax for this.
- This is the first tag that TS users would be using to specify types in JSDoc. Is this OK?
Tag name
The tag is intended to specify a subtype of the declared type. Tools besides the compiler will use the type as an auxiliary to the declared type. Here are a few ideas for names:
@prefer
@suggest
@suggest-type
@suggesttype
@suggestType
@intended
@expected
[1] Thanks to cspotcode, @Gerrit0, Andrew Kay, d-fischer and Elijah from #language-design.