typia
Version:
Superfast runtime validators with only one line
782 lines (743 loc) • 25.3 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 { Metadata } from "../../schemas/metadata/Metadata";
import { MetadataArray } from "../../schemas/metadata/MetadataArray";
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 { CloneJoiner } from "../helpers/CloneJoiner";
import { FunctionImporter } from "../helpers/FunctionImporter";
import { UnionExplorer } from "../helpers/UnionExplorer";
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 MiscCloneProgrammer {
export const decompose = (props: {
validated: boolean;
project: IProject;
importer: FunctionImporter;
type: ts.Type;
name: string | undefined;
}): FeatureProgrammer.IDecomposed => {
const config = configure(props.project)(props.importer);
if (props.validated === false)
config.addition = (collection) =>
IsProgrammer.write_function_statements(props.project)(props.importer)(
collection,
);
const composed: FeatureProgrammer.IComposed = FeatureProgrammer.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) =>
(type: ts.Type, name?: string): ts.CallExpression => {
const importer: FunctionImporter = new FunctionImporter(modulo.getText());
const result: FeatureProgrammer.IDecomposed = decompose({
validated: false,
project,
importer,
type,
name,
});
return FeatureProgrammer.writeDecomposed({
modulo,
importer,
result,
});
};
const write_array_functions =
(config: FeatureProgrammer.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(config)(importer)(
ts.factory.createIdentifier("input"),
MetadataArray.create({
type,
tags: [],
}),
{
tracable: config.trace,
source: "function",
from: "array",
postfix: "",
},
),
),
),
);
const write_tuple_functions =
(project: IProject) =>
(config: FeatureProgrammer.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: "",
},
),
),
),
);
/* -----------------------------------------------------------
DECODERS
----------------------------------------------------------- */
const decode =
(project: IProject) =>
(config: FeatureProgrammer.IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
meta: Metadata,
explore: FeatureProgrammer.IExplore,
): ts.Expression => {
// ANY TYPE
if (
meta.any ||
meta.arrays.some((a) => a.type.value.any) ||
meta.tuples.some(
(t) =>
!!t.type.elements.length && t.type.elements.every((e) => e.any),
)
)
return ts.factory.createCallExpression(importer.use("any"), undefined, [
input,
]);
interface IUnion {
type: string;
is: () => ts.Expression;
value: () => ts.Expression;
}
const unions: IUnion[] = [];
//----
// LIST UP UNION TYPES
//----
// FUNCTIONAL
if (meta.functions.length)
unions.push({
type: "functional",
is: () =>
ts.factory.createStrictEquality(
ts.factory.createStringLiteral("function"),
ts.factory.createTypeOfExpression(input),
),
value: () => ts.factory.createIdentifier("undefined"),
});
// TUPLES
for (const tuple of meta.tuples)
unions.push({
type: "tuple",
is: () =>
IsProgrammer.decode(project)(importer)(
input,
(() => {
const partial = Metadata.initialize();
partial.tuples.push(tuple);
return partial;
})(),
explore,
),
value: () =>
decode_tuple(project)(config)(importer)(input, tuple, explore),
});
// ARRAYS
if (meta.arrays.length)
unions.push({
type: "array",
is: () => ExpressionFactory.isArray(input),
value: () =>
explore_arrays(project)(config)(importer)(input, meta.arrays, {
...explore,
from: "array",
}),
});
// NATIVE TYPES
if (meta.sets.length)
unions.push({
type: "set",
is: () => ExpressionFactory.isInstanceOf("Set")(input),
value: () =>
explore_sets(project)(config)(importer)(input, meta.sets, {
...explore,
from: "array",
}),
});
if (meta.maps.length)
unions.push({
type: "map",
is: () => ExpressionFactory.isInstanceOf("Map")(input),
value: () =>
explore_maps(project)(config)(importer)(input, meta.maps, {
...explore,
from: "array",
}),
});
for (const native of meta.natives)
unions.push({
type: "native",
is: () => ExpressionFactory.isInstanceOf(native)(input),
value: () =>
native === "Boolean" || native === "Number" || native === "String"
? ts.factory.createCallExpression(
IdentifierFactory.access(input)("valueOf"),
undefined,
undefined,
)
: decode_native(native)(input),
});
// OBJECTS
if (meta.objects.length)
unions.push({
type: "object",
is: () =>
ExpressionFactory.isObject({
checkNull: true,
checkArray: false,
})(input),
value: () =>
explore_objects(config)(importer)(input, meta, {
...explore,
from: "object",
}),
});
// COMPOSITION
if (unions.length === 0) return input;
else if (unions.length === 1 && meta.size() === 1) {
const value: ts.Expression =
(meta.nullable || meta.isRequired() === false) && is_instance(meta)
? ts.factory.createConditionalExpression(
input,
undefined,
unions[0]!.value(),
undefined,
input,
)
: unions[0]!.value();
return ts.factory.createAsExpression(value, TypeFactory.keyword("any"));
} else {
let last: ts.Expression = input;
for (const u of unions.reverse())
last = ts.factory.createConditionalExpression(
u.is(),
undefined,
u.value(),
undefined,
last,
);
return ts.factory.createAsExpression(last, TypeFactory.keyword("any"));
}
};
const decode_object = (importer: FunctionImporter) =>
FeatureProgrammer.decode_object({
trace: false,
path: false,
prefix: PREFIX,
})(importer);
const decode_array =
(config: FeatureProgrammer.IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
array: MetadataArray,
explore: FeatureProgrammer.IExplore,
) =>
array.type.recursive
? ts.factory.createCallExpression(
ts.factory.createIdentifier(
importer.useLocal(`${config.prefix}a${array.type.index}`),
),
undefined,
FeatureProgrammer.argumentsArray(config)({
...explore,
source: "function",
from: "array",
})(input),
)
: decode_array_inline(config)(importer)(input, array, explore);
const decode_array_inline =
(config: FeatureProgrammer.IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
array: MetadataArray,
explore: FeatureProgrammer.IExplore,
) =>
FeatureProgrammer.decode_array(config)(importer)(CloneJoiner.array)(
input,
array,
explore,
);
const decode_tuple =
(project: IProject) =>
(config: FeatureProgrammer.IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
tuple: MetadataTuple,
explore: FeatureProgrammer.IExplore,
): ts.Expression =>
tuple.type.recursive
? ts.factory.createCallExpression(
ts.factory.createIdentifier(
importer.useLocal(`${config.prefix}t${tuple.type.index}`),
),
undefined,
FeatureProgrammer.argumentsArray(config)({
...explore,
source: "function",
})(input),
)
: decode_tuple_inline(project)(config)(importer)(
input,
tuple.type,
explore,
);
const decode_tuple_inline =
(project: IProject) =>
(config: FeatureProgrammer.IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
tuple: MetadataTupleType,
explore: FeatureProgrammer.IExplore,
): ts.Expression => {
const children: ts.Expression[] = tuple.elements
.filter((m) => m.rest === null)
.map((elem, index) =>
decode(project)(config)(importer)(
ts.factory.createElementAccessExpression(input, index),
elem,
{
...explore,
from: "array",
postfix: explore.postfix.length
? `${postfix_of_tuple(explore.postfix)}[${index}]"`
: `"[${index}]"`,
},
),
);
const rest = (() => {
if (tuple.elements.length === 0) return null;
const last: Metadata = tuple.elements.at(-1)!;
const rest: Metadata | null = last.rest;
if (rest === null) return null;
return 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,
},
);
})();
return CloneJoiner.tuple(children, rest);
};
/* -----------------------------------------------------------
NATIVE CLASSES
----------------------------------------------------------- */
const decode_native = (type: string) => (input: ts.Expression) =>
type === "Date" ||
type === "Uint8Array" ||
type === "Uint8ClampedArray" ||
type === "Uint16Array" ||
type === "Uint32Array" ||
type === "BigUint64Array" ||
type === "Int8Array" ||
type === "Int16Array" ||
type === "Int32Array" ||
type === "BigInt64Array" ||
type === "Float32Array" ||
type === "Float64Array" ||
type === "RegExp"
? decode_native_copyable(type)(input)
: type === "ArrayBuffer" || type === "SharedArrayBuffer"
? decode_native_buffer(type)(input)
: type === "DataView"
? decode_native_data_view(input)
: ts.factory.createCallExpression(
ts.factory.createIdentifier(type),
undefined,
[],
);
const decode_native_copyable = (type: string) => (input: ts.Expression) =>
ts.factory.createNewExpression(
ts.factory.createIdentifier(type),
undefined,
[input],
);
const decode_native_buffer =
(type: "ArrayBuffer" | "SharedArrayBuffer") => (input: ts.Expression) =>
ExpressionFactory.selfCall(
ts.factory.createBlock(
[
StatementFactory.constant(
"buffer",
ts.factory.createNewExpression(
ts.factory.createIdentifier(type),
undefined,
[IdentifierFactory.access(input)("byteLength")],
),
),
ts.factory.createExpressionStatement(
ts.factory.createCallExpression(
IdentifierFactory.access(
ts.factory.createNewExpression(
ts.factory.createIdentifier("Uint8Array"),
undefined,
[ts.factory.createIdentifier("buffer")],
),
)("set"),
undefined,
[
ts.factory.createNewExpression(
ts.factory.createIdentifier("Uint8Array"),
undefined,
[input],
),
],
),
),
ts.factory.createReturnStatement(
ts.factory.createIdentifier("buffer"),
),
],
true,
),
);
const decode_native_data_view = (input: ts.Expression) =>
ts.factory.createNewExpression(
ts.factory.createIdentifier("DataView"),
undefined,
[IdentifierFactory.access(input)("buffer")],
);
/* -----------------------------------------------------------
EXPLORERS FOR UNION TYPES
----------------------------------------------------------- */
const explore_sets =
(project: IProject) =>
(config: FeatureProgrammer.IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
sets: Metadata[],
explore: FeatureProgrammer.IExplore,
): ts.Expression =>
ts.factory.createCallExpression(
UnionExplorer.set({
checker: IsProgrammer.decode(project)(importer),
decoder: (input, array, explore) =>
ts.factory.createNewExpression(
ts.factory.createIdentifier("Set"),
[TypeFactory.keyword("any")],
[decode_array(config)(importer)(input, array, explore)],
),
empty: ts.factory.createNewExpression(
ts.factory.createIdentifier("Set"),
[TypeFactory.keyword("any")],
[],
),
success: ts.factory.createTrue(),
failure: (input, expected) =>
create_throw_error(importer)(expected)(input),
})([])(input, sets, explore),
undefined,
undefined,
);
const explore_maps =
(project: IProject) =>
(config: FeatureProgrammer.IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
maps: Metadata.Entry[],
explore: FeatureProgrammer.IExplore,
): ts.Expression =>
ts.factory.createCallExpression(
UnionExplorer.map({
checker: (top, entry, explore) => {
const func = IsProgrammer.decode(project)(importer);
return ts.factory.createLogicalAnd(
func(ts.factory.createElementAccessExpression(top, 0), entry[0], {
...explore,
postfix: `${explore.postfix}[0]`,
}),
func(ts.factory.createElementAccessExpression(top, 1), entry[1], {
...explore,
postfix: `${explore.postfix}[1]`,
}),
);
},
decoder: (input, array, explore) =>
ts.factory.createNewExpression(
ts.factory.createIdentifier("Map"),
[TypeFactory.keyword("any"), TypeFactory.keyword("any")],
[decode_array(config)(importer)(input, array, explore)],
),
empty: ts.factory.createNewExpression(
ts.factory.createIdentifier("Map"),
[TypeFactory.keyword("any"), TypeFactory.keyword("any")],
[],
),
success: ts.factory.createTrue(),
failure: (input, expected) =>
create_throw_error(importer)(expected)(input),
})([])(input, maps, explore),
undefined,
undefined,
);
const explore_objects =
(config: FeatureProgrammer.IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
meta: Metadata,
explore: FeatureProgrammer.IExplore,
) =>
meta.objects.length === 1
? decode_object(importer)(input, meta.objects[0]!, explore)
: ts.factory.createCallExpression(
ts.factory.createIdentifier(
importer.useLocal(`${PREFIX}u${meta.union_index!}`),
),
undefined,
FeatureProgrammer.argumentsArray(config)(explore)(input),
);
const explore_arrays =
(project: IProject) =>
(config: FeatureProgrammer.IConfig) =>
(importer: FunctionImporter) =>
(
input: ts.Expression,
elements: MetadataArray[],
explore: FeatureProgrammer.IExplore,
): ts.Expression =>
explore_array_like_union_types(config)(importer)(
UnionExplorer.array({
checker: IsProgrammer.decode(project)(importer),
decoder: decode_array(config)(importer),
empty: ts.factory.createIdentifier("[]"),
success: ts.factory.createTrue(),
failure: (input, expected) =>
create_throw_error(importer)(expected)(input),
}),
)(input, elements, explore);
const explore_array_like_union_types =
(config: FeatureProgrammer.IConfig) =>
(importer: FunctionImporter) =>
<T extends MetadataArray | MetadataTuple>(
factory: (
parameters: ts.ParameterDeclaration[],
) => (
input: ts.Expression,
elements: T[],
explore: FeatureProgrammer.IExplore,
) => ts.ArrowFunction,
) =>
(
input: ts.Expression,
elements: T[],
explore: FeatureProgrammer.IExplore,
): ts.Expression => {
const arrow =
(parameters: ts.ParameterDeclaration[]) =>
(explore: FeatureProgrammer.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.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),
);
};
/* -----------------------------------------------------------
CONFIGURATIONS
----------------------------------------------------------- */
const PREFIX = "$c";
const configure =
(project: IProject) =>
(importer: FunctionImporter): FeatureProgrammer.IConfig => {
const config: FeatureProgrammer.IConfig = {
types: {
input: (type, name) =>
ts.factory.createTypeReferenceNode(
name ?? TypeFactory.getFullName(project.checker)(type),
),
output: (type, name) =>
ts.factory.createImportTypeNode(
ts.factory.createLiteralTypeNode(
ts.factory.createStringLiteral("typia"),
),
undefined,
ts.factory.createIdentifier("Resolved"),
[
ts.factory.createTypeReferenceNode(
name ?? TypeFactory.getFullName(project.checker)(type),
),
],
false,
),
},
prefix: PREFIX,
trace: false,
path: false,
initializer,
decoder: () => decode(project)(config)(importer),
objector: {
checker: () => IsProgrammer.decode(project)(importer),
decoder: () => decode_object(importer),
joiner: CloneJoiner.object,
unionizer: decode_union_object(
IsProgrammer.decode_object(project)(importer),
)(decode_object(importer))((exp) => exp)((input, expected) =>
create_throw_error(importer)(expected)(input),
),
failure: (input, expected) =>
create_throw_error(importer)(expected)(input),
},
generator: {
arrays: () => write_array_functions(config)(importer),
tuples: () => write_tuple_functions(project)(config)(importer),
},
};
return config;
};
const initializer: FeatureProgrammer.IConfig["initializer"] =
(project) => (importer) => (type) => {
const collection = new MetadataCollection();
const result = MetadataFactory.analyze(
project.checker,
project.context,
)({
escape: false,
constant: true,
absorb: true,
validate: (meta) => {
const output: string[] = [];
if (meta.natives.some((n) => n === "WeakSet"))
output.push("unable to clone WeakSet");
else if (meta.natives.some((n) => n === "WeakMap"))
output.push("unable to clone WeakMap");
return output;
},
})(collection)(type);
if (result.success === false)
throw TransformerError.from(`typia.misc.${importer.method}`)(
result.errors,
);
return [collection, result.data];
};
const create_throw_error =
(importer: FunctionImporter) =>
(expected: string) =>
(value: ts.Expression) =>
ts.factory.createExpressionStatement(
ts.factory.createCallExpression(
importer.use("throws"),
[],
[
ts.factory.createObjectLiteralExpression(
[
ts.factory.createPropertyAssignment(
"expected",
ts.factory.createStringLiteral(expected),
),
ts.factory.createPropertyAssignment("value", value),
],
true,
),
],
),
);
const is_instance = (meta: Metadata): boolean =>
!!meta.objects.length ||
!!meta.arrays.length ||
!!meta.tuples.length ||
!!meta.sets.length ||
!!meta.maps.length ||
!!meta.natives.length ||
(meta.rest !== null && is_instance(meta.rest));
}