effect
Version:
The missing standard library for TypeScript, for writing production-grade software.
2,131 lines (1,956 loc) • 77.7 kB
text/typescript
/**
* @since 3.10.0
*/
import * as Arr from "./Array.js"
import type { Effect } from "./Effect.js"
import type { Equivalence } from "./Equivalence.js"
import { dual, identity } from "./Function.js"
import { globalValue } from "./GlobalValue.js"
import * as errors_ from "./internal/schema/errors.js"
import * as util_ from "./internal/schema/util.js"
import * as Number from "./Number.js"
import * as Option from "./Option.js"
import * as Order from "./Order.js"
import type { ParseIssue } from "./ParseResult.js"
import * as Predicate from "./Predicate.js"
import * as regexp from "./RegExp.js"
import type { Concurrency } from "./Types.js"
/**
* @category model
* @since 3.10.0
*/
export type AST =
| Declaration
| Literal
| UniqueSymbol
| UndefinedKeyword
| VoidKeyword
| NeverKeyword
| UnknownKeyword
| AnyKeyword
| StringKeyword
| NumberKeyword
| BooleanKeyword
| BigIntKeyword
| SymbolKeyword
| ObjectKeyword
| Enums
| TemplateLiteral
// possible transformations
| Refinement
| TupleType
| TypeLiteral
| Union
| Suspend
// transformations
| Transformation
// -------------------------------------------------------------------------------------
// annotations
// -------------------------------------------------------------------------------------
/**
* @category annotations
* @since 3.10.0
*/
export type BrandAnnotation = Arr.NonEmptyReadonlyArray<string | symbol>
/**
* @category annotations
* @since 3.10.0
*/
export const BrandAnnotationId: unique symbol = Symbol.for("effect/annotation/Brand")
/**
* @category annotations
* @since 3.10.0
*/
export type SchemaIdAnnotation = string | symbol
/**
* @category annotations
* @since 3.10.0
*/
export const SchemaIdAnnotationId: unique symbol = Symbol.for("effect/annotation/SchemaId")
/**
* @category annotations
* @since 3.10.0
*/
export type MessageAnnotation = (issue: ParseIssue) => string | Effect<string> | {
readonly message: string | Effect<string>
readonly override: boolean
}
/**
* @category annotations
* @since 3.10.0
*/
export const MessageAnnotationId: unique symbol = Symbol.for("effect/annotation/Message")
/**
* @category annotations
* @since 3.10.0
*/
export type MissingMessageAnnotation = () => string | Effect<string>
/**
* @category annotations
* @since 3.10.0
*/
export const MissingMessageAnnotationId: unique symbol = Symbol.for("effect/annotation/MissingMessage")
/**
* @category annotations
* @since 3.10.0
*/
export type IdentifierAnnotation = string
/**
* @category annotations
* @since 3.10.0
*/
export const IdentifierAnnotationId: unique symbol = Symbol.for("effect/annotation/Identifier")
/**
* @category annotations
* @since 3.10.0
*/
export type TitleAnnotation = string
/**
* @category annotations
* @since 3.10.0
*/
export const TitleAnnotationId: unique symbol = Symbol.for("effect/annotation/Title")
/** @internal */
export const AutoTitleAnnotationId: unique symbol = Symbol.for("effect/annotation/AutoTitle")
/**
* @category annotations
* @since 3.10.0
*/
export type DescriptionAnnotation = string
/**
* @category annotations
* @since 3.10.0
*/
export const DescriptionAnnotationId: unique symbol = Symbol.for("effect/annotation/Description")
/**
* @category annotations
* @since 3.10.0
*/
export type ExamplesAnnotation<A> = Arr.NonEmptyReadonlyArray<A>
/**
* @category annotations
* @since 3.10.0
*/
export const ExamplesAnnotationId: unique symbol = Symbol.for("effect/annotation/Examples")
/**
* @category annotations
* @since 3.10.0
*/
export type DefaultAnnotation<A> = A
/**
* @category annotations
* @since 3.10.0
*/
export const DefaultAnnotationId: unique symbol = Symbol.for("effect/annotation/Default")
/**
* @category annotations
* @since 3.10.0
*/
export type JSONSchemaAnnotation = object
/**
* @category annotations
* @since 3.10.0
*/
export const JSONSchemaAnnotationId: unique symbol = Symbol.for("effect/annotation/JSONSchema")
/**
* @category annotations
* @since 3.10.0
*/
export const ArbitraryAnnotationId: unique symbol = Symbol.for("effect/annotation/Arbitrary")
/**
* @category annotations
* @since 3.10.0
*/
export const PrettyAnnotationId: unique symbol = Symbol.for("effect/annotation/Pretty")
/**
* @category annotations
* @since 3.10.0
*/
export type EquivalenceAnnotation<A, TypeParameters extends ReadonlyArray<any> = readonly []> = (
...equivalences: { readonly [K in keyof TypeParameters]: Equivalence<TypeParameters[K]> }
) => Equivalence<A>
/**
* @category annotations
* @since 3.10.0
*/
export const EquivalenceAnnotationId: unique symbol = Symbol.for("effect/annotation/Equivalence")
/**
* @category annotations
* @since 3.10.0
*/
export type DocumentationAnnotation = string
/**
* @category annotations
* @since 3.10.0
*/
export const DocumentationAnnotationId: unique symbol = Symbol.for("effect/annotation/Documentation")
/**
* @category annotations
* @since 3.10.0
*/
export type ConcurrencyAnnotation = Concurrency | undefined
/**
* @category annotations
* @since 3.10.0
*/
export const ConcurrencyAnnotationId: unique symbol = Symbol.for("effect/annotation/Concurrency")
/**
* @category annotations
* @since 3.10.0
*/
export type BatchingAnnotation = boolean | "inherit" | undefined
/**
* @category annotations
* @since 3.10.0
*/
export const BatchingAnnotationId: unique symbol = Symbol.for("effect/annotation/Batching")
/**
* @category annotations
* @since 3.10.0
*/
export type ParseIssueTitleAnnotation = (issue: ParseIssue) => string | undefined
/**
* @category annotations
* @since 3.10.0
*/
export const ParseIssueTitleAnnotationId: unique symbol = Symbol.for("effect/annotation/ParseIssueTitle")
/**
* @category annotations
* @since 3.10.0
*/
export const ParseOptionsAnnotationId: unique symbol = Symbol.for("effect/annotation/ParseOptions")
/**
* @category annotations
* @since 3.10.0
*/
export type DecodingFallbackAnnotation<A> = (issue: ParseIssue) => Effect<A, ParseIssue>
/**
* @category annotations
* @since 3.10.0
*/
export const DecodingFallbackAnnotationId: unique symbol = Symbol.for("effect/annotation/DecodingFallback")
/**
* @category annotations
* @since 3.10.0
*/
export const SurrogateAnnotationId: unique symbol = Symbol.for("effect/annotation/Surrogate")
/**
* @category annotations
* @since 3.10.0
*/
export type SurrogateAnnotation = AST
/** @internal */
export const StableFilterAnnotationId: unique symbol = Symbol.for("effect/annotation/StableFilter")
/**
* A stable filter consistently applies fixed validation rules, such as
* 'minItems', 'maxItems', and 'itemsCount', to ensure array length complies
* with set criteria regardless of the input data's content.
*
* @internal
*/
export type StableFilterAnnotation = boolean
/**
* @category annotations
* @since 3.10.0
*/
export interface Annotations {
readonly [_: string]: unknown
readonly [_: symbol]: unknown
}
/**
* @category annotations
* @since 3.10.0
*/
export interface Annotated {
readonly annotations: Annotations
}
/**
* @category annotations
* @since 3.10.0
*/
export const getAnnotation: {
/**
* @category annotations
* @since 3.10.0
*/
<A>(key: symbol): (annotated: Annotated) => Option.Option<A>
/**
* @category annotations
* @since 3.10.0
*/
<A>(annotated: Annotated, key: symbol): Option.Option<A>
} = dual(
2,
<A>(annotated: Annotated, key: symbol): Option.Option<A> =>
Object.prototype.hasOwnProperty.call(annotated.annotations, key) ?
Option.some(annotated.annotations[key] as any) :
Option.none()
)
/**
* @category annotations
* @since 3.10.0
*/
export const getBrandAnnotation = getAnnotation<BrandAnnotation>(BrandAnnotationId)
/**
* @category annotations
* @since 3.10.0
*/
export const getMessageAnnotation = getAnnotation<MessageAnnotation>(MessageAnnotationId)
/**
* @category annotations
* @since 3.10.0
*/
export const getMissingMessageAnnotation = getAnnotation<MissingMessageAnnotation>(MissingMessageAnnotationId)
/**
* @category annotations
* @since 3.10.0
*/
export const getTitleAnnotation = getAnnotation<TitleAnnotation>(TitleAnnotationId)
/** @internal */
export const getAutoTitleAnnotation = getAnnotation<TitleAnnotation>(AutoTitleAnnotationId)
/**
* @category annotations
* @since 3.10.0
*/
export const getIdentifierAnnotation = getAnnotation<IdentifierAnnotation>(IdentifierAnnotationId)
/**
* @category annotations
* @since 3.10.0
*/
export const getDescriptionAnnotation = getAnnotation<DescriptionAnnotation>(DescriptionAnnotationId)
/**
* @category annotations
* @since 3.10.0
*/
export const getExamplesAnnotation = getAnnotation<ExamplesAnnotation<unknown>>(ExamplesAnnotationId)
/**
* @category annotations
* @since 3.10.0
*/
export const getDefaultAnnotation = getAnnotation<DefaultAnnotation<unknown>>(DefaultAnnotationId)
/**
* @category annotations
* @since 3.10.0
*/
export const getJSONSchemaAnnotation = getAnnotation<JSONSchemaAnnotation>(JSONSchemaAnnotationId)
/**
* @category annotations
* @since 3.10.0
*/
export const getDocumentationAnnotation = getAnnotation<DocumentationAnnotation>(DocumentationAnnotationId)
/**
* @category annotations
* @since 3.10.0
*/
export const getConcurrencyAnnotation = getAnnotation<ConcurrencyAnnotation>(ConcurrencyAnnotationId)
/**
* @category annotations
* @since 3.10.0
*/
export const getBatchingAnnotation = getAnnotation<BatchingAnnotation>(BatchingAnnotationId)
/**
* @category annotations
* @since 3.10.0
*/
export const getParseIssueTitleAnnotation = getAnnotation<ParseIssueTitleAnnotation>(ParseIssueTitleAnnotationId)
/**
* @category annotations
* @since 3.10.0
*/
export const getParseOptionsAnnotation = getAnnotation<ParseOptions>(ParseOptionsAnnotationId)
/**
* @category annotations
* @since 3.10.0
*/
export const getDecodingFallbackAnnotation = getAnnotation<DecodingFallbackAnnotation<unknown>>(
DecodingFallbackAnnotationId
)
/**
* @category annotations
* @since 3.10.0
*/
export const getSurrogateAnnotation = getAnnotation<SurrogateAnnotation>(SurrogateAnnotationId)
const getStableFilterAnnotation = getAnnotation<StableFilterAnnotation>(StableFilterAnnotationId)
/** @internal */
export const hasStableFilter = (annotated: Annotated) =>
Option.exists(getStableFilterAnnotation(annotated), (b) => b === true)
/**
* @category annotations
* @since 3.10.0
*/
export const JSONIdentifierAnnotationId: unique symbol = Symbol.for("effect/annotation/JSONIdentifier")
/**
* @category annotations
* @since 3.10.0
*/
export const getJSONIdentifierAnnotation = getAnnotation<IdentifierAnnotation>(JSONIdentifierAnnotationId)
/**
* @category annotations
* @since 3.10.0
*/
export const getJSONIdentifier = (annotated: Annotated) =>
Option.orElse(getJSONIdentifierAnnotation(annotated), () => getIdentifierAnnotation(annotated))
// -------------------------------------------------------------------------------------
// schema ids
// -------------------------------------------------------------------------------------
/**
* @category schema id
* @since 3.10.0
*/
export const ParseJsonSchemaId: unique symbol = Symbol.for("effect/schema/ParseJson")
/**
* @category model
* @since 3.10.0
*/
export class Declaration implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "Declaration"
constructor(
readonly typeParameters: ReadonlyArray<AST>,
readonly decodeUnknown: (
...typeParameters: ReadonlyArray<AST>
) => (input: unknown, options: ParseOptions, self: Declaration) => Effect<any, ParseIssue, any>,
readonly encodeUnknown: (
...typeParameters: ReadonlyArray<AST>
) => (input: unknown, options: ParseOptions, self: Declaration) => Effect<any, ParseIssue, any>,
readonly annotations: Annotations = {}
) {}
/**
* @since 3.10.0
*/
toString() {
return Option.getOrElse(getExpected(this), () => "<declaration schema>")
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
typeParameters: this.typeParameters.map((ast) => ast.toJSON()),
annotations: toJSONAnnotations(this.annotations)
}
}
}
const createASTGuard = <T extends AST["_tag"]>(tag: T) => (ast: AST): ast is Extract<AST, { _tag: T }> =>
ast._tag === tag
/**
* @category guards
* @since 3.10.0
*/
export const isDeclaration: (ast: AST) => ast is Declaration = createASTGuard("Declaration")
/**
* @category model
* @since 3.10.0
*/
export type LiteralValue = string | number | boolean | null | bigint
/**
* @category model
* @since 3.10.0
*/
export class Literal implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "Literal"
constructor(readonly literal: LiteralValue, readonly annotations: Annotations = {}) {}
/**
* @since 3.10.0
*/
toString() {
return Option.getOrElse(getExpected(this), () => util_.formatUnknown(this.literal))
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
literal: Predicate.isBigInt(this.literal) ? String(this.literal) : this.literal,
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category guards
* @since 3.10.0
*/
export const isLiteral: (ast: AST) => ast is Literal = createASTGuard("Literal")
const $null = new Literal(null)
export {
/**
* @category constructors
* @since 3.10.0
*/
$null as null
}
/**
* @category model
* @since 3.10.0
*/
export class UniqueSymbol implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "UniqueSymbol"
constructor(readonly symbol: symbol, readonly annotations: Annotations = {}) {}
/**
* @since 3.10.0
*/
toString() {
return Option.getOrElse(getExpected(this), () => util_.formatUnknown(this.symbol))
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
symbol: String(this.symbol),
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category guards
* @since 3.10.0
*/
export const isUniqueSymbol: (ast: AST) => ast is UniqueSymbol = createASTGuard("UniqueSymbol")
/**
* @category model
* @since 3.10.0
*/
export class UndefinedKeyword implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "UndefinedKeyword"
constructor(readonly annotations: Annotations = {}) {}
/**
* @since 3.10.0
*/
toString() {
return formatKeyword(this)
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category constructors
* @since 3.10.0
*/
export const undefinedKeyword: UndefinedKeyword = new UndefinedKeyword({
[TitleAnnotationId]: "undefined"
})
/**
* @category guards
* @since 3.10.0
*/
export const isUndefinedKeyword: (ast: AST) => ast is UndefinedKeyword = createASTGuard("UndefinedKeyword")
/**
* @category model
* @since 3.10.0
*/
export class VoidKeyword implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "VoidKeyword"
constructor(readonly annotations: Annotations = {}) {}
/**
* @since 3.10.0
*/
toString() {
return formatKeyword(this)
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category constructors
* @since 3.10.0
*/
export const voidKeyword: VoidKeyword = new VoidKeyword({
[TitleAnnotationId]: "void"
})
/**
* @category guards
* @since 3.10.0
*/
export const isVoidKeyword: (ast: AST) => ast is VoidKeyword = createASTGuard("VoidKeyword")
/**
* @category model
* @since 3.10.0
*/
export class NeverKeyword implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "NeverKeyword"
constructor(readonly annotations: Annotations = {}) {}
/**
* @since 3.10.0
*/
toString() {
return formatKeyword(this)
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category constructors
* @since 3.10.0
*/
export const neverKeyword: NeverKeyword = new NeverKeyword({
[TitleAnnotationId]: "never"
})
/**
* @category guards
* @since 3.10.0
*/
export const isNeverKeyword: (ast: AST) => ast is NeverKeyword = createASTGuard("NeverKeyword")
/**
* @category model
* @since 3.10.0
*/
export class UnknownKeyword implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "UnknownKeyword"
constructor(readonly annotations: Annotations = {}) {}
/**
* @since 3.10.0
*/
toString() {
return formatKeyword(this)
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category constructors
* @since 3.10.0
*/
export const unknownKeyword: UnknownKeyword = new UnknownKeyword({
[TitleAnnotationId]: "unknown"
})
/**
* @category guards
* @since 3.10.0
*/
export const isUnknownKeyword: (ast: AST) => ast is UnknownKeyword = createASTGuard("UnknownKeyword")
/**
* @category model
* @since 3.10.0
*/
export class AnyKeyword implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "AnyKeyword"
constructor(readonly annotations: Annotations = {}) {}
/**
* @since 3.10.0
*/
toString() {
return formatKeyword(this)
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category constructors
* @since 3.10.0
*/
export const anyKeyword: AnyKeyword = new AnyKeyword({
[TitleAnnotationId]: "any"
})
/**
* @category guards
* @since 3.10.0
*/
export const isAnyKeyword: (ast: AST) => ast is AnyKeyword = createASTGuard("AnyKeyword")
/**
* @category model
* @since 3.10.0
*/
export class StringKeyword implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "StringKeyword"
constructor(readonly annotations: Annotations = {}) {}
/**
* @since 3.10.0
*/
toString() {
return formatKeyword(this)
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category constructors
* @since 3.10.0
*/
export const stringKeyword: StringKeyword = new StringKeyword({
[TitleAnnotationId]: "string",
[DescriptionAnnotationId]: "a string"
})
/**
* @category guards
* @since 3.10.0
*/
export const isStringKeyword: (ast: AST) => ast is StringKeyword = createASTGuard("StringKeyword")
/**
* @category model
* @since 3.10.0
*/
export class NumberKeyword implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "NumberKeyword"
constructor(readonly annotations: Annotations = {}) {}
/**
* @since 3.10.0
*/
toString() {
return formatKeyword(this)
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category constructors
* @since 3.10.0
*/
export const numberKeyword: NumberKeyword = new NumberKeyword({
[TitleAnnotationId]: "number",
[DescriptionAnnotationId]: "a number"
})
/**
* @category guards
* @since 3.10.0
*/
export const isNumberKeyword: (ast: AST) => ast is NumberKeyword = createASTGuard("NumberKeyword")
/**
* @category model
* @since 3.10.0
*/
export class BooleanKeyword implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "BooleanKeyword"
constructor(readonly annotations: Annotations = {}) {}
/**
* @since 3.10.0
*/
toString() {
return formatKeyword(this)
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category constructors
* @since 3.10.0
*/
export const booleanKeyword: BooleanKeyword = new BooleanKeyword({
[TitleAnnotationId]: "boolean",
[DescriptionAnnotationId]: "a boolean"
})
/**
* @category guards
* @since 3.10.0
*/
export const isBooleanKeyword: (ast: AST) => ast is BooleanKeyword = createASTGuard("BooleanKeyword")
/**
* @category model
* @since 3.10.0
*/
export class BigIntKeyword implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "BigIntKeyword"
constructor(readonly annotations: Annotations = {}) {}
/**
* @since 3.10.0
*/
toString() {
return formatKeyword(this)
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category constructors
* @since 3.10.0
*/
export const bigIntKeyword: BigIntKeyword = new BigIntKeyword({
[TitleAnnotationId]: "bigint",
[DescriptionAnnotationId]: "a bigint"
})
/**
* @category guards
* @since 3.10.0
*/
export const isBigIntKeyword: (ast: AST) => ast is BigIntKeyword = createASTGuard("BigIntKeyword")
/**
* @category model
* @since 3.10.0
*/
export class SymbolKeyword implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "SymbolKeyword"
constructor(readonly annotations: Annotations = {}) {}
/**
* @since 3.10.0
*/
toString() {
return formatKeyword(this)
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category constructors
* @since 3.10.0
*/
export const symbolKeyword: SymbolKeyword = new SymbolKeyword({
[TitleAnnotationId]: "symbol",
[DescriptionAnnotationId]: "a symbol"
})
/**
* @category guards
* @since 3.10.0
*/
export const isSymbolKeyword: (ast: AST) => ast is SymbolKeyword = createASTGuard("SymbolKeyword")
/**
* @category model
* @since 3.10.0
*/
export class ObjectKeyword implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "ObjectKeyword"
constructor(readonly annotations: Annotations = {}) {}
/**
* @since 3.10.0
*/
toString() {
return formatKeyword(this)
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category constructors
* @since 3.10.0
*/
export const objectKeyword: ObjectKeyword = new ObjectKeyword({
[TitleAnnotationId]: "object",
[DescriptionAnnotationId]: "an object in the TypeScript meaning, i.e. the `object` type"
})
/**
* @category guards
* @since 3.10.0
*/
export const isObjectKeyword: (ast: AST) => ast is ObjectKeyword = createASTGuard("ObjectKeyword")
/**
* @category model
* @since 3.10.0
*/
export class Enums implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "Enums"
constructor(
readonly enums: ReadonlyArray<readonly [string, string | number]>,
readonly annotations: Annotations = {}
) {}
/**
* @since 3.10.0
*/
toString() {
return Option.getOrElse(
getExpected(this),
() => `<enum ${this.enums.length} value(s): ${this.enums.map(([_, value]) => JSON.stringify(value)).join(" | ")}>`
)
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
enums: this.enums,
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category guards
* @since 3.10.0
*/
export const isEnums: (ast: AST) => ast is Enums = createASTGuard("Enums")
type TemplateLiteralSpanBaseType = StringKeyword | NumberKeyword | Literal | TemplateLiteral
type TemplateLiteralSpanType = TemplateLiteralSpanBaseType | Union<TemplateLiteralSpanType>
const isTemplateLiteralSpanType = (ast: AST): ast is TemplateLiteralSpanType => {
switch (ast._tag) {
case "Literal":
case "NumberKeyword":
case "StringKeyword":
case "TemplateLiteral":
return true
case "Union":
return ast.types.every(isTemplateLiteralSpanType)
}
return false
}
const templateLiteralSpanUnionTypeToString = (type: TemplateLiteralSpanType): string => {
switch (type._tag) {
case "Literal":
return JSON.stringify(String(type.literal))
case "StringKeyword":
return "string"
case "NumberKeyword":
return "number"
case "TemplateLiteral":
return String(type)
case "Union":
return type.types.map(templateLiteralSpanUnionTypeToString).join(" | ")
}
}
const templateLiteralSpanTypeToString = (type: TemplateLiteralSpanType): string => {
switch (type._tag) {
case "Literal":
return String(type.literal)
case "StringKeyword":
return "${string}"
case "NumberKeyword":
return "${number}"
case "TemplateLiteral":
return "${" + String(type) + "}"
case "Union":
return "${" + type.types.map(templateLiteralSpanUnionTypeToString).join(" | ") + "}"
}
}
/**
* @category model
* @since 3.10.0
*/
export class TemplateLiteralSpan {
/**
* @since 3.10.0
*/
readonly type: TemplateLiteralSpanType
constructor(type: AST, readonly literal: string) {
if (isTemplateLiteralSpanType(type)) {
this.type = type
} else {
throw new Error(errors_.getSchemaUnsupportedLiteralSpanErrorMessage(type))
}
}
/**
* @since 3.10.0
*/
toString() {
return templateLiteralSpanTypeToString(this.type) + this.literal
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
type: this.type.toJSON(),
literal: this.literal
}
}
}
/**
* @category model
* @since 3.10.0
*/
export class TemplateLiteral implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "TemplateLiteral"
constructor(
readonly head: string,
readonly spans: Arr.NonEmptyReadonlyArray<TemplateLiteralSpan>,
readonly annotations: Annotations = {}
) {}
/**
* @since 3.10.0
*/
toString() {
return Option.getOrElse(getExpected(this), () => formatTemplateLiteral(this))
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
head: this.head,
spans: this.spans.map((span) => span.toJSON()),
annotations: toJSONAnnotations(this.annotations)
}
}
}
const formatTemplateLiteral = (ast: TemplateLiteral): string =>
"`" + ast.head + ast.spans.map(String).join("") +
"`"
/**
* @category guards
* @since 3.10.0
*/
export const isTemplateLiteral: (ast: AST) => ast is TemplateLiteral = createASTGuard("TemplateLiteral")
/**
* @category model
* @since 3.10.0
*/
export class Type implements Annotated {
constructor(
readonly type: AST,
readonly annotations: Annotations = {}
) {}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
type: this.type.toJSON(),
annotations: toJSONAnnotations(this.annotations)
}
}
/**
* @since 3.10.0
*/
toString() {
return String(this.type)
}
}
/**
* @category model
* @since 3.10.0
*/
export class OptionalType extends Type {
constructor(
type: AST,
readonly isOptional: boolean,
annotations: Annotations = {}
) {
super(type, annotations)
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
type: this.type.toJSON(),
isOptional: this.isOptional,
annotations: toJSONAnnotations(this.annotations)
}
}
/**
* @since 3.10.0
*/
toString() {
return String(this.type) + (this.isOptional ? "?" : "")
}
}
const getRestASTs = (rest: ReadonlyArray<Type>): ReadonlyArray<AST> => rest.map((annotatedAST) => annotatedAST.type)
/**
* @category model
* @since 3.10.0
*/
export class TupleType implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "TupleType"
constructor(
readonly elements: ReadonlyArray<OptionalType>,
readonly rest: ReadonlyArray<Type>,
readonly isReadonly: boolean,
readonly annotations: Annotations = {}
) {
let hasOptionalElement = false
let hasIllegalRequiredElement = false
for (const e of elements) {
if (e.isOptional) {
hasOptionalElement = true
} else if (hasOptionalElement) {
hasIllegalRequiredElement = true
break
}
}
if (hasIllegalRequiredElement || (hasOptionalElement && rest.length > 1)) {
throw new Error(errors_.getASTRequiredElementFollowinAnOptionalElementErrorMessage)
}
}
/**
* @since 3.10.0
*/
toString() {
return Option.getOrElse(getExpected(this), () => formatTuple(this))
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
elements: this.elements.map((e) => e.toJSON()),
rest: this.rest.map((ast) => ast.toJSON()),
isReadonly: this.isReadonly,
annotations: toJSONAnnotations(this.annotations)
}
}
}
const formatTuple = (ast: TupleType): string => {
const formattedElements = ast.elements.map(String)
.join(", ")
return Arr.matchLeft(ast.rest, {
onEmpty: () => `readonly [${formattedElements}]`,
onNonEmpty: (head, tail) => {
const formattedHead = String(head)
const wrappedHead = formattedHead.includes(" | ") ? `(${formattedHead})` : formattedHead
if (tail.length > 0) {
const formattedTail = tail.map(String).join(", ")
if (ast.elements.length > 0) {
return `readonly [${formattedElements}, ...${wrappedHead}[], ${formattedTail}]`
} else {
return `readonly [...${wrappedHead}[], ${formattedTail}]`
}
} else {
if (ast.elements.length > 0) {
return `readonly [${formattedElements}, ...${wrappedHead}[]]`
} else {
return `ReadonlyArray<${formattedHead}>`
}
}
}
})
}
/**
* @category guards
* @since 3.10.0
*/
export const isTupleType: (ast: AST) => ast is TupleType = createASTGuard("TupleType")
/**
* @category model
* @since 3.10.0
*/
export class PropertySignature extends OptionalType {
constructor(
readonly name: PropertyKey,
type: AST,
isOptional: boolean,
readonly isReadonly: boolean,
annotations?: Annotations
) {
super(type, isOptional, annotations)
}
/**
* @since 3.10.0
*/
toString(): string {
return (this.isReadonly ? "readonly " : "") + String(this.name) + (this.isOptional ? "?" : "") + ": " +
this.type
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
name: String(this.name),
type: this.type.toJSON(),
isOptional: this.isOptional,
isReadonly: this.isReadonly,
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @since 3.10.0
*/
export type Parameter = StringKeyword | SymbolKeyword | TemplateLiteral | Refinement<Parameter>
/**
* @since 3.10.0
*/
export const isParameter = (ast: AST): ast is Parameter => {
switch (ast._tag) {
case "StringKeyword":
case "SymbolKeyword":
case "TemplateLiteral":
return true
case "Refinement":
return isParameter(ast.from)
}
return false
}
/**
* @category model
* @since 3.10.0
*/
export class IndexSignature {
/**
* @since 3.10.0
*/
readonly parameter: Parameter
constructor(
parameter: AST,
readonly type: AST,
readonly isReadonly: boolean
) {
if (isParameter(parameter)) {
this.parameter = parameter
} else {
throw new Error(errors_.getASTIndexSignatureParameterErrorMessage)
}
}
/**
* @since 3.10.0
*/
toString(): string {
return (this.isReadonly ? "readonly " : "") + `[x: ${this.parameter}]: ${this.type}`
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
parameter: this.parameter.toJSON(),
type: this.type.toJSON(),
isReadonly: this.isReadonly
}
}
}
/**
* @category model
* @since 3.10.0
*/
export class TypeLiteral implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "TypeLiteral"
/**
* @since 3.10.0
*/
readonly propertySignatures: ReadonlyArray<PropertySignature>
/**
* @since 3.10.0
*/
readonly indexSignatures: ReadonlyArray<IndexSignature>
constructor(
propertySignatures: ReadonlyArray<PropertySignature>,
indexSignatures: ReadonlyArray<IndexSignature>,
readonly annotations: Annotations = {}
) {
// check for duplicate property signatures
const keys: Record<PropertyKey, null> = {}
for (let i = 0; i < propertySignatures.length; i++) {
const name = propertySignatures[i].name
if (Object.prototype.hasOwnProperty.call(keys, name)) {
throw new Error(errors_.getASTDuplicatePropertySignatureErrorMessage(name))
}
keys[name] = null
}
// check for duplicate index signatures
const parameters = {
string: false,
symbol: false
}
for (let i = 0; i < indexSignatures.length; i++) {
const parameter = getParameterBase(indexSignatures[i].parameter)
if (isStringKeyword(parameter)) {
if (parameters.string) {
throw new Error(errors_.getASTDuplicateIndexSignatureErrorMessage("string"))
}
parameters.string = true
} else if (isSymbolKeyword(parameter)) {
if (parameters.symbol) {
throw new Error(errors_.getASTDuplicateIndexSignatureErrorMessage("symbol"))
}
parameters.symbol = true
}
}
this.propertySignatures = propertySignatures
this.indexSignatures = indexSignatures
}
/**
* @since 3.10.0
*/
toString() {
return Option.getOrElse(getExpected(this), () => formatTypeLiteral(this))
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
propertySignatures: this.propertySignatures.map((ps) => ps.toJSON()),
indexSignatures: this.indexSignatures.map((ps) => ps.toJSON()),
annotations: toJSONAnnotations(this.annotations)
}
}
}
const formatIndexSignatures = (iss: ReadonlyArray<IndexSignature>): string => iss.map(String).join("; ")
const formatTypeLiteral = (ast: TypeLiteral): string => {
if (ast.propertySignatures.length > 0) {
const pss = ast.propertySignatures.map(String).join("; ")
if (ast.indexSignatures.length > 0) {
return `{ ${pss}; ${formatIndexSignatures(ast.indexSignatures)} }`
} else {
return `{ ${pss} }`
}
} else {
if (ast.indexSignatures.length > 0) {
return `{ ${formatIndexSignatures(ast.indexSignatures)} }`
} else {
return "{}"
}
}
}
/**
* @category guards
* @since 3.10.0
*/
export const isTypeLiteral: (ast: AST) => ast is TypeLiteral = createASTGuard("TypeLiteral")
/**
* @since 3.10.0
*/
export type Members<A> = readonly [A, A, ...Array<A>]
const sortCandidates = Arr.sort(
Order.mapInput(Number.Order, (ast: AST) => {
switch (ast._tag) {
case "AnyKeyword":
return 0
case "UnknownKeyword":
return 1
case "ObjectKeyword":
return 2
case "StringKeyword":
case "NumberKeyword":
case "BooleanKeyword":
case "BigIntKeyword":
case "SymbolKeyword":
return 3
}
return 4
})
)
const literalMap = {
string: "StringKeyword",
number: "NumberKeyword",
boolean: "BooleanKeyword",
bigint: "BigIntKeyword"
} as const
/** @internal */
export const flatten = (candidates: ReadonlyArray<AST>): Array<AST> =>
Arr.flatMap(candidates, (ast) => isUnion(ast) ? flatten(ast.types) : [ast])
/** @internal */
export const unify = (candidates: ReadonlyArray<AST>): Array<AST> => {
const cs = sortCandidates(candidates)
const out: Array<AST> = []
const uniques: { [K in AST["_tag"] | "{}"]?: AST } = {}
const literals: Array<LiteralValue | symbol> = []
for (const ast of cs) {
switch (ast._tag) {
case "NeverKeyword":
break
case "AnyKeyword":
return [anyKeyword]
case "UnknownKeyword":
return [unknownKeyword]
// uniques
case "ObjectKeyword":
case "UndefinedKeyword":
case "VoidKeyword":
case "StringKeyword":
case "NumberKeyword":
case "BooleanKeyword":
case "BigIntKeyword":
case "SymbolKeyword": {
if (!uniques[ast._tag]) {
uniques[ast._tag] = ast
out.push(ast)
}
break
}
case "Literal": {
const type = typeof ast.literal
switch (type) {
case "string":
case "number":
case "bigint":
case "boolean": {
const _tag = literalMap[type]
if (!uniques[_tag] && !literals.includes(ast.literal)) {
literals.push(ast.literal)
out.push(ast)
}
break
}
// null
case "object": {
if (!literals.includes(ast.literal)) {
literals.push(ast.literal)
out.push(ast)
}
break
}
}
break
}
case "UniqueSymbol": {
if (!uniques["SymbolKeyword"] && !literals.includes(ast.symbol)) {
literals.push(ast.symbol)
out.push(ast)
}
break
}
case "TupleType": {
if (!uniques["ObjectKeyword"]) {
out.push(ast)
}
break
}
case "TypeLiteral": {
if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) {
if (!uniques["{}"]) {
uniques["{}"] = ast
out.push(ast)
}
} else if (!uniques["ObjectKeyword"]) {
out.push(ast)
}
break
}
default:
out.push(ast)
}
}
return out
}
/**
* @category model
* @since 3.10.0
*/
export class Union<M extends AST = AST> implements Annotated {
static make = (types: ReadonlyArray<AST>, annotations?: Annotations): AST => {
return isMembers(types) ? new Union(types, annotations) : types.length === 1 ? types[0] : neverKeyword
}
/** @internal */
static unify = (candidates: ReadonlyArray<AST>, annotations?: Annotations): AST => {
return Union.make(unify(flatten(candidates)), annotations)
}
/**
* @since 3.10.0
*/
readonly _tag = "Union"
private constructor(readonly types: Members<M>, readonly annotations: Annotations = {}) {}
/**
* @since 3.10.0
*/
toString() {
return Option.getOrElse(getExpected(this), () => this.types.map(String).join(" | "))
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
types: this.types.map((ast) => ast.toJSON()),
annotations: toJSONAnnotations(this.annotations)
}
}
}
/** @internal */
export const mapMembers = <A, B>(members: Members<A>, f: (a: A) => B): Members<B> => members.map(f) as any
/** @internal */
export const isMembers = <A>(as: ReadonlyArray<A>): as is Members<A> => as.length > 1
/**
* @category guards
* @since 3.10.0
*/
export const isUnion: (ast: AST) => ast is Union = createASTGuard("Union")
const toJSONMemoMap = globalValue(
Symbol.for("effect/Schema/AST/toJSONMemoMap"),
() => new WeakMap<AST, object>()
)
/**
* @category model
* @since 3.10.0
*/
export class Suspend implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "Suspend"
constructor(readonly f: () => AST, readonly annotations: Annotations = {}) {
this.f = util_.memoizeThunk(f)
}
/**
* @since 3.10.0
*/
toString() {
return getExpected(this).pipe(
Option.orElse(() =>
Option.flatMap(
Option.liftThrowable(this.f)(),
(ast) => getExpected(ast)
)
),
Option.getOrElse(() => "<suspended schema>")
)
}
/**
* @since 3.10.0
*/
toJSON(): object {
const ast = this.f()
let out = toJSONMemoMap.get(ast)
if (out) {
return out
}
toJSONMemoMap.set(ast, { _tag: this._tag })
out = {
_tag: this._tag,
ast: ast.toJSON(),
annotations: toJSONAnnotations(this.annotations)
}
toJSONMemoMap.set(ast, out)
return out
}
}
/**
* @category guards
* @since 3.10.0
*/
export const isSuspend: (ast: AST) => ast is Suspend = createASTGuard("Suspend")
/**
* @category model
* @since 3.10.0
*/
export class Refinement<From extends AST = AST> implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "Refinement"
constructor(
readonly from: From,
readonly filter: (
input: any,
options: ParseOptions,
self: Refinement
) => Option.Option<ParseIssue>,
readonly annotations: Annotations = {}
) {}
/**
* @since 3.10.0
*/
toString() {
return getIdentifierAnnotation(this).pipe(Option.getOrElse(() =>
Option.match(getOrElseExpected(this), {
onNone: () => `{ ${this.from} | filter }`,
onSome: (expected) => isRefinement(this.from) ? String(this.from) + " & " + expected : expected
})
))
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
from: this.from.toJSON(),
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category guards
* @since 3.10.0
*/
export const isRefinement: (ast: AST) => ast is Refinement<AST> = createASTGuard("Refinement")
/**
* @category model
* @since 3.10.0
*/
export interface ParseOptions {
/**
* The `errors` option allows you to receive all parsing errors when
* attempting to parse a value using a schema. By default only the first error
* is returned, but by setting the `errors` option to `"all"`, you can receive
* all errors that occurred during the parsing process. This can be useful for
* debugging or for providing more comprehensive error messages to the user.
*
* default: "first"
*
* @since 3.10.0
*/
readonly errors?: "first" | "all" | undefined
/**
* When using a `Schema` to parse a value, by default any properties that are
* not specified in the `Schema` will be stripped out from the output. This is
* because the `Schema` is expecting a specific shape for the parsed value,
* and any excess properties do not conform to that shape.
*
* However, you can use the `onExcessProperty` option (default value:
* `"ignore"`) to trigger a parsing error. This can be particularly useful in
* cases where you need to detect and handle potential errors or unexpected
* values.
*
* If you want to allow excess properties to remain, you can use
* `onExcessProperty` set to `"preserve"`.
*
* default: "ignore"
*
* @since 3.10.0
*/
readonly onExcessProperty?: "ignore" | "error" | "preserve" | undefined
/**
* The `propertyOrder` option provides control over the order of object fields
* in the output. This feature is particularly useful when the sequence of
* keys is important for the consuming processes or when maintaining the input
* order enhances readability and usability.
*
* By default, the `propertyOrder` option is set to `"none"`. This means that
* the internal system decides the order of keys to optimize parsing speed.
* The order of keys in this mode should not be considered stable, and it's
* recommended not to rely on key ordering as it may change in future updates
* without notice.
*
* Setting `propertyOrder` to `"original"` ensures that the keys are ordered
* as they appear in the input during the decoding/encoding process.
*
* default: "none"
*
* @since 3.10.0
*/
readonly propertyOrder?: "none" | "original" | undefined
/**
* Handles missing properties in data structures. By default, missing
* properties are treated as if present with an `undefined` value. To treat
* missing properties as errors, set the `exact` option to `true`. This
* setting is already enabled by default for `is` and `asserts` functions,
* treating absent properties strictly unless overridden.
*
* default: false
*
* @since 3.10.0
*/
readonly exact?: boolean | undefined
}
/**
* @since 3.10.0
*/
export const defaultParseOption: ParseOptions = {}
/**
* @category model
* @since 3.10.0
*/
export class Transformation implements Annotated {
/**
* @since 3.10.0
*/
readonly _tag = "Transformation"
constructor(
readonly from: AST,
readonly to: AST,
readonly transformation: TransformationKind,
readonly annotations: Annotations = {}
) {}
/**
* @since 3.10.0
*/
toString() {
return Option.getOrElse(
getExpected(this),
() => `(${String(this.from)} <-> ${String(this.to)})`
)
}
/**
* @since 3.10.0
*/
toJSON(): object {
return {
_tag: this._tag,
from: this.from.toJSON(),
to: this.to.toJSON(),
annotations: toJSONAnnotations(this.annotations)
}
}
}
/**
* @category guards
* @since 3.10.0
*/
export const isTransformation: (ast: AST) => ast is Transformation = createASTGuard("Transformation")
/**
* @category model
* @since 3.10.0
*/
export type TransformationKind =
| FinalTransformation
| ComposeTransformation
| TypeLiteralTransformation
/**
* @category model
* @since 3.10.0
*/
export class FinalTransformation {
/**
* @since 3.10.0
*/
readonly _tag = "FinalTransformation"
constructor(
readonly decode: (
fromA: any,
options: ParseOptions,
self: Transformation,
fromI: any
) => Effect<any, ParseIssue, any>,
readonly encode: (toI: any, options: ParseOptions, self: Transformation, toA: any) => Effect<any, ParseIssue, any>
) {}
}
const createTransformationGuard =
<T extends TransformationKind["_tag"]>(tag: T) =>
(ast: TransformationKind): ast is Extract<TransformationKind, { _tag: T }> => ast._tag === tag
/**
* @category guards
* @since 3.10.0
*/
export const isFinalTransformation: (ast: TransformationKind) => ast is FinalTransformation = createTransformationGuard(
"FinalTransformation"
)
/**
* @category model
* @since 3.10.0
*/
export class ComposeTransformation {
/**
* @since 3.10.0
*/
readonly _tag = "ComposeTransformation"
}
/**
* @category constructors
* @since 3.10.0
*/
export const composeTransformation: ComposeTransformation = new ComposeTransformation()
/**
* @category guards
* @since 3.10.0
*/
export const isComposeTransformation: (ast: TransformationKind) => ast is ComposeTransformation =
createTransformationGuard(
"ComposeTransformation"
)
/**
* Represents a `PropertySignature -> PropertySignature` transformation
*
* The semantic of `decode` is:
* - `none()` represents the absence of the key/value pair
* - `some(value)` represents the presence of the key/value pair
*
* The semantic of `encode` is:
* - `none()` you don't want to output the key/value pair
* - `some(value)` you want to output the key/value pair
*
* @category model
* @since 3.10.0
*/
export class PropertySignatureTransformation {
constructor(
readonly from: PropertyKey,
readonly to: PropertyKey,
readonly decode: (o: Option.Option<any>) => Option.Option<any>,
readonly encode: (o: Option.Option<any>) => Option.Option<any>
) {}
}
const isRenamingPropertySignatureTransformation = (t: PropertySignatureTransformation) =>
t.decode === identity && t.encode === identity
/**
* @category model
* @since 3.10.0
*/
export class TypeLiteralTransformation {
/**
* @since 3.10.0
*/
readonly _tag = "TypeLiteralTransformation"
constructor(
readonly propertySignatureTransformations: ReadonlyArray<
PropertySignatureTransformation
>
) {
// check for duplicate property signature transformations
const fromKeys: Record<PropertyKey, true> = {}
const toKeys: Record<PropertyKey, true> = {}
for (const pst of propertySignatureTransformations) {
const from = pst.from
if (fromKeys[from]) {
throw new Error(errors_.getASTDuplicatePropertySignatureTransformationErrorMessage(from))
}
fromKeys[from] = true
const to = pst.to
if (toKeys[to]) {
throw new Error(errors_.getASTDuplicatePropertySignatureTransformationErrorMessage(to))
}
toKeys[to] = true
}
}
}
/**
* @category guards
* @since 3.10.0
*/
export const isTypeLiteralTransformation: (ast: TransformationKind) => ast is TypeLiteralTransformation =
createTransformationGuard("TypeLiteralTransformation")
// -------------------------------------------------------------------------------------
// API
// -------------------------------------------------------------------------------------
/**
* Merges a set of new annotations with existing ones, potentially overwriting
* any duplicates.
*
* @since 3.10.0
*/
export const annotations = (ast: AST, a: Annotations): AST => {
const d = Object.getOwnPropertyDescriptors(ast)
const value = { ...ast.annotations, ...a }
const surrogate = getSurrogateAnnotation(ast)
if (Option.isSome(surrogate)) {
value[SurrogateAnnotationId] = annotations(surrogate.value, a)
}
d.annotations.value = value
return Object.create(Object.getPrototypeOf(ast), d)
}
/**
* Equivalent at runtime to the TypeScript type-level `keyof` operator.
*
* @since 3.10.0
*/
export const keyof = (ast: AST): AST => Union.unify(_keyof(ast))
const STRING_KEYWORD_PATTERN = "[\\s\\S]*" // any string, including newlines
const NUMBER_KEYWORD_PATTERN = "[+-]?\\d*\\.?\\d+(?:[Ee][+-]?\\d+)?"
const getTemplateLiteralSpanTypePattern = (type: TemplateLiteralSpanType, capture: boolean): string => {
switch (type._tag) {
case "Literal":
return regexp.escape(String(type.literal))
case "StringKeyword":
return STRING_KEYWORD_PATTERN
case "NumberKeyword":
return NUMBER_KEYWORD_PATTERN
case "TemplateLiteral":
return getTemplateLiteralPattern(type, capture, false)
case "Union":
return type.types.map((type) => getTemplateLiteralSpanTypePattern(type, capture)).join("|")
}
}
const handleTemplateLiteralSpanTypeParens = (
type: TemplateLiteralSpanType,
s: string,
capture: boolean,
top: boolean
) => {
if (isUnion(type)) {
if (capture && !top) {
return `(?:${s})`
}
} else i