JSDoc 101

JSDoc has been my go-to tool for documenting JavaScript code for a while, and with the recent buzz around Svelte migration, I decided to share some tips and tricks on how to use it.

These are the essential things you need to know.

Typescript

Some things have a different way of writing them, but for most things, you can use TypeScript logic and it will work.

.d.ts files

My common use case for them is to define types for helper and utility functions (they are more convenient to create, especially with generics, and then I can just apply 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} */ ({})

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"

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;
}

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.

No comments:

Post a Comment