From 14342a69b0f2b84db2aad580755ed1c974a7c7bd Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Thu, 12 Oct 2023 16:29:38 -0400 Subject: [PATCH 1/5] TEST: Pass expression functions as context for testing expressions --- bids-validator/src/tests/schema-expression-language.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bids-validator/src/tests/schema-expression-language.test.ts b/bids-validator/src/tests/schema-expression-language.test.ts index cc9b50e0c..f7fc65374 100644 --- a/bids-validator/src/tests/schema-expression-language.test.ts +++ b/bids-validator/src/tests/schema-expression-language.test.ts @@ -4,6 +4,7 @@ import { colors } from '../deps/fmt.ts' import { BIDSContext } from '../schema/context.ts' import { assert, assertEquals } from '../deps/asserts.ts' import { evalCheck } from '../schema/applyRules.ts' +import { expressionFunctions } from '../schema/expressionLanguage.ts' const schema = await loadSchema() const pretty_null = (x: string | null): string => (x === null ? 'null' : x) @@ -15,7 +16,9 @@ Deno.test('validate schema expression tests', async (t) => { ) for (const test of schema.meta.expression_tests) { await t.step(`${test.expression} evals to ${test.result}`, () => { - const actual_result = evalCheck(test.expression, {} as BIDSContext) + // @ts-expect-error + const context = expressionFunctions as BIDSContext + const actual_result = evalCheck(test.expression, context) if (actual_result == test.result) { results.push([ colors.cyan(test.expression), From 0210488aaf591946d28e03dca1cd53411822a05a Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Thu, 12 Oct 2023 17:09:34 -0400 Subject: [PATCH 2/5] FIX: Handle nulls in min/max/substr --- bids-validator/src/schema/expressionLanguage.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/bids-validator/src/schema/expressionLanguage.ts b/bids-validator/src/schema/expressionLanguage.ts index e6036a3f8..01bc1d48d 100644 --- a/bids-validator/src/schema/expressionLanguage.ts +++ b/bids-validator/src/schema/expressionLanguage.ts @@ -52,11 +52,15 @@ export const expressionFunctions = { } return typeof operand }, - min: (list: number[]): number => { - return Math.min(...list) + min: (list: number[]): number | null => { + return list != null + ? Math.min(...list.filter((x) => typeof x === 'number')) + : null }, - max: (list: number[]): number => { - return Math.max(...list) + max: (list: number[]): number | null => { + return list != null + ? Math.max(...list.filter((x) => typeof x === 'number')) + : null }, length: (list: T[]): number | null => { if (Array.isArray(list) || typeof list == 'string') { @@ -68,7 +72,10 @@ export const expressionFunctions = { return list.filter((x) => x === val).length }, exists: exists, - substr: (arg: string, start: number, end: number): string => { + substr: (arg: string, start: number, end: number): string | null => { + if (arg == null || start == null || end == null) { + return null + } return arg.substr(start, end - start) }, sorted: (list: T[]): T[] => { From 266dfbaedb71bba060b896566b0fd7d731ab5e8e Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Thu, 12 Oct 2023 17:09:49 -0400 Subject: [PATCH 3/5] FIX: Use array-aware equality check --- .../src/tests/schema-expression-language.test.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bids-validator/src/tests/schema-expression-language.test.ts b/bids-validator/src/tests/schema-expression-language.test.ts index f7fc65374..798f640bf 100644 --- a/bids-validator/src/tests/schema-expression-language.test.ts +++ b/bids-validator/src/tests/schema-expression-language.test.ts @@ -9,6 +9,13 @@ import { expressionFunctions } from '../schema/expressionLanguage.ts' const schema = await loadSchema() const pretty_null = (x: string | null): string => (x === null ? 'null' : x) +const equal = (a: T, b: T): boolean => { + if (Array.isArray(a) && Array.isArray(b)) { + return a.length === b.length && a.every((val, idx) => val === b[idx]) + } + return a === b +} + Deno.test('validate schema expression tests', async (t) => { const results: string[][] = [] const header = ['expression', 'desired', 'actual', 'result'].map((x) => @@ -19,7 +26,7 @@ Deno.test('validate schema expression tests', async (t) => { // @ts-expect-error const context = expressionFunctions as BIDSContext const actual_result = evalCheck(test.expression, context) - if (actual_result == test.result) { + if (equal(actual_result, test.result)) { results.push([ colors.cyan(test.expression), pretty_null(test.result), From c04a63a5047a89dd26754acab9ea243b4af35260 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Thu, 12 Oct 2023 19:13:54 -0400 Subject: [PATCH 4/5] FIX: type(null) = "null" --- bids-validator/src/schema/expressionLanguage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bids-validator/src/schema/expressionLanguage.ts b/bids-validator/src/schema/expressionLanguage.ts index 01bc1d48d..9817e982a 100644 --- a/bids-validator/src/schema/expressionLanguage.ts +++ b/bids-validator/src/schema/expressionLanguage.ts @@ -47,7 +47,7 @@ export const expressionFunctions = { if (Array.isArray(operand)) { return 'array' } - if (typeof operand === 'undefined') { + if (typeof operand === 'undefined' || operand === null) { return 'null' } return typeof operand From 2d6288c11c4eb0879824dc568d7c3227b505caee Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Thu, 12 Oct 2023 19:17:42 -0400 Subject: [PATCH 5/5] FIX: exists(null, ...) == 0 --- bids-validator/src/schema/expressionLanguage.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bids-validator/src/schema/expressionLanguage.ts b/bids-validator/src/schema/expressionLanguage.ts index 9817e982a..88e58e5ce 100644 --- a/bids-validator/src/schema/expressionLanguage.ts +++ b/bids-validator/src/schema/expressionLanguage.ts @@ -1,4 +1,8 @@ function exists(list: string[], rule: string = 'dataset'): number { + if (list == null) { + return 0 + } + const prefix: string[] = [] // Stimuli and subject-relative paths get prefixes