UNPKG

typia

Version:

Superfast runtime validators with only one line

220 lines (205 loc) 6.78 kB
import ts from "typescript"; import { ImportProgrammer } from "../programmers/ImportProgrammer"; import { _randomFormatUuid } from "../internal/_randomFormatUuid"; export namespace ExpressionFactory { export const number = (value: number) => value < 0 ? ts.factory.createPrefixUnaryExpression( ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral(Math.abs(value)), ) : ts.factory.createNumericLiteral(value); export const bigint = (value: number | bigint) => ts.factory.createCallExpression( ts.factory.createIdentifier("BigInt"), undefined, [ts.factory.createIdentifier(value.toString())], ); export const isRequired = (input: ts.Expression): ts.Expression => ts.factory.createStrictInequality( ts.factory.createIdentifier("undefined"), input, ); export const isArray = (input: ts.Expression): ts.Expression => ts.factory.createCallExpression( ts.factory.createIdentifier("Array.isArray"), undefined, [input], ); export const isObject = (props: { checkNull: boolean; checkArray: boolean; input: ts.Expression; }): ts.Expression => { const conditions: ts.Expression[] = [ ts.factory.createStrictEquality( ts.factory.createStringLiteral("object"), ts.factory.createTypeOfExpression(props.input), ), ]; if (props.checkNull === true) conditions.push( ts.factory.createStrictInequality(ts.factory.createNull(), props.input), ); if (props.checkArray === true) conditions.push( ts.factory.createStrictEquality( ts.factory.createFalse(), ts.factory.createCallExpression( ts.factory.createIdentifier("Array.isArray"), undefined, [props.input], ), ), ); return conditions.length === 1 ? conditions[0]! : conditions.reduce((x, y) => ts.factory.createLogicalAnd(x, y)); }; export const isInstanceOf = ( type: string, input: ts.Expression, ): ts.Expression => { return ts.factory.createBinaryExpression( input, ts.factory.createToken(ts.SyntaxKind.InstanceOfKeyword), ts.factory.createIdentifier(type), ); }; export const coalesce = (x: ts.Expression, y: ts.Expression): ts.Expression => ts.factory.createBinaryExpression( x, ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), y, ); export const currying = (props: { function: ts.Expression; arguments: ts.Expression[]; }): ts.CallExpression => { if (props.arguments.length === 0) return ts.factory.createCallExpression( props.function, undefined, undefined, ); let prev: ts.CallExpression = ts.factory.createCallExpression( props.function, undefined, [props.arguments[0]!], ); for (const param of props.arguments.slice(1)) prev = ts.factory.createCallExpression(prev, undefined, [param]); return prev; }; export const selfCall = ( body: ts.ConciseBody, type?: ts.TypeNode | undefined, ) => ts.isCallExpression(body) ? body : ts.factory.createCallExpression( ts.factory.createParenthesizedExpression( ts.factory.createArrowFunction( undefined, undefined, [], type, undefined, body, ), ), undefined, undefined, ); export const getEscapedText = (props: { printer: ts.Printer; input: ts.Expression; }): string => props.printer.printNode( ts.EmitHint.Expression, props.input, props.input.getSourceFile(), ); export const transpile = (props: { transformer?: ts.TransformationContext; importer?: ImportProgrammer; script: string; }) => { const file: ts.SourceFile = ts.createSourceFile( `${_randomFormatUuid()}.ts`, props.script, ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS, ); const statement: ts.Statement | undefined = file.statements[0]; if (statement === undefined) throw new ReferenceError( "Error on ExpressionFactory.transpile(): no statement exists.", ); else if (!ts.isExpressionStatement(statement)) throw new TypeError( "Error on ExpressionFactory.transpile(): statement is not an expression statement.", ); return (input: ts.Expression): ts.Expression => { const visitor = (node: ts.Node): ts.Node => { if (ts.isIdentifier(node) && node.text === "$input") return input; else if (props.importer !== undefined && ts.isCallExpression(node)) if ( node.expression.getText() === "$importInternal" && node.arguments.length === 1 && ts.isStringLiteralLike(node.arguments[0]!) ) { const name: string = node.arguments[0]!.text; return props.importer.internal(name); } else if ( node.expression.getText() === "$importInstance" && node.arguments.length === 2 && ts.isStringLiteralLike(node.arguments[0]!) && ts.isStringLiteralLike(node.arguments[1]!) ) { const name: string = node.arguments[0]!.text; const file: string = node.arguments[1]!.text; return props.importer.instance({ file, name, alias: null, }); } else if ( node.expression.getText() === "$importNamespace" && node.arguments.length === 2 && ts.isStringLiteralLike(node.arguments[0]!) && ts.isStringLiteralLike(node.arguments[1]!) ) { const name: string = node.arguments[0]!.text; const file: string = node.arguments[1]!.text; return props.importer.namespace({ file, name, }); } else if ( node.expression.getText() === "$importDefault" && node.arguments.length === 3 && ts.isStringLiteralLike(node.arguments[0]!) && ts.isStringLiteralLike(node.arguments[1]!) ) { const name: string = node.arguments[0]!.text; const file: string = node.arguments[1]!.text; return props.importer.default({ file, name, type: false, }); } return ts.visitEachChild( ts.factory.cloneNode(node), visitor, props.transformer, ); }; return visitor( ts.factory.cloneNode(statement.expression), ) as ts.Expression; }; }; }