Blog June 2nd, 2023

The power of `.toString()`

I was building this little tool recently that allows me to send text messages with specific slash commands to automate certain things, like /slack-status or /track-hours.

The way I implemented this is by having an array of available commands in my source code, like so:

const commands = [
	{
		id: "slack-status",
		handler: (text, snooze = false) => {
			// ...
		}
	},
	// ...
];

As part of that, I also implemented a /help command. Rather than manually updating the text this returns, I wanted a way to automatically list the available functions and the arguments they accepted.

Many compiled languages usually have a set of tools referred to as Reflection to accomplish this.

In JavaScript, using toString() on a Function is a good alternative, as it will return a stringified version of that function (basically its entire source code):

function hello(name) {
	console.log(`Hello, ${name}`);
}

console.log(hello.toString()); 
// outputs: "function hello(name) { console.log(`Hello, ${name}`); }"

Given our commands array above, you can use this to generate a nice list of available commands:

const getFunctionArguments = 
    (fn) => fn.toString()
        .split(')')[0]
        .replace('(', '')
        .split(/\s*,\s*/)
        .filter(Boolean);

console.log('Available commands:');
for (const {id, handler} of commands) {
	const args = getFunctionArguments(handler);
	console.log(`/${id}${args.map(a => ` [${a}]`).join('')}`);
}

Given our earlier commands array, this would output:

Available commands:
/slack-status [text] [snooze = false]