typia
Version:
Superfast runtime validators with only one line
221 lines (206 loc) • 7.3 kB
text/typescript
import ts from "typescript";
import { ExpressionFactory } from "../factories/ExpressionFactory";
import { IdentifierFactory } from "../factories/IdentifierFactory";
import { MetadataCollection } from "../factories/MetadataCollection";
import { ValueFactory } from "../factories/ValueFactory";
import { IProject } from "../transformers/IProject";
import { CheckerProgrammer } from "./CheckerProgrammer";
import { FeatureProgrammer } from "./FeatureProgrammer";
import { FunctionImporter } from "./helpers/FunctionImporter";
import { IExpressionEntry } from "./helpers/IExpressionEntry";
import { OptionPredicator } from "./helpers/OptionPredicator";
import { check_object } from "./internal/check_object";
export namespace IsProgrammer {
export const configure =
(options?: Partial<CONFIG.IOptions>) =>
(project: IProject) =>
(importer: FunctionImporter): CheckerProgrammer.IConfig => ({
prefix: "$i",
equals: !!options?.object,
trace: false,
path: false,
numeric: OptionPredicator.numeric({
numeric: options?.numeric,
}),
atomist: () => (entry) => () =>
[
...(entry.expression ? [entry.expression] : []),
...(entry.conditions.length === 0
? []
: [
entry.conditions
.map((set) =>
set
.map((s) => s.expression)
.reduce((a, b) => ts.factory.createLogicalAnd(a, b)),
)
.reduce((a, b) => ts.factory.createLogicalOr(a, b)),
]),
].reduce((x, y) => ts.factory.createLogicalAnd(x, y)),
combiner: () => (type: "and" | "or") => {
const initial: ts.TrueLiteral | ts.FalseLiteral =
type === "and" ? ts.factory.createTrue() : ts.factory.createFalse();
const binder =
type === "and"
? ts.factory.createLogicalAnd
: ts.factory.createLogicalOr;
return (
_input: ts.Expression,
binaries: CheckerProgrammer.IBinary[],
) =>
binaries.length
? binaries
.map((binary) => binary.expression)
.reduce((x, y) => binder(x, y))
: initial;
},
joiner: {
object:
options?.object ||
check_object({
equals: !!options?.object,
undefined: OptionPredicator.undefined({
undefined: options?.undefined,
}),
assert: true,
reduce: ts.factory.createLogicalAnd,
positive: ts.factory.createTrue(),
superfluous: () => ts.factory.createFalse(),
})(project)(importer),
array: (input, arrow) =>
ts.factory.createCallExpression(
IdentifierFactory.access(input)("every"),
undefined,
[arrow],
),
failure: () => ts.factory.createFalse(),
},
success: ts.factory.createTrue(),
});
export namespace CONFIG {
export interface IOptions {
numeric: boolean;
undefined: boolean;
object: (
input: ts.Expression,
entries: IExpressionEntry<ts.Expression>[],
) => ts.Expression;
}
}
/* -----------------------------------------------------------
WRITERS
----------------------------------------------------------- */
export const decompose = (props: {
project: IProject;
importer: FunctionImporter;
equals: boolean;
type: ts.Type;
name: string | undefined;
}): FeatureProgrammer.IDecomposed => {
// CONFIGURATION
const config: CheckerProgrammer.IConfig = {
...configure({
object: check_object({
equals: props.equals,
undefined: OptionPredicator.undefined(props.project.options),
assert: true,
reduce: ts.factory.createLogicalAnd,
positive: ts.factory.createTrue(),
superfluous: () => ts.factory.createFalse(),
})(props.project)(props.importer),
numeric: OptionPredicator.numeric(props.project.options),
})(props.project)(props.importer),
trace: props.equals,
};
// COMPOSITION
const composed: FeatureProgrammer.IComposed = CheckerProgrammer.compose({
...props,
config,
});
return {
functions: composed.functions,
statements: composed.statements,
arrow: ts.factory.createArrowFunction(
undefined,
undefined,
composed.parameters,
composed.response,
undefined,
composed.body,
),
};
};
export const write =
(project: IProject) =>
(modulo: ts.LeftHandSideExpression) =>
(equals: boolean) =>
(type: ts.Type, name?: string) => {
const importer: FunctionImporter = new FunctionImporter(modulo.getText());
const result: FeatureProgrammer.IDecomposed = decompose({
equals,
project,
importer,
type,
name,
});
return FeatureProgrammer.writeDecomposed({
modulo,
importer,
result,
});
};
export const write_function_statements =
(project: IProject) =>
(importer: FunctionImporter) =>
(collection: MetadataCollection) => {
const config = configure()(project)(importer);
const objects =
CheckerProgrammer.write_object_functions(project)(config)(importer)(
collection,
);
const unions =
CheckerProgrammer.write_union_functions(project)(config)(importer)(
collection,
);
const arrays =
CheckerProgrammer.write_array_functions(project)(config)(importer)(
collection,
);
const tuples =
CheckerProgrammer.write_tuple_functions(project)(config)(importer)(
collection,
);
return [
...objects.filter((_, i) => importer.hasLocal(`${config.prefix}o${i}`)),
...unions.filter((_, i) => importer.hasLocal(`${config.prefix}u${i}`)),
...arrays.filter((_, i) => importer.hasLocal(`${config.prefix}a${i}`)),
...tuples.filter((_, i) => importer.hasLocal(`${config.prefix}t${i}`)),
];
};
/* -----------------------------------------------------------
DECODERS
----------------------------------------------------------- */
export const decode = (project: IProject) => (importer: FunctionImporter) =>
CheckerProgrammer.decode(project)(configure()(project)(importer))(importer);
export const decode_object =
(project: IProject) => (importer: FunctionImporter) =>
CheckerProgrammer.decode_object(configure()(project)(importer))(importer);
export const decode_to_json =
(checkNull: boolean) =>
(input: ts.Expression): ts.Expression =>
ts.factory.createLogicalAnd(
ExpressionFactory.isObject({
checkArray: false,
checkNull,
})(input),
ts.factory.createStrictEquality(
ts.factory.createStringLiteral("function"),
ValueFactory.TYPEOF(IdentifierFactory.access(input)("toJSON")),
),
);
export const decode_functional = (input: ts.Expression) =>
ts.factory.createStrictEquality(
ts.factory.createStringLiteral("function"),
ValueFactory.TYPEOF(input),
);
}