UNPKG

typia

Version:

Superfast runtime validators with only one line

647 lines (624 loc) • 21.9 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 { ProtobufFactory } from "../../factories/ProtobufFactory"; import { StatementFactory } from "../../factories/StatementFactory"; import { TypeFactory } from "../../factories/TypeFactory"; import { Metadata } from "../../schemas/metadata/Metadata"; import { MetadataArray } from "../../schemas/metadata/MetadataArray"; import { MetadataAtomic } from "../../schemas/metadata/MetadataAtomic"; import { MetadataObject } from "../../schemas/metadata/MetadataObject"; import { MetadataProperty } from "../../schemas/metadata/MetadataProperty"; import { IProject } from "../../transformers/IProject"; import { ProtobufAtomic } from "../../typings/ProtobufAtomic"; import { FeatureProgrammer } from "../FeatureProgrammer"; import { FunctionImporter } from "../helpers/FunctionImporter"; import { ProtobufUtil } from "../helpers/ProtobufUtil"; export namespace ProtobufDecodeProgrammer { export const decompose = (props: { project: IProject; modulo: ts.LeftHandSideExpression; importer: FunctionImporter; type: ts.Type; name: string | undefined; }): FeatureProgrammer.IDecomposed => { const collection: MetadataCollection = new MetadataCollection(); const meta: Metadata = ProtobufFactory.metadata(props.modulo.getText())( props.project.checker, props.project.context, )(collection)(props.type); return { functions: Object.fromEntries( collection .objects() .filter((obj) => ProtobufUtil.isStaticObject(obj)) .map((obj) => [ `${PREFIX}o${obj.index}`, StatementFactory.constant( props.importer.useLocal(`${PREFIX}o${obj.index}`), write_object_function(props.project)(props.importer)(obj), ), ]), ), statements: [], arrow: ts.factory.createArrowFunction( undefined, undefined, [ IdentifierFactory.parameter( "input", ts.factory.createTypeReferenceNode("Uint8Array"), ), ], 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), ), ], ), undefined, ts.factory.createBlock( [ StatementFactory.constant( "reader", ts.factory.createNewExpression( props.importer.use("Reader"), undefined, [ts.factory.createIdentifier("input")], ), ), ts.factory.createReturnStatement( decode_regular_object(true)(meta.objects[0]!), ), ], true, ), ), }; }; 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({ project, modulo, importer, type, name, }); return FeatureProgrammer.writeDecomposed({ modulo, importer, result, }); }; const write_object_function = (project: IProject) => (importer: FunctionImporter) => (obj: MetadataObject): ts.ArrowFunction => ts.factory.createArrowFunction( undefined, undefined, [ IdentifierFactory.parameter("reader"), IdentifierFactory.parameter( "length", TypeFactory.keyword("number"), ExpressionFactory.number(-1), ), ], TypeFactory.keyword("any"), undefined, ts.factory.createBlock( [ ts.factory.createExpressionStatement( ts.factory.createBinaryExpression( ts.factory.createIdentifier("length"), ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createConditionalExpression( ts.factory.createLessThan( ts.factory.createIdentifier("length"), ExpressionFactory.number(0), ), undefined, ts.factory.createCallExpression( IdentifierFactory.access(READER())("size"), undefined, undefined, ), undefined, ts.factory.createAdd( ts.factory.createCallExpression( IdentifierFactory.access(READER())("index"), undefined, undefined, ), ts.factory.createIdentifier("length"), ), ), ), ), ...write_object_function_body(project)(importer)({ condition: ts.factory.createLessThan( ts.factory.createCallExpression( IdentifierFactory.access(READER())("index"), undefined, undefined, ), ts.factory.createIdentifier("length"), ), tag: "tag", output: "output", })(obj.properties), ts.factory.createReturnStatement( ts.factory.createIdentifier("output"), ), ], true, ), ); const write_object_function_body = (project: IProject) => (importer: FunctionImporter) => (props: { condition: ts.Expression; tag: string; output: string }) => (properties: MetadataProperty[]): ts.Statement[] => { let i: number = 1; const clauses: ts.CaseClause[] = properties .map((p) => { const clause = decode_property(project)(importer)(i)( IdentifierFactory.access(ts.factory.createIdentifier(props.output))( p.key.getSoleLiteral()!, ), p.value, ); i += ProtobufUtil.size(p.value); return clause; }) .flat(); return [ StatementFactory.constant( props.output, ts.factory.createAsExpression( ts.factory.createObjectLiteralExpression( properties .filter( (p) => !( project.compilerOptions.exactOptionalPropertyTypes === true && p.value.optional === true ), ) .map((p) => ts.factory.createPropertyAssignment( IdentifierFactory.identifier(p.key.getSoleLiteral()!), write_property_default_value(p.value), ), ), true, ), TypeFactory.keyword("any"), ), ), ts.factory.createWhileStatement( props.condition, ts.factory.createBlock([ StatementFactory.constant( props.tag, ts.factory.createCallExpression( IdentifierFactory.access(READER())("uint32"), undefined, undefined, ), ), ts.factory.createSwitchStatement( ts.factory.createUnsignedRightShift( ts.factory.createIdentifier(props.tag), ExpressionFactory.number(3), ), ts.factory.createCaseBlock([ ...clauses, ts.factory.createDefaultClause([ ts.factory.createExpressionStatement( ts.factory.createCallExpression( IdentifierFactory.access(READER())("skipType"), undefined, [ ts.factory.createBitwiseAnd( ts.factory.createIdentifier(props.tag), ExpressionFactory.number(7), ), ], ), ), ts.factory.createBreakStatement(), ]), ]), ), ]), ), ]; }; const write_property_default_value = (value: Metadata) => ts.factory.createAsExpression( value.nullable ? ts.factory.createNull() : value.isRequired() === false ? ts.factory.createIdentifier("undefined") : value.arrays.length ? ts.factory.createArrayLiteralExpression() : value.maps.length ? ts.factory.createNewExpression( ts.factory.createIdentifier("Map"), undefined, [], ) : value.natives.length ? ts.factory.createNewExpression( ts.factory.createIdentifier("Uint8Array"), undefined, [], ) : value.atomics.some((a) => a.type === "string") || value.constants.some( (c) => c.type === "string" && c.values.some((v) => v.value === ""), ) || value.templates.some( (tpl) => tpl.row.length === 1 && tpl.row[0]!.getName() === "string", ) ? ts.factory.createStringLiteral("") : value.objects.length && value.objects.some( (obj) => !ProtobufUtil.isStaticObject(obj), ) ? ts.factory.createObjectLiteralExpression() : ts.factory.createIdentifier("undefined"), TypeFactory.keyword("any"), ); /* ----------------------------------------------------------- DECODERS ----------------------------------------------------------- */ const decode_property = (project: IProject) => (importer: FunctionImporter) => (index: number) => ( accessor: ts.ElementAccessExpression | ts.PropertyAccessExpression, meta: Metadata, ): ts.CaseClause[] => { const clauses: ts.CaseClause[] = []; const emplace = (name: string) => (v: ts.Expression | ts.Statement[]) => clauses.push( ts.factory.createCaseClause( ExpressionFactory.number(index++), Array.isArray(v) ? [ ts.factory.createExpressionStatement( ts.factory.createIdentifier(`// type: ${name}`), ), ...v, ts.factory.createBreakStatement(), ] : [ ts.factory.createExpressionStatement( ts.factory.createIdentifier(`// ${name}`), ), ts.factory.createExpressionStatement( ts.factory.createBinaryExpression( accessor, ts.factory.createToken(ts.SyntaxKind.EqualsToken), v, ), ), ts.factory.createBreakStatement(), ], ), ); const required: boolean = meta.isRequired() && !meta.nullable; for (const atomic of ProtobufUtil.getAtomics(meta)) emplace(atomic)(decode_atomic(meta)(atomic)); if (meta.natives.length) emplace("bytes")(decode_bytes("bytes")); for (const array of meta.arrays) emplace(`Array<${array.type.value.getName()}>`)( decode_array(accessor, array, required), ); for (const map of meta.maps) emplace(`Map<string, ${map.value.getName()}>`)( decode_map(project)(importer)(accessor, map, required), ); for (const obj of meta.objects) emplace(obj.name)( ProtobufUtil.isStaticObject(obj) ? decode_regular_object(false)(obj) : decode_dynamic_object(project)(importer)(accessor, obj, required), ); return clauses; }; const decode_atomic = (meta: Metadata) => (atomic: ProtobufAtomic): ts.Expression => { if (atomic === "string") return decode_bytes("string"); const call: ts.CallExpression = ts.factory.createCallExpression( IdentifierFactory.access(ts.factory.createIdentifier("reader"))(atomic), undefined, undefined, ); if (atomic !== "int64" && atomic !== "uint64") return call; const isNumber: boolean = ProtobufUtil.getNumbers(meta).some( (n) => n === atomic, ); return isNumber ? ts.factory.createCallExpression( ts.factory.createIdentifier("Number"), undefined, [call], ) : call; }; const decode_bytes = (method: "bytes" | "string"): ts.Expression => ts.factory.createCallExpression( IdentifierFactory.access(ts.factory.createIdentifier("reader"))(method), undefined, undefined, ); const decode_array = ( accessor: ts.ElementAccessExpression | ts.PropertyAccessExpression, array: MetadataArray, required: boolean, ): ts.Statement[] => { const statements: Array<ts.Expression | ts.Statement> = []; if (required === false) statements.push( ts.factory.createBinaryExpression( accessor, ts.factory.createToken(ts.SyntaxKind.QuestionQuestionEqualsToken), ts.factory.createAsExpression( ts.factory.createArrayLiteralExpression(), ts.factory.createTypeReferenceNode("any[]"), ), ), ); const atomics = ProtobufUtil.getAtomics(array.type.value); const decoder = atomics.length ? () => decode_atomic(array.type.value)(atomics[0]!) : array.type.value.natives.length ? () => decode_bytes("bytes") : array.type.value.objects.length ? () => decode_regular_object(false)(array.type.value.objects[0]!) : null; if (decoder === null) throw new Error("Never reach here."); else if (atomics.length && atomics[0] !== "string") { statements.push( ts.factory.createIfStatement( ts.factory.createStrictEquality( ExpressionFactory.number(2), ts.factory.createBitwiseAnd( ts.factory.createIdentifier("tag"), ExpressionFactory.number(7), ), ), ts.factory.createBlock( [ StatementFactory.constant( "piece", ts.factory.createAdd( ts.factory.createCallExpression( IdentifierFactory.access(READER())("uint32"), undefined, undefined, ), ts.factory.createCallExpression( IdentifierFactory.access(READER())("index"), undefined, undefined, ), ), ), ts.factory.createWhileStatement( ts.factory.createLessThan( ts.factory.createCallExpression( IdentifierFactory.access(READER())("index"), undefined, undefined, ), ts.factory.createIdentifier("piece"), ), ts.factory.createExpressionStatement( ts.factory.createCallExpression( IdentifierFactory.access(accessor)("push"), undefined, [decoder()], ), ), ), ], true, ), ts.factory.createExpressionStatement( ts.factory.createCallExpression( IdentifierFactory.access(accessor)("push"), undefined, [decoder()], ), ), ), ); } else statements.push( ts.factory.createCallExpression( IdentifierFactory.access(accessor)("push"), undefined, [decoder()], ), ); return statements.map((stmt) => ts.isExpression(stmt) ? ts.factory.createExpressionStatement(stmt) : stmt, ); }; const decode_regular_object = (top: boolean) => (obj: MetadataObject): ts.Expression => ts.factory.createCallExpression( ts.factory.createIdentifier(`${PREFIX}o${obj.index}`), undefined, [ ts.factory.createIdentifier("reader"), ...(top ? [] : [ ts.factory.createCallExpression( IdentifierFactory.access(READER())("uint32"), undefined, undefined, ), ]), ], ); const decode_dynamic_object = (project: IProject) => (importer: FunctionImporter) => ( accessor: ts.ElementAccessExpression | ts.PropertyAccessExpression, obj: MetadataObject, required: boolean, ): ts.Statement[] => { const top = obj.properties[0]!; return decode_entry(project)(importer)({ initializer: () => ts.factory.createBinaryExpression( accessor, ts.factory.createToken(ts.SyntaxKind.QuestionQuestionEqualsToken), ts.factory.createObjectLiteralExpression(), ), setter: () => ts.factory.createBinaryExpression( ts.factory.createElementAccessExpression( accessor, ts.factory.createIdentifier("entry.key"), ), ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createIdentifier("entry.value"), ), })( MetadataProperty.create({ ...top, key: (() => { const key: Metadata = Metadata.initialize(); key.atomics.push( MetadataAtomic.create({ type: "string", tags: [], }), ); return key; })(), }), required, ); }; const decode_map = (project: IProject) => (importer: FunctionImporter) => ( accessor: ts.ElementAccessExpression | ts.PropertyAccessExpression, map: Metadata.Entry, required: boolean, ): ts.Statement[] => decode_entry(project)(importer)({ initializer: () => ts.factory.createBinaryExpression( accessor, ts.factory.createToken(ts.SyntaxKind.QuestionQuestionEqualsToken), ts.factory.createNewExpression( ts.factory.createIdentifier("Map"), [TypeFactory.keyword("any"), TypeFactory.keyword("any")], [], ), ), setter: () => ts.factory.createCallExpression( IdentifierFactory.access(accessor)("set"), undefined, [ ts.factory.createIdentifier("entry.key"), ts.factory.createIdentifier("entry.value"), ], ), })(map, required); const decode_entry = (project: IProject) => (importer: FunctionImporter) => (props: { initializer: () => ts.Expression; setter: () => ts.Expression; }) => (map: Metadata.Entry, required: boolean): ts.Statement[] => { const statements: ts.Statement[] = [ ...(required ? [] : [ts.factory.createExpressionStatement(props.initializer())]), StatementFactory.constant( "piece", ts.factory.createAdd( ts.factory.createCallExpression( IdentifierFactory.access(READER())("uint32"), undefined, undefined, ), ts.factory.createCallExpression( IdentifierFactory.access(READER())("index"), undefined, undefined, ), ), ), ...write_object_function_body(project)(importer)({ condition: ts.factory.createLessThan( ts.factory.createCallExpression( IdentifierFactory.access(READER())("index"), undefined, undefined, ), ts.factory.createIdentifier("piece"), ), tag: "kind", output: "entry", })([ MetadataProperty.create({ key: MetadataFactory.soleLiteral("key"), value: map.key, description: null, jsDocTags: [], }), MetadataProperty.create({ key: MetadataFactory.soleLiteral("value"), value: map.value, description: null, jsDocTags: [], }), ]), ts.factory.createExpressionStatement(props.setter()), ]; return [ ts.factory.createExpressionStatement( ExpressionFactory.selfCall(ts.factory.createBlock(statements, true)), ), ]; }; } const PREFIX = "$pd"; const READER = () => ts.factory.createIdentifier("reader");