DEV Community

Bruno Noriller
Bruno Noriller

Posted on • Originally published at Medium

JSDoc 101

I’ve been using JSDoc for some time now, and with the recent hype because of Svelte migration, today I come to share tips and how to use them.

Those are the very basics of what you will need.

Typescript

The syntax for some things is different, but for most things, just think of it as Typescript and you’ll be fine.

.d.ts files

I usually use them for helper and utility types (it’s easier to create them, especially those with generics and you can then just use them)

Setup

Add // @ts-check to the top of each file you need or add a jsconfig,json in the root of your project with the check JS files. (Don’t forget to enable js checking in your IDE).

JSDoc

@type

Just as with TS, there's a difference between:

/** @type {Foo} */
const foo = {}
const foo: Foo = {}

// and

const foo = /** @type {Foo} */ ({})
const foo = {} as Foo

// also includes
const foo = /** @satisfies {Foo} */ ({})
Enter fullscreen mode Exit fullscreen mode

Depending on how you want TS to handle the type, autocomplete, and errors that it shows you.

For @type I usually go with it inline because I normally use it to cast something to the type I need:

// React common example
const [state, setState] = useState(/** @type {{ foo: Bar, bar: Baz }} */ ({}));
//     ^? { foo: Bar, bar: Baz }

// And then sometimes you need some casting
Object.keys(state).map(k => k)
//                     ^? string

/** @type {(keyof state)[]} */ (Object.keys(state)).map(k => k)
//                                                      ^? "foo"|"bar"
Enter fullscreen mode Exit fullscreen mode

And, as you could see when casting something to a type you put it between parenthesis /** @type {Foo} */ (toBeCast), and of course, if you need you can /** @type {Foo} */(/** @type {unknown} */ (toBeCast)) and yes, you need to put it inside two for this.

@typedef

You’ll probably use a lot, usually for more complex types.

For this one, I declare it as a block and usually in the first manner without any comments.

But sometimes you want the second version because wherever those types go, so do the comments.

/**
 * @template {string} [T='bar']
 * @typedef {{
 *  bar: T, // you can't see this comment
 *  baz?: string,
 * }} Foo visible comments here
 */

/**
 * @template {string} [T='bar']
 * @typedef {Object} Foo2
 * @property {T} bar this comment is visible
 * @property {string} [baz] this is an optional property
 */

// both equivalent to:
type Foo<T extends string = "bar"> = {
  bar: T;
  baz?: string;
}
Enter fullscreen mode Exit fullscreen mode

About the @template, the simplest way to declare a generic is @template T.

The one in the example is the most complex one you would need with a type it extends and then a default value, but you can also only have only one or the other.

Top comments (2)

Collapse
 
iskandarreza profile image
Iskandar Reza

I started using jsdoc in a new project and I'm really liking what the annotations do to my IDE.

Collapse
 
noriller profile image
Bruno Noriller

I suffered enough in a legacy project and I'm happy JSDoc is getting some hype.

JSDoc support is already good, and it will only get better. (I even found a bug, reported and people already fixed!)