My profile picture. My mom says I'm a handsome boy!
← All Posts

Using IntelliSense with JSDoc

#Code, #Guides, #Tools

Did you know that you can get VS Code IntelliSense with vanilla JavaScript? By using JSDoc annotations, VS Code's TypeScript Language Server can understand your code without it actually being strictly typed.

I use JSDoc extensively in the codebase for my Discord bot, Aquarius. As Aquarius grew in size, it became harder to remember function names and parameters. Problems that, you know, a type system would solve. I have a strong preference for dynamic languages though, and didn't want to rewrite my codebase in TypeScript. JSDoc offers me some benefits of a strictly typed system without having to fully switch to one. Plus, using JSDoc forces me to document my code - which I think I read once is a recommended practice or something?

Using JSDoc

You can find additional information by reading the JSDoc documentation and on the TypeScript reference page. Note that not all JSDoc parameters are currently supported!

Documenting Classes and Variables

You can document both class variables and methods with types and descriptions. As an added benefit, this enables "Go to Definition" by ctrl/cmd+clicking usages throughout your codebase.

javascript
1class Aquarius {
2 constructor() {
3 /**
4 * Database connection for Aquarius
5 * @type {PrismaClient}
6 */
7 this.database = database;
8
9 /**
10 * Manages the Guilds for which Aquarius is a member
11 * @type { typeof import('./core/managers/guild-manager') }
12 */
13 this.guildManager = new GuildManager();
14
15 /**
16 * Stores interaction handlers
17 * @type {Map<string, SlashCommandBuilder>}
18 */
19 this.applicationCommands = new Map();
20
21 /**
22 * Stores interaction handlers
23 * @type {Map<string, (CommandInteraction) => unknown}>}
24 */
25 this.applicationHandlers = new Map();
26 }
27}
Class methods and properties are displayed

Documenting Functions

For functions, I list the parameter types, return types, and default values.

javascript
1 /**
2 * Get the list of Global Command Names
3 * @param {boolean} [includeHidden=true] - Whether to include hidden commands
4 * @return {string[]} List of Global Command Names
5 */
6 getGlobalCommandNames(includeHidden = true) {
7 return Array.from(this.help.entries())
8 .filter(([, info]) => info.global && (includeHidden || !info.hidden))
9 .map(([key]) => key.toLowerCase());
10 }
Methods show up in IntelliSense with argument data

Importing Definitions across Files

For core classes and functions I need to reference from elsewhere, I use inline imports or typedef imports. I also import annotations from npm packages this way!

javascript
1/**
2 * @typedef {import('./aquarius').Aquarius} Aquarius
3 * @typedef {import('./core/commands/settings').default} Settings
4 * @typedef {import('./core/commands/analytics').default} Analytics
5 * @typedef {import('discord.js').Message} Message
6 */
7
8class SettingsManager {
9 constructor() {
10 /**
11 * Manages the Guilds for which Aquarius is a member
12 * @type { typeof import('./core/managers/guild-manager') }
13 */
14 this.guildManager = new GuildManager();
15
16 /**
17 * The configured guild settings
18 * @type {Settings}
19 */
20 this.settings = new Settings();
21 }
22}
Package annotations are also supported

Defining Complex Objects

I define a handful of complex types that I use throughout the codebase for Aquarius in a typedefs.js file (I know, I know).

javascript
1/**
2 * Description of the exported Info object by Commands and Plugins
3 * @typedef {Object} CommandInfo
4 * @property {string} name - The name of the Command
5 * @property {string} description - A short description of the Command's function
6 * @property {string} [usage] - A DocOpt usage description
7 * @property {number[]} [permissions] - An array of required permissions for the command
8 * @property {boolean} [hidden] - Whether the command should show up in the command list or not
9 * @property {boolean} [disabled] - Whether the command is currently disabled or not
10 * @property {boolean} [deprecated] - Whether the command is deprecated or not
11 */
12
13/**
14 * Description of a CommandParameter passed into Plugins and Commands
15 * @typedef {Object} CommandParameter
16 * @property {Aquarius} aquarius - reference to the Aquarius Client
17 * @property {Settings} settings - modify settings for the command
18 * @property {Analytics} analytics - track events for the command
19 */
20
21/**
22 * Function that defines commands and plugins
23 * @typedef {({ aquarius, settings, analytics}: CommandParameter) => null} Command
24 */

If you already use TypeScript, this post isn't meant to convince you to stop. But if you're like me and don't enjoy typed languages, give JSDoc annotations a shot. You might really like them!