Just a little side project to get back into a bit of JS.
Looking at partial application, but one of the issues in JS is being able to reliably get the number of parameters a function expects.
The length property of a function ignores default arguments and spread parameters.
I’m sure there is a better way of doing this, but have opted to call the toString method and use some regexery to try and solve this.
// inspect:
// A function to inspect a function's parameters
// Returns the name, parameters and length
// The length includes default arguments aswell as spread arguments
const groupsRx = {
// A collection of regexes to match balanced groups to a depth of 3
// e.g. match ... [1, 2, [3]] ... or ... { x: 2, y : { z: 3 }} ...
// Used to match default parameter groups e.g. function (x, y = →[1, 2]←))
groups: {
parens: /\([^)(]*(?:\([^)(]*(?:\([^)(]*(?:[^)(]*)*\)[^)(]*)*\)[^)(]*)*\)/,
braces: /\{[^}{]*(?:\{[^}{]*(?:\{[^}{]*(?:[^}{]*)*\}[^}{]*)*\}[^}{]*)*\}/,
brackets: /\[[^\]\[]*(?:\[[^\]\[]*(?:\[[^\]\[]*(?:[^\]\[]*)*\][^\]\[]*)*\][^\]\[]*)*\]/
},
join() {
// concatenates the regex groups with pipe returning one regex
const groups = Object.values(this.groups)
return new RegExp(groups.map((rx) => rx.source).join('|'), 'g')
}
}
function inspect(fn) {
// matches all the parameters within parentheses
const params = fn.toString().match(groupsRx.groups.parens)[0].slice(1, -1)
const balancedGroups = groupsRx.join()
// matches groups e.g. (x, y = foo→('bar')←, z = →[1,2,3]←)
const groups = params.matchAll(balancedGroups)
// replace the groups with a % placeholder so that
// params can be split on commas into an array, and
// then replace placeholders with the groups
const parameters = params
.replace(balancedGroups, '%')
.split(/\s*,\s*/)
.map((param) => param.replaceAll('%', () => groups.next().value))
return { name: fn.name, parameters, length: parameters.length }
}
Examples
const foo = (x, y, z = [1,2,3], ...args) => {}
const bar = (x, y = foo(1, 2), { property: prop }) => {}
console.log(foo.length) // 2
console.log(inspect(foo))
/*
{
'name': 'foo',
'parameters': ['x', 'y', 'z = [1, 2, 3]', '...args'],
'length': 4
}
*/
console.log(bar.length) // 1
console.log(inspect(bar))
/*
{
'name': 'bar',
'parameters': ['x', 'y = foo(1, 2)', '{ property: prop }'],
'length': 3
}
*/
I may well be barking up the wrong tree here. I know it’s a tad flakey, so any input is welcome.