diff --git a/src/typeEvaluator/typeEvaluate.ts b/src/typeEvaluator/typeEvaluate.ts index 855ef191..986733aa 100644 --- a/src/typeEvaluator/typeEvaluate.ts +++ b/src/typeEvaluator/typeEvaluate.ts @@ -18,10 +18,12 @@ import type { FilterNode, FlatMapNode, MapNode, + NegNode, NotNode, ObjectNode, OpCallNode, ParentNode, + PosNode, ProjectionNode, SelectNode, SliceNode, @@ -583,6 +585,24 @@ function handleNotNode(node: NotNode, scope: Scope): TypeNode { return {type: 'boolean'} } +function handleNegNode(node: NegNode, scope: Scope): NumberTypeNode | NullTypeNode { + const base = walk({node: node.base, scope}) + if (base.type !== 'number') { + return {type: 'null'} + } + if (base.value !== undefined) { + return {type: 'number', value: -base.value} + } + return base +} +function handlePosNode(node: PosNode, scope: Scope): NumberTypeNode | NullTypeNode { + const base = walk({node: node.base, scope}) + if (base.type !== 'number') { + return {type: 'null'} + } + return base +} + function handleEverythingNode(_: EverythingNode, scope: Scope): TypeNode { return { type: 'array', @@ -713,11 +733,15 @@ export function walk({node, scope}: {node: ExprNode; scope: Scope}): TypeNode { case 'Slice': { return handleSlice(node, scope) } + case 'Neg': { + return handleNegNode(node, scope) + } + case 'Pos': { + return handlePosNode(node, scope) + } // everything else case 'Asc': case 'Desc': - case 'Neg': - case 'Pos': case 'Context': case 'Tuple': case 'Selector': diff --git a/test/typeEvaluate.test.ts b/test/typeEvaluate.test.ts index 16289547..44747bd8 100644 --- a/test/typeEvaluate.test.ts +++ b/test/typeEvaluate.test.ts @@ -1838,6 +1838,82 @@ t.test('this operator', (t) => { t.end() }) +t.test('neg node', (t) => { + const query = `*[_type == "author"] { + "minusAge": -age, + "notNumber": -name, + "constant": -(3 + 4), + }` + const ast = parse(query) + const res = typeEvaluate(ast, schemas) + t.strictSame(res, { + type: 'array', + of: { + type: 'object', + attributes: { + minusAge: { + type: 'objectAttribute', + value: { + type: 'number', + }, + }, + notNumber: { + type: 'objectAttribute', + value: { + type: 'null', + }, + }, + constant: { + type: 'objectAttribute', + value: { + type: 'number', + value: -7, + }, + }, + }, + }, + }) + t.end() +}) + +t.test('pos node', (t) => { + const query = `*[_type == "author"] { + "age": +age, + "notNumber": +name, + "constant": +(3 + 4), + }` + const ast = parse(query) + const res = typeEvaluate(ast, schemas) + t.strictSame(res, { + type: 'array', + of: { + type: 'object', + attributes: { + age: { + type: 'objectAttribute', + value: { + type: 'number', + }, + }, + notNumber: { + type: 'objectAttribute', + value: { + type: 'null', + }, + }, + constant: { + type: 'objectAttribute', + value: { + type: 'number', + value: 7, + }, + }, + }, + }, + }) + t.end() +}) + function findSchemaType(name: string): TypeNode { const type = schemas.find((s) => s.name === name) if (!type) {