Skip to content

Commit ae6a43c

Browse files
committed
feat: keys, values, sort functions
1 parent 8c4cb88 commit ae6a43c

File tree

5 files changed

+71
-41
lines changed

5 files changed

+71
-41
lines changed

packages/jmespath/src/Parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import type { Node, Token } from './types';
4040
* If you don't want to read the full paper, there are some other good
4141
* overviews that explain the general idea:
4242
* - [Pratt Parsers: Expression Parsing Made Easy](https://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/)
43-
* - [Simple Top-Down Parsing in Python](http://effbot.org/zone/simple-top-down-parsing.htm)
43+
* - [Simple Top-Down Parsing in Python](https://11l-lang.org/archive/simple-top-down-parsing/)
4444
* - [Top Down Operator Precedence](http://javascript.crockford.com/tdop/tdop.html)
4545
*/
4646
class Parser {

packages/jmespath/src/functions/Functions.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Expression, isNumber, isRecord } from '../visitor/utils';
1+
import { Expression, getType, isNumber, isRecord } from '../visitor/utils';
22
import type { JSONArray, JSONObject, JSONValue } from '../types';
33
import { typeCheck, arityCheck } from './typeChecking';
44

@@ -234,6 +234,19 @@ class Functions {
234234
: arg.split('').reverse().join('');
235235
}
236236

237+
/**
238+
* Sort the provided array.
239+
*
240+
* @param arg The array to sort
241+
* @returns The sorted array
242+
*/
243+
@Functions.signature({
244+
argumentsSpecs: [['array-number', 'array-string']],
245+
})
246+
public funcSort(arg: Array<unknown>): Array<unknown> {
247+
return arg.sort();
248+
}
249+
237250
/**
238251
* Determines if the provided string starts with the provided suffix.
239252
*
@@ -330,21 +343,7 @@ class Functions {
330343
argumentsSpecs: [['any']],
331344
})
332345
public funcType(arg: JSONValue): string {
333-
if (Array.isArray(arg)) {
334-
return 'array';
335-
} else if (isRecord(arg)) {
336-
return 'object';
337-
} else if (typeof arg === 'string') {
338-
return 'string';
339-
} else if (typeof arg === 'number') {
340-
return 'number';
341-
} else if (typeof arg === 'boolean') {
342-
return 'boolean';
343-
} else if (Object.is(arg, null)) {
344-
return 'null';
345-
} else {
346-
return 'unknown';
347-
}
346+
return getType(arg);
348347
}
349348

350349
/**

packages/jmespath/src/functions/typeChecking.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isRecord } from '../visitor/utils';
1+
import { getType, isRecord } from '../visitor/utils';
22
import { JMESPathTypeError, ArityError, VariadicArityError } from '../errors';
33

44
/**
@@ -78,7 +78,7 @@ const typeCheckArgument = (arg: unknown, argumentSpec: Array<string>): void => {
7878
throw new JMESPathTypeError({
7979
currentValue: arg,
8080
expectedTypes: argumentSpec,
81-
actualType: Object.is(arg, null) ? 'null' : typeof arg,
81+
actualType: getType(arg),
8282
});
8383
}
8484
if (type.includes('-')) {
@@ -103,7 +103,7 @@ const typeCheckArgument = (arg: unknown, argumentSpec: Array<string>): void => {
103103
throw new JMESPathTypeError({
104104
currentValue: arg,
105105
expectedTypes: argumentSpec,
106-
actualType: type === 'boolean' ? 'boolean' : typeof arg, // TODO: fix this
106+
actualType: getType(arg),
107107
});
108108
}
109109
continue;
@@ -115,7 +115,7 @@ const typeCheckArgument = (arg: unknown, argumentSpec: Array<string>): void => {
115115
throw new JMESPathTypeError({
116116
currentValue: arg,
117117
expectedTypes: argumentSpec,
118-
actualType: typeof arg,
118+
actualType: getType(arg),
119119
});
120120
}
121121
continue;
@@ -127,7 +127,7 @@ const typeCheckArgument = (arg: unknown, argumentSpec: Array<string>): void => {
127127
throw new JMESPathTypeError({
128128
currentValue: arg,
129129
expectedTypes: argumentSpec,
130-
actualType: typeof arg,
130+
actualType: getType(arg),
131131
});
132132
}
133133
continue;

packages/jmespath/src/visitor/utils.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,24 @@ const sliceArray = <T>(
205205
return result;
206206
};
207207

208+
const getType = (value: unknown): string => {
209+
if (Array.isArray(value)) {
210+
return 'array';
211+
} else if (isRecord(value)) {
212+
return 'object';
213+
} else if (typeof value === 'string') {
214+
return 'string';
215+
} else if (isNumber(value)) {
216+
return 'number';
217+
} else if (typeof value === 'boolean') {
218+
return 'boolean';
219+
} else if (Object.is(value, null)) {
220+
return 'null';
221+
} else {
222+
return 'unknown';
223+
}
224+
};
225+
208226
export {
209227
Expression,
210228
isRecord,
@@ -213,4 +231,5 @@ export {
213231
isNumber,
214232
isIntegerNumber,
215233
sliceArray,
234+
getType,
216235
};

packages/jmespath/tests/unit/functions.test.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -935,12 +935,11 @@ describe('Functions tests', () => {
935935
expect(result).toStrictEqual(expected);
936936
});
937937

938-
/* it.each([
938+
it.each([
939939
{
940940
expression: 'sort(keys(objects))',
941941
expected: ['bar', 'foo'],
942942
},
943-
944943
{
945944
expression: 'sort(values(objects))',
946945
expected: ['bar', 'baz'],
@@ -961,6 +960,10 @@ describe('Functions tests', () => {
961960
expression: 'sort(decimals)',
962961
expected: [-1.5, 1.01, 1.2],
963962
},
963+
{
964+
expression: 'sort(empty_list)',
965+
expected: [],
966+
},
964967
])(
965968
'should support the sort(), key(), and values() functions',
966969
({ expression, expected }) => {
@@ -989,61 +992,70 @@ describe('Functions tests', () => {
989992
// Assess
990993
expect(result).toStrictEqual(expected);
991994
}
992-
); */
995+
);
993996

994-
/* it.each([
997+
it.each([
995998
{
996999
expression: 'keys(foo)',
9971000
error:
998-
'TypeError: keys() expected argument 1 to be type (object) but received type number instead.',
1001+
'Invalid argument type for function keys(), expected "object" but found "number" in expression: keys(foo)',
9991002
},
10001003
{
10011004
expression: 'keys(strings)',
10021005
error:
1003-
'TypeError: keys() expected argument 1 to be type (object) but received type array instead.',
1006+
'Invalid argument type for function keys(), expected "object" but found "array" in expression: keys(strings)',
10041007
},
10051008
{
10061009
expression: 'keys(`false`)',
10071010
error:
1008-
'TypeError: keys() expected argument 1 to be type (object) but received type boolean instead.',
1011+
'Invalid argument type for function keys(), expected "object" but found "boolean" in expression: keys(`false`)',
10091012
},
10101013
{
10111014
expression: 'values(foo)',
10121015
error:
1013-
'TypeError: values() expected argument 1 to be type (object) but received type number instead.',
1016+
'Invalid argument type for function values(), expected "object" but found "number" in expression: values(foo)',
10141017
},
10151018
{
10161019
expression: 'sort(array)',
10171020
error:
1018-
'TypeError: sort() expected argument 1 to be type (Array<string> | Array<number>) but received type array instead.',
1021+
'Invalid argument type for function sort(), expected "number" but found "string" in expression: sort(array)',
10191022
},
10201023
{
10211024
expression: 'sort(abc)',
10221025
error:
1023-
'TypeError: sort() expected argument 1 to be type (Array<string> | Array<number>) but received type null instead.',
1024-
},
1025-
{
1026-
expression: 'sort(empty_list)',
1027-
expected: [],
1026+
'Invalid argument type for function sort(), expected one of "array-number", "array-string" but found "null" in expression: sort(abc)',
10281027
},
10291028
{
10301029
expression: 'sort(@)',
10311030
error:
1032-
'TypeError: sort() expected argument 1 to be type (Array<string> | Array<number>) but received type object instead.',
1031+
'Invalid argument type for function sort(), expected one of "array-number", "array-string" but found "object" in expression: sort(@)',
10331032
},
10341033
])(
10351034
'sort(), keys(), and values() function errors',
10361035
({ expression, error }) => {
1037-
// TODO: see if we can assert the error type as well in sort(), keys(), values() errors tests
10381036
// Prepare
10391037
const data = {
1040-
type: 'object',
1038+
foo: -1,
1039+
zero: 0,
1040+
numbers: [-1, 3, 4, 5],
1041+
array: [-1, 3, 4, 5, 'a', '100'],
1042+
strings: ['a', 'b', 'c'],
1043+
decimals: [1.01, 1.2, -1.5],
1044+
str: 'Str',
1045+
false: false,
1046+
empty_list: [],
1047+
empty_hash: {},
1048+
objects: {
1049+
foo: 'bar',
1050+
bar: 'baz',
1051+
},
1052+
null_key: null,
10411053
};
10421054

10431055
// Act & Assess
10441056
expect(() => search(expression, data)).toThrow(error);
10451057
}
1046-
); */
1058+
);
10471059

10481060
/* it.each([
10491061
{
@@ -1228,7 +1240,7 @@ describe('Functions tests', () => {
12281240
{
12291241
expression: 'starts_with(str, `0`)',
12301242
error:
1231-
'Invalid argument type for function starts_with(), expected "string" but found "object" in expression: starts_with(str, `0`)',
1243+
'Invalid argument type for function starts_with(), expected "string" but found "null" in expression: starts_with(str, `0`)',
12321244
},
12331245
])('starts_with() function errors', ({ expression, error }) => {
12341246
// Prepare

0 commit comments

Comments
 (0)