UNPKG

typia

Version:

Superfast runtime validators with only one line

956 lines (914 loc) • 31.8 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 { TemplateFactory } from "../factories/TemplateFactory"; import { TypeFactory } from "../factories/TypeFactory"; import { Metadata } from "../schemas/metadata/Metadata"; import { MetadataArray } from "../schemas/metadata/MetadataArray"; import { MetadataArrayType } from "../schemas/metadata/MetadataArrayType"; import { MetadataAtomic } from "../schemas/metadata/MetadataAtomic"; import { MetadataObject } from "../schemas/metadata/MetadataObject"; import { MetadataTemplate } from "../schemas/metadata/MetadataTemplate"; import { MetadataTuple } from "../schemas/metadata/MetadataTuple"; import { MetadataTupleType } from "../schemas/metadata/MetadataTupleType"; import { IProject } from "../transformers/IProject"; import { TransformerError } from "../transformers/TransformerError"; import { Escaper } from "../utils/Escaper"; import { Format } from "../tags"; import { FeatureProgrammer } from "./FeatureProgrammer"; import { FunctionImporter } from "./helpers/FunctionImporter"; import { RandomJoiner } from "./helpers/RandomJoiner"; import { RandomRanger } from "./helpers/RandomRanger"; import { random_custom } from "./internal/random_custom"; export namespace RandomProgrammer { export const decompose = (props: { project: IProject; importer: FunctionImporter; type: ts.Type; name: string | undefined; init: ts.Expression | undefined; }): FeatureProgrammer.IDecomposed => { const collection: MetadataCollection = new MetadataCollection(); const result = MetadataFactory.analyze( props.project.checker, props.project.context, )({ escape: false, constant: true, absorb: true, validate: (meta) => { const output: string[] = []; if (meta.natives.some((n) => n === "WeakSet")) output.push(`WeakSet is not supported.`); else if (meta.natives.some((n) => n === "WeakMap")) output.push(`WeakMap is not supported.`); return output; }, })(collection)(props.type); if (result.success === false) throw TransformerError.from(`typia.${props.importer.method}`)( result.errors, ); // GENERATE FUNCTION const functions: Record<string, ts.VariableStatement> = Object.fromEntries([ ...write_object_functions(props.importer)(collection).map((v, i) => [ Prefix.object(i), v, ]), ...write_array_functions(props.importer)(collection).map((v, i) => [ Prefix.array(i), v, ]), ...write_tuple_functions(props.importer)(collection).map((v, i) => [ Prefix.tuple(i), v, ]), ]); const arrow: ts.ArrowFunction = ts.factory.createArrowFunction( undefined, undefined, [ IdentifierFactory.parameter( "generator", ts.factory.createTypeReferenceNode("Partial<typia.IRandomGenerator>"), props.init ?? ts.factory.createToken(ts.SyntaxKind.QuestionToken), ), ], ts.factory.createImportTypeNode( ts.factory.createLiteralTypeNode( ts.factory.createStringLiteral("typia"), ), undefined, ts.factory.createIdentifier("Resolved"), [ ts.factory.createTypeReferenceNode( props.name ?? TypeFactory.getFullName(props.project.checker)(props.type), ), ], false, ), undefined, ts.factory.createBlock( [ ts.factory.createExpressionStatement( ts.factory.createBinaryExpression( ts.factory.createIdentifier("_generator"), ts.SyntaxKind.EqualsToken, ts.factory.createIdentifier("generator"), ), ), ts.factory.createReturnStatement( decode(props.importer)({ function: false, recursive: false, })(result.data), ), ], true, ), ); return { functions, statements: [StatementFactory.mut("_generator")], arrow, }; }; export const write = (project: IProject) => (modulo: ts.LeftHandSideExpression) => (init?: ts.Expression) => (type: ts.Type, name?: string) => { const importer: FunctionImporter = new FunctionImporter(modulo.getText()); const result: FeatureProgrammer.IDecomposed = decompose({ project, importer, type, name, init, }); return FeatureProgrammer.writeDecomposed({ modulo, importer, result, }); }; const write_object_functions = (importer: FunctionImporter) => (collection: MetadataCollection): ts.VariableStatement[] => collection.objects().map((obj, i) => StatementFactory.constant( Prefix.object(i), ts.factory.createArrowFunction( undefined, undefined, [ IdentifierFactory.parameter( "_recursive", TypeFactory.keyword("boolean"), ts.factory.createIdentifier(String(obj.recursive)), ), IdentifierFactory.parameter( "_depth", TypeFactory.keyword("number"), ExpressionFactory.number(0), ), ], TypeFactory.keyword("any"), undefined, RandomJoiner.object(COALESCE(importer))( decode(importer)({ recursive: obj.recursive, function: true, }), )(obj), ), ), ); const write_array_functions = (importer: FunctionImporter) => (collection: MetadataCollection): ts.VariableStatement[] => collection .arrays() .filter((a) => a.recursive) .map((array, i) => StatementFactory.constant( Prefix.array(i), ts.factory.createArrowFunction( undefined, undefined, [ IdentifierFactory.parameter( "length", TypeFactory.keyword("number"), ), IdentifierFactory.parameter( "unique", TypeFactory.keyword("boolean"), ), IdentifierFactory.parameter( "_recursive", TypeFactory.keyword("boolean"), ts.factory.createTrue(), ), IdentifierFactory.parameter( "_depth", TypeFactory.keyword("number"), ExpressionFactory.number(0), ), ], TypeFactory.keyword("any"), undefined, RandomJoiner.array(COALESCE(importer))( decode(importer)({ recursive: true, function: true, }), )({ recursive: true, function: true, })( ts.factory.createIdentifier("length"), ts.factory.createIdentifier("unique"), )(array.value), ), ), ); const write_tuple_functions = (importer: FunctionImporter) => (collection: MetadataCollection): ts.VariableStatement[] => collection .tuples() .filter((a) => a.recursive) .map((tuple, i) => StatementFactory.constant( Prefix.tuple(i), ts.factory.createArrowFunction( undefined, undefined, [ IdentifierFactory.parameter( "_recursive", TypeFactory.keyword("boolean"), ts.factory.createTrue(), ), IdentifierFactory.parameter( "_depth", TypeFactory.keyword("number"), ExpressionFactory.number(0), ), ], TypeFactory.keyword("any"), undefined, RandomJoiner.tuple( decode(importer)({ function: true, recursive: true, }), )(tuple.elements), ), ), ); /* ----------------------------------------------------------- DECODERS ----------------------------------------------------------- */ const decode = (importer: FunctionImporter) => (explore: IExplore) => (meta: Metadata): ts.Expression => { const expressions: ts.Expression[] = []; if (meta.any) expressions.push(ts.factory.createStringLiteral("any type used...")); // NULL COALESCING if (meta.isRequired() === false || meta.functions.length) expressions.push(ts.factory.createIdentifier("undefined")); if (meta.nullable === true) expressions.push(ts.factory.createNull()); // CONSTANT TYPES for (const constant of meta.constants) for (const { value } of constant.values) expressions.push(decode_atomic(value)); // ATOMIC VARIABLES for (const template of meta.templates) expressions.push(decode_template(importer)(explore)(template)); for (const atomic of meta.atomics) if (atomic.type === "boolean") expressions.push(decode_boolean(importer)); else if (atomic.type === "number") expressions.push(...decode_number(importer)(atomic)); else if (atomic.type === "string") expressions.push(...decode_string(importer)(atomic)); else if (atomic.type === "bigint") expressions.push(...decode_bigint(importer)(atomic)); // INSTANCE TYPES if (meta.escaped) expressions.push(decode(importer)(explore)(meta.escaped.returns)); for (const array of meta.arrays) expressions.push(...decode_array(importer)(explore)(array)); for (const tuple of meta.tuples) expressions.push(decode_tuple(importer)(explore)(tuple)); for (const o of meta.objects) expressions.push(decode_object(importer)(explore)(o)); for (const native of meta.natives) expressions.push(decode_native(importer)(native)); for (const set of meta.sets) expressions.push(decode_set(importer)(explore)(set)); for (const map of meta.maps) expressions.push(decode_map(importer)(explore)(map)); // PICK UP A TYPE if (expressions.length === 1) return expressions[0]!; return ts.factory.createCallExpression( ts.factory.createCallExpression(importer.use("pick"), undefined, [ ts.factory.createArrayLiteralExpression( expressions.map((expr) => ts.factory.createArrowFunction( undefined, undefined, [], undefined, undefined, expr, ), ), true, ), ]), undefined, undefined, ); }; const decode_boolean = (importer: FunctionImporter) => ts.factory.createCallExpression( COALESCE(importer)("boolean"), undefined, undefined, ); const decode_atomic = (value: Atomic) => typeof value === "boolean" ? ts.factory.createIdentifier(value.toString()) : typeof value === "number" ? ExpressionFactory.number(value) : typeof value === "string" ? ts.factory.createStringLiteral(value) : ExpressionFactory.bigint(Number(value)); const decode_template = (importer: FunctionImporter) => (explore: IExplore) => (template: MetadataTemplate) => TemplateFactory.generate( template.row.map((meta) => decode(importer)(explore)(meta)), ); const decode_number = (importer: FunctionImporter) => (atomic: MetadataAtomic): ts.Expression[] => (atomic.tags.length ? atomic.tags : [[]]).map((tags) => { const type = tags.find( (t) => t.kind === "type" && (t.value === "int32" || t.value === "int64"), ) ? "int" : tags.find( (t) => t.kind === "type" && (t.value === "uint32" || t.value === "uint64"), ) ? "uint" : "double"; const multiply = tags.find((t) => t.kind === "multipleOf"); return random_custom(COALESCE(importer))("number")(tags)( RandomRanger.number({ type, transform: (value) => ExpressionFactory.number(value), setter: (args) => ts.factory.createCallExpression( type !== "double" || multiply !== undefined ? COALESCE(importer)("integer") : COALESCE(importer)("number"), undefined, args.map((val) => ExpressionFactory.number(val)), ), })({ minimum: 0, maximum: 100, gap: 10, })(tags), ); }); const decode_bigint = (importer: FunctionImporter) => (atomic: MetadataAtomic): ts.Expression[] => (atomic.tags.length ? atomic.tags : [[]]).map((tags) => random_custom(COALESCE(importer))("bigint")(tags)( RandomRanger.number({ type: tags.find( (t) => t.kind === "type" && (t.value === "uint" || t.value === "uint64"), ) ? "uint" : "int", transform: (value) => ExpressionFactory.bigint(value), setter: (args) => ts.factory.createCallExpression( COALESCE(importer)("bigint"), undefined, args.map((value) => ExpressionFactory.bigint(value)), ), })({ minimum: 0, maximum: 100, gap: 10, })(tags), ), ); const decode_string = (importer: FunctionImporter) => (atomic: MetadataAtomic): ts.Expression[] => (atomic.tags.length ? atomic.tags : [[]]).map((tags) => random_custom(COALESCE(importer))("string")(tags)( (() => { for (const t of tags) if (t.kind === "format") return ts.factory.createCallExpression( COALESCE(importer)(emendFormat(t.value)), undefined, undefined, ); else if (t.kind === "pattern") return ts.factory.createCallExpression( COALESCE(importer)("pattern"), undefined, [ ts.factory.createIdentifier( `RegExp(${JSON.stringify(t.value)})`, ), ], ); const tail = RandomRanger.length(COALESCE(importer))({ minimum: 5, maximum: 25, gap: 5, })({ minimum: "minLength", maximum: "maxLength", })(tags); return ts.factory.createCallExpression( COALESCE(importer)("string"), undefined, tail ? [tail] : undefined, ); })(), ), ); const decode_array = (importer: FunctionImporter) => (explore: IExplore) => (array: MetadataArray): ts.Expression[] => { const fixed: Array< [ts.Expression | undefined, ts.Expression | undefined] > = (array.tags.length ? array.tags : [[]]).map((tags) => [ RandomRanger.length(COALESCE(importer))({ minimum: 0, maximum: 3, gap: 3, })({ minimum: "minItems", maximum: "maxItems", })(tags), (() => { const uniqueItems = tags.find((t) => t.kind === "uniqueItems"); return uniqueItems === undefined ? undefined : uniqueItems.value === false ? ts.factory.createFalse() : ts.factory.createTrue(); })(), ]); if (array.type.recursive) return fixed.map(([len, unique]) => ts.factory.createCallExpression( ts.factory.createIdentifier( importer.useLocal(Prefix.array(array.type.index!)), ), undefined, [ len ?? COALESCE(importer)("length"), unique ?? ts.factory.createFalse(), ts.factory.createTrue(), explore.recursive ? ts.factory.createAdd( ExpressionFactory.number(1), ts.factory.createIdentifier("_depth"), ) : ExpressionFactory.number(0), ], ), ); return fixed.map(([len, unique]) => { const expr: ts.Expression = RandomJoiner.array(COALESCE(importer))( decode(importer)(explore), )(explore)( len, unique, )(array.type.value); return explore.recursive ? ts.factory.createConditionalExpression( ts.factory.createLogicalAnd( ts.factory.createIdentifier("_recursive"), ts.factory.createLessThan( ExpressionFactory.number(5), ts.factory.createIdentifier("_depth"), ), ), undefined, ts.factory.createIdentifier("[]"), undefined, expr, ) : expr; }); }; const decode_tuple = (importer: FunctionImporter) => (explore: IExplore) => (tuple: MetadataTuple): ts.Expression => tuple.type.recursive ? ts.factory.createCallExpression( ts.factory.createIdentifier( importer.useLocal(Prefix.tuple(tuple.type.index!)), ), undefined, [ ts.factory.createTrue(), explore.recursive ? ts.factory.createAdd( ExpressionFactory.number(1), ts.factory.createIdentifier("_depth"), ) : ExpressionFactory.number(0), ], ) : RandomJoiner.tuple(decode(importer)(explore))(tuple.type.elements); const decode_object = (importer: FunctionImporter) => (explore: IExplore) => (object: MetadataObject) => ts.factory.createCallExpression( ts.factory.createIdentifier( importer.useLocal(Prefix.object(object.index)), ), undefined, explore.function ? [ explore.recursive ? ts.factory.createTrue() : ts.factory.createIdentifier("_recursive"), ts.factory.createConditionalExpression( ts.factory.createIdentifier("_recursive"), undefined, ts.factory.createAdd( ExpressionFactory.number(1), ts.factory.createIdentifier("_depth"), ), undefined, ts.factory.createIdentifier("_depth"), ), ] : undefined, ); /* ----------------------------------------------------------- NATIVE CLASSES ----------------------------------------------------------- */ const decode_set = (importer: FunctionImporter) => (explore: IExplore) => (meta: Metadata) => ts.factory.createNewExpression( ts.factory.createIdentifier("Set"), undefined, [ decode_array(importer)(explore)( MetadataArray.create({ tags: [], type: MetadataArrayType.create({ value: meta, recursive: false, index: null, nullables: [], name: `Set<${meta.getName()}>`, }), }), )[0]!, ], ); const decode_map = (importer: FunctionImporter) => (explore: IExplore) => (map: Metadata.Entry) => ts.factory.createNewExpression( ts.factory.createIdentifier("Map"), undefined, [ decode_array(importer)(explore)( MetadataArray.create({ tags: [], type: MetadataArrayType.create({ name: `Map<${map.key.getName()}, ${map.value.getName()}>`, index: null, recursive: false, nullables: [], value: Metadata.create({ ...Metadata.initialize(), tuples: [ (() => { const type = MetadataTupleType.create({ name: `[${map.key.getName()}, ${map.value.getName()}]`, index: null, recursive: false, nullables: [], elements: [map.key, map.value], }); type.of_map = true; return MetadataTuple.create({ type, tags: [], }); })(), ], }), }), }), )[0]!, ], ); const decode_native = (importer: FunctionImporter) => (type: string): ts.Expression => { if (type === "Boolean") return decode_boolean(importer); else if (type === "Number") return decode_number(importer)( MetadataAtomic.create({ type: "number", tags: [], }), )[0]!; else if (type === "String") return decode_string(importer)( MetadataAtomic.create({ type: "string", tags: [], }), )[0]!; else if (type === "Date") return decode_native_date(importer); else if ( type === "Uint8Array" || type === "Uint8ClampedArray" || type === "Uint16Array" || type === "Uint32Array" || type === "BigUint64Array" || type === "Int8Array" || type === "Int16Array" || type === "Int32Array" || type === "BigInt64Array" || type === "Float32Array" || type === "Float64Array" ) return decode_native_byte_array(importer)(type); else if (type === "ArrayBuffer" || type === "SharedArrayBuffer") return decode_native_array_buffer(importer)(type); else if (type === "DataView") return decode_native_data_view(importer); else if (type === "Blob") return decode_native_blob(importer); else if (type === "File") return decode_native_file(importer); else if (type === "RegExp") return decode_regexp(); else return ts.factory.createNewExpression( ts.factory.createIdentifier(type), undefined, [], ); }; const decode_native_date = (importer: FunctionImporter) => ts.factory.createNewExpression( ts.factory.createIdentifier("Date"), undefined, [ ts.factory.createCallExpression( COALESCE(importer)("datetime"), undefined, [], ), ], ); const decode_native_byte_array = (importer: FunctionImporter) => ( type: | "Uint8Array" | "Uint8ClampedArray" | "Uint16Array" | "Uint32Array" | "BigUint64Array" | "Int8Array" | "Int16Array" | "Int32Array" | "BigInt64Array" | "Float32Array" | "Float64Array", ): ts.Expression => { new BigInt64Array(); const [minimum, maximum]: [number, number] = (() => { if (type === "Uint8Array" || type === "Uint8ClampedArray") return [0, 255]; else if (type === "Uint16Array") return [0, 65535]; else if (type === "Uint32Array") return [0, 4294967295]; else if (type === "BigUint64Array") return [0, 18446744073709551615]; else if (type === "Int8Array") return [-128, 127]; else if (type === "Int16Array") return [-32768, 32767]; else if (type === "Int32Array") return [-2147483648, 2147483647]; else if (type === "BigInt64Array") return [-9223372036854775808, 9223372036854775807]; else if (type === "Float32Array") return [-1.175494351e38, 3.4028235e38]; return [Number.MIN_VALUE, Number.MAX_VALUE]; })(); const literal = type === "BigInt64Array" || type === "BigUint64Array" ? ExpressionFactory.bigint : ExpressionFactory.number; return ts.factory.createNewExpression( ts.factory.createIdentifier(type), [], [ ts.factory.createCallExpression( COALESCE(importer)("array"), undefined, [ ts.factory.createArrowFunction( undefined, undefined, [], TypeFactory.keyword("any"), undefined, ts.factory.createCallExpression( COALESCE(importer)( type === "Float32Array" || type === "Float64Array" ? "number" : type === "BigInt64Array" || type === "BigUint64Array" ? "bigint" : "integer", ), undefined, [literal(minimum), literal(maximum)], ), ), ], ), ], ); }; const decode_native_blob = (importer: FunctionImporter) => ts.factory.createNewExpression( ts.factory.createIdentifier("Blob"), undefined, [ ts.factory.createArrayLiteralExpression( [decode_native_byte_array(importer)("Uint8Array")], true, ), ], ); const decode_native_file = (importer: FunctionImporter) => ts.factory.createNewExpression( ts.factory.createIdentifier("File"), undefined, [ ts.factory.createArrayLiteralExpression( [decode_native_byte_array(importer)("Uint8Array")], true, ), ts.factory.createTemplateExpression(ts.factory.createTemplateHead(""), [ ts.factory.createTemplateSpan( ts.factory.createCallExpression( COALESCE(importer)("string"), undefined, [ts.factory.createNumericLiteral(8)], ), ts.factory.createTemplateMiddle("."), ), ts.factory.createTemplateSpan( ts.factory.createCallExpression( COALESCE(importer)("string"), undefined, [ts.factory.createNumericLiteral(3)], ), ts.factory.createTemplateTail(""), ), ]), ], ); const decode_native_array_buffer = (importer: FunctionImporter) => (type: "ArrayBuffer" | "SharedArrayBuffer"): ts.Expression => type === "ArrayBuffer" ? IdentifierFactory.access( decode_native_byte_array(importer)("Uint8Array"), )("buffer") : ExpressionFactory.selfCall( ts.factory.createBlock( [ StatementFactory.constant( "length", ts.factory.createCallExpression( COALESCE(importer)("integer"), undefined, [], ), ), StatementFactory.constant( "buffer", ts.factory.createNewExpression( ts.factory.createIdentifier("SharedArrayBuffer"), [], [ts.factory.createIdentifier("length")], ), ), StatementFactory.constant( "bytes", ts.factory.createNewExpression( ts.factory.createIdentifier("Uint8Array"), [], [ts.factory.createIdentifier("buffer")], ), ), ts.factory.createExpressionStatement( ts.factory.createCallExpression( IdentifierFactory.access( ts.factory.createIdentifier("bytes"), )("set"), undefined, [ ts.factory.createCallExpression( COALESCE(importer)("array"), undefined, [ ts.factory.createArrowFunction( undefined, undefined, [], TypeFactory.keyword("any"), undefined, ts.factory.createCallExpression( COALESCE(importer)("integer"), undefined, [ ExpressionFactory.number(0), ExpressionFactory.number(255), ], ), ), ts.factory.createIdentifier("length"), ], ), ExpressionFactory.number(0), ], ), ), ts.factory.createReturnStatement( ts.factory.createIdentifier("buffer"), ), ], true, ), ); const decode_native_data_view = (importer: FunctionImporter) => ts.factory.createNewExpression( ts.factory.createIdentifier("DataView"), [], [ IdentifierFactory.access( decode_native_byte_array(importer)("Uint8Array"), )("buffer"), ], ); const decode_regexp = () => ts.factory.createNewExpression( ts.factory.createIdentifier("RegExp"), [], [ts.factory.createIdentifier("/(?:)/")], ); } type Atomic = boolean | number | string | bigint; interface IExplore { function: boolean; recursive: boolean; } const Prefix = { object: (i: number) => `$ro${i}`, array: (i: number) => `$ra${i}`, tuple: (i: number) => `$rt${i}`, }; const COALESCE = (importer: FunctionImporter) => (name: string) => ExpressionFactory.coalesce( Escaper.variable(name) ? ts.factory.createPropertyAccessChain( ts.factory.createIdentifier("_generator"), ts.factory.createToken(ts.SyntaxKind.QuestionDotToken), ts.factory.createIdentifier(name), ) : ts.factory.createElementAccessChain( ts.factory.createIdentifier("_generator"), ts.factory.createToken(ts.SyntaxKind.QuestionDotToken), ts.factory.createStringLiteral(name), ), )(IdentifierFactory.access(importer.use("generator"))(name)); const emendFormat = (key: keyof Format.Validator) => key === "date-time" ? "datetime" : key .split("-") .map((str, i) => i === 0 || str.length === 0 ? str : str[0]!.toUpperCase() + str.substring(1), ) .join("");