Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

groq-js

Package Overview
Dependencies
Maintainers
56
Versions
72
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

groq-js - npm Package Compare versions

Comparing version 1.12.0 to 1.13.0

src/typeEvaluator/booleans.ts

2

package.json
{
"name": "groq-js",
"version": "1.12.0",
"version": "1.13.0",
"keywords": [

@@ -5,0 +5,0 @@ "sanity",

@@ -122,3 +122,3 @@ # GROQ-JS<!-- omit in toc -->

```shell
GROQTEST_SUITE_VERSION=v0.1.33 ./test/generate.sh
GROQTEST_SUITE_VERSION=v0.1.45 ./test/generate.sh
```

@@ -125,0 +125,0 @@

@@ -438,3 +438,3 @@ import type {ExprNode} from '../nodeTypes'

// All the document are a version of the given ID if:
// 1. Document ID is of the ford bundleId.documentGroupId
// 1. Document ID is of the form bundleId.documentGroupId
// 2. And, they have a field called _version which is an object.

@@ -462,2 +462,34 @@ const versionIds: string[] = []

// eslint-disable-next-line require-await
sanity['documentsOf'] = async function (args, scope, execute) {
if (!scope.source.isArray()) return NULL_VALUE
const value = await execute(args[0], scope)
if (value.type !== 'string') return NULL_VALUE
const baseId = value.data
// A document belongs to a bundle ID if:
// 1. Document ID is of the form bundleId.documentGroupId
// 2. And, they have a field called _version which is an object.
const documentIdsInBundle: string[] = []
for await (const value of scope.source) {
if (getType(value) === 'object') {
const val = await value.get()
if (
val &&
'_id' in val &&
val._id.split('.').length === 2 &&
val._id.startsWith(`${baseId}.`) &&
'_version' in val &&
typeof val._version === 'object'
) {
documentIdsInBundle.push(val._id)
}
}
}
return fromJS(documentIdsInBundle)
}
sanity['documentsOf'].arity = 1
export type GroqPipeFunction = (

@@ -464,0 +496,0 @@ base: Value,

@@ -5,3 +5,3 @@ /* eslint-disable max-statements */

import {walk} from './typeEvaluate'
import {mapConcrete, nullUnion} from './typeHelpers'
import {mapNode, nullUnion} from './typeHelpers'
import type {NullTypeNode, TypeNode} from './types'

@@ -19,2 +19,3 @@

// eslint-disable-next-line complexity
export function handleFuncCallNode(node: FuncCallNode, scope: Scope): TypeNode {

@@ -25,3 +26,6 @@ switch (`${node.namespace}.${node.name}`) {

return mapConcrete(arg, scope, (arg) => {
return mapNode(arg, scope, (arg) => {
if (arg.type === 'unknown') {
return nullUnion({type: 'array', of: {type: 'unknown'}})
}
if (arg.type !== 'array') {

@@ -31,3 +35,3 @@ return {type: 'null'}

const of = mapConcrete(arg.of, scope, (of) => of)
const of = mapNode(arg.of, scope, (of) => of)
return {

@@ -44,15 +48,18 @@ type: 'array',

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

@@ -69,3 +76,6 @@

return mapConcrete(arg, scope, (arg) => {
return mapNode(arg, scope, (arg) => {
if (arg.type === 'unknown') {
return nullUnion({type: 'array', of: {type: 'unknown'}})
}
if (arg.type !== 'array') {

@@ -82,14 +92,17 @@ return {type: 'null'}

return mapConcrete(arg, scope, (arg) => {
if (arg.type === 'string') {
if (arg.value !== undefined) {
return {
type: 'string',
value: arg.value.toLowerCase(),
}
return mapNode(arg, scope, (arg) => {
if (arg.type === 'unknown') {
return nullUnion({type: 'string'})
}
if (arg.type !== 'string') {
return {type: 'null'}
}
if (arg.value !== undefined) {
return {
type: 'string',
value: arg.value.toLowerCase(),
}
return {type: 'string'}
}
return {type: 'null'}
return {type: 'string'}
})

@@ -100,14 +113,16 @@ }

return mapConcrete(arg, scope, (arg) => {
if (arg.type === 'string') {
if (arg.value !== undefined) {
return {
type: 'string',
value: arg.value.toUpperCase(),
}
return mapNode(arg, scope, (arg) => {
if (arg.type === 'unknown') {
return nullUnion({type: 'string'})
}
if (arg.type !== 'string') {
return {type: 'null'}
}
if (arg.value !== undefined) {
return {
type: 'string',
value: arg.value.toUpperCase(),
}
return {type: 'string'}
}
return {type: 'null'}
return {type: 'string'}
})

@@ -124,2 +139,16 @@ }

}
case 'global.path': {
const arg = walk({node: node.args[0], scope})
return mapNode(arg, scope, (arg) => {
if (arg.type === 'unknown') {
return nullUnion({type: 'string'})
}
if (arg.type === 'string') {
return {type: 'string'}
}
return {type: 'null'}
})
}
case 'global.coalesce': {

@@ -151,3 +180,7 @@ if (node.args.length === 0) {

return mapConcrete(arg, scope, (arg) => {
return mapNode(arg, scope, (arg) => {
if (arg.type === 'unknown') {
return nullUnion({type: 'string'})
}
if (arg.type === 'array') {

@@ -164,3 +197,7 @@ return {type: 'number'}

return mapConcrete(arg, scope, (arg) => {
return mapNode(arg, scope, (arg) => {
if (arg.type === 'unknown') {
return nullUnion({type: 'string'})
}
if (arg.type === 'string') {

@@ -177,8 +214,7 @@ return nullUnion({type: 'string'}) // we don't know wether the string is a valid date or not, so we return a [null, string]-union

return mapConcrete(arg, scope, (arg) => {
if (arg.type === 'array') {
return {type: 'number'}
return mapNode(arg, scope, (arg) => {
if (arg.type === 'unknown') {
return nullUnion({type: 'number'})
}
if (arg.type === 'string') {
if (arg.type === 'array' || arg.type === 'string') {
return {type: 'number'}

@@ -198,3 +234,7 @@ }

return mapConcrete(numNode, scope, (num) => {
return mapNode(numNode, scope, (num) => {
if (num.type === 'unknown') {
return nullUnion({type: 'number'})
}
if (num.type !== 'number') {

@@ -205,3 +245,7 @@ return {type: 'null'}

const precisionNode = walk({node: node.args[1], scope})
return mapConcrete(precisionNode, scope, (precision) => {
return mapNode(precisionNode, scope, (precision) => {
if (precision.type === 'unknown') {
return nullUnion({type: 'number'})
}
if (precision.type !== 'number') {

@@ -221,3 +265,7 @@ return {type: 'null'}

const arg = walk({node: node.args[0], scope})
return mapConcrete(arg, scope, (node) => {
return mapNode(arg, scope, (node) => {
if (node.type === 'unknown') {
return nullUnion({type: 'string'})
}
if (node.type === 'string' || node.type === 'number' || node.type === 'boolean') {

@@ -242,17 +290,25 @@ if (node.value) {

const values = walk({node: node.args[0], scope})
// use mapConcrete to get concrete resolved value, it will also handle cases where the value is a union
return mapConcrete(values, scope, (node) => {
// use mapNode to get concrete resolved value, it will also handle cases where the value is a union
return mapNode(values, scope, (node) => {
if (node.type === 'unknown') {
return nullUnion({type: 'number'})
}
// Aggregate functions can only be applied to arrays
if (node.type === 'array') {
// Resolve the concrete type of the array elements
return mapConcrete(node.of, scope, (node) => {
// Math functions can only be applied to numbers, but we should also ignore nulls
if (node.type === 'number' || node.type === 'null') {
return {type: 'number'}
}
return {type: 'null'}
})
if (node.type !== 'array') {
return {type: 'null'}
}
return {type: 'null'}
// Resolve the concrete type of the array elements
return mapNode(node.of, scope, (node) => {
if (node.type === 'unknown') {
return nullUnion({type: 'number'})
}
// Math functions can only be applied to numbers, but we should also ignore nulls
if (node.type === 'number' || node.type === 'null') {
return {type: 'number'}
}
return {type: 'null'}
})
})

@@ -263,17 +319,24 @@ }

const values = walk({node: node.args[0], scope})
// use mapConcrete to get concrete resolved value, it will also handle cases where the value is a union
return mapConcrete(values, scope, (node) => {
// use mapNode to get concrete resolved value, it will also handle cases where the value is a union
return mapNode(values, scope, (node) => {
if (node.type === 'unknown') {
return nullUnion({type: 'number'})
}
// Aggregate functions can only be applied to arrays
if (node.type === 'array') {
// Resolve the concrete type of the array elements
return mapConcrete(node.of, scope, (node) => {
// Math functions can only be applied to numbers
if (node.type === 'number') {
return {type: 'number'}
}
return {type: 'null'}
})
if (node.type !== 'array') {
return {type: 'null'}
}
// Resolve the concrete type of the array elements
return mapNode(node.of, scope, (node) => {
if (node.type === 'unknown') {
return nullUnion({type: 'number'})
}
return {type: 'null'}
// Math functions can only be applied to numbers
if (node.type === 'number') {
return {type: 'number'}
}
return {type: 'null'}
})
})

@@ -285,17 +348,25 @@ }

const values = walk({node: node.args[0], scope})
// use mapConcrete to get concrete resolved value, it will also handle cases where the value is a union
return mapConcrete(values, scope, (node) => {
// use mapNode to get concrete resolved value, it will also handle cases where the value is a union
return mapNode(values, scope, (node) => {
if (node.type === 'unknown') {
return nullUnion({type: 'number'})
}
// Aggregate functions can only be applied to arrays
if (node.type === 'array') {
// Resolve the concrete type of the array elements
return mapConcrete(node.of, scope, (node) => {
// Math functions can only be applied to numbers
if (node.type === 'number') {
return node
}
return {type: 'null'}
})
if (node.type !== 'array') {
return {type: 'null'}
}
return {type: 'null'}
// Resolve the concrete type of the array elements
return mapNode(node.of, scope, (node) => {
if (node.type === 'unknown') {
return nullUnion({type: 'number'})
}
// Math functions can only be applied to numbers
if (node.type === 'number') {
return node
}
return {type: 'null'}
})
})

@@ -312,12 +383,13 @@ }

}
case 'string.startsWith': {
const strTypeNode = walk({node: node.args[0], scope})
const prefixTypeNode = walk({node: node.args[1], scope})
return mapConcrete(strTypeNode, scope, (strNode) => {
if (strNode.type !== 'string') {
return {type: 'null'}
}
return mapNode(strTypeNode, scope, (strNode) => {
return mapNode(prefixTypeNode, scope, (prefixNode) => {
if (strNode.type === 'unknown' || prefixNode.type === 'unknown') {
return nullUnion({type: 'boolean'})
}
return mapConcrete(prefixTypeNode, scope, (prefixNode) => {
if (prefixNode.type !== 'string') {
if (strNode.type !== 'string' || prefixNode.type !== 'string') {
return {type: 'null'}

@@ -333,9 +405,9 @@ }

const sepTypeNode = walk({node: node.args[1], scope})
return mapConcrete(strTypeNode, scope, (strNode) => {
if (strNode.type !== 'string') {
return {type: 'null'}
}
return mapNode(strTypeNode, scope, (strNode) => {
return mapNode(sepTypeNode, scope, (sepNode) => {
if (strNode.type === 'unknown' || sepNode.type === 'unknown') {
return nullUnion({type: 'array', of: {type: 'string'}})
}
return mapConcrete(sepTypeNode, scope, (sepNode) => {
if (sepNode.type !== 'string') {
if (strNode.type !== 'string' || sepNode.type !== 'string') {
return {type: 'null'}

@@ -350,3 +422,6 @@ }

const typeNode = walk({node: node.args[0], scope})
return mapConcrete(typeNode, scope, (typeNode) => {
return mapNode(typeNode, scope, (typeNode) => {
if (typeNode.type === 'unknown') {
return nullUnion({type: 'array', of: {type: 'string'}})
}
if (typeNode.type !== 'string') {

@@ -358,2 +433,15 @@ return {type: 'null'}

}
case 'sanity.documentsOf': {
const typeNode = walk({node: node.args[0], scope})
return mapNode(typeNode, scope, (typeNode) => {
if (typeNode.type === 'unknown') {
return nullUnion({type: 'array', of: {type: 'string'}})
}
if (typeNode.type !== 'string') {
return {type: 'null'}
}
return {type: 'array', of: {type: 'string'}}
})
}
default: {

@@ -360,0 +448,0 @@ return {type: 'unknown'}

import debug from 'debug'
import {
matchAnalyzePattern,
matchText,
matchTokenize,
type Pattern,
type Token,
} from '../evaluator/matching'
import type {
AccessAttributeNode,
AccessElementNode,
AndNode,
ArrayCoerceNode,

@@ -26,3 +20,5 @@ ArrayNode,

ObjectSplatNode,
OpCall,
OpCallNode,
OrNode,
ParentNode,

@@ -35,5 +31,8 @@ PosNode,

} from '../nodeTypes'
import {booleanAnd, booleanInterpretationToTypeNode, booleanOr, booleanValue} from './booleans'
import {handleFuncCallNode} from './functions'
import {match} from './matching'
import {optimizeUnions} from './optimizations'
import {Context, Scope} from './scope'
import {isFuncCall, mapNode, nullUnion, resolveInline} from './typeHelpers'
import type {

@@ -54,3 +53,2 @@ ArrayTypeNode,

} from './types'
import {mapConcrete, nullUnion, resolveInline} from './typeHelpers'

@@ -131,3 +129,7 @@ const $trace = debug('typeEvaluator:evaluate:trace')

$trace('object.splat.value %O', value)
return mapConcrete(value, scope, (node) => {
return mapNode(value, scope, (node) => {
// splatting over unknown is unknown, we can't know what the attributes are
if (node.type === 'unknown') {
return {type: 'unknown'}
}
// splatting over a non-object is a no-op

@@ -230,6 +232,6 @@ if (node.type !== 'object') {

if (attr.type === 'ObjectConditionalSplat') {
const condition = resolveCondition(attr.condition, scope)
const condition = booleanValue(walk({node: attr.condition, scope}), scope)
$trace('object.conditional.splat.condition %O', condition)
// condition is never met, skip this attribute
if (condition === false) {
if (condition.canBeTrue === false) {
continue

@@ -241,3 +243,3 @@ }

// condition is always met, we can treat this as a normal splat
if (condition === true) {
if (condition.canBeFalse === false && condition.canBeNull === false) {
switch (attributeNode.type) {

@@ -265,3 +267,3 @@ case 'object': {

const variant = mapConcrete(attributeNode, scope, (attributeNode) => {
const variant = mapNode(attributeNode, scope, (attributeNode) => {
$trace('object.conditional.splat.result.concrete %O', attributeNode)

@@ -470,13 +472,65 @@ if (attributeNode.type !== 'object') {

const rhs = walk({node: node.right, scope})
return mapConcrete(lhs, scope, (left) =>
// eslint-disable-next-line complexity
mapConcrete(rhs, scope, (right) => {
return mapNode(lhs, scope, (left) =>
// eslint-disable-next-line complexity, max-statements
mapNode(rhs, scope, (right) => {
$trace('opcall.node.concrete "%s" %O', node.op, {left, right})
switch (node.op) {
case '==':
case '==': {
// == always returns a boolean, no matter the compared types.
if (left.type === 'unknown' || right.type === 'unknown') {
return {type: 'boolean'}
}
if (left.type !== right.type) {
return {
type: 'boolean',
value: false,
} satisfies BooleanTypeNode
}
if (left.type === 'null') {
return {
type: 'boolean',
value: true,
} satisfies BooleanTypeNode
}
if (!isPrimitiveTypeNode(left) || !isPrimitiveTypeNode(right)) {
return {
type: 'boolean',
value: false,
} satisfies BooleanTypeNode
}
return {
type: 'boolean',
value: evaluateComparison(node.op, left, right),
} satisfies BooleanTypeNode
}
case '!=': {
// != always returns a boolean, no matter the compared types.
if (left.type === 'unknown' || right.type === 'unknown') {
return {type: 'boolean'}
}
if (left.type !== right.type) {
return {
type: 'boolean',
value: true,
} satisfies BooleanTypeNode
}
if (left.type === 'null') {
return {
type: 'boolean',
value: false,
} satisfies BooleanTypeNode
}
if (!isPrimitiveTypeNode(left) || !isPrimitiveTypeNode(right)) {
return {
type: 'boolean',
value: true,
} satisfies BooleanTypeNode
}
let value = evaluateComparison('==', left, right)
if (value !== undefined) value = !value
return {
type: 'boolean',
value: resolveCondition(node, scope),
value,
} satisfies BooleanTypeNode

@@ -488,34 +542,84 @@ }

case '<=': {
if (left.type === 'unknown' || right.type === 'unknown') {
return nullUnion({type: 'boolean'})
}
if (left.type !== right.type) {
return {type: 'null'} satisfies NullTypeNode
}
if (!isPrimitiveTypeNode(left) || !isPrimitiveTypeNode(right)) {
return {type: 'null'} satisfies NullTypeNode
}
return {
type: 'boolean',
value: evaluateComparison(node.op, left, right),
} satisfies BooleanTypeNode
}
case 'in': {
if (left.type === 'unknown' || right.type === 'unknown') {
return nullUnion({type: 'boolean'})
}
if (right.type !== 'array') {
// Special case for global::path, since it can be used with in operator, but the type returned otherwise is a string
if (isFuncCall(node.right, 'global::path')) {
return {type: 'boolean'}
}
return {type: 'null'}
}
if (isPrimitiveTypeNode(left)) {
const resolved = resolveCondition(node, scope)
if (!isPrimitiveTypeNode(left) && left.type !== 'null') {
return {
type: 'boolean',
value: resolved,
value: false,
} satisfies BooleanTypeNode
}
return mapNode(right.of, scope, (arrayTypeNode) => {
if (arrayTypeNode.type === 'unknown') {
return nullUnion({type: 'boolean'})
}
return {type: 'null'}
}
case 'in': {
if (right.type === 'array') {
const resolved = resolveCondition(node, scope)
if (left.type === 'null') {
return {
type: 'boolean',
value: arrayTypeNode.type === 'null',
} satisfies BooleanTypeNode
}
if (left.value === undefined) {
return {
type: 'boolean',
} satisfies BooleanTypeNode
}
if (isPrimitiveTypeNode(arrayTypeNode)) {
if (arrayTypeNode.value === undefined) {
return {
type: 'boolean',
} satisfies BooleanTypeNode
}
return {
type: 'boolean',
value: left.value === arrayTypeNode.value,
} satisfies BooleanTypeNode
}
return {
type: 'boolean',
value: resolved,
value: false,
} satisfies BooleanTypeNode
}
return {type: 'null'}
})
}
case 'match': {
const resolved = resolveCondition(node, scope)
if (left.type === 'unknown' || right.type === 'unknown') {
// match always returns a boolean, no matter the compared types.
return {type: 'boolean'}
}
return {
type: 'boolean',
value: resolved,
value: match(left, right),
} satisfies BooleanTypeNode
}
case '+': {
if (left.type === 'unknown' || right.type === 'unknown') {
// + is ambiguous without the concrete types of the operands, so we return unknown and leave the excersise to the caller
return {type: 'unknown'}
}
if (left.type === 'string' && right.type === 'string') {

@@ -558,2 +662,5 @@ return {

case '-': {
if (left.type === 'unknown' || right.type === 'unknown') {
return nullUnion({type: 'number'})
}
if (left.type === 'number' && right.type === 'number') {

@@ -571,2 +678,5 @@ return {

case '*': {
if (left.type === 'unknown' || right.type === 'unknown') {
return nullUnion({type: 'number'})
}
if (left.type === 'number' && right.type === 'number') {

@@ -584,2 +694,5 @@ return {

case '/': {
if (left.type === 'unknown' || right.type === 'unknown') {
return nullUnion({type: 'number'})
}
if (left.type === 'number' && right.type === 'number') {

@@ -597,2 +710,5 @@ return {

case '**': {
if (left.type === 'unknown' || right.type === 'unknown') {
return nullUnion({type: 'number'})
}
if (left.type === 'number' && right.type === 'number') {

@@ -610,2 +726,5 @@ return {

case '%': {
if (left.type === 'unknown' || right.type === 'unknown') {
return nullUnion({type: 'number'})
}
if (left.type === 'number' && right.type === 'number') {

@@ -623,2 +742,5 @@ return {

default: {
// TS only: make sure we handle all cases
node.op satisfies never
return {

@@ -669,3 +791,3 @@ type: 'unknown',

return mapConcrete(
return mapNode(
inner,

@@ -732,3 +854,3 @@ scope,

return mapConcrete(base, scope, (base) => {
return mapNode(base, scope, (base) => {
$trace('filter.resolving %O', base)

@@ -886,24 +1008,45 @@ if (base.type === 'null') {

const base = walk({node: node.base, scope})
if (base.type === 'boolean' && base.value !== undefined) {
return {type: 'boolean', value: base.value === false}
}
return {type: 'boolean'}
return mapNode(base, scope, (base) => {
if (base.type === 'unknown') {
return nullUnion({type: 'boolean'})
}
if (base.type === 'boolean') {
if (base.value !== undefined) {
return {type: 'boolean', value: base.value === false}
}
return {type: 'boolean'}
}
return {type: 'null'}
})
}
function handleNegNode(node: NegNode, scope: Scope): NumberTypeNode | NullTypeNode {
function handleNegNode(node: NegNode, scope: Scope): TypeNode {
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
return mapNode(base, scope, (base) => {
if (base.type === 'unknown') {
return nullUnion({type: 'number'})
}
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 {
function handlePosNode(node: PosNode, scope: Scope): TypeNode {
const base = walk({node: node.base, scope})
if (base.type !== 'number') {
return {type: 'null'}
}
return base
return mapNode(base, scope, (base) => {
if (base.type === 'unknown') {
return nullUnion({type: 'number'})
}
if (base.type !== 'number') {
return {type: 'null'}
}
return base
})
}

@@ -926,2 +1069,26 @@

function handleAndNode(node: AndNode, scope: Scope): TypeNode {
const left = walk({node: node.left, scope})
const right = walk({node: node.right, scope})
return mapNode(left, scope, (lhs) =>
mapNode(right, scope, (rhs) => {
const value = booleanAnd(booleanValue(lhs, scope), booleanValue(rhs, scope))
return booleanInterpretationToTypeNode(value)
}),
)
}
function handleOrNode(node: OrNode, scope: Scope): TypeNode {
const left = walk({node: node.left, scope})
const right = walk({node: node.right, scope})
return mapNode(left, scope, (lhs) =>
mapNode(right, scope, (rhs) => {
const value = booleanOr(booleanValue(lhs, scope), booleanValue(rhs, scope))
return booleanInterpretationToTypeNode(value)
}),
)
}
const OVERRIDE_TYPE_SYMBOL = Symbol('groq-js.type')

@@ -982,8 +1149,8 @@

case 'And':
case 'And': {
return handleAndNode(node, scope)
}
case 'Or': {
return {
type: 'boolean',
value: resolveCondition(node, scope),
} satisfies BooleanTypeNode
return handleOrNode(node, scope)
}

@@ -1070,299 +1237,28 @@

function evaluateEquality(left: TypeNode, right: TypeNode): boolean | undefined {
$trace('evaluateEquality %O', {left, right})
if (left.type === 'null' && right.type === 'null') {
return true
function evaluateComparison(
opcall: OpCall,
left: PrimitiveTypeNode,
right: PrimitiveTypeNode,
): boolean | undefined {
if (left.value === undefined || right.value === undefined) {
return undefined
}
if (
isPrimitiveTypeNode(left) &&
isPrimitiveTypeNode(right) &&
left.value !== undefined &&
right.value !== undefined
) {
return left.value === right.value
}
if (left.type === 'union' && isPrimitiveTypeNode(right)) {
for (const node of left.of) {
// both are primitive types, and their values are equal, we can return true
if (isPrimitiveTypeNode(node) && node.value === right.value) {
return true
}
// both are the same type, but the value is undefined, we can't determine the result
if (isPrimitiveTypeNode(node) && node.value === undefined) {
return undefined
}
switch (opcall) {
case '==': {
return left.value === right.value
}
}
if (left.type !== right.type) {
return false
}
return undefined
}
/**
* Resolves the condition expression and returns a boolean value or undefined.
* Undefined is returned when the condition can't be resolved.
*
* @param expr - The expression node to resolve.
* @param scope - The scope in which the expression is evaluated.
* @returns The resolved boolean value or undefined.
*/
// eslint-disable-next-line complexity, max-statements
function resolveCondition(expr: ExprNode, scope: Scope): boolean | undefined {
$trace('resolveCondition.expr %O', expr)
switch (expr.type) {
case 'AccessAttribute':
case 'AccessElement':
case 'Value': {
const value = mapConcrete(walk({node: expr, scope}), scope, (node) => node)
if (value.type === 'boolean') {
return value.value
}
if (value.type === 'null' || value.type === 'object' || value.type === 'array') {
return false
}
return undefined
case '<': {
return left.value < right.value
}
case 'And': {
const left = resolveCondition(expr.left, scope)
$trace('resolveCondition.and.left %O', left)
if (left === false) {
return false
}
const right = resolveCondition(expr.right, scope)
$trace('resolveCondition.and.right %O', right)
if (right === false) {
return false
}
if (left === undefined || right === undefined) {
return undefined
}
return true
case '<=': {
return left.value <= right.value
}
case 'Or': {
$trace('resolveCondition.or.expr %O', expr)
const left = resolveCondition(expr.left, scope)
$trace('resolveCondition.or.left %O', left)
if (left === true) {
return true
}
const right = resolveCondition(expr.right, scope)
$trace('resolveCondition.or.right %O', right)
if (right === true) {
return true
}
if (left === undefined || right === undefined) {
return undefined
}
return false
case '>': {
return left.value > right.value
}
case 'OpCall': {
const left = walk({node: expr.left, scope})
const right = walk({node: expr.right, scope})
$trace('opcall "%s" %O', expr.op, {left, right})
if (left.type === 'unknown' || right.type === 'unknown') {
return undefined
}
switch (expr.op) {
case '==': {
return evaluateEquality(left, right)
}
case '!=': {
const result = evaluateEquality(left, right)
if (result === undefined) {
return undefined
}
return !result
}
case 'in': {
if (right.type === 'array') {
if (left.type === 'null' && right.of.type === 'unknown') {
return undefined
}
if (left.type === 'null' && right.of.type === 'null') {
return true
}
if (isPrimitiveTypeNode(left)) {
// eslint-disable-next-line max-depth
if (right.of.type === 'unknown') {
return undefined
}
// eslint-disable-next-line max-depth
if (left.value === undefined) {
return undefined
}
// eslint-disable-next-line max-depth
if (isPrimitiveTypeNode(right.of)) {
// eslint-disable-next-line max-depth
if (right.of.value === undefined) {
return undefined
}
return left.value === right.of.value
}
// eslint-disable-next-line max-depth
if (right.of.type === 'union') {
// eslint-disable-next-line max-depth
for (const node of right.of.of) {
// eslint-disable-next-line max-depth
if (node.type === 'unknown') {
return undefined
}
// eslint-disable-next-line max-depth
if (isPrimitiveTypeNode(node) && left.value === node.value) {
return true
}
// eslint-disable-next-line max-depth
if (left.type === node.type && node.value === undefined) {
return undefined
}
}
}
}
}
return false
}
case 'match': {
let tokens: Token[] = []
let patterns: Pattern[] = []
if (left.type === 'string') {
if (left.value === undefined) {
return undefined
}
tokens = tokens.concat(matchTokenize(left.value))
}
if (left.type === 'array') {
if (left.of.type === 'unknown') {
return undefined
}
if (left.of.type === 'string') {
// eslint-disable-next-line max-depth
if (left.of.value === undefined) {
return undefined
}
tokens = tokens.concat(matchTokenize(left.of.value))
}
if (left.of.type === 'union') {
// eslint-disable-next-line max-depth
for (const node of left.of.of) {
// eslint-disable-next-line max-depth
if (node.type === 'string' && node.value !== undefined) {
tokens = tokens.concat(matchTokenize(node.value))
}
}
}
}
if (right.type === 'string') {
if (right.value === undefined) {
return undefined
}
patterns = patterns.concat(matchAnalyzePattern(right.value))
}
if (right.type === 'array') {
if (right.of.type === 'unknown') {
return undefined
}
if (right.of.type === 'string') {
// eslint-disable-next-line max-depth
if (right.of.value === undefined) {
return undefined
}
patterns = patterns.concat(matchAnalyzePattern(right.of.value))
}
if (right.of.type === 'union') {
// eslint-disable-next-line max-depth
for (const node of right.of.of) {
// eslint-disable-next-line max-depth
if (node.type === 'string') {
// eslint-disable-next-line max-depth
if (node.value === undefined) {
return undefined
}
patterns = patterns.concat(matchAnalyzePattern(node.value))
}
// eslint-disable-next-line max-depth
if (node.type !== 'string') {
return false
}
}
}
}
return matchText(tokens, patterns)
}
case '<': {
if (isPrimitiveTypeNode(left) && isPrimitiveTypeNode(right)) {
if (left.value === undefined || right.value === undefined) {
return undefined
}
return left.value < right.value
}
return undefined
}
case '<=': {
if (isPrimitiveTypeNode(left) && isPrimitiveTypeNode(right)) {
if (left.value === undefined || right.value === undefined) {
return undefined
}
return left.value <= right.value
}
return undefined
}
case '>': {
if (isPrimitiveTypeNode(left) && isPrimitiveTypeNode(right)) {
if (left.value === undefined || right.value === undefined) {
return undefined
}
return left.value > right.value
}
return undefined
}
case '>=': {
if (isPrimitiveTypeNode(left) && isPrimitiveTypeNode(right)) {
if (left.value === undefined || right.value === undefined) {
return undefined
}
return left.value >= right.value
}
return undefined
}
default: {
return undefined
}
}
case '>=': {
return left.value >= right.value
}
case 'Not': {
const result = resolveCondition(expr.base, scope)
// check if the result is undefined or false. Undefined means that the condition can't be resolved, and we should keep the node
return result === undefined ? undefined : result === false
}
case 'Group': {
return resolveCondition(expr.base, scope)
}
default: {
return undefined
throw new Error(`unknown comparison operator ${opcall}`)
}

@@ -1375,8 +1271,9 @@ }

$trace('resolveFilter.expr %O', expr)
const filtered = scope.value.of.filter(
(node) =>
// create a new scope with the current scopes parent as the parent. It's only a temporary scope since we only want to resolve the condition
// check if the result is true or undefined. Undefined means that the condition can't be resolved, and we should keep the node
resolveCondition(expr, scope.createHidden([node])) !== false,
)
const filtered = scope.value.of.filter((node) => {
// create a new scope with the current scopes parent as the parent. It's only a temporary scope since we only want to resolve the condition
// and check if the result can be true.
const subScope = scope.createHidden([node])
const cond = walk({node: expr, scope: subScope})
return booleanValue(cond, subScope).canBeTrue
})
$trace(

@@ -1394,3 +1291,11 @@ `resolveFilter ${expr.type === 'OpCall' ? `${expr.type}/${expr.op}` : expr.type} %O`,

): TypeNode {
return mapConcrete(node, scope, (base) => (base.type === 'array' ? mapper(base) : {type: 'null'}))
return mapNode(node, scope, (base) => {
if (base.type === 'unknown') {
return base
}
if (base.type === 'array') {
return mapper(base)
}
return {type: 'null'}
})
}

@@ -1403,5 +1308,11 @@

): TypeNode {
return mapConcrete(node, scope, (base) =>
base.type === 'object' ? mapper(base) : {type: 'null'},
)
return mapNode(node, scope, (base) => {
if (base.type === 'unknown') {
return base
}
if (base.type === 'object') {
return mapper(base)
}
return {type: 'null'}
})
}

@@ -0,1 +1,2 @@

import type {ExprNode} from '../nodeTypes'
import {optimizeUnions} from './optimizations'

@@ -14,2 +15,3 @@ import type {Scope} from './scope'

UnionTypeNode,
UnknownTypeNode,
} from './types'

@@ -98,16 +100,11 @@

/**
* mapConcrete extracts a _concrete type_ from a type node, applies the mapping
* mapNode extracts either a _concrete type_ OR an _unknown 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.
*/
export function mapConcrete(
**/
export function mapNode<T extends TypeNode = TypeNode>(
node: TypeNode,
scope: Scope,
mapper: (node: ConcreteTypeNode) => TypeNode,
mapper: (node: ConcreteTypeNode | UnknownTypeNode) => T,
mergeUnions: (nodes: TypeNode[]) => TypeNode = (nodes) =>

@@ -123,10 +120,9 @@ optimizeUnions({type: 'union', of: nodes}),

case 'number':
case 'unknown':
return mapper(node)
case 'unknown':
return node
case 'union':
return mergeUnions(node.of.map((inner) => mapConcrete(inner, scope, mapper), mergeUnions))
return mergeUnions(node.of.map((inner) => mapNode(inner, scope, mapper), mergeUnions))
case 'inline': {
const resolvedInline = resolveInline(node, scope)
return mapConcrete(resolvedInline, scope, mapper, mergeUnions)
return mapNode(resolvedInline, scope, mapper, mergeUnions)
}

@@ -138,1 +134,9 @@ default:

}
export function isFuncCall(node: ExprNode, name: string): boolean {
if (node.type === 'Group') {
return isFuncCall(node.base, name)
}
return node.type === 'FuncCall' && `${node.namespace}::${node.name}` === name
}

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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc