Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to explicitly pass type parameters via JSDoc #27387

Open
4 tasks done
ifeltsweet opened this issue Sep 27, 2018 · 34 comments · May be fixed by #59666
Open
4 tasks done

Allow to explicitly pass type parameters via JSDoc #27387

ifeltsweet opened this issue Sep 27, 2018 · 34 comments · May be fixed by #59666
Labels
checkJs Relates to checking JavaScript using TypeScript Domain: JavaScript The issue relates to JavaScript specifically Domain: JSDoc Relates to JSDoc parsing and type generation In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@ifeltsweet
Copy link

ifeltsweet commented Sep 27, 2018

Search Terms

jsdoc generics type parameters constraints

Suggestion

It seems like it is not possible via JSDoc to explicitly tell compiler which type parameters to pass to a generic function.

Use Cases

In TypeScript it is possible to explicitly pass type parameters such as mongoose.model<Model, Schema>('modelName', Schema) while I could not find a way to do same with JSDoc.

Examples

mongoose.model has two signatures, first one with one type parameter and the other with two. To make use of the second signature we must pass types explicitly.

export function model<T extends Document>(
  name: string,
  schema?: Schema,
  collection?: string,
  skipInit?: boolean
): Model<T>;

export function model<T extends Document, U extends Model<T>>(
  name: string,
  schema?: Schema,
  collection?: string,
  skipInit?: boolean
): U;
//  Choose second signature in typescript.
const model = mongoose.model<Model, Schema>('modelName', Schema);

// With JSDoc, compiler always chooses first signature and we receive a type mismatch error.
/** @type {Schema & mongoose.Model<Model>} */
const model = mongoose.model('modelName', Schema);

// But something like this would be great.
const model = mongoose.model/**<Model, Schema>*/('modelName', Schema);

My apologies if this is already possible, but I've spend almost a week battling this.

Related: microsoft/TypeScript-Node-Starter#101

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript / JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. new expression-level syntax)
@ghost ghost added Suggestion An idea for TypeScript Domain: JavaScript The issue relates to JavaScript specifically Salsa Domain: JSDoc Relates to JSDoc parsing and type generation checkJs Relates to checking JavaScript using TypeScript labels Sep 27, 2018
@weswigham weswigham added the In Discussion Not yet reached consensus label Nov 6, 2018
@weswigham weswigham added Domain: JavaScript The issue relates to JavaScript specifically and removed Domain: JavaScript The issue relates to JavaScript specifically Salsa labels Nov 29, 2018
@Stuk
Copy link

Stuk commented Mar 12, 2019

I would also like to see something like this. Currently it's really hard to use functions with generic types from JS.

@ekulabuhov
Copy link

Another use case with React Hooks:

// useState<string> is derived correctly
const [aString, setAString] = useState("default value");

Removing default value there is no way to pass type info:

// useState<any>
const [aString, setAString] = useState();

@pm0u
Copy link

pm0u commented May 1, 2020

Anything on this? Also stuck on the useState example as shared above:

// Doesn't set type properly
const [error, setError] = /** @type {React.useState<?string>} */ useState(null);
// Nor does this
/** @type {[?string, React.Dispatch<React.SetStateAction<?string>>]} */
const [error, setError] = /** @type {React.useState<?string>} */ useState(null);


// we have an error we want to set now.
setError('Error!') // Shows a type error

Edit: Ok this might be a hack, but in the case of the useState example it works:

const [error, setError] = useState(/** @type {?string} */ (null));

implied types for error are string | null and setError is React.Dispatch<React.SetStateAction<?string>>

i think what's happening "under the hood" is we are forcing the "type" that useState is being passed.

@robertknight
Copy link

For functions that take an argument of the generic type, casting the argument itself is reasonably practical. We've settled on these patterns with useState and useRef in React for example:

const [thing, setThing] = useState(/** @type {SomeType|null} */ (null));
const widgetRef = useRef(/** @type {HTMLElement|null} */(null));

In cases where there is no argument for everything else to be inferred from, it gets much more verbose unfortunately. What would be helpful is being able to do something like:

const aSet = /** @type {?<string>} */(new Set());

Where the ? is inferred as Set. In this example it only saves a few characters, but it could be a lot more if the generic type name is lengthy and/or has to be imported from somewhere.

@k-yle
Copy link
Contributor

k-yle commented Jul 16, 2020

Where the ? is inferred as Set. In this example it only saves a few characters, but it could be a lot more if the generic type name is lengthy and/or has to be imported from somewhere.

Something like that would be awesome, it would majorly simplify the code required for React.forwardRef and React.memo:

- const Component = /** @type {React.ForwardRefExoticComponent<React.RefAttributes<Props>>} */ (React.forwardRef(() => { ... }))
+ const Component = /** @type {?<Props>} */ (React.forwardRef(() => { ... }))

@sebinsua
Copy link

sebinsua commented Dec 11, 2020

We have the same issue with Recoil.

import {
  atom,
  selector,
  useRecoilState,
  useRecoilValue,
  DefaultValue,
} from "recoil";

/**
 * @typedef {{
 *   a?: string,
 *   b?: string,
 * }} BasicState
 */

// Horrific!
const basicAtom = atom(
  /** @type {import("recoil").AtomOptions<BasicState>} */
  ({
    key: "basicAtom",
    default: {
      a: undefined,
      b: undefined,
    },
  })
);

// Better.
const basicAtom2 = atom({
  key: "basicAtom2",
  /** @type {BasicState} */
  default: {
    a: undefined,
    b: undefined,
  },
});

// Easier to read but perhaps more confusing, because it reveals
// how the defaults properties are seen as mandatory otherwise...
/** @type {import("recoil").RecoilState<BasicState>} */
const basicAtom3 = atom({
  key: "basicAtom3",
  default: {},
});

// This works immediately.
const aSelector = selector({
  key: "aSelector",
  get({ get }) {
    const basicState = get(basicAtom2);

    return basicState.a;
  },
});

// But selectors are horrible!
const bSelector = selector(
  /** @type {import("recoil").ReadWriteSelectorOptions<string>} */
  ({
    key: "bSelector",
    get({ get }) {
      const basicState = get(basicAtom2);

      return basicState.b;
    },
    set({ get, set }, newValue) {
      if (newValue instanceof DefaultValue) {
        return;
      }

      set(basicAtom2, { ...get(basicAtom2), b: newValue });
    },
  })
);

// Is this way of doing selectors preferable? The <any> confuses me...
const bSelector2 = selector({
  key: "bSelector2",
  /**
   * @type {import("recoil").ReadWriteSelectorOptions<any>['get']}
   */
  get({ get }) {
    const basicValue = get(basicAtom2);

    return basicValue.b;
  },
  /**
   * @type {import("recoil").ReadWriteSelectorOptions<string>['set']}
   */
  set({ get, set }, newValue) {
    if (newValue instanceof DefaultValue) {
      return;
    }

    const basicValue = get(basicAtom2);

    set(basicAtom2, {
      ...basicValue,
      b: newValue,
    });
  },
});

// I think this is the best way...
/** @type {import("recoil").RecoilState<string | undefined>} */
const bSelector3 = selector({
  key: "bSelector3",
  get({ get }) {
    const basicValue = get(basicAtom2);

    return basicValue.b;
  },
  set({ get, set }, newValue) {
    if (newValue instanceof DefaultValue) {
      return;
    }

    const basicValue = get(basicAtom2);

    set(basicAtom2, {
      ...basicValue,
      b: newValue,
    });
  },
});

const a = useRecoilValue(aSelector);
const [b, setB] = useRecoilState(bSelector);
const [b2, setB2] = useRecoilState(bSelector2);
const [b3, setB3] = useRecoilState(bSelector3);

if (
  typeof a === "string" &&
  typeof b === "string" &&
  typeof b2 === "string" &&
  typeof b3 === "string"
) {
  setB("str");
  setB((str) => str + "str");

  setB2("str");
  setB2((str) => str + "str");

  setB3("str");
  setB3((str) => str + "str");
}

I don't like having to wrap an argument with brackets in order to be able to apply a comment to it - that seems like I'm needing to alter the implementation, which is something I'd like to avoid.

But it's difficult right now since you have to kind of guess at what to set, in order to keep things simple.

For now, I'll probably do this...

import {
  atom,
  selector,
  DefaultValue
} from "recoil";

/**
 * @typedef {{
 *   a?: string,
 *   b?: string,
 * }} BasicState
 */

const basicStateAtom = atom({
  key: "basicStateAtom",
  /** @type {BasicState} */
  default: {
    a: undefined,
    b: undefined,
  },
});

/** @type {import("recoil").RecoilValueReadOnly<string | undefined>} */
const aSelector = selector({
  key: "aSelector",
  get({ get }) {
    const basicState = get(basicAtom2);

    return basicState.a;
  },
});

/** @type {import("recoil").RecoilState<string | undefined>} */
const bSelector = selector({
  key: "bSelector",
  get({ get }) {
    const basicValue = get(basicStateAtom);

    return basicValue.b;
  },
  set({ get, set }, newValue) {
    if (newValue instanceof DefaultValue) {
      return;
    }

    const basicValue = get(basicStateAtom);

    set(basicStateAtom, {
      ...basicValue,
      b: newValue,
    });
  },
});

For React, it doesn't seem so bad, particularly if you don't mind defining your own UseStateTuple helper type...

import { useState, useRef } from "react";

/**
 * @template S
 * @typedef {[S, import("react").Dispatch<import("react").SetStateAction<S>>]} UseStateTuple
 */

// This is not very nice...
const [aString, setAString] = useState(
  /** @type {string | undefined} */ (undefined)
);

// This is a little long-winded to say the least...
/** @type {[string | undefined, import("react").Dispatch<import("react").SetStateAction<string | undefined>> ]} */
const [aString2, setAString2] = useState();

// This is much, much better...
/** @type {UseStateTuple<string | undefined>} */
const [aString3, setAString3] = useState();

// This is OK...
/** @type {import("react").MutableRefObject<HTMLElement | undefined>} */
const htmlElement = useRef();

@trusktr
Copy link
Contributor

trusktr commented Feb 28, 2021

Calling functions with generic types is important in AssemblyScript (TypeScript that compiles to WebAssembly). This would help to possibly get us to a point where we can write working plain JS, but also compile the same code to WebAssembly.

@thesoftwarephilosopher
Copy link

Casting the argument sometimes is too verbose. For example, when you pass a map of strings to T, now you have to cast the whole map to /** @type {{ [key: string]: MyInterface }} */. When you want the map values to be () => T or even more complex, the cast gets uglier.

I'd suggest changing the parser to look for a /** @typeparam {MyInterface} */ in between the function expression and the function argument-set parentheses.

@petrkrejcik
Copy link

@weswigham Hi, I would like to kindly as if the discussion has evolved? (you've added a tag "In Discussion")
Could we hope for this feature?

@andrewbranch andrewbranch changed the title Allow to explicitly pass parameter types via JSDoc Allow to explicitly pass type parameters via JSDoc Sep 17, 2021
@trusktr
Copy link
Contributor

trusktr commented Oct 28, 2021

I think we should take something into consideration: JSDoc comments are for documentation. We should avoid repurposing JSDoc comments for features that aren't documentation, but call sites for type functions. Otherwise we might make things more difficult for documentation generators that piggy back on JSDoc syntax.

What would the syntax be though? Here's an idea:

// @ts-typeparam T Dog
doSomething(dog)

where the function itself may be documented as:

/**
@template {!Animal} T
@param {T} animal
*/
function doSomething(animal) {...}

This takes advantage of TypeScript's special comment syntax (f.e. // @ts-whatever). Hey look, GitHub syntax highlight even colors the @ts already.

In that example, there is a clear distinction between documentation and usage.

Any other syntax ideas?

@phil294
Copy link

phil294 commented Jan 28, 2022

JSDoc comments are for documentation. We should avoid repurposing JSDoc comments for features that aren't documentation

@trusktr JSDoc is has not been only for documentation for a long time now. It supports almost anything TS does, with very few exceptions. As for syntax, the /** */ is well-established and parsed by the compiler if you're programming in JS.


// But something like this would be great.
const model = mongoose.model/**<Model, Schema>*/('modelName', Schema);

There is already existing JSDoc syntax for declaring generic types: @template. So surely this tag should be reused (?)

const model = mongoose.model/** @template Model, Schema */('modelName', Schema);

@skapxd
Copy link

skapxd commented Mar 5, 2022

I use this way to send types as parameters, but it's a bit dirty

/**
 * @template T
 * @param {Object} props
 * @param {string} props.key
 * @param {T} [props.templateType]
 * @returns {T}
 */
const getLocalStorage = ({ key = "" }) => {
  const data = localStorage.getItem(key);
  return JSON.parse(data);
};

/**
 * @type {{
 * id: number,
 * name: string,
 * phoneNumber: number
 * }}
 */
const templateType = null;
getLocalStorage2({
  templateType,
  key: "somebody",
});

@wenq1
Copy link

wenq1 commented Jul 16, 2022

is there any plan moving this forward? @RyanCavanaugh

@cobaltt7
Copy link

Any updates?

@phaux
Copy link

phaux commented Aug 23, 2022

BTW I just found out this works:

import { useState } from "react"

/** @type {typeof useState<number>} */
const useNumberState = useState

const [value] = useNumberState(123)

or

import { useState } from "react"

const [value] = /** @type {typeof useState<number>} */ (useState)(123)

equivalent to

import { useState } from "react"

const [value] = useState<number>(123)

@noinkling
Copy link

noinkling commented Aug 25, 2022

For anyone unaware, the above are called "instantiation expressions" (see #47607) and were added in 4.7

Another option is doing something like:

/** @type {ReturnType<typeof fn<SomeType>>} */
const myVar = fn('whatever')

No intermediate variables or nasty inline casting syntax, but I guess less semantically 'pure', and can't be used in certain edge cases where ReturnType doesn't work well.

@zhenzhenChange
Copy link

zhenzhenChange commented Sep 5, 2022

I don't think there's much of a problem with using type assertions (type conversions) :

const elRef = useRef(/** @type {HTMLElement | null} */ (null));
const [value, setValue] = useState(/** @type {SomeType | null} */ (null));

After all, this should essentially be a matter of generic passing.
It's just that the readability of the code decreases as the type comment increase. Also note the inline () that wrap the value to form an expression to be valid. Like this: (val) ---> expr


TypeScript Doc

image

Cast Syntax

image

@milahu
Copy link

milahu commented Dec 8, 2022

#27387 (comment)

this should be in https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#template

@Jemt
Copy link

Jemt commented Mar 20, 2023

+1
Any progress on this one ?

@thepassle
Copy link

thepassle commented Oct 18, 2023

As mentioned in #56102 , I think it'd be really great to add better support for this. Lots of projects are using JSDoc for typechecking instead of full-on TS (either approaches are valid and fine, lets not start that discussion here), and it seems to me like better support for generics in jsdoc is long overdue.

I also think the example proposed by the OP would be a great candidate:

const model = mongoose.model/**<Model, Schema>*/('modelName', Schema);
// or perhaps some variation of an @ tag, that seems more in-line with the current approach JSDoc takes
const model = mongoose.model/** @generic {<Model, Schema>} */('modelName', Schema);

What I think is nice about these approaches is that:

  • Still valid JS, so will run without a compilation/build step
  • Visually similar to the TS way of typing this

Would love to discuss and see what we can do to get the ball rolling on this, especially considering that this issue has been open for 5 years :)

@noinkling
Copy link

noinkling commented Oct 18, 2023

@thepassle Technically this is already solved as per #27387 (comment) - not really sure why this issue hasn't been closed yet.

For example you can now do:

const model = /** @type {typeof mongoose.model<Model, Schema>} */(mongoose.model)('modelName', Schema);

If you're aware of this but your argument is that you would prefer for there to be a more ergonomic syntax, probably best to say that explicitly.

@thepassle
Copy link

Those indeed seem more like workarounds, so I understand the issue is still open.

So yes, I would prefer for there to be a more ergonomic syntax.

@mhofman
Copy link

mhofman commented Oct 18, 2023

@noinkling, those are not equivalent. A generic method would become unbound from its target if aliased or type annotated that way.

@noinkling
Copy link

noinkling commented Oct 19, 2023

@noinkling, those are not equivalent. A generic method would become unbound from its target if aliased or type annotated that way.

You mean methods won't have the right this value/type, essentially? I can see the issue with the alias approach in those cases, but nothing new there. Can you give an example where the casting syntax could be problematic/have a different result compared to TS syntax?

In my example both references to the method (runtime and in the annotation) are accessed directly from the object using dot notation (mongoose.model) so I don't see an issue, but maybe I'm missing something.

If you're concerned about the parentheses they don't change anything:

const foo = {
  bar: function() { return this === foo }
};

(foo.bar)();  // true

@gustavopch
Copy link

const model = /** @type {typeof mongoose.model<Model, Schema>} */(mongoose.model)('modelName', Schema)

is equivalent to:

const model = (mongoose.model as typeof mongoose.model<Model, Schema>)('modelName', Schema)

not to:

const model = mongoose.model<Model, Schema>('modelName', Schema)

The fact that this ugly but useful workaround exists doesn't mean a proper syntax for actually passing type parameters (instead of casting the whole function) shouldn't be pursued.

@noinkling
Copy link

@gustavopch It didn't exist when this issue was created was the point, it was impossible to provide type params unless they corresponded to runtime params. In that sense the person who created the issue might consider it "solved" now. Personally I've learned to take what I can get when it comes to JSDoc, given that it's something of a second-class citizen for TS. But you're right, it would be nice to have a nicer syntax, just wanted to make it clear that there's an option that works now (even if it isn't pretty).

@phaux
Copy link

phaux commented Feb 4, 2024

I return to this thread to point out that the "solution" with instantiation expressions doesn't seem to work when the function has multiple overloads:

const fileInput =
  /** @type {typeof document.querySelector<HTMLInputElement>} */
  (document.querySelector)("input[type=file]")

It seems to check all the overloads at once instead of choosing only one:

Type 'HTMLInputElement' does not satisfy the constraint 'keyof HTMLElementDeprecatedTagNameMap'. deno-ts(2344)
Type 'HTMLInputElement' does not satisfy the constraint 'keyof HTMLElementTagNameMap'. deno-ts(2344)
Type 'HTMLInputElement' does not satisfy the constraint 'keyof MathMLElementTagNameMap'. deno-ts(2344)
Type 'HTMLInputElement' does not satisfy the constraint 'keyof SVGElementTagNameMap'. deno-ts(2344)

In TypeScript it works:

const fileInput =
  document.querySelector<HTMLInputElement>("input[type=file]")

@noinkling
Copy link

@phaux You're right, worth noting it could potentially be considered a bug: #51694

@apendua
Copy link
Contributor

apendua commented Aug 4, 2024

@sandersn I am looking into implementing something like:

const model =
  /** @specialize {Model, Schema} */
  mongoose.model('modelName', Schema)

Do you think there's a chance this approach will be accepted?

@sandersn
Copy link
Member

sandersn commented Aug 7, 2024

Our approach to JSDoc has evolved since this issue was created. At that time I didn't want to add anything that didn't have precedent in the jsdoc documentation generator, or at least in Closure compiler. However, Typescript is the largest processor of JSDoc at this point, and TS users who want typing are probably the most prolific authors of it. So we've decided on a few new principles:

  1. If you're exposing existing TS syntax and semantics to JS, the semantics should be identical and the syntax should be as close as possible. @import is a good example, as is your own @overload work.
  2. However, new tags should be normal tags as much as possible. That means:
    a. Starts with @alphanumeric.
    b. Isn't nested -- starts at @ and continues till the next @.
    c. Is used in a /** comment directly above a declaration. Both @import and @overload violate this, for good reasons.

(1) means that TS users will be able to use the new JS syntax with a minimum of surprise. (2) means that jsdoc processors (including TS itself!) will be able to understand the new tag with a minimum of new parsing.

Re (2c), the rough descending order of goodness, or familiarity, in my opinion is:

  1. In a /** comment directly preceding a declaration -- like @type.
  2. A /** comment not connected to any JS syntax (which is to say, preceding some non-declaration) -- like @import.
  3. A /** comment preceding an expression -- like @type as a cast.
  4. A /** comment preceding another /** comment that eventually precedes a declaration -- like @overload.
  5. A /** comment preceding some other, non-top-level node in the parse tree.
  6. A /*:: comment or some other new syntax.
  7. A // @ts-alphanumeric comment preceding a declaration.

I think we can come up with something for (3).

This is a very long way to say that, yes, we'd consider a tag for this that looks a lot like your @specialise. You're probably going to need parentheses around the call expression to make it attach correctly, though.
Also I'm not sure whether <Model, Schema> would be better than {Model, Schema}. I think it's down to how hard it is to convert all the type-parameter syntax to that form. The grammar isn't huge there so probably pretty easy.

@DanielRosenwasser you've thought about jsdoc a lot too and you may have interesting insights from working on the types-in-JS proposal, which ran into exactly this problem.

@apendua apendua linked a pull request Aug 17, 2024 that will close this issue
@apendua
Copy link
Contributor

apendua commented Aug 17, 2024

I've got a PR ready for the following syntax:

let a = /** @specialize <number> */(f());

/** @specialize <string, number> */
let b = f();

/** @specialize {string} */
let c = f`template with ${variable}`;

let d = /** @specialize <string> */(new D());

So basically one can put the @specialize tag at any parent node that accepts JSDoc, e.g. assignment statement or parethesized expression. We can also choose between wrapping type arguments with <...> or {...}.

This will also work for JSX, though the element with type arguments will need to put inside curly braces so that we can also add JSDoc there:

function MyForm() {
  return (
    <form>
     {/** @specialize {number} */(
       <Input value={0} />
     )}
    </form>
  )
}

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Aug 19, 2024

It's something we'll have to consider. It's not perfect, but the solution right now is to cast to a value that's already specialized:

/**
 * @template T
 * @param x {T}
 */
function f(x) {
    return x;
}

let obj = {
    /**
     * @template T
     * @param x {T}
     */
    f(x) {
        return x;
    }
};

let g = /** @type {typeof f<string>} */ (f);
//  ^?
// typeof f<string>

let objG = (/** @type {typeof obj.f<string>} */ (obj.f)).bind(obj);
//  ^?
// typeof obj.f<string>

https://www.typescriptlang.org/play/?filetype=js#code/PQKhFgCgAIWgBALgUwLYAcA2BDF0AqUsC62ATtqtAB7QDe+AvkSMFAGYCuAdgMaIBLAPbdo7ABTUAlPSLR5ZZIk5lR1ANxRmkKJiXQhAIwBW0ALyyY86KAhXrcJGiy5kBOQ5LlKNekw-yrAES0pbW4dCKyqo0mvbQ2oxxuvoA5uY2YAiIAJ7obnS5+ULsYgA8AM6IZALcqQB8jLDA0OLsUnHALdAAegD8UCmIBiYA4hnittl5BUXIJSPGAHTsldW1DU2srUbL7VJLhrUAJuK7HVBd8v1AA

Though noted above in #27387 (comment), it doesn't work in the case of overloads.

@apendua
Copy link
Contributor

apendua commented Aug 22, 2024

@DanielRosenwasser

Though noted above in #27387 (comment), it doesn't work in the case of overloads.

Good point! Btw, the reason this currently does not work:

const fileInput =
  /** @type {typeof document.querySelector<HTMLInputElement>} */
  (document.querySelector)("input[type=file]")

is already pointed out in the other issue, here:

#51694

Do you think there's a chance the other PR will be merged? i.e. #51695

@tif-calin
Copy link

tif-calin commented Oct 14, 2024

I'm a big TypeScript fan that's trying out a new project where I use JSDocs instead of TS. Here are some of the learnings I've been forced to do along the way.

Passing in a generic slot to a function

The key here is utilizing ReturnType.

TypeScript version:

const patchReq = async <T>(path: string, data: Partial<T>): T => makeRequest('PATCH', path, data);

const updateBallot = async (ballot: Partial<Ballot>) => patchReq<Ballot>(`elections/${ballot.id}`, ballot);
//      ^? returns Promise<Ballot>

JSDocs version:

/** @template T; @param {string} path; @param {Partial<T>} data; @returns {Promise<T>} */
const patchReq = async (path, data) => makeRequest('PATCH', path, data);

/** @param {Partial<Ballot>} ballot; @returns {ReturnType<typeof patchReq<Ballot>>} */
const updateBallot = async (ballot) => patchReq(`ballots/${ballot.id}`, ballot);

Warning

As pointed out by @phaux above, this often fails when there are overloads.

React useState

This was already mentioned above, but I think it's useful to rewrite. Typecasting the initial value has never failed me. Make sure to remeber the parentheses

const [optionalTitle, setOptionalTitle] = useState(/** @type {null | string} */(null));
//      ^? null | string

Note

I would prefer to have instead written on the line before the useState statement /** @type {ReturnType<typeof useState<null | string>>} */ but this also fails due to useState's overloads with undefined

Peeja added a commit to storacha/gateway-lib that referenced this issue Jan 15, 2025
* Generate `composeMiddleware` overloads up to 30 arguments.
* Use file extensions in `import`s.
* Use `Simplify` in the return type of `composeMiddleware` for
  better ergonomics.
* Add `extends {}` to the type parameters of a `Middleware` function--I
  don't actually remember why, but the types didn't infer correctly
  otherwise.
* Use `const`s instead of `function`s for middleware functions. The
  `@type` tag doesn't apply correctly to a `function` declaration--which
  makes sense, since there's no equivalent syntax that would do it in
  TypeScript: microsoft/TypeScript#27387
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
checkJs Relates to checking JavaScript using TypeScript Domain: JavaScript The issue relates to JavaScript specifically Domain: JSDoc Relates to JSDoc parsing and type generation In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.