diff --git a/src/typeEvaluator/typeEvaluate.ts b/src/typeEvaluator/typeEvaluate.ts index 10c930cf..e6fdaf8e 100644 --- a/src/typeEvaluator/typeEvaluate.ts +++ b/src/typeEvaluator/typeEvaluate.ts @@ -119,7 +119,7 @@ function handleDerefNode(node: DerefNode, scope: Scope): TypeNode { function mapObjectSplat( node: TypeNode, scope: Scope, - mapper: (field: Document | ObjectTypeNode) => void, + mapper: (name: string, attribute: ObjectAttribute) => void, ) { if (node.type === 'union') { for (const scoped of node.of) { @@ -128,7 +128,22 @@ function mapObjectSplat( } if (node.type === 'object') { - mapper(node) + // if the rest is unknown the entire object is unknown + if (node.rest !== undefined && node.rest.type === 'unknown') { + return + } + + for (const name in node.attributes) { + if (!node.attributes.hasOwnProperty(name)) { + continue + } + mapper(name, node.attributes[name]) + } + + if (node.rest !== undefined) { + const rest = mapConcrete(node.rest, scope, (rest) => rest) + mapObjectSplat(rest, scope, mapper) + } } } function handleObjectNode(node: ObjectNode, scope: Scope) { @@ -147,14 +162,8 @@ function handleObjectNode(node: ObjectNode, scope: Scope) { if (attr.type === 'ObjectSplat') { const value = walk({node: attr.value, scope}) $trace('object.splat.value %O', value) - mapObjectSplat(value, scope, (node) => { - for (const name in node.attributes) { - if (!Object.hasOwn(node.attributes, name)) { - continue - } - - attributes[name] = node.attributes[name] - } + mapObjectSplat(value, scope, (name, attribute) => { + attributes[name] = attribute }) } if (attr.type === 'ObjectConditionalSplat') { @@ -163,24 +172,17 @@ function handleObjectNode(node: ObjectNode, scope: Scope) { if (condition || condition === undefined) { const value = walk({node: attr.value, scope}) - mapObjectSplat(value, scope, (node) => { - for (const name in node.attributes) { - if (!Object.hasOwn(node.attributes, name)) { - continue - } - const attribute = node.attributes[name] - - if (condition) { - attributes[name] = attribute - } else if (condition === undefined) { - attributes[name] = { - type: 'objectAttribute', - value: attribute.value, - optional: true, - } - } else { - throw new Error('Unexpected condition') + mapObjectSplat(value, scope, (name, attribute) => { + if (condition) { + attributes[name] = attribute + } else if (condition === undefined) { + attributes[name] = { + type: 'objectAttribute', + value: attribute.value, + optional: true, } + } else { + throw new Error('Unexpected condition') } }) } diff --git a/test/typeEvaluate.test.ts b/test/typeEvaluate.test.ts index 2aa7d70e..c2ee1729 100644 --- a/test/typeEvaluate.test.ts +++ b/test/typeEvaluate.test.ts @@ -2169,6 +2169,233 @@ t.test('function: string::split', (t) => { t.end() }) +t.test('splat object with inline', (t) => { + const query = `*[_type == "author" || _type == "post" || _type == "test"] { + _type == "author" => { + "bar": _id + }, + _type == "test" => { + foo[] { + _type, + _type != "slug" => @ + } + } + }` + + const ast = parse(query) + const res = typeEvaluate(ast, [ + ...schemas, + { + name: 'inline1', + type: 'type', + value: { + type: 'object', + attributes: { + _type: { + type: 'objectAttribute', + value: { + type: 'string', + value: 'inline1', + }, + }, + inlineValue1: { + type: 'objectAttribute', + value: { + type: 'string', + }, + }, + }, + }, + }, + { + name: 'inline2', + type: 'type', + value: { + type: 'object', + attributes: { + _type: { + type: 'objectAttribute', + value: { + type: 'string', + value: 'inline2', + }, + }, + inlineValue2: { + type: 'objectAttribute', + value: { + type: 'string', + }, + }, + }, + }, + }, + { + name: 'test', + type: 'document', + attributes: { + _type: { + type: 'objectAttribute', + value: { + type: 'string', + value: 'test', + }, + }, + foo: { + type: 'objectAttribute', + value: { + type: 'array', + of: { + type: 'union', + of: [ + { + type: 'object', + attributes: { + _key: { + type: 'objectAttribute', + value: {type: 'string'}, + }, + }, + rest: { + type: 'inline', + name: 'inline1', + }, + }, + { + type: 'object', + attributes: { + _key: { + type: 'objectAttribute', + value: {type: 'string'}, + }, + }, + rest: { + type: 'inline', + name: 'inline2', + }, + }, + { + type: 'object', + attributes: { + _key: { + type: 'objectAttribute', + value: {type: 'string'}, + }, + }, + rest: { + type: 'inline', + name: 'slug', + }, + }, + ], + }, + }, + }, + }, + }, + ]) + + t.strictSame(res, { + type: 'array', + of: { + type: 'union', + of: [ + { + type: 'object', + attributes: {}, + }, + { + type: 'object', + attributes: { + bar: { + type: 'objectAttribute', + value: { + type: 'string', + }, + }, + }, + }, + { + type: 'object', + attributes: { + foo: { + type: 'objectAttribute', + value: { + type: 'array', + of: { + type: 'union', + of: [ + { + type: 'object', + attributes: { + _type: { + type: 'objectAttribute', + value: { + type: 'string', + value: 'inline1', + }, + }, + _key: { + type: 'objectAttribute', + value: { + type: 'string', + }, + }, + inlineValue1: { + type: 'objectAttribute', + value: { + type: 'string', + }, + }, + }, + }, + { + type: 'object', + attributes: { + _type: { + type: 'objectAttribute', + value: { + type: 'string', + value: 'inline2', + }, + }, + _key: { + type: 'objectAttribute', + value: { + type: 'string', + }, + }, + inlineValue2: { + type: 'objectAttribute', + value: { + type: 'string', + }, + }, + }, + }, + { + type: 'object', + attributes: { + _type: { + type: 'objectAttribute', + value: { + type: 'string', + value: 'slug', + }, + }, + }, + }, + ], + }, + }, + }, + }, + }, + ], + }, + }) + t.end() +}) + function findSchemaType(name: string): TypeNode { const type = schemas.find((s) => s.name === name) if (!type) {