microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.12k stars 12.5k forks source link

Declaration merging in plain JS with JSDoc #57559

Open trusktr opened 8 months ago

trusktr commented 8 months ago

🔍 Search Terms

jsdoc declaration merging

✅ Viability Checklist

⭐ Suggestion

Ability to merge declarations.

📃 Motivating Example

For example, this is a declaration merge in TS:

export const Foo = makeFooClass()
export type Foo = InstanceType<typeof Foo>

In JS we can't do this. I was hoping for something similar to this to achieve the same:

export const Foo = makeFooClass() // returns a class
/** @export {InstanceType<typeof Foo>} Foo */

💻 Use Cases

  1. What do you want to use this for? To merge declarations in certain cases.
  2. What shortcomings exist with current approaches? There's no way to do it AFAICT
  3. What workarounds are you using in the meantime? None
fatcerberus commented 8 months ago

I don’t think that counts as a declaration merge, it’s just “declaring a type and a value with the same name”. There’s no requirement that the two Foos be related as they exist in separate namespaces within the compiler. Declaration merge is when e.g. you declare an interface more than once and the second one augments the first.

trusktr commented 8 months ago

Maybe its named something else. If so, what is this pattern named where a single identifier carries both a type and a value? I could not find anything about it in the TS docs (maybe I don't know what to search for).

From my perspective, I thought it was one (of multiple) forms of "declaration merging" because two things are being merged: a variable declaration, and a type declaration. I however did not see this on the Declaration Merging page in the docs.

trusktr commented 8 months ago

Maybe the syntax needs to be

/**
 * @export
 * @typedef {InstanceType<typeof Foo>} Foo
 */
trusktr commented 8 months ago

Ah, @Gerrit0 pointed out that the following works because typedef is automatically exported:

export const Foo = makeFooClass() // returns a class
/** @typedef {InstanceType<typeof Foo>} Foo */
trusktr commented 8 months ago

I do not see that this type exports feature is mentioned here:

https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html

Perhaps we need to add more info to that page.

fatcerberus commented 8 months ago

If so, what is this pattern named where a single identifier carries both a type and a value? I could not find anything about it in the TS docs

I don't think there's a name for it because it's just declaring two different things that happen to have the same name, but the compiler considers them separate entities as one exists in type-space and the other in value-space[^1]. Nothing is really being merged.

[^1]: This split between type-space and value-space is why the typeof operator exists. There's no requirement that Foo and typeof Foo be related, it's just in practice they usually are.

trusktr commented 2 months ago

one exists in type-space and the other in value-space1. Nothing is really being merged.

They're being "merged" in the sense that the end user who will import the type can reference it with a single identifier, and to them it is the "one and same thing".

import {Foo} from './Foo.js'

const f = new Foo() // uses *both* Foos, which to the user is a single thing.

Maybe it can be called type value merging or something, but it definitely is some type of a merge (yeah, not merging of two interfaces)