diff --git a/bids-validator/src/schema/expressionLanguage.ts b/bids-validator/src/schema/expressionLanguage.ts index e6036a3f8..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 @@ -47,16 +51,20 @@ export const expressionFunctions = { if (Array.isArray(operand)) { return 'array' } - if (typeof operand === 'undefined') { + if (typeof operand === 'undefined' || operand === null) { return 'null' } 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 +76,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[] => { diff --git a/bids-validator/src/tests/schema-expression-language.test.ts b/bids-validator/src/tests/schema-expression-language.test.ts index cc9b50e0c..798f640bf 100644 --- a/bids-validator/src/tests/schema-expression-language.test.ts +++ b/bids-validator/src/tests/schema-expression-language.test.ts @@ -4,10 +4,18 @@ 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) +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) => @@ -15,8 +23,10 @@ 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) - if (actual_result == test.result) { + // @ts-expect-error + const context = expressionFunctions as BIDSContext + const actual_result = evalCheck(test.expression, context) + if (equal(actual_result, test.result)) { results.push([ colors.cyan(test.expression), pretty_null(test.result),