UNPKG

typia

Version:

Superfast runtime validators with only one line

561 lines (527 loc) • 18 kB
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 { FunctionImporter } from "../helpers/FunctionImporter"; import { PruneJoiner } from "../helpers/PruneJoiner"; 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 MiscPruneProgrammer { 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.ConciseBody => { if (filter(meta) === false) return ts.factory.createBlock([]); interface IUnion { type: string; is: () => ts.Expression; value: () => ts.Expression | ts.Block | ts.ReturnStatement; } const unions: IUnion[] = []; //---- // LIST UP UNION TYPES //---- // TUPLES for (const tuple of meta.tuples.filter((tuple) => tuple.type.elements.some((e) => filter(e.rest ?? e)), )) 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.filter((a) => filter(a.type.value)).length) unions.push({ type: "array", is: () => ExpressionFactory.isArray(input), value: () => explore_arrays(project)(config)(importer)(input, meta.arrays, { ...explore, from: "array", }), }); // BUILT-IN CLASSES if (meta.natives.length) for (const native of meta.natives) unions.push({ type: "native", is: () => ExpressionFactory.isInstanceOf(native)(input), value: () => ts.factory.createReturnStatement(), }); if (meta.sets.length) unions.push({ type: "set", is: () => ExpressionFactory.isInstanceOf("Set")(input), value: () => ts.factory.createReturnStatement(), }); if (meta.maps.length) unions.push({ type: "map", is: () => ExpressionFactory.isInstanceOf("Map")(input), value: () => ts.factory.createReturnStatement(), }); // 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", }), }); //---- // STATEMENTS //---- const converter = (v: ts.Expression | ts.Block | ts.ReturnStatement) => ts.isReturnStatement(v) || ts.isBlock(v) ? v : ts.factory.createExpressionStatement(v); const statements: ts.Statement[] = unions.map((u) => ts.factory.createIfStatement(u.is(), converter(u.value())), ); return ts.factory.createBlock(statements, true); }; 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, ): ts.Expression => FeatureProgrammer.decode_array(config)(importer)(PruneJoiner.array)( input, array, explore, ); const decode_tuple = (project: IProject) => (config: FeatureProgrammer.IConfig) => (importer: FunctionImporter) => ( input: ts.Expression, tuple: MetadataTuple, explore: FeatureProgrammer.IExplore, ): ts.Expression | ts.Block => 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.Block => { const children: ts.ConciseBody[] = tuple.elements .map((elem, index) => [elem, index] as const) .filter(([elem]) => filter(elem) && elem.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 || filter(rest) === false) 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 PruneJoiner.tuple(children, rest); }; /* ----------------------------------------------------------- UNION TYPE EXPLORERS ----------------------------------------------------------- */ const explore_objects = (config: FeatureProgrammer.IConfig) => (importer: FunctionImporter) => ( input: ts.Expression, meta: Metadata, explore: FeatureProgrammer.IExplore, ) => { if (meta.objects.length === 1) return decode_object(importer)(input, meta.objects[0]!, explore); return 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.createStringLiteral("[]"), 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), ); }; // @todo -> must filter out recursive visit const filter = (meta: Metadata): boolean => meta.any === false && (meta.objects.length !== 0 || meta.tuples.some( (t) => !!t.type.elements.length && t.type.elements.some((e) => filter(e.rest ?? e)), ) || meta.arrays.some((e) => filter(e.type.value))); /* ----------------------------------------------------------- CONFIGURATIONS ----------------------------------------------------------- */ const PREFIX = "$p"; 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: () => TypeFactory.keyword("void"), }, prefix: PREFIX, trace: false, path: false, initializer, decoder: () => decode(project)(config)(importer), objector: { checker: () => IsProgrammer.decode(project)(importer), decoder: () => decode_object(importer), joiner: PruneJoiner.object, unionizer: decode_union_object( IsProgrammer.decode_object(project)(importer), )(decode_object(importer))((exp) => exp)((value, expected) => create_throw_error(importer)(expected)(value), ), 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, })(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, ), ], ), ); }