Skip to content

Commit a29e14d

Browse files
authored
Let expressions learns to use custom registered functions (#32)
* fixed vulnerabilities * clarified intent for usage of a type * removed compiler warning * Let expressions learned to use custom registered functions (#29)
1 parent c4c1e0f commit a29e14d

File tree

4 files changed

+59
-31
lines changed

4 files changed

+59
-31
lines changed

package-lock.json

Lines changed: 36 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Runtime.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export interface FunctionTable {
6161

6262
export class Runtime {
6363
_interpreter: TreeInterpreter;
64+
_functionTable: FunctionTable;
6465
TYPE_NAME_TABLE: { [InputArgument: number]: string } = {
6566
[InputArgument.TYPE_NUMBER]: 'number',
6667
[InputArgument.TYPE_ANY]: 'any',
@@ -78,24 +79,25 @@ export class Runtime {
7879

7980
constructor(interpreter: TreeInterpreter) {
8081
this._interpreter = interpreter;
82+
this._functionTable = this.functionTable
8183
}
8284

8385
registerFunction(
8486
name: string,
8587
customFunction: RuntimeFunction<(JSONValue | ExpressionNode)[], JSONValue>,
8688
signature: InputSignature[],
8789
): void {
88-
if (name in this.functionTable) {
90+
if (name in this._functionTable) {
8991
throw new Error(`Function already defined: ${name}()`);
9092
}
91-
this.functionTable[name] = {
93+
this._functionTable[name] = {
9294
_func: customFunction.bind(this),
9395
_signature: signature,
9496
};
9597
}
9698

9799
callFunction(name: string, resolvedArgs: (JSONValue | ExpressionNode)[]): JSONValue {
98-
const functionEntry = this.functionTable[name];
100+
const functionEntry = this._functionTable[name];
99101
if (functionEntry === undefined) {
100102
throw new Error(`Unknown function: ${name}()`);
101103
}
@@ -114,7 +116,7 @@ export class Runtime {
114116
private validateArgs(name: string, args: (JSONValue | ExpressionNode)[], signature: InputSignature[]): void {
115117
let pluralized: boolean;
116118
this.validateInputSignatures(name, signature);
117-
const numberOfRequiredArgs = signature.filter(argSignature => !argSignature.optional ?? false).length;
119+
const numberOfRequiredArgs = signature.filter(argSignature => !(argSignature.optional ?? false)).length;
118120
const lastArgIsVariadic = signature[signature.length - 1]?.variadic ?? false;
119121
const tooFewArgs = args.length < numberOfRequiredArgs;
120122
const tooManyArgs = args.length > signature.length;

src/TreeInterpreter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export class TreeInterpreter {
1919

2020
withScope(scope: ScopeEntry): TreeInterpreter {
2121
const interpreter = new TreeInterpreter();
22+
interpreter.runtime._functionTable = this.runtime._functionTable;
2223
interpreter._rootValue = this._rootValue;
2324
interpreter._scope = this._scope.withScope(scope);
2425
return interpreter;
@@ -36,7 +37,7 @@ export class TreeInterpreter {
3637
const identifier = node.name;
3738
let result: JSONValue = null;
3839
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
39-
result = value[identifier] ?? null;
40+
result = (value as JSONObject)[identifier] ?? null;
4041
}
4142
return result;
4243
case 'LetExpression': {

test/jmespath-functions.spec.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { search } from '../src';
1+
import { search, registerFunction, TYPE_NUMBER } from '../src';
22
import { expectError } from './error.utils';
33

44
describe('Evaluates functions', () => {
@@ -125,3 +125,17 @@ describe('Type-checks function arguments', () => {
125125
}, 'invalid-value');
126126
});
127127
});
128+
129+
describe('custom functions', () => {
130+
it('must be in scope for let expression', () => {
131+
registerFunction(
132+
'plusplus', // FUNCTION NAME
133+
(resolvedArgs) => { // CUSTOM FUNCTION
134+
const [num] = resolvedArgs;
135+
return num + 1;
136+
},
137+
[{ types: [TYPE_NUMBER] }] //SIGNATURE
138+
);
139+
expect(search({index: 0}, 'let $n = index in plusplus($n)')).toEqual(1);
140+
});
141+
});

0 commit comments

Comments
 (0)