import type { defined, Dict, domainOf, equals, error, evaluate, isAny, isUnknown, List, optionalKeyOf, Primitive, requiredKeyOf } from "../../dev/utils/src/main.js" import { objectKindOf, stringify, throwParseError } from "../../dev/utils/src/main.js" import { hasArkKind } from "../compile/registry.js" import type { TypeNode } from "../nodes/composite/type.js" import { node } from "../nodes/composite/type.js" import { serializeRegex } from "../nodes/primitive/regex.js" import type { ParseContext } from "../scope.js" import { Type } from "../type.js" import type { validateString } from "./ast/ast.js" import type { inferObjectLiteral, validateObjectLiteral } from "./objectLiteral.js" import { parseObjectLiteral } from "./objectLiteral.js" import type { BaseCompletions, inferString } from "./string/string.js" import type { inferTuple, TupleExpression, validateTuple } from "./tuple.js" import { parseTuple } from "./tuple.js" export const parseObject = (def: object, ctx: ParseContext): TypeNode => { const objectKind = objectKindOf(def) switch (objectKind) { case "Object": if (hasArkKind(def, "node") && def.hasKind("type")) { return def } return parseObjectLiteral(def as Dict, ctx) case "Array": return parseTuple(def as List, ctx) case "RegExp": return node({ basis: "string", regex: serializeRegex(def as RegExp) }) case "Function": if (def instanceof Type) { return def.root } return throwParseError(writeBadDefinitionTypeMessage("Function")) default: return throwParseError( writeBadDefinitionTypeMessage(objectKind ?? stringify(def)) ) } } export type inferDefinition = isAny extends true ? never : def extends CastTo | ThunkCast ? t : def extends string ? inferString : def extends List ? inferTuple : def extends RegExp ? string : def extends Dict ? inferObjectLiteral : never export type validateDefinition = null extends undefined ? `'strict' or 'strictNullChecks' must be set to true in your tsconfig's 'compilerOptions'` : def extends Terminal ? def : def extends string ? validateString extends error ? message : def : def extends List ? validateTuple : def extends BadDefinitionType ? writeBadDefinitionTypeMessage< objectKindOf extends string ? objectKindOf : domainOf > : isUnknown extends true ? // this allows the initial list of autocompletions to be populated when a user writes "type()", // before having specified a definition BaseCompletions<$, args> | {} : validateObjectLiteral export type validateDeclared = def extends validateDefinition ? validateInference : validateDefinition type validateInference = def extends | RegExp | CastTo | ThunkCast | TupleExpression ? validateShallowInference : def extends readonly unknown[] ? declared extends readonly unknown[] ? { [i in keyof declared]: i extends keyof def ? validateInference : unknown } : evaluate> : def extends object ? evaluate< { [k in requiredKeyOf]: k extends keyof def ? validateInference : unknown } & { [k in optionalKeyOf & string as `${k}?`]: `${k}?` extends keyof def ? validateInference< def[`${k}?`], defined, $, args > : unknown } > : validateShallowInference type validateShallowInference = equals< inferDefinition, declared > extends true ? def : evaluate> type declarationMismatch = { declared: declared inferred: inferDefinition } // functions are ignored in validation so that cyclic thunk definitions can be // inferred in scopes type Terminal = RegExp | CastTo | ((...args: never[]) => unknown) // ideally this could be just declared since it is not used at runtime, // but it doesn't play well with typescript-eslint: https://github.com/typescript-eslint/typescript-eslint/issues/4608 // easiest solution seems to be just having it declared as a value so it doesn't break when we import at runtime export const inferred = Symbol("inferred") export type CastTo = { [inferred]?: t } export type ThunkCast = () => CastTo type BadDefinitionType = Exclude export const writeBadDefinitionTypeMessage = ( actual: actual ): writeBadDefinitionTypeMessage => `Type definitions must be strings or objects (was ${actual})` type writeBadDefinitionTypeMessage = `Type definitions must be strings or objects (was ${actual})`