typia
Version:
Superfast runtime validators with only one line
1,139 lines (1,090 loc) • 36.9 kB
text/typescript
import ts from "typescript";
import { ExpressionFactory } from "../factories/ExpressionFactory";
import { IdentifierFactory } from "../factories/IdentifierFactory";
import { MetadataCollection } from "../factories/MetadataCollection";
import { MetadataFactory } from "../factories/MetadataFactory";
import { StatementFactory } from "../factories/StatementFactory";
import { TypeFactory } from "../factories/TypeFactory";
import { ValueFactory } from "../factories/ValueFactory";
import { Metadata } from "../schemas/metadata/Metadata";
import { MetadataArray } from "../schemas/metadata/MetadataArray";
import { MetadataConstant } from "../schemas/metadata/MetadataConstant";
import { MetadataObject } from "../schemas/metadata/MetadataObject";
import { MetadataTuple } from "../schemas/metadata/MetadataTuple";
import { MetadataTupleType } from "../schemas/metadata/MetadataTupleType";
import { IProject } from "../transformers/IProject";
import { TransformerError } from "../transformers/TransformerError";
import { FeatureProgrammer } from "./FeatureProgrammer";
import { IsProgrammer } from "./IsProgrammer";
import { AtomicPredicator } from "./helpers/AtomicPredicator";
import { FunctionImporter } from "./helpers/FunctionImporter";
import { ICheckEntry } from "./helpers/ICheckEntry";
import { IExpressionEntry } from "./helpers/IExpressionEntry";
import { OptionPredicator } from "./helpers/OptionPredicator";
import { UnionExplorer } from "./helpers/UnionExplorer";
import { check_array_length } from "./internal/check_array_length";
import { check_bigint } from "./internal/check_bigint";
import { check_native } from "./internal/check_native";
import { check_number } from "./internal/check_number";
import { check_string } from "./internal/check_string";
import { check_template } from "./internal/check_template";
import { decode_union_object } from "./internal/decode_union_object";
import { postfix_of_tuple } from "./internal/postfix_of_tuple";
import { wrap_metadata_rest_tuple } from "./internal/wrap_metadata_rest_tuple";
export namespace CheckerProgrammer {
export interface IConfig {
prefix: string;
path: boolean;
trace: boolean;
equals: boolean;
numeric: boolean;
addition?: () => ts.Statement[];
decoder?: () => FeatureProgrammer.Decoder<Metadata, ts.Expression>;
combiner: IConfig.Combiner;
atomist: (
explore: IExplore,
) => (check: ICheckEntry) => (input: ts.Expression) => ts.Expression;
joiner: IConfig.IJoiner;
success: ts.Expression;
}
export namespace IConfig {
export interface Combiner {
(explorer: IExplore): {
(logic: "and" | "or"): {
(
input: ts.Expression,
binaries: IBinary[],
expected: string,
): ts.Expression;
};
};
}
export interface IJoiner {
object(input: ts.Expression, entries: IExpressionEntry[]): ts.Expression;
array(input: ts.Expression, arrow: ts.ArrowFunction): ts.Expression;
tuple?: undefined | ((exprs: ts.Expression[]) => ts.Expression);
failure(
value: ts.Expression,
expected: string,
explore?: undefined | FeatureProgrammer.IExplore,
): ts.Expression;
is?(expression: ts.Expression): ts.Expression;
required?(exp: ts.Expression): ts.Expression;
full?:
| undefined
| ((
condition: ts.Expression,
) => (
input: ts.Expression,
expected: string,
explore: IExplore,
) => ts.Expression);
}
}
export type IExplore = FeatureProgrammer.IExplore;
export interface IBinary {
expression: ts.Expression;
combined: boolean;
}
/* -----------------------------------------------------------
WRITERS
----------------------------------------------------------- */
export const compose = (props: {
project: IProject;
config: IConfig;
importer: FunctionImporter;
type: ts.Type;
name: string | undefined;
}) =>
FeatureProgrammer.compose({
...props,
config: configure(props.project)(props.config)(props.importer),
});
export const write =
(project: IProject) => (config: IConfig) => (importer: FunctionImporter) =>
FeatureProgrammer.write(project)(configure(project)(config)(importer))(
importer,
);
export const write_object_functions =
(project: IProject) => (config: IConfig) => (importer: FunctionImporter) =>
FeatureProgrammer.write_object_functions(
configure(project)(config)(importer),
)(importer);
export const write_union_functions =
(project: IProject) => (config: IConfig) => (importer: FunctionImporter) =>
FeatureProgrammer.write_union_functions(
configure(project)({ ...config, numeric: false })(importer),
);
export const write_array_functions =
(project: IProject) =>
(config: IConfig) =>
(importer: FunctionImporter) =>
(collection: MetadataCollection): ts.VariableStatement[] =>
collection
.arrays()
.filter((a) => a.recursive)
.map((type, i) =>
StatementFactory.constant(
`${config.prefix}a${i}`,
ts.factory.createArrowFunction(
undefined,
undefined,
FeatureProgrammer.parameterDeclarations(config)(
TypeFactory.keyword("any"),
)(ts.factory.createIdentifier("input")),
TypeFactory.keyword("any"),
undefined,
decode_array_inline(project)(config)(importer)(
ts.factory.createIdentifier("input"),
MetadataArray.create({
type,
tags: [],
}),
{
tracable: config.trace,
source: "function",
from: "array",
postfix: "",
},
),
),
),
);
export const write_tuple_functions =
(project: IProject) =>
(config: IConfig) =>
(importer: FunctionImporter) =>
(collection: MetadataCollection): ts.VariableStatement[] =>
collection
.tuples()
.filter((t) => t.recursive)
.map((tuple, i) =>
StatementFactory.constant(
`${config.prefix}t${i}`,
ts.factory.createArrowFunction(
undefined,
undefined,
FeatureProgrammer.parameterDeclarations(config)(
TypeFactory.keyword("any"),
)(ts.factory.createIdentifier("input")),
TypeFactory.keyword("any"),
undefined,
decode_tuple_inline(project)(config)(importer)(
ts.factory.createIdentifier("input"),
tuple,
{
tracable: config.trace,
source: "function",
from: "array",
postfix: "",
},
),
),
),
);
const configure =
(project: IProject) =>
(config: IConfig) =>
(importer: FunctionImporter): FeatureProgrammer.IConfig => ({
types: {
input: () => TypeFactory.keyword("any"),
output: (type, name) =>
ts.factory.createTypePredicateNode(
undefined,
"input",
ts.factory.createTypeReferenceNode(
name ?? TypeFactory.getFullName(project.checker)(type),
),
),
},
trace: config.trace,
path: config.path,
prefix: config.prefix,
initializer: (project) => (importer) => (type) => {
const collection: MetadataCollection = new MetadataCollection();
const result = MetadataFactory.analyze(
project.checker,
project.context,
)({
escape: false,
constant: true,
absorb: true,
})(collection)(type);
if (result.success === false)
throw TransformerError.from(`typia.${importer.method}`)(
result.errors,
);
return [collection, result.data];
},
addition: config.addition,
decoder: () => config.decoder?.() ?? decode(project)(config)(importer),
objector: {
checker: () => config.decoder?.() ?? decode(project)(config)(importer),
decoder: () => decode_object(config)(importer),
joiner: config.joiner.object,
unionizer: config.equals
? decode_union_object(decode_object(config)(importer))(
(input, obj, explore) =>
decode_object(config)(importer)(input, obj, {
...explore,
tracable: true,
}),
)(config.joiner.is ?? ((expr) => expr))((value, expected) =>
ts.factory.createReturnStatement(
config.joiner.failure(value, expected),
),
)
: (input, targets, explore) =>
config.combiner(explore)("or")(
input,
targets.map((obj) => ({
expression: decode_object(config)(importer)(
input,
obj,
explore,
),
combined: true,
})),
`(${targets.map((t) => t.name).join(" | ")})`,
),
failure: (value, expected) =>
ts.factory.createReturnStatement(
config.joiner.failure(value, expected),
),
is: config.joiner.is,
required: config.joiner.required,
full: config.joiner.full,
type: TypeFactory.keyword("boolean"),
},
generator: {
unions: config.numeric
? () =>
FeatureProgrammer.write_union_functions(
configure(project)({ ...config, numeric: false })(importer),
)
: undefined,
arrays: () => write_array_functions(project)(config)(importer),
tuples: () => write_tuple_functions(project)(config)(importer),
},
});
/* -----------------------------------------------------------
DECODERS
----------------------------------------------------------- */
/**
* @internal
*/
export const decode =
(project: IProject) =>
(config: IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
meta: Metadata,
explore: IExplore,
): ts.Expression => {
if (meta.any) return config.success;
const top: IBinary[] = [];
const binaries: IBinary[] = [];
const add = create_add(binaries)(input);
const getConstantValue = (value: number | string | bigint | boolean) => {
if (typeof value === "string")
return ts.factory.createStringLiteral(value);
else if (typeof value === "bigint")
return ExpressionFactory.bigint(value);
return ts.factory.createIdentifier(value.toString());
};
//----
// CHECK OPTIONAL
//----
// @todo -> should be elaborated
const checkOptional: boolean = meta.empty() || meta.isUnionBucket();
// NULLABLE
if (checkOptional || meta.nullable)
(meta.nullable ? add : create_add(top)(input))(
meta.nullable,
ValueFactory.NULL(),
);
// UNDEFINDABLE
if (checkOptional || !meta.isRequired())
(meta.isRequired() ? create_add(top)(input) : add)(
!meta.isRequired(),
ValueFactory.UNDEFINED(),
);
// FUNCTIONAL
if (meta.functions.length)
if (OptionPredicator.functional(project.options) || meta.size() !== 1)
add(
true,
ts.factory.createStringLiteral("function"),
ValueFactory.TYPEOF(input),
);
else
binaries.push({
combined: false,
expression: config.success,
});
//----
// VALUES
//----
// CONSTANT VALUES
const constants: MetadataConstant[] = meta.constants.filter((c) =>
AtomicPredicator.constant(meta)(c.type),
);
const constantLength: number = constants
.map((c) => c.values.length)
.reduce((a, b) => a + b, 0);
if (constantLength >= 10) {
const values: Array<boolean | number | string | bigint> = constants
.map((c) => c.values.map((v) => v.value))
.flat();
add(
true,
ts.factory.createTrue(),
ts.factory.createCallExpression(
IdentifierFactory.access(
importer.emplaceVariable(
`${config.prefix}v${importer.increment()}`,
ts.factory.createNewExpression(
ts.factory.createIdentifier("Set"),
undefined,
[
ts.factory.createArrayLiteralExpression(
values.map((v) =>
typeof v === "boolean"
? v === true
? ts.factory.createTrue()
: ts.factory.createFalse()
: typeof v === "bigint"
? ExpressionFactory.bigint(v)
: typeof v === "number"
? ExpressionFactory.number(v)
: ts.factory.createStringLiteral(v.toString()),
),
),
],
),
),
)("has"),
undefined,
[input],
),
);
} else
for (const c of constants)
if (AtomicPredicator.constant(meta)(c.type))
for (const v of c.values) add(true, getConstantValue(v.value));
// ATOMIC VALUES
for (const atom of meta.atomics)
if (AtomicPredicator.atomic(meta)(atom.type) === false) continue;
else if (atom.type === "number")
binaries.push({
expression: config.atomist(explore)(
check_number(project, config.numeric)(atom)(input),
)(input),
combined: false,
});
else if (atom.type === "bigint")
binaries.push({
expression: config.atomist(explore)(
check_bigint(project)(atom)(input),
)(input),
combined: false,
});
else if (atom.type === "string")
binaries.push({
expression: config.atomist(explore)(
check_string(project)(atom)(input),
)(input),
combined: false,
});
else
add(
true,
ts.factory.createStringLiteral(atom.type),
ValueFactory.TYPEOF(input),
);
// TEMPLATE LITERAL VALUES
if (meta.templates.length)
if (AtomicPredicator.template(meta))
binaries.push({
expression: config.atomist(explore)(
check_template(meta.templates)(input),
)(input),
combined: false,
});
// NATIVE CLASSES
for (const native of meta.natives)
binaries.push({
expression: check_native(native)(input),
combined: false,
});
//----
// INSTANCES
//----
interface IInstance {
pre: ts.Expression;
body: ts.Expression | null;
expected: string;
}
const instances: IInstance[] = [];
const prepare =
(pre: ts.Expression, expected: string) =>
(body: ts.Expression | null) =>
instances.push({
pre,
expected,
body,
});
// SETS
if (meta.sets.length) {
const install = prepare(
check_native("Set")(input),
meta.sets.map((elem) => `Set<${elem.getName()}>`).join(" | "),
);
if (meta.sets.some((elem) => elem.any)) install(null);
else
install(
explore_sets(project)(config)(importer)(input, meta.sets, {
...explore,
from: "array",
}),
);
}
// MAPS
if (meta.maps.length) {
const install = prepare(
check_native("Map")(input),
meta.maps
.map(({ key, value }) => `Map<${key}, ${value}>`)
.join(" | "),
);
if (meta.maps.some((elem) => elem.key.any && elem.value.any))
install(null);
else
install(
explore_maps(project)(config)(importer)(input, meta.maps, {
...explore,
from: "array",
}),
);
}
// ARRAYS AND TUPLES
if (meta.tuples.length + meta.arrays.length > 0) {
const install = prepare(
config.atomist(explore)({
expected: [
...meta.tuples.map((t) => t.type.name),
...meta.arrays.map((a) => a.getName()),
].join(" | "),
expression: ExpressionFactory.isArray(input),
conditions: [],
})(input),
[...meta.tuples, ...meta.arrays]
.map((elem) => elem.type.name)
.join(" | "),
);
if (meta.arrays.length === 0)
if (meta.tuples.length === 1)
install(
decode_tuple(project)(config)(importer)(input, meta.tuples[0]!, {
...explore,
from: "array",
}),
);
// TUPLE ONLY
else
install(
explore_tuples(project)(config)(importer)(input, meta.tuples, {
...explore,
from: "array",
}),
);
else if (meta.arrays.some((elem) => elem.type.value.any)) install(null);
else if (meta.tuples.length === 0)
if (meta.arrays.length === 1)
// ARRAY ONLY
install(
decode_array(project)(config)(importer)(input, meta.arrays[0]!, {
...explore,
from: "array",
}),
);
else
install(
explore_arrays(project)(config)(importer)(input, meta.arrays, {
...explore,
from: "array",
}),
);
else
install(
explore_arrays_and_tuples(project)(config)(importer)(
input,
[...meta.tuples, ...meta.arrays],
explore,
),
);
}
// OBJECT
if (meta.objects.length > 0)
prepare(
ExpressionFactory.isObject({
checkNull: true,
checkArray: meta.objects.some((obj) =>
obj.properties.every(
(prop) => !prop.key.isSoleLiteral() || !prop.value.isRequired(),
),
),
})(input),
meta.objects.map((obj) => obj.name).join(" | "),
)(
explore_objects(config)(importer)(input, meta, {
...explore,
from: "object",
}),
);
if (instances.length) {
const transformer =
(merger: (x: ts.Expression, y: ts.Expression) => ts.Expression) =>
(ins: IInstance) =>
ins.body
? {
expression: merger(ins.pre, ins.body),
combined: true,
}
: {
expression: ins.pre,
combined: false,
};
if (instances.length === 1)
binaries.push(
transformer((pre, body) =>
config.combiner(explore)("and")(
input,
[pre, body].map((expression) => ({
expression,
combined: expression !== pre,
})),
meta.getName(),
),
)(instances[0]!),
);
else
binaries.push({
expression: config.combiner(explore)("or")(
input,
instances.map(transformer(ts.factory.createLogicalAnd)),
meta.getName(),
),
combined: true,
});
}
// ESCAPED CASE
if (meta.escaped !== null)
binaries.push({
combined: false,
expression:
meta.escaped.original.size() === 1 &&
meta.escaped.original.natives.length === 1
? check_native(meta.escaped.original.natives[0]!)(input)
: ts.factory.createLogicalAnd(
decode(project)(config)(importer)(
input,
meta.escaped.original,
explore,
),
ts.factory.createLogicalAnd(
IsProgrammer.decode_to_json(false)(input),
decode_escaped(project)(config)(importer)(
input,
meta.escaped.returns,
explore,
),
),
),
});
//----
// COMBINE CONDITIONS
//----
return top.length && binaries.length
? config.combiner(explore)("and")(
input,
[
...top,
{
expression: config.combiner(explore)("or")(
input,
binaries,
meta.getName(),
),
combined: true,
},
],
meta.getName(),
)
: binaries.length
? config.combiner(explore)("or")(input, binaries, meta.getName())
: config.success;
};
export const decode_object =
(config: IConfig) => (importer: FunctionImporter) => {
const func = FeatureProgrammer.decode_object(config)(importer);
return (input: ts.Expression, obj: MetadataObject, explore: IExplore) => {
obj.validated = true;
return func(input, obj, explore);
};
};
const decode_array =
(project: IProject) =>
(config: IConfig) =>
(importer: FunctionImporter) =>
(input: ts.Expression, array: MetadataArray, explore: IExplore) => {
if (array.type.recursive === false)
return decode_array_inline(project)(config)(importer)(
input,
array,
explore,
);
explore = {
...explore,
source: "function",
from: "array",
};
return ts.factory.createLogicalOr(
ts.factory.createCallExpression(
ts.factory.createIdentifier(
importer.useLocal(`${config.prefix}a${array.type.index}`),
),
undefined,
FeatureProgrammer.argumentsArray(config)({
...explore,
source: "function",
from: "array",
})(input),
),
config.joiner.failure(input, array.type.name, explore),
);
};
const decode_array_inline =
(project: IProject) =>
(config: IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
array: MetadataArray,
explore: IExplore,
): ts.Expression => {
const length = check_array_length(project)(array)(input);
const main = FeatureProgrammer.decode_array({
prefix: config.prefix,
trace: config.trace,
path: config.path,
decoder: () => decode(project)(config)(importer),
})(importer)(config.joiner.array)(input, array, explore);
return length.expression === null && length.conditions.length === 0
? main
: ts.factory.createLogicalAnd(
config.atomist(explore)(length)(input),
main,
);
};
const decode_tuple =
(project: IProject) =>
(config: IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
tuple: MetadataTuple,
explore: IExplore,
): ts.Expression => {
if (tuple.type.recursive === false)
return decode_tuple_inline(project)(config)(importer)(
input,
tuple.type,
explore,
);
explore = {
...explore,
source: "function",
from: "array",
};
return ts.factory.createLogicalOr(
ts.factory.createCallExpression(
ts.factory.createIdentifier(
importer.useLocal(`${config.prefix}t${tuple.type.index}`),
),
undefined,
FeatureProgrammer.argumentsArray(config)({
...explore,
source: "function",
})(input),
),
config.joiner.failure(input, tuple.type.name, explore),
);
};
const decode_tuple_inline =
(project: IProject) =>
(config: IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
tuple: MetadataTupleType,
explore: IExplore,
): ts.Expression => {
const binaries: ts.Expression[] = tuple.elements
.filter((meta) => meta.rest === null)
.map((meta, index) =>
decode(project)(config)(importer)(
ts.factory.createElementAccessExpression(input, index),
meta,
{
...explore,
from: "array",
postfix: explore.postfix.length
? `${postfix_of_tuple(explore.postfix)}[${index}]"`
: `"[${index}]"`,
},
),
);
const rest: ts.Expression | null =
tuple.elements.length && tuple.elements.at(-1)!.rest !== null
? decode(project)(config)(importer)(
ts.factory.createCallExpression(
IdentifierFactory.access(input)("slice"),
undefined,
[ExpressionFactory.number(tuple.elements.length - 1)],
),
wrap_metadata_rest_tuple(tuple.elements.at(-1)!.rest!),
{
...explore,
start: tuple.elements.length - 1,
},
)
: null;
const arrayLength = ts.factory.createPropertyAccessExpression(
input,
"length",
);
return config.combiner(explore)("and")(
input,
[
...(rest === null
? tuple.elements.every((t) => t.optional === false)
? [
{
combined: false,
expression: ts.factory.createStrictEquality(
arrayLength,
ExpressionFactory.number(tuple.elements.length),
),
},
]
: [
{
combined: false,
expression: ts.factory.createLogicalAnd(
ts.factory.createLessThanEquals(
ExpressionFactory.number(
tuple.elements.filter((t) => t.optional === false)
.length,
),
arrayLength,
),
ts.factory.createGreaterThanEquals(
ExpressionFactory.number(tuple.elements.length),
arrayLength,
),
),
},
]
: []),
...(config.joiner.tuple
? [
{
expression: config.joiner.tuple(binaries),
combined: true,
},
]
: binaries.map((expression) => ({
expression,
combined: true,
}))),
...(rest !== null
? [
{
expression: rest,
combined: true,
},
]
: []),
],
`[${tuple.elements.map((t) => t.getName()).join(", ")}]`,
);
};
const decode_escaped =
(project: IProject) =>
(config: IConfig) =>
(importer: FunctionImporter) =>
(input: ts.Expression, meta: Metadata, explore: IExplore): ts.Expression =>
ts.factory.createCallExpression(
ts.factory.createParenthesizedExpression(
ts.factory.createArrowFunction(
undefined,
undefined,
[IdentifierFactory.parameter("input", TypeFactory.keyword("any"))],
undefined,
undefined,
decode(project)(config)(importer)(
ts.factory.createIdentifier("input"),
meta,
explore,
),
),
),
undefined,
[
ts.factory.createCallExpression(
IdentifierFactory.access(input)("toJSON"),
undefined,
[],
),
],
);
/* -----------------------------------------------------------
UNION TYPE EXPLORERS
----------------------------------------------------------- */
const explore_sets =
(project: IProject) =>
(config: IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
sets: Metadata[],
explore: IExplore,
): ts.Expression =>
ts.factory.createCallExpression(
UnionExplorer.set({
checker: decode(project)(config)(importer),
decoder: decode_array(project)(config)(importer),
empty: config.success,
success: config.success,
failure: (input, expected, explore) =>
ts.factory.createReturnStatement(
config.joiner.failure(input, expected, explore),
),
})([])(input, sets, explore),
undefined,
undefined,
);
const explore_maps =
(project: IProject) =>
(config: IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
maps: Metadata.Entry[],
explore: IExplore,
): ts.Expression =>
ts.factory.createCallExpression(
UnionExplorer.map({
checker: (input, entry, explore) => {
const func = decode(project)(config)(importer);
return ts.factory.createLogicalAnd(
func(
ts.factory.createElementAccessExpression(input, 0),
entry[0],
{
...explore,
postfix: `${explore.postfix}[0]`,
},
),
func(
ts.factory.createElementAccessExpression(input, 1),
entry[1],
{
...explore,
postfix: `${explore.postfix}[1]`,
},
),
);
},
decoder: decode_array(project)(config)(importer),
empty: config.success,
success: config.success,
failure: (input, expected, explore) =>
ts.factory.createReturnStatement(
config.joiner.failure(input, expected, explore),
),
})([])(input, maps, explore),
undefined,
undefined,
);
const explore_tuples =
(project: IProject) =>
(config: IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
tuples: MetadataTuple[],
explore: IExplore,
): ts.Expression =>
explore_array_like_union_types(config)(importer)(
UnionExplorer.tuple({
checker: decode_tuple(project)(config)(importer),
decoder: decode_tuple(project)(config)(importer),
empty: config.success,
success: config.success,
failure: (input, expected, explore) =>
ts.factory.createReturnStatement(
config.joiner.failure(input, expected, explore),
),
}),
)(input, tuples, explore);
const explore_arrays =
(project: IProject) =>
(config: IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
arrays: MetadataArray[],
explore: IExplore,
): ts.Expression =>
explore_array_like_union_types(config)(importer)(
UnionExplorer.array({
checker: decode(project)(config)(importer),
decoder: decode_array(project)(config)(importer),
empty: config.success,
success: config.success,
failure: (input, expected, explore) =>
ts.factory.createReturnStatement(
config.joiner.failure(input, expected, explore),
),
}),
)(input, arrays, explore);
const explore_arrays_and_tuples =
(project: IProject) =>
(config: IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
elements: Array<MetadataArray | MetadataTuple>,
explore: IExplore,
): ts.Expression =>
explore_array_like_union_types(config)(importer)(
UnionExplorer.array_or_tuple({
checker: (front, target, explore, array) =>
target instanceof MetadataTuple
? decode_tuple(project)(config)(importer)(front, target, explore)
: config.atomist(explore)({
expected: elements
.map((elem) =>
elem instanceof MetadataArray
? elem.getName()
: elem.type.name,
)
.join(" | "),
expression: decode(project)(config)(importer)(
front,
target,
explore,
),
conditions: [],
})(array),
decoder: (input, target, explore) =>
target instanceof MetadataTuple
? decode_tuple(project)(config)(importer)(input, target, explore)
: decode_array(project)(config)(importer)(input, target, explore),
empty: config.success,
success: config.success,
failure: (input, expected, explore) =>
ts.factory.createReturnStatement(
config.joiner.failure(input, expected, explore),
),
}),
)(input, elements, explore);
const explore_array_like_union_types =
(config: IConfig) =>
(importer: FunctionImporter) =>
<T extends MetadataArray | MetadataTuple>(
factory: (
parameters: ts.ParameterDeclaration[],
) => (
input: ts.Expression,
elements: T[],
explore: IExplore,
) => ts.ArrowFunction,
) =>
(input: ts.Expression, elements: T[], explore: IExplore): ts.Expression => {
const arrow =
(parameters: ts.ParameterDeclaration[]) =>
(explore: IExplore) =>
(input: ts.Expression): ts.ArrowFunction =>
factory(parameters)(input, elements, explore);
if (elements.every((e) => e.type.recursive === false))
ts.factory.createCallExpression(
arrow([])(explore)(input),
undefined,
[],
);
explore = {
...explore,
source: "function",
from: "array",
};
return ts.factory.createLogicalOr(
ts.factory.createCallExpression(
ts.factory.createIdentifier(
importer.emplaceUnion(
config.prefix,
elements.map((e) => e.type.name).join(" | "),
() =>
arrow(
FeatureProgrammer.parameterDeclarations(config)(
TypeFactory.keyword("any"),
)(ts.factory.createIdentifier("input")),
)({
...explore,
postfix: "",
})(ts.factory.createIdentifier("input")),
),
),
undefined,
FeatureProgrammer.argumentsArray(config)(explore)(input),
),
config.joiner.failure(
input,
elements.map((e) => e.type.name).join(" | "),
explore,
),
);
};
const explore_objects =
(config: IConfig) =>
(importer: FunctionImporter) =>
(input: ts.Expression, meta: Metadata, explore: IExplore) =>
meta.objects.length === 1
? decode_object(config)(importer)(input, meta.objects[0]!, explore)
: ts.factory.createCallExpression(
ts.factory.createIdentifier(
importer.useLocal(`${config.prefix}u${meta.union_index!}`),
),
undefined,
FeatureProgrammer.argumentsArray(config)(explore)(input),
);
}
const create_add =
(binaries: CheckerProgrammer.IBinary[]) =>
(defaultInput: ts.Expression) =>
(
exact: boolean,
left: ts.Expression,
right: ts.Expression = defaultInput,
) => {
const factory = exact
? ts.factory.createStrictEquality
: ts.factory.createStrictInequality;
binaries.push({
expression: factory(left, right),
combined: false,
});
};