Proposal: JSDoc tag to specify preferred type
See original GitHub issueAuthors 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 https://github.com/microsoft/TypeScript/pull/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 #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.
Issue Analytics
- State:
- Created a year ago
- Reactions:31
- Comments:16 (13 by maintainers)
Top GitHub Comments
@orta ah, that’s the other option. I linked to it in the proposal.
Also, you can see @fatcerberus suggesting a JSDoc solution 3 years ago. Guess we’re just slow on the uptake.
I love this feature idea, since today we end up having to export enums (of one kind or another) of ‘known’ values in the Azure SDKs when we have what is referred to internally as an “extensible enum” (the service may later add values) – this seems much cleaner and is still something we can auto-generate