UNPKG

@matatbread/typia

Version:

Superfast runtime validators with only one line

716 lines (713 loc) 32.2 kB
import ts from 'typescript'; import { ExpressionFactory } from '../factories/ExpressionFactory.mjs'; import { IdentifierFactory } from '../factories/IdentifierFactory.mjs'; import { LiteralFactory } from '../factories/LiteralFactory.mjs'; import { MetadataCollection } from '../factories/MetadataCollection.mjs'; import { MetadataCommentTagFactory } from '../factories/MetadataCommentTagFactory.mjs'; import { MetadataFactory } from '../factories/MetadataFactory.mjs'; import { StatementFactory } from '../factories/StatementFactory.mjs'; import { TemplateFactory } from '../factories/TemplateFactory.mjs'; import { TypeFactory } from '../factories/TypeFactory.mjs'; import { Metadata } from '../schemas/metadata/Metadata.mjs'; import { MetadataArray } from '../schemas/metadata/MetadataArray.mjs'; import { MetadataArrayType } from '../schemas/metadata/MetadataArrayType.mjs'; import { MetadataAtomic } from '../schemas/metadata/MetadataAtomic.mjs'; import { MetadataTuple } from '../schemas/metadata/MetadataTuple.mjs'; import { MetadataTupleType } from '../schemas/metadata/MetadataTupleType.mjs'; import { TransformerError } from '../transformers/TransformerError.mjs'; import { StringUtil } from '../utils/StringUtil.mjs'; import { FeatureProgrammer } from './FeatureProgrammer.mjs'; import { FunctionProgrammer } from './helpers/FunctionProgrammer.mjs'; import { RandomJoiner } from './helpers/RandomJoiner.mjs'; import { json_schema_array } from './internal/json_schema_array.mjs'; import { json_schema_bigint } from './internal/json_schema_bigint.mjs'; import { json_schema_boolean } from './internal/json_schema_boolean.mjs'; import { json_schema_number } from './internal/json_schema_number.mjs'; import { json_schema_string } from './internal/json_schema_string.mjs'; var RandomProgrammer; (function (RandomProgrammer) { RandomProgrammer.decompose = (props) => { const collection = new MetadataCollection(); const result = MetadataFactory.analyze({ checker: props.context.checker, transformer: props.context.transformer, options: { escape: false, constant: true, absorb: true, validate: (meta) => { const output = []; if (meta.natives.some((native) => native.name === "WeakSet")) output.push(`WeakSet is not supported.`); else if (meta.natives.some((native) => native.name === "WeakMap")) output.push(`WeakMap is not supported.`); return output; }, }, collection, type: props.type, }); if (result.success === false) throw TransformerError.from({ code: props.functor.method, errors: result.errors, }); // GENERATE FUNCTION const functions = Object.fromEntries([ ...write_object_functions({ context: props.context, functor: props.functor, collection, }).map((v, i) => [Prefix.object(i), v]), ...write_array_functions({ context: props.context, functor: props.functor, collection, }).map((v, i) => [Prefix.array(i), v]), ...write_tuple_functions({ context: props.context, functor: props.functor, collection, }).map((v, i) => [Prefix.tuple(i), v]), ]); const arrow = ts.factory.createArrowFunction(undefined, undefined, [ IdentifierFactory.parameter("generator", ts.factory.createTypeReferenceNode("Partial", [ props.context.importer.type({ file: "typia", name: "IRandomGenerator", }), ]), props.init ?? ts.factory.createToken(ts.SyntaxKind.QuestionToken)), ], props.context.importer.type({ file: "typia", name: "Resolved", arguments: [ ts.factory.createTypeReferenceNode(props.name ?? TypeFactory.getFullName({ checker: props.context.checker, type: props.type, })), ], }), 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({ context: props.context, functor: props.functor, explore: { function: false, recursive: false, }, metadata: result.data, })), ], true)); return { functions, statements: [ StatementFactory.mut({ name: "_generator", type: ts.factory.createUnionTypeNode([ ts.factory.createTypeReferenceNode("Partial", [ props.context.importer.type({ file: "typia", name: "IRandomGenerator", }), ]), ts.factory.createTypeReferenceNode("undefined"), ]), }), ], arrow, }; }; RandomProgrammer.write = (props) => { const functor = new FunctionProgrammer(props.modulo.getText()); const result = RandomProgrammer.decompose({ ...props, functor, }); return FeatureProgrammer.writeDecomposed({ modulo: props.modulo, functor, result, }); }; const write_object_functions = (props) => props.collection.objects().map((obj, i) => StatementFactory.constant({ name: Prefix.object(i), value: 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({ decode: (metadata) => decode({ context: props.context, functor: props.functor, explore: { recursive: obj.recursive, function: true, }, metadata, }), object: obj, })), })); const write_array_functions = (props) => props.collection .arrays() .filter((a) => a.recursive) .map((array, i) => StatementFactory.constant({ name: Prefix.array(i), value: ts.factory.createArrowFunction(undefined, undefined, [ IdentifierFactory.parameter("_schema", 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({ decode: (metadata) => decode({ context: props.context, functor: props.functor, explore: { recursive: true, function: true, }, metadata, }), recursive: true, expression: coalesce({ context: props.context, method: "array", internal: "randomArray", }), array, schema: undefined, })), })); const write_tuple_functions = (props) => props.collection .tuples() .filter((a) => a.recursive) .map((tuple, i) => StatementFactory.constant({ name: Prefix.tuple(i), value: 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: (metadata) => decode({ context: props.context, functor: props.functor, explore: { function: true, recursive: true, }, metadata, }), elements: tuple.elements, })), })); /* ----------------------------------------------------------- DECODERS ----------------------------------------------------------- */ const decode = (props) => { const expressions = []; if (props.metadata.any === true) expressions.push(ts.factory.createStringLiteral("any type used...")); // NULL COALESCING if (props.metadata.isRequired() === false || props.metadata.functions.length !== 0) expressions.push(ts.factory.createIdentifier("undefined")); if (props.metadata.nullable === true) expressions.push(ts.factory.createNull()); // CONSTANT TYPES for (const constant of props.metadata.constants) for (const { value } of constant.values) expressions.push(constant.type === "boolean" ? value === true ? ts.factory.createTrue() : ts.factory.createFalse() : constant.type === "bigint" ? ExpressionFactory.bigint(value) : constant.type === "number" ? ExpressionFactory.number(value) : ts.factory.createStringLiteral(value)); // ATOMIC VARIABLES for (const template of props.metadata.templates) expressions.push(decode_template({ ...props, template, })); for (const atomic of props.metadata.atomics) expressions.push(...decode_atomic({ context: props.context, atomic, })); // INSTANCE TYPES if (props.metadata.escaped) expressions.push(decode({ ...props, metadata: props.metadata.escaped.returns, })); for (const array of props.metadata.arrays) expressions.push(...decode_array({ ...props, array, })); for (const tuple of props.metadata.tuples) expressions.push(decode_tuple({ ...props, tuple, })); for (const object of props.metadata.objects) expressions.push(decode_object({ ...props, object: object.type, })); for (const native of props.metadata.natives) expressions.push(decode_native({ context: props.context, functor: props.functor, explore: props.explore, name: native.name, })); for (const set of props.metadata.sets) expressions.push(decode_set({ ...props, set, })); for (const entry of props.metadata.maps) expressions.push(decode_map({ ...props, map: entry, })); // PICK UP A TYPE if (expressions.length === 1) return expressions[0]; return ts.factory.createCallExpression(ts.factory.createCallExpression(props.context.importer.internal("randomPick"), undefined, [ ts.factory.createArrayLiteralExpression(expressions.map((expr) => ts.factory.createArrowFunction(undefined, undefined, [], undefined, undefined, expr)), true), ]), undefined, undefined); }; const decode_atomic = (props) => { const schemaList = props.atomic.type === "boolean" ? json_schema_boolean(props.atomic) : props.atomic.type === "string" ? json_schema_string(props.atomic) : props.atomic.type === "bigint" ? json_schema_bigint(props.atomic) : json_schema_number(props.atomic); return schemaList.map((schema) => { const composed = (() => { if (props.atomic.type === "string") { const string = schema; if (string.format !== undefined) { const format = string.format; if (format === "date-time") return { method: "datetime", internal: "randomFormatDatetime", arguments: [], }; return { method: format .split("-") .map((s, i) => (i === 0 ? s : StringUtil.capitalize(s))) .join(""), internal: `randomFormat${format .split("-") .map(StringUtil.capitalize) .join("")}`, arguments: [], }; } else if (string.pattern !== undefined) return { method: "pattern", internal: "randomPattern", arguments: [ ts.factory.createNewExpression(ts.factory.createIdentifier("RegExp"), undefined, [ ts.factory.createStringLiteral(schema.pattern), ]), ], }; } else if (props.atomic.type === "number") { const number = schema; if (number.type === "integer") return { method: "integer", internal: "randomInteger", arguments: [LiteralFactory.write(schema)], }; } else if (props.atomic.type === "boolean") return { method: props.atomic.type, internal: "randomBoolean", arguments: [], }; return { method: props.atomic.type, internal: `random${StringUtil.capitalize(props.atomic.type)}`, arguments: [LiteralFactory.write(schema)], }; })(); return ts.factory.createCallExpression(ExpressionFactory.coalesce(ts.factory.createPropertyAccessChain(ts.factory.createIdentifier("_generator"), ts.factory.createToken(ts.SyntaxKind.QuestionDotToken), ts.factory.createIdentifier(composed.method)), props.context.importer.internal(composed.internal)), undefined, composed.arguments); }); }; const decode_template = (props) => TemplateFactory.generate(props.template.row.map((metadata) => decode({ ...props, metadata, }))); const decode_array = (props) => { const components = {}; const schemaList = json_schema_array({ components, array: props.array, }); if (props.array.type.recursive) return schemaList.map((schema) => ts.factory.createCallExpression(ts.factory.createIdentifier(props.functor.useLocal(Prefix.array(props.array.type.index))), undefined, [ ts.factory.createObjectLiteralExpression(Object.entries(schema) .filter(([key]) => key !== "items") .map(([key, value]) => ts.factory.createPropertyAssignment(key, LiteralFactory.write(value))), true), ])); return schemaList.map((schema) => RandomJoiner.array({ decode: (metadata) => decode({ ...props, metadata, }), expression: coalesce({ context: props.context, method: "array", internal: "randomArray", }), array: props.array.type, recursive: props.explore.recursive, schema, })); }; const decode_tuple = (props) => props.tuple.type.recursive ? ts.factory.createCallExpression(ts.factory.createIdentifier(props.functor.useLocal(Prefix.tuple(props.tuple.type.index))), undefined, [ ts.factory.createTrue(), props.explore.recursive ? ts.factory.createAdd(ExpressionFactory.number(1), ts.factory.createIdentifier("_depth")) : ExpressionFactory.number(0), ]) : RandomJoiner.tuple({ decode: (metadata) => decode({ ...props, metadata, }), elements: props.tuple.type.elements, }); const decode_object = (props) => ts.factory.createCallExpression(ts.factory.createIdentifier(props.functor.useLocal(Prefix.object(props.object.index))), undefined, props.explore.function ? [ props.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 = (props) => ts.factory.createNewExpression(ts.factory.createIdentifier("Set"), undefined, [ decode_array({ ...props, array: MetadataArray.create({ tags: [], type: MetadataArrayType.create({ value: props.set.value, recursive: false, index: null, nullables: [], name: props.set.getName(), }), }), })[0], ]); const decode_map = (props) => ts.factory.createNewExpression(ts.factory.createIdentifier("Map"), undefined, [ decode_array({ ...props, array: MetadataArray.create({ tags: [], type: MetadataArrayType.create({ name: props.map.getName(), index: null, recursive: false, nullables: [], value: Metadata.create({ ...Metadata.initialize(), tuples: [ (() => { const type = MetadataTupleType.create({ name: `[${props.map.key.getName()}, ${props.map.value.getName()}]`, index: null, recursive: false, nullables: [], elements: [props.map.key, props.map.value], }); type.of_map = true; return MetadataTuple.create({ type, tags: [], }); })(), ], }), }), }), })[0], ]); const decode_native = (props) => { if (props.name === "Boolean" || props.name === "Number" || props.name === "BigInt" || props.name === "String") return decode_atomic({ context: props.context, atomic: MetadataAtomic.create({ type: props.name.toLowerCase(), tags: [], }), })[0]; else if (props.name === "Date") return decode_native_date(props.context); else if (props.name === "Uint8Array" || props.name === "Uint8ClampedArray" || props.name === "Uint16Array" || props.name === "Uint32Array" || props.name === "BigUint64Array" || props.name === "Int8Array" || props.name === "Int16Array" || props.name === "Int32Array" || props.name === "BigInt64Array" || props.name === "Float32Array" || props.name === "Float64Array") return decode_native_byte_array({ ...props, name: props.name, }); else if (props.name === "ArrayBuffer" || props.name === "SharedArrayBuffer") return decode_native_array_buffer({ ...props, name: props.name, }); else if (props.name === "DataView") return decode_native_data_view(props); else if (props.name === "Blob") return decode_native_blob(props); else if (props.name === "File") return decode_native_file(props); else if (props.name === "RegExp") return decode_regexp(props.context); else return ts.factory.createNewExpression(ts.factory.createIdentifier(props.name), undefined, []); }; const decode_native_date = (context) => ts.factory.createNewExpression(ts.factory.createIdentifier("Date"), undefined, [ ts.factory.createCallExpression(coalesce({ context, method: "datetime", internal: "randomFormatDatetime", }), undefined, []), ]); const decode_native_byte_array = (props) => { new BigInt64Array(); const [type, minimum, maximum] = (() => { if (props.name === "Uint8Array" || props.name === "Uint8ClampedArray") return ["uint32", 0, 255]; else if (props.name === "Uint16Array") return ["uint32", 0, 65535]; else if (props.name === "Uint32Array") return ["uint32", 0, 4294967295]; else if (props.name === "BigUint64Array") return ["uint64", 0, 18446744073709551615]; else if (props.name === "Int8Array") return ["int32", -128, 127]; else if (props.name === "Int16Array") return ["int32", -32768, 32767]; else if (props.name === "Int32Array") return ["int32", -2147483648, 2147483647]; else if (props.name === "BigInt64Array") return ["uint64", -9223372036854775808, 9223372036854775807]; else if (props.name === "Float32Array") return ["float", -1.175494351e38, 3.4028235e38]; return ["double", Number.MIN_VALUE, Number.MAX_VALUE]; })(); const atomic = props.name === "BigInt64Array" || props.name === "BigUint64Array" ? "bigint" : "number"; const value = Metadata.create({ ...Metadata.initialize(), atomics: [ MetadataAtomic.create({ type: atomic, tags: [ [ ...MetadataCommentTagFactory.get({ kind: "type", type: atomic, value: type, }), ...MetadataCommentTagFactory.get({ kind: "minimum", type: "number", value: minimum.toString(), }), ...MetadataCommentTagFactory.get({ kind: "maximum", type: "number", value: maximum.toString(), }), ], ], }), ], }); return ts.factory.createNewExpression(ts.factory.createIdentifier(props.name), [], decode_array({ context: props.context, functor: props.functor, explore: props.explore, array: MetadataArray.create({ tags: [], type: MetadataArrayType.create({ name: `${props.name}<${atomic}>`, value, recursive: false, index: null, nullables: [], }), }), })); }; const decode_native_blob = (props) => ts.factory.createNewExpression(ts.factory.createIdentifier("Blob"), undefined, [ ts.factory.createArrayLiteralExpression([ decode_native_byte_array({ context: props.context, functor: props.functor, explore: props.explore, name: "Uint8Array", }), ], true), ]); const decode_native_file = (props) => ts.factory.createNewExpression(ts.factory.createIdentifier("File"), undefined, [ ts.factory.createArrayLiteralExpression([ decode_native_byte_array({ context: props.context, functor: props.functor, explore: props.explore, name: "Uint8Array", }), ], true), ts.factory.createTemplateExpression(ts.factory.createTemplateHead(""), [ ts.factory.createTemplateSpan(writeRangedString({ context: props.context, minLength: 1, maxLength: 8, }), ts.factory.createTemplateMiddle(".")), ts.factory.createTemplateSpan(writeRangedString({ context: props.context, minLength: 3, maxLength: 3, }), ts.factory.createTemplateTail("")), ]), ]); const decode_native_array_buffer = (props) => props.name === "ArrayBuffer" ? IdentifierFactory.access(decode_native_byte_array({ context: props.context, functor: props.functor, explore: props.explore, name: "Uint8Array", }), "buffer") : ExpressionFactory.selfCall(ts.factory.createBlock([ StatementFactory.constant({ name: "length", value: decode_atomic({ context: props.context, atomic: MetadataAtomic.create({ type: "number", tags: [ MetadataCommentTagFactory.get({ type: "number", kind: "type", value: "uint32", }), ], }), })[0], }), StatementFactory.constant({ name: "buffer", value: ts.factory.createNewExpression(ts.factory.createIdentifier("SharedArrayBuffer"), [], [ts.factory.createIdentifier("length")]), }), StatementFactory.constant({ name: "bytes", value: 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(ts.factory.createPropertyAccessExpression(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createNewExpression(ts.factory.createIdentifier("Array"), undefined, [ts.factory.createIdentifier("length")]), ts.factory.createIdentifier("fill")), undefined, [ts.factory.createNumericLiteral("0")]), ts.factory.createIdentifier("map")), undefined, [ ts.factory.createArrowFunction(undefined, undefined, [], undefined, undefined, decode_atomic({ context: props.context, atomic: MetadataAtomic.create({ type: "number", tags: [ [ ...MetadataCommentTagFactory.get({ kind: "type", type: "number", value: "uint32", }), ...MetadataCommentTagFactory.get({ kind: "minimum", type: "number", value: "0", }), ...MetadataCommentTagFactory.get({ kind: "maximum", type: "number", value: "255", }), ], ], }), })[0]), ]), ExpressionFactory.number(0), ])), ts.factory.createReturnStatement(ts.factory.createIdentifier("buffer")), ], true)); const decode_native_data_view = (props) => ts.factory.createNewExpression(ts.factory.createIdentifier("DataView"), [], [ IdentifierFactory.access(decode_native_byte_array({ context: props.context, functor: props.functor, explore: props.explore, name: "Uint8Array", }), "buffer"), ]); const decode_regexp = (context) => ts.factory.createNewExpression(ts.factory.createIdentifier("RegExp"), [], [ ts.factory.createCallExpression(coalesce({ context, method: "regex", internal: "randomFormatRegex", }), undefined, undefined), ]); const writeRangedString = (props) => decode_atomic({ context: props.context, atomic: MetadataAtomic.create({ type: "string", tags: [ [ ...MetadataCommentTagFactory.get({ kind: "minLength", type: "string", value: props.minLength.toString(), }), ...MetadataCommentTagFactory.get({ kind: "maxLength", type: "string", value: props.maxLength.toString(), }), ], ], }), })[0]; })(RandomProgrammer || (RandomProgrammer = {})); const coalesce = (props) => ExpressionFactory.coalesce(ts.factory.createPropertyAccessChain(ts.factory.createIdentifier("_generator"), ts.factory.createToken(ts.SyntaxKind.QuestionDotToken), ts.factory.createIdentifier(props.method)), props.context.importer.internal(props.internal)); const Prefix = { object: (i) => `_ro${i}`, array: (i) => `_ra${i}`, tuple: (i) => `_rt${i}`, }; export { RandomProgrammer }; //# sourceMappingURL=RandomProgrammer.mjs.map