import type { Dict, error, evaluate } from "../../dev/utils/src/main.js" import { predicateNode } from "../nodes/composite/predicate.js" import type { NamedPropsInput } from "../nodes/composite/props.js" import { propsNode } from "../nodes/composite/props.js" import { typeNode } from "../nodes/composite/type.js" import { domainNode } from "../nodes/primitive/basis/domain.js" import type { ParseContext } from "../scope.js" import type { validateString } from "./ast/ast.js" import type { inferDefinition, validateDefinition } from "./definition.js" import { Scanner } from "./string/shift/scanner.js" export const parseObjectLiteral = (def: Dict, ctx: ParseContext) => { const named: NamedPropsInput = {} for (const definitionKey in def) { let keyName = definitionKey let optional = false if (definitionKey[definitionKey.length - 1] === "?") { if ( definitionKey[definitionKey.length - 2] === Scanner.escapeToken ) { keyName = `${definitionKey.slice(0, -2)}?` } else { keyName = definitionKey.slice(0, -1) optional = true } } ctx.path.push(keyName) named[keyName] = { prerequisite: false, optional, value: ctx.scope.parse(def[definitionKey], ctx) } ctx.path.pop() } const props = propsNode(named) const predicate = predicateNode([objectBasisNode, props]) return typeNode([predicate]) } const objectBasisNode = domainNode("object") export type inferObjectLiteral = evaluate< { [k in keyof def as nonOptionalKeyFrom]: inferDefinition< def[k], $, args > } & { [k in keyof def as optionalKeyFrom]?: inferDefinition< def[k], $, args > } > type nonOptionalKeyFrom = parseKey extends { kind: infer kind extends "required" | "indexed" value: infer value } ? (kind extends "required" ? value : inferDefinition) & PropertyKey : never type optionalKeyFrom = parseKey extends { kind: "optional" value: infer value extends string } ? value : never export type validateObjectLiteral = { [k in keyof def]: k extends IndexedKey ? validateString extends error ? message : inferDefinition extends PropertyKey ? // if the indexDef is syntactically and semantically valid, // move on to the validating the value definition validateDefinition : writeInvalidPropertyKeyMessage : validateDefinition } export const writeInvalidPropertyKeyMessage = ( indexDef: indexDef ): writeInvalidPropertyKeyMessage => `Indexed key definition '${indexDef}' must be a string, number or symbol` type writeInvalidPropertyKeyMessage = `Indexed key definition '${indexDef}' must be a string, number or symbol` type ParsedKeyKind = "required" | "optional" | "indexed" type KeyParseResult = { kind: ParsedKeyKind value: string } export type IndexedKey = `[${def}]` export type OptionalKey = `${name}?` type parsedKey = result type parseKey = k extends OptionalKey ? inner extends `${infer baseName}${Scanner.EscapeToken}` ? parsedKey<{ kind: "required" value: OptionalKey }> : parsedKey<{ kind: "optional" value: inner }> : k extends IndexedKey ? parsedKey<{ kind: "indexed" value: def }> : k extends `${Scanner.EscapeToken}${infer escapedIndexKey extends IndexedKey}` ? parsedKey<{ kind: "required" value: escapedIndexKey }> : parsedKey<{ kind: "required" value: k & string }>