Skip to content

Commit

Permalink
feat(typeEvaluator): add support for array functions
Browse files Browse the repository at this point in the history
  • Loading branch information
sgulseth committed May 15, 2024
1 parent 7d1a264 commit 86b1053
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 2 deletions.
53 changes: 53 additions & 0 deletions src/typeEvaluator/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,59 @@ function unionWithoutNull(unionTypeNode: TypeNode): TypeNode {

export function handleFuncCallNode(node: FuncCallNode, scope: Scope): TypeNode {
switch (`${node.namespace}.${node.name}`) {
case 'array.compact': {
const arg = walk({node: node.args[0], scope})

return mapConcrete(arg, scope, (arg) => {
if (arg.type !== 'array') {
return {type: 'null'}
}

const of = mapConcrete(arg.of, scope, (of) => of)
return {
type: 'array',
of: unionWithoutNull(of),
}
})
}

case 'array.join': {
const arrayArg = walk({node: node.args[0], scope})
const sepArg = walk({node: node.args[1], scope})

return mapConcrete(arrayArg, scope, (arrayArg) =>
mapConcrete(sepArg, scope, (sepArg) => {
if (arrayArg.type !== 'array') {
return {type: 'null'}
}
if (sepArg.type !== 'string') {
return {type: 'null'}
}

return mapConcrete(arrayArg.of, scope, (of) => {
// we can only join strings, numbers, and booleans
if (of.type !== 'string' && of.type !== 'number' && of.type !== 'boolean') {
return {type: 'unknown'}
}

return {type: 'string'}
})
}),
)
}

case 'array.unique': {
const arg = walk({node: node.args[0], scope})

return mapConcrete(arg, scope, (arg) => {
if (arg.type !== 'array') {
return {type: 'null'}
}

return arg
})
}

case 'global.defined': {
return {type: 'boolean'}
}
Expand Down
118 changes: 118 additions & 0 deletions test/typeEvaluate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2203,6 +2203,124 @@ t.test('function: string::split', (t) => {
t.end()
})

t.test('function: array::compact', (t) => {
const query = `*[_type == "author"] {
"ages": array::compact(ages),
"tuple": array::compact([1,2, true, null]),
}`
const ast = parse(query)
const res = typeEvaluate(ast, schemas)
t.strictSame(res, {
type: 'array',
of: {
type: 'object',
attributes: {
ages: {
type: 'objectAttribute',
value: nullUnion({
type: 'array',
of: {
type: 'number',
},
}),
},
tuple: {
type: 'objectAttribute',
value: {
type: 'array',
of: {
type: 'union',
of: [
{type: 'boolean', value: true},
{type: 'number', value: 1},
{type: 'number', value: 2},
],
},
},
},
},
},
})
t.end()
})

t.test('function: array::join', (t) => {
const query = `*[_type == "author"] {
"ages": array::join(ages, " "),
"tuple": array::join([1,2, true], " "),
"invalidSep": array::join(ages, 123),
}`
const ast = parse(query)
const res = typeEvaluate(ast, schemas)
t.strictSame(res, {
type: 'array',
of: {
type: 'object',
attributes: {
ages: {
type: 'objectAttribute',
value: nullUnion({
type: 'string',
}),
},
tuple: {
type: 'objectAttribute',
value: {
type: 'string',
},
},
invalidSep: {
type: 'objectAttribute',
value: {
type: 'null',
},
},
},
},
})
t.end()
})

t.test('function: array::unique', (t) => {
const query = `*[_type == "author"] {
"ages": array::unique(ages),
"tuple": array::unique([1,2]),
}`
const ast = parse(query)
const res = typeEvaluate(ast, schemas)
t.strictSame(res, {
type: 'array',
of: {
type: 'object',
attributes: {
ages: {
type: 'objectAttribute',
value: nullUnion({
type: 'array',
of: {
type: 'number',
},
}),
},
tuple: {
type: 'objectAttribute',
value: {
type: 'array',
of: {
type: 'union',
of: [
{type: 'number', value: 1},
{type: 'number', value: 2},
],
},
},
},
},
},
})
t.end()
})

t.test('function: math::*', (t) => {
const query = `*[_type == "author"] {
"ages": math::min(ages),
Expand Down
25 changes: 23 additions & 2 deletions test/typeEvaluateCompare.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,14 +417,16 @@ for (const op of ops) {
})
}

const functionTests: {namespace: string; funcName: string}[] = [
const unaryFunctionTests: {namespace: string; funcName: string}[] = [
{namespace: 'math', funcName: 'sum'},
{namespace: 'math', funcName: 'min'},
{namespace: 'math', funcName: 'max'},
{namespace: 'math', funcName: 'avg'},
{namespace: 'array', funcName: 'compact'},
{namespace: 'array', funcName: 'unique'},
]

for (const {namespace, funcName} of functionTests) {
for (const {namespace, funcName} of unaryFunctionTests) {
t.test(`${namespace}::${funcName}`, async (t) => {
subtestUnary({
t,
Expand All @@ -438,3 +440,22 @@ for (const {namespace, funcName} of functionTests) {
})
})
}

const binaryFunctionTests: {namespace: string; funcName: string}[] = [
{namespace: 'array', funcName: 'join'},
]

for (const {namespace, funcName} of binaryFunctionTests) {
t.test(`${namespace}::${funcName}`, async (t) => {
subtestBinary({
t,
build: (param1, param2) => ({
type: 'FuncCall',
name: funcName,
namespace,
args: [param1, param2],
func: namespaces[namespace]![funcName] as GroqFunction,
}),
})
})
}

0 comments on commit 86b1053

Please sign in to comment.