Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
danopia committed Mar 9, 2021
2 parents fad814b + fe40b45 commit a5eeb82
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 228 deletions.
10 changes: 6 additions & 4 deletions src/Lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export enum Token {
TOK_LITERAL = 'Literal',
}

export type LexerTokenValue = string | number | JSONValue;
export type LexerTokenValue = JSONValue;

export interface LexerToken {
type: Token;
Expand Down Expand Up @@ -108,7 +108,9 @@ export interface ComparitorNode<T = ASTNode> {
jmespathType?: Token;
}

export const basicTokens: {[key: string]: Token} = {
export type ExpressionNodeTree = ASTNode | ExpressionNode | FieldNode | ValueNode;

export const basicTokens: Record<string, Token> = {
'(': Token.TOK_LPAREN,
')': Token.TOK_RPAREN,
'*': Token.TOK_STAR,
Expand All @@ -122,14 +124,14 @@ export const basicTokens: {[key: string]: Token} = {
'}': Token.TOK_RBRACE,
};

const operatorStartToken: {[key: string]: true} = {
const operatorStartToken: Record<string, boolean> = {
'!': true,
'<': true,
'=': true,
'>': true,
};

const skipChars: {[key: string]: true} = {
const skipChars: Record<string, boolean> = {
'\t': true,
'\n': true,
'\r': true,
Expand Down
7 changes: 3 additions & 4 deletions src/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import {
ExpressionNode,
IndexNode,
SliceNode,
ASTNode,
FieldNode,
KeyValuePairNode,
LexerToken,
ValueNode,
Token,
ASTNode,
} from './Lexer.ts';
import Lexer from './Lexer.ts';
import Lexer, { Token } from './Lexer.ts';

const bindingPower: { [token: string]: number } = {
const bindingPower: Record<string, number> = {
[Token.TOK_EOF]: 0,
[Token.TOK_UNQUOTEDIDENTIFIER]: 0,
[Token.TOK_QUOTEDIDENTIFIER]: 0,
Expand Down
29 changes: 20 additions & 9 deletions src/Runtime.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { TreeInterpreter } from './TreeInterpreter.ts';
import { Token, ExpressionNode } from './Lexer.ts';
import type { ExpressionNode } from './Lexer.ts';
import type { JSONValue, JSONObject, JSONArray, ObjectDict } from './index.ts';
import { Token } from './Lexer.ts';

import { isObject } from './utils/index.ts';
import type { JSONValue, JSONObject, JSONArray, ObjectDict } from './index.ts';

export enum InputArgument {
TYPE_NUMBER = 0,
Expand Down Expand Up @@ -188,7 +189,9 @@ export class Runtime {
exprefNode: ExpressionNode,
allowedTypes: InputArgument[],
): ((x: JSONValue) => JSONValue) | undefined {
if (!this._interpreter) return;
if (!this._interpreter) {
return;
}
const interpreter = this._interpreter;
const keyFunc = (x: JSONValue): JSONValue => {
const current = interpreter.visit(exprefNode, x) as JSONValue;
Expand Down Expand Up @@ -249,8 +252,10 @@ export class Runtime {
return Object.keys(inputValue).length;
};

private functionMap = (resolvedArgs: any[]) => {
if (!this._interpreter) return [];
private functionMap = (resolvedArgs: any[]): any[] => {
if (!this._interpreter) {
return [];
}
const mapped = [];
const interpreter = this._interpreter;
const exprefNode = resolvedArgs[0];
Expand All @@ -262,7 +267,9 @@ export class Runtime {
};

private functionMax: RuntimeFunction<[(string | number)[]], string | number | null> = ([inputValue]) => {
if (!inputValue.length) return null;
if (!inputValue.length) {
return null;
}

const typeName = this.getTypeName(inputValue[0]);
if (typeName === InputArgument.TYPE_NUMBER) {
Expand Down Expand Up @@ -309,7 +316,9 @@ export class Runtime {
};

private functionMin: RuntimeFunction<[(string | number)[]], string | number | null> = ([inputValue]) => {
if (!inputValue.length) return null;
if (!inputValue.length) {
return null;
}

const typeName = this.getTypeName(inputValue[0]);
if (typeName === InputArgument.TYPE_NUMBER) {
Expand Down Expand Up @@ -343,7 +352,7 @@ export class Runtime {
return minRecord;
};

private functionNotNull = (resolvedArgs: any[]) => {
private functionNotNull = (resolvedArgs: any[]): JSONValue => {
for (let i = 0; i < resolvedArgs.length; i += 1) {
if (this.getTypeName(resolvedArgs[i]) !== InputArgument.TYPE_NULL) {
return resolvedArgs[i];
Expand Down Expand Up @@ -372,7 +381,9 @@ export class Runtime {
};

private functionSortBy = (resolvedArgs: [number[] | string[], ExpressionNode]): number[] | string[] => {
if (!this._interpreter) return [];
if (!this._interpreter) {
return [];
}
const sortedArray = resolvedArgs[0].slice(0);
if (sortedArray.length === 0) {
return sortedArray;
Expand Down
9 changes: 5 additions & 4 deletions src/TreeInterpreter.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import {
Token,
ASTNode,
import type {
ExpressionNodeTree,
FieldNode,
ExpressionNode,
ValueNode,
ComparitorNode,
KeyValuePairNode,
ASTNode,
} from './Lexer.ts';
import { isFalse, isObject, strictDeepEqual } from './utils/index.ts';
import { Token } from './Lexer.ts';
import { Runtime } from './Runtime.ts';
import type { JSONValue, JSONObject } from './index.ts';
import type { JSONValue } from './index.ts';

export class TreeInterpreter {
runtime: Runtime;
Expand Down
3 changes: 2 additions & 1 deletion test/compliance.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable @typescript-eslint/no-var-requires */
import { search } from '../src/index.ts';

import { basename, join } from 'https://deno.land/[email protected]/path/mod.ts';
Expand Down
1 change: 1 addition & 0 deletions test/deno-shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function expect(actual) {
return {
toMatchObject(expected) { assertEquals(actual, expected) },
toEqual(expected) { assertEquals(actual, expected) },
toBe(expected) { assertEquals(actual, expected) },
toStrictEqual(expected) { assertStrictEquals(actual, expected) },
toThrow(message) { assertThrows(actual, Error, message) },
toContain(slice) { assertStringContains(actual, slice) },
Expand Down
208 changes: 208 additions & 0 deletions test/jmespath.extensions.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import jmespath, { search, registerFunction } from '../src/index.ts';

import { describe, it, expect } from './deno-shim.js';

describe('registerFunction', () => {
it('register a customFunction', () => {
expect(() =>
search(
{
foo: 60,
bar: 10,
},
'divide(foo, bar)',
),
).toThrow('Unknown function: divide()');
jmespath.registerFunction(
'divide',
resolvedArgs => {
const [dividend, divisor] = resolvedArgs;
return dividend / divisor;
},
[{ types: [jmespath.TYPE_NUMBER] }, { types: [jmespath.TYPE_NUMBER] }],
);
expect(() =>
search(
{
foo: 60,
bar: 10,
},
'divide(foo, bar)',
),
).not.toThrow();
expect(
search(
{
foo: 60,
bar: 10,
},
'divide(foo, bar)',
),
).toEqual(6);
});
it("won't register a customFunction if one already exists", () => {
expect(() =>
registerFunction(
'sum',
() => {
/* EMPTY FUNCTION */
},
[],
),
).toThrow('Function already defined: sum()');
});
it('alerts too few arguments', () => {
registerFunction(
'tooFewArgs',
() => {
/* EMPTY FUNCTION */
},
[{ types: [jmespath.TYPE_ANY] }, { types: [jmespath.TYPE_NUMBER], optional: true }],
);
expect(() =>
search(
{
foo: 60,
bar: 10,
},
'tooFewArgs()',
),
).toThrow('ArgumentError: tooFewArgs() takes 1 arguments but received 0');
expect(() =>
search(
{
foo: 60,
bar: 10,
},
'tooFewArgs(foo, @)',
),
).toThrow('TypeError: tooFewArgs() expected argument 2 to be type (number) but received type object instead.');
expect(() =>
search(
{
foo: 60,
bar: 10,
},
'tooFewArgs(foo, `2`, @)',
),
).toThrow('ArgumentError: tooFewArgs() takes 1 arguments but received 3');
});
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)',
),
).not.toThrow();
});

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');
});
});

describe('root', () => {
it('$ should give access to the root value', () => {
const value = search({ foo: { bar: 1 } }, 'foo.{ value: $.foo.bar }');
expect(value.value).toBe(1);
});
it('$ should give access to the root value after pipe', () => {
const value = search({ foo: { bar: 1 } }, 'foo | $.foo.bar');
expect(value).toEqual(1);
});
it('$ should give access in expressions', () => {
const value = search([{ foo: { bar: 1 } }, { foo: { bar: 99 } }], 'map(&foo.{boo: bar, root: $}, @)');
expect(value).toEqual([
{ boo: 1, root: [{ foo: { bar: 1 } }, { foo: { bar: 99 } }] },
{ boo: 99, root: [{ foo: { bar: 1 } }, { foo: { bar: 99 } }] },
]);
});
it('$ can be used in parallel', () => {
const value = search([{ foo: { bar: 1 } }, { foo: { bar: 99 } }], '[$[0].foo.bar, $[1].foo.bar]');
expect(value).toEqual([1, 99]);
});
});
Loading

0 comments on commit a5eeb82

Please sign in to comment.