Skip to content

Proposal: JSDoc tag to specify preferred type #49220

Open
@sandersn

Description

@sandersn

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:

  1. Do nothing. Make sure at least one workaround remains to make the compiler leave reducible unions unreduced.
  2. 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

  1. Should the tag specify values instead of types?
    This seems clunky for the common case, a list of values:
/**
 * @suggest {['a', 'b', 'c']} p
 */
  1. 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.

  1. 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.

  1. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions