Comparing version 1.5.0-canary.2 to 1.5.0-canary.3
{ | ||
"name": "groq-js", | ||
"version": "1.5.0-canary.2", | ||
"version": "1.5.0-canary.3", | ||
"keywords": [ | ||
@@ -83,11 +83,11 @@ "sanity", | ||
"devDependencies": { | ||
"@sanity/pkg-utils": "^5.0.0", | ||
"@sanity/pkg-utils": "^5.0.4", | ||
"@sanity/semantic-release-preset": "^4.1.7", | ||
"@types/debug": "^4.1.12", | ||
"@types/tap": "^15.0.11", | ||
"@typescript-eslint/eslint-plugin": "^7.1.1", | ||
"@typescript-eslint/parser": "^7.1.1", | ||
"@typescript-eslint/eslint-plugin": "^7.3.0", | ||
"@typescript-eslint/parser": "^7.3.0", | ||
"eslint": "^8.57.0", | ||
"eslint-config-prettier": "^9.1.0", | ||
"eslint-config-sanity": "^7.1.1", | ||
"eslint-config-sanity": "^7.1.2", | ||
"eslint-plugin-prettier": "^5.1.3", | ||
@@ -94,0 +94,0 @@ "eslint-plugin-simple-import-sort": "^12.0.0", |
@@ -372,18 +372,34 @@ import debug from 'debug' | ||
$trace('arrayCoerce.base %O', base) | ||
return mapUnion(base, (base) => { | ||
if (base.type !== 'array') { | ||
return {type: 'null'} satisfies NullTypeNode | ||
} | ||
return base | ||
}) | ||
return mapArray(base, scope, (base) => base) | ||
} | ||
function handleFlatMap(node: FlatMapNode, scope: Scope): TypeNode { | ||
const base = walk({node: node.base, scope}) | ||
return mapUnion(base, (base) => { | ||
if (base.type !== 'array') { | ||
return base | ||
} | ||
return mapArray(base, scope, (base) => { | ||
const inner = walk({node: node.expr, scope: scope.createHidden([base.of])}) | ||
return walk({node: node.expr, scope: scope.createHidden([base.of])}) | ||
return mapConcrete( | ||
inner, | ||
scope, | ||
(inner) => { | ||
if (inner.type === 'array') { | ||
return inner | ||
} | ||
return {type: 'array', of: inner} | ||
}, | ||
(nodes) => { | ||
const inner: TypeNode[] = [] | ||
for (const node of nodes) { | ||
// Bail out early if we've detected an unknown. | ||
if (node.type === 'unknown') return {type: 'array', of: node} | ||
// The mapper above ensures that all types returned are arrays. | ||
if (node.type !== 'array') throw new Error(`Unexpected type: ${node.type}`) | ||
inner.push(node.of) | ||
} | ||
return { | ||
type: 'array', | ||
of: optimizeUnions({type: 'union', of: inner}), | ||
} | ||
}, | ||
) | ||
}) | ||
@@ -394,54 +410,9 @@ } | ||
$trace('map.base %O', base) | ||
return mapUnion(base, (base) => { | ||
if (base.type !== 'array') { | ||
return base | ||
} | ||
// if this is an inline type we resolve it since we want to map over the actual value. | ||
return maybeResolveInline(base.of, scope, (base) => { | ||
if (base.type === 'union') { | ||
const value = walk({node: node.expr, scope: scope.createHidden(base.of)}) // re use the current parent, this is a "sub" scope | ||
$trace('map.expr %O', value) | ||
return { | ||
type: 'array', | ||
of: value, | ||
} satisfies ArrayTypeNode | ||
} | ||
if (base.type === 'object') { | ||
const value = walk({node: node.expr, scope: scope.createHidden([base])}) // re use the current parent, this is a "sub" scope | ||
$trace('map.expr %O', value) | ||
return { | ||
type: 'array', | ||
of: value, | ||
} satisfies ArrayTypeNode | ||
} | ||
return {type: 'unknown'} satisfies UnknownTypeNode | ||
}) | ||
}) | ||
} | ||
function mapProjectionInScope( | ||
base: TypeNode, | ||
scope: Scope, | ||
mapper: (field: TypeNode) => TypeNode, | ||
): TypeNode { | ||
if (base.type === 'union') { | ||
if (base.of.length === 1) { | ||
return mapProjectionInScope(base.of[0], scope, mapper) | ||
} | ||
const of = base.of.map((node) => mapProjectionInScope(node, scope, mapper)) | ||
return mapArray(base, scope, (base) => { | ||
return { | ||
type: 'union', | ||
of, | ||
type: 'array', | ||
of: walk({node: node.expr, scope: scope.createHidden([base.of])}), | ||
} | ||
} | ||
if (base.type === 'array') { | ||
return mapper(base.of) | ||
} | ||
return mapper(base) | ||
}) | ||
} | ||
@@ -453,15 +424,5 @@ | ||
if (base.type === 'unknown' || base.type === 'null') { | ||
return {type: 'null'} | ||
} | ||
return mapProjectionInScope(base, scope, (field) => { | ||
if (field.type === 'null' || field.type === 'unknown') { | ||
return {type: 'null'} | ||
} | ||
return walk({ | ||
node: node.expr, | ||
scope: scope.createNested([field]), | ||
}) | ||
}) | ||
return mapObject(base, scope, (base) => | ||
walk({node: node.expr, scope: scope.createNested([base])}), | ||
) | ||
} | ||
@@ -493,55 +454,2 @@ | ||
function mergeInlineObject(dst: ObjectTypeNode, src: ObjectTypeNode): ObjectTypeNode { | ||
return { | ||
type: 'object', | ||
attributes: {...dst.attributes, ...src.attributes}, | ||
rest: dst.rest, | ||
dereferencesTo: dst.dereferencesTo, | ||
} | ||
} | ||
function mapFieldInScope( | ||
field: TypeNode, | ||
scope: Scope, | ||
mapper: (field: Document | ObjectTypeNode) => TypeNode, | ||
): TypeNode { | ||
if (field.type === 'union') { | ||
return { | ||
type: 'union', | ||
of: field.of.map((subField) => mapFieldInScope(subField, scope, mapper)), | ||
} | ||
} | ||
if (field.type === 'inline') { | ||
return mapFieldInScope(scope.context.lookupTypeDeclaration(field), scope, mapper) | ||
} | ||
if (field.type === 'object') { | ||
// If the rest is not defined we can just map the object | ||
if (field.rest === undefined) { | ||
return mapper(field) | ||
} | ||
// If it's an unknown type it means that it's an open object and we can't be sure of the types | ||
if (field.rest.type === 'unknown') { | ||
return {type: 'unknown'} | ||
} | ||
// If it's an inline type we need to merge the inline type with the rest type | ||
// throw an error if the rest type is not an object | ||
if (field.rest.type === 'inline') { | ||
const rest = scope.context.lookupTypeDeclaration(field.rest) | ||
if (rest.type !== 'object') { | ||
throw new Error(`rest inline type must be an object, got ${rest.type}`) | ||
} | ||
return mapper(mergeInlineObject(field, rest)) | ||
} | ||
// We need to merge the rest type, which could now only be an object, with the current object | ||
return mapper(mergeInlineObject(field, field.rest)) | ||
} | ||
return {type: 'null'} | ||
} | ||
export function handleAccessAttributeNode(node: AccessAttributeNode, scope: Scope): TypeNode { | ||
@@ -552,5 +460,6 @@ let attributeBase: TypeNode = scope.value | ||
} | ||
$trace('accessAttribute.base %s %O', node.name, attributeBase) | ||
return mapFieldInScope(attributeBase, scope, (base) => { | ||
return mapObject(attributeBase, scope, (base) => { | ||
const attribute = base.attributes[node.name] | ||
@@ -565,5 +474,3 @@ if (attribute !== undefined) { | ||
} | ||
$warn( | ||
`attribute "${node.name}" not found in ${base.type === 'document' ? `document "${base.name}"` : 'object'}`, | ||
) | ||
$warn(`attribute "${node.name}" not found in object`) | ||
return {type: 'null'} | ||
@@ -574,17 +481,5 @@ }) | ||
function handleAccessElementNode(node: AccessElementNode, scope: Scope): TypeNode { | ||
if (!node.base) { | ||
return {type: 'unknown'} satisfies UnknownTypeNode | ||
} | ||
const base = walk({node: node.base, scope}) | ||
$trace('accessElement.base %O', base) | ||
return mapUnion(base, (base) => { | ||
if (base.type !== 'array') { | ||
return {type: 'null'} satisfies NullTypeNode | ||
} | ||
return { | ||
type: 'union', | ||
of: [base.of, {type: 'null'}], | ||
} satisfies UnionTypeNode | ||
}) | ||
return mapArray(base, scope, (base) => nullUnion(base.of)) | ||
} | ||
@@ -661,11 +556,4 @@ | ||
$trace('slice.node %O', node) | ||
const base = walk({node: node.base, scope}) | ||
return mapUnion(base, (base) => { | ||
if (base.type !== 'array') { | ||
return {type: 'null'} satisfies NullTypeNode | ||
} | ||
return base | ||
}) | ||
return mapArray(base, scope, (base) => base) | ||
} | ||
@@ -832,3 +720,2 @@ | ||
case 'Pos': | ||
case 'Slice': | ||
case 'Context': | ||
@@ -842,3 +729,3 @@ case 'Tuple': | ||
default: { | ||
// @ts-expect-error | ||
// @ts-expect-error - we should have handled all cases | ||
throw new Error(`unknown node type ${node.type}`) | ||
@@ -1157,12 +1044,66 @@ } | ||
function maybeResolveInline( | ||
type ConcreteTypeNode = | ||
| BooleanTypeNode | ||
| NullTypeNode | ||
| NumberTypeNode | ||
| StringTypeNode | ||
| ArrayTypeNode | ||
| ObjectTypeNode | ||
/** | ||
* mapConcrete extracts a _concrete type_ from a type node, applies the mapping | ||
* function to it and returns. Most notably, this will work through unions | ||
* (applying the mapping function for each variant) and inline (resolving the | ||
* reference). | ||
* | ||
* An `unknown` input type causes it to return `unknown` as well. | ||
* | ||
* After encountering unions the resulting types gets passed into `mergeUnions`. | ||
* By default this will just union them together again. | ||
*/ | ||
function mapConcrete( | ||
node: TypeNode, | ||
scope: Scope, | ||
mapper: (node: TypeNode) => TypeNode, | ||
mapper: (node: ConcreteTypeNode) => TypeNode, | ||
mergeUnions: (nodes: TypeNode[]) => TypeNode = (nodes) => | ||
optimizeUnions({type: 'union', of: nodes}), | ||
): TypeNode { | ||
if (node.type === 'inline') { | ||
const resolvedInline = scope.context.lookupTypeDeclaration(node) | ||
return mapper(resolvedInline) | ||
switch (node.type) { | ||
case 'boolean': | ||
case 'array': | ||
case 'null': | ||
case 'object': | ||
case 'string': | ||
case 'number': | ||
return mapper(node) | ||
case 'unknown': | ||
return node | ||
case 'union': | ||
return mergeUnions(node.of.map((inner) => mapConcrete(inner, scope, mapper), mergeUnions)) | ||
case 'inline': { | ||
const resolvedInline = scope.context.lookupTypeDeclaration(node) | ||
return mapConcrete(resolvedInline, scope, mapper, mergeUnions) | ||
} | ||
default: | ||
// @ts-expect-error | ||
throw new Error(`Unknown type: ${node.type}`) | ||
} | ||
return mapper(node) | ||
} | ||
function mapArray( | ||
node: TypeNode, | ||
scope: Scope, | ||
mapper: (node: ArrayTypeNode) => TypeNode, | ||
): TypeNode { | ||
return mapConcrete(node, scope, (base) => (base.type === 'array' ? mapper(base) : {type: 'null'})) | ||
} | ||
function mapObject( | ||
node: TypeNode, | ||
scope: Scope, | ||
mapper: (node: ObjectTypeNode) => TypeNode, | ||
): TypeNode { | ||
return mapConcrete(node, scope, (base) => | ||
base.type === 'object' ? mapper(base) : {type: 'null'}, | ||
) | ||
} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
824844
11798