Skip to content

Commit

Permalink
Add support for optional arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
Glen Van Ginkel committed Oct 10, 2020
1 parent 2f2d479 commit 9ba315d
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 14 deletions.
42 changes: 28 additions & 14 deletions src/Runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export enum InputArgument {
export interface InputSignature {
types: InputArgument[];
variadic?: boolean;
optional?: boolean;
}

export type RuntimeFunction<T, U> = (resolvedArgs: T) => U;
Expand Down Expand Up @@ -71,31 +72,44 @@ export class Runtime {
return functionEntry._func.call(this, resolvedArgs);
}

private validateInputSignatures(name: string, signature: InputSignature[]): void {
for (let i = 0; i < signature.length; i += 1) {
if ('variadic' in signature[i] && i !== signature.length - 1) {
throw new Error(`ArgumentError: ${name}() 'variadic' argument ${i + 1} must occur last`);
}
if ('variadic' in signature[i] && 'optional' in signature[i]) {
throw new Error(`ArgumentError: ${name}() 'variadic' argument ${i + 1} cannot also be 'optional'`);
}
}
}

private validateArgs(name: string, args: any[], signature: InputSignature[]): void {
let pluralized: boolean;
if (signature[signature.length - 1].variadic) {
if (args.length < signature.length) {
pluralized = signature.length > 1;
throw new Error(
`ArgumentError: ${name}() takes at least ${signature.length} argument${
(pluralized && 's') || ''
} but received ${args.length}`,
);
}
} else if (args.length !== signature.length) {
this.validateInputSignatures(name, signature);
const numberOfRequiredArgs = signature.filter(argSignature => !argSignature.optional ?? false).length;
const lastArgIsVariadic = signature[signature.length - 1]?.variadic ?? false;
const tooFewArgs = args.length < numberOfRequiredArgs;
const tooManyArgs = args.length > signature.length;
const tooFewModifier =
tooFewArgs && ((!lastArgIsVariadic && numberOfRequiredArgs > 1) || lastArgIsVariadic) ? 'at least ' : '';

if ((lastArgIsVariadic && tooFewArgs) || (!lastArgIsVariadic && (tooFewArgs || tooManyArgs))) {
pluralized = signature.length > 1;
throw new Error(
`ArgumentError: ${name}() takes ${signature.length} argument${(pluralized && 's') || ''} but received ${
args.length
}`,
`ArgumentError: ${name}() takes ${tooFewModifier}${numberOfRequiredArgs} argument${
(pluralized && 's') || ''
} but received ${args.length}`,
);
}

let currentSpec: InputArgument[];
let actualType: InputArgument;
let typeMatched: boolean;
let isOptional: boolean;
for (let i = 0; i < signature.length; i += 1) {
typeMatched = false;
currentSpec = signature[i].types;
isOptional = signature[i].optional ?? false;
actualType = this.getTypeName(args[i]) as InputArgument;
let j: number;
for (j = 0; j < currentSpec.length; j += 1) {
Expand All @@ -104,7 +118,7 @@ export class Runtime {
break;
}
}
if (!typeMatched) {
if (!typeMatched && !isOptional) {
const expected = currentSpec
.map((typeIdentifier): string => {
return this.TYPE_NAME_TABLE[typeIdentifier];
Expand Down
114 changes: 114 additions & 0 deletions test/jmespath.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,4 +234,118 @@ describe('registerFunction', () => {
),
).toThrow('Function already defined: sum()');
});
it('alerts too few arguments', () => {
registerFunction(
'tooFewArgs',
() => {
/* EMPTY FUNCTION */
},
[{ types: [jmespath.TYPE_ANY] }],
);
expect(() =>
search(
{
foo: 60,
bar: 10,
},
'tooFewArgs()',
),
).toThrow('ArgumentError: tooFewArgs() takes 1 argument but received 0');
});
it('alerts too many arguments', () => {
registerFunction(
'tooManyArgs',
() => {
/* EMPTY FUNCTION */
},
[],
);
expect(() =>
search(
{
foo: 60,
bar: 10,
},
'tooManyArgs(foo)',
),
).toThrow('ArgumentError: tooManyArgs() takes 0 argument but received 1');
});

it('alerts optional variadic arguments', () => {
registerFunction(
'optionalVariadic',
() => {
/* EMPTY FUNCTION */
},
[{ types: [jmespath.TYPE_ANY], optional: true, variadic: true }],
);
expect(() =>
search(
{
foo: 60,
bar: 10,
},
'optionalVariadic(foo)',
),
).toThrow("ArgumentError: optionalVariadic() 'variadic' argument 1 cannot also be 'optional'");
});

it('alerts variadic is always last argument', () => {
registerFunction(
'variadicAlwaysLast',
() => {
/* EMPTY FUNCTION */
},
[
{ types: [jmespath.TYPE_ANY], variadic: true },
{ types: [jmespath.TYPE_ANY], optional: true },
],
);
expect(() =>
search(
{
foo: 60,
bar: 10,
},
'variadicAlwaysLast(foo)',
),
).toThrow("ArgumentError: variadicAlwaysLast() 'variadic' argument 1 must occur last");
});

it('accounts for optional arguments', () => {
registerFunction(
'optionalArgs',
([first, second, third]) => {
return { first, second: second ?? 'default[2]', third: third ?? 'default[3]' };
},
[{ types: [jmespath.TYPE_ANY] }, { types: [jmespath.TYPE_ANY], optional: true }],
);
expect(
search(
{
foo: 60,
bar: 10,
},
'optionalArgs(foo)',
),
).toEqual({ first: 60, second: 'default[2]', third: 'default[3]' });
expect(
search(
{
foo: 60,
bar: 10,
},
'optionalArgs(foo, bar)',
),
).toEqual({ first: 60, second: 10, third: 'default[3]' });
expect(() =>
search(
{
foo: 60,
bar: 10,
},
'optionalArgs(foo, bar, [foo, bar])',
),
).toThrow('ArgumentError: optionalArgs() takes 1 arguments but received 3');
});
});

0 comments on commit 9ba315d

Please sign in to comment.