@matatbread/typia
Version:
Superfast runtime validators with only one line
716 lines (713 loc) • 32.2 kB
JavaScript
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