import { TypeAssertion , PrimitiveTypeAssertion , PrimitiveValueTypeAssertion , RepeatedAssertion , SpreadAssertion , SequenceAssertion , OneOfAssertion , OptionalAssertion , EnumAssertion , ObjectAssertion , TypeAssertionMap , CodegenContext } from '../../types' ; import { escapeString } from '../../lib/escape' ; import { nvl2 } from './../util' ; function formatTypeName ( ty , ctx , typeName ) { if ( typeName . includes ( '.' ) || ty . kind === 'symlink' || ty . kind === 'enum' ) { return generateCSharpCodeInner ( ty , false , ctx ) ; } return typeName ; } function formatCSharpCodeDocComment ( ty , nestLevel ) { let code = '' ; const indent = '    ' . repeat ( nestLevel ) ; const docComment = typeof ty === 'string' ? ty : ty . docComment ; if ( docComment ) { if ( 0 <= docComment . indexOf ( '\n' ) ) { code += `${ indent }/**\n${ indent }  ${ docComment . split ( '\n' ) . map ( x => x . trimLeft ( ) ) . join ( `\n${ indent } ` ) }\n${ indent } */\n` ; } else { code += `${ indent }/** ${ docComment } */\n` ; } } return code ; } function formatMemberType ( ty , ctx ) { if ( ty . typeName ) { return formatTypeName ( ty , ctx , ty . typeName ) ; } else { switch ( ty . kind ) { case 'primitive' : return generateCSharpCodePrimitive ( ty , ctx ) ; case 'primitive-value' : return generateCSharpCodePrimitiveValue ( ty , ctx ) ; case 'repeated' : return generateCSharpCodeRepeated ( ty , ctx ) ; case 'one-of' : return generateCSharpCodeOneOf ( ty , ctx ) ; default : return 'object' ; } } } function appendOptionalModifier ( name ) { switch ( name ) { case 'decimal' : case 'int' : case 'double' : case 'bool' : return `${ name }?` ; default : return name ; } } function isNullableOneOf ( ty , ctx ) { const filtered = ty . oneOf . filter ( x => ! ( x . kind === 'primitive' && ( x . primitiveName === 'null' || x . primitiveName === 'undefined' ) || x . kind === 'primitive-value' && ( x . value === null || x . value === void 0 ) ) ) ; return ( filtered . length === 1 && ty . oneOf . length !== 1 ? filtered [ 0 ] : null ) ; } function generateCSharpCodePrimitive ( ty , ctx ) { switch ( ty . primitiveName ) { case 'null' : case 'undefined' : return 'object' ; case 'integer' : return 'int' ; case 'bigint' : return 'decimal' ; case 'number' : return 'double' ; case 'boolean' : return 'bool' ; default : return ty . primitiveName ; } } function generateCSharpCodePrimitiveValue ( ty , ctx ) { if ( ty . value === null || ty . value === void 0 ) { return 'object' ; } switch ( typeof ty . primitiveName ) { case 'bigint' : return 'decimal' ; default : switch ( typeof ty . value ) { case 'number' : return 'double' ; case 'string' : return 'string' ; case 'boolean' : return 'bool' ; default : return 'object' ; } } } function generateCSharpCodeRepeated ( ty , ctx ) { return `${ formatMemberType ( ty . repeated , ctx ) }[]` ; } function generateCSharpCodeSpread ( ty , ctx ) { return '' ; } function generateCSharpCodeSequence ( ty , ctx ) { return 'object[]' ; } function generateCSharpCodeOneOf ( ty , ctx ) { const z = isNullableOneOf ( ty , ctx ) ; if ( z ) { return appendOptionalModifier ( formatMemberType ( z , ctx ) ) ; } else { return 'object' ; } } function generateCSharpCodeOptional ( ty , ctx ) { return appendOptionalModifier ( generateCSharpCodeInner ( ty . optional , false , ctx ) ) ; } function generateCSharpCodeEnum ( ty , ctx ) { return 'object' ; } function addAttributes ( ty , ctx , typeName ) { const attrs = [ ] ; let ty2 = ty ; if ( ty2 . kind !== 'optional' ) { switch ( typeName ) { case 'decimal' : case 'int' : case 'double' : case 'bool' : break ; default : if ( ty2 . kind === 'one-of' ) { if ( ! isNullableOneOf ( ty2 , ctx ) ) { attrs . push ( 'Required' ) ; } } else { attrs . push ( 'Required' ) ; } break ; } ty2 = ty ; } switch ( ty2 . kind ) { case 'primitive' : { if ( typeof ty2 . minLength === 'number' ) { attrs . push ( `MinLength(${ ty2 . minLength })` ) ; } if ( typeof ty2 . maxLength === 'number' ) { attrs . push ( `MaxLength(${ ty2 . maxLength })` ) ; } if ( ty2 . minValue !== null && ty2 . minValue !== void 0 || ty2 . maxValue !== null && ty2 . maxValue !== void 0 ) { switch ( ty2 . primitiveName ) { case 'string' : attrs . push ( `Range(typeof(string), "${ nvl2 ( ty2 . minValue , x => escapeString ( x ) , '' ) }", "${ nvl2 ( ty2 . maxValue , x => escapeString ( x ) , '\\U00010FFFF' ) }")` ) ; break ; case 'bigint' : attrs . push ( `Range(typeof(decimal), ${ nvl2 ( ty2 . minValue , x => `new decimal(@"${ String ( x ) }").ToString()` , 'Decimal.MinValue' ) }, ${ nvl2 ( ty2 . maxValue , x => `new decimal(@"${ String ( x ) }").ToString()` , 'Decimal.MaxValue' ) })` ) ; break ; case 'integer' : attrs . push ( `Range(${ nvl2 ( ty2 . minValue , x => `(int)${ String ( x ) }` , 'Int32.MinValue' ) }, ${ nvl2 ( ty2 . maxValue , x => `(int)${ String ( x ) }` , 'Int32.MaxValue' ) })` ) ; break ; case 'number' : attrs . push ( `Range(${ nvl2 ( ty2 . minValue , x => `(double)${ String ( x ) }` , 'Double.MinValue' ) }, ${ nvl2 ( ty2 . maxValue , x => `(double)${ String ( x ) }` , 'Double.MaxValue' ) })` ) ; break ; } } if ( ty2 . pattern ) { attrs . push ( `RegularExpression(@"${ ty2 . pattern . source . replace ( /"/g , '""' ) }")` ) ; } } break ; case 'repeated' : { if ( typeof ty2 . min === 'number' ) { attrs . push ( `MinLength(${ ty2 . min })` ) ; } if ( typeof ty2 . max === 'number' ) { attrs . push ( `MaxLength(${ ty2 . max })` ) ; } } break ; } if ( 0 < attrs . length ) { return `[${ attrs . join ( ', ' ) }]\n${ '    ' . repeat ( ctx . nestLevel + 1 ) }` ; } else { return '' ; } } function generateCSharpCodeObject ( ty , isInterface , ctx ) { const sep = '\n\n' ; const memberLines = ty . members . filter ( x => ! ( x [ 2 ] ) ) . map ( x => { const typeName = x [ 1 ] . typeName ? formatTypeName ( x [ 1 ] , { ... ctx , nestLevel : ctx . nestLevel + 1 } , x [ 1 ] . typeName ) : generateCSharpCodeInner ( x [ 1 ] , false , { ... ctx , nestLevel : ctx . nestLevel + 1 } ) ; return ( `${ formatCSharpCodeDocComment ( x [ 3 ] || '' , ctx . nestLevel + 1 ) }${ '    ' . repeat ( ctx . nestLevel + 1 ) }${ addAttributes ( x [ 1 ] , ctx , typeName ) }public ${ typeName } ${ x [ 0 ] } { get; set; }` ) ; } ) ; if ( memberLines . length === 0 ) { return ( `\n${ '    ' . repeat ( ctx . nestLevel ) }{\n${ '    ' . repeat ( ctx . nestLevel ) }}` ) ; } return ( `\n${ '    ' . repeat ( ctx . nestLevel ) }{\n${ memberLines . join ( sep ) }\n${ '    ' . repeat ( ctx . nestLevel ) }}` ) ; } function generateCSharpCodeInner ( ty , isInterface , ctx ) { switch ( ty . kind ) { case 'never' : case 'any' : case 'unknown' : return 'object' ; case 'primitive' : return generateCSharpCodePrimitive ( ty , ctx ) ; case 'primitive-value' : return generateCSharpCodePrimitiveValue ( ty , ctx ) ; case 'repeated' : return generateCSharpCodeRepeated ( ty , ctx ) ; case 'spread' : return generateCSharpCodeSpread ( ty , ctx ) ; case 'sequence' : return generateCSharpCodeSequence ( ty , ctx ) ; case 'one-of' : return generateCSharpCodeOneOf ( ty , ctx ) ; case 'optional' : return generateCSharpCodeOptional ( ty , ctx ) ; case 'enum' : return generateCSharpCodeEnum ( ty , ctx ) ; case 'object' : return generateCSharpCodeObject ( ty , isInterface , ctx ) ; case 'symlink' : if ( ctx . schema ?. has ( ty . symlinkTargetName ) ) { const target = ctx . schema . get ( ty . symlinkTargetName ) ; switch ( target ?. ty . kind ) { case 'enum' : return 'object' ; } } return ty . symlinkTargetName ; case 'operator' : throw new Error ( `Unexpected type assertion: ${ ( ty as any ) . kind }` ) ; default : throw new Error ( `Unknown type assertion: ${ ( ty as any ) . kind }` ) ; } } export function generateCSharpCode ( schema ) { let code = `using System.ComponentModel.DataAnnotations;

namespace Tynder.UserSchema
{
` ; const ctx = { nestLevel : 1 , schema , } ; for ( const ty of schema . entries ( ) ) { const indent0 = '    ' . repeat ( ctx . nestLevel ) ; if ( ty [ 1 ] . ty . kind === 'object' ) { } else if ( ty [ 1 ] . ty . kind === 'enum' ) { } else if ( ty [ 1 ] . ty . kind === 'never' && ty [ 1 ] . ty . passThruCodeBlock ) { } else { code += formatCSharpCodeDocComment ( ty [ 1 ] . ty , ctx . nestLevel ) ; let tyName = 'System.Object' ; switch ( ty [ 1 ] . ty . kind ) { case 'primitive' : switch ( ty [ 1 ] . ty . primitiveName ) { case 'integer' : tyName = 'System.Int32' ; break ; case 'bigint' : tyName = 'System.Decimal' ; break ; case 'number' : tyName = 'System.Double' ; break ; case 'boolean' : tyName = 'System.Boolean' ; break ; case 'string' : tyName = 'System.String' ; break ; } break ; case 'primitive-value' : if ( ty [ 1 ] . ty . value !== null && ty [ 1 ] . ty . value !== void 0 ) { switch ( typeof ty [ 1 ] . ty . primitiveName ) { case 'bigint' : tyName = 'System.Decimal' ; break ; default : switch ( typeof ty [ 1 ] . ty . value ) { case 'number' : tyName = 'System.Double' ; break ; case 'boolean' : tyName = 'System.Boolean' ; break ; case 'string' : tyName = 'System.String' ; break ; } } } break ; } code += `${ indent0 }using ${ ty [ 0 ] } = ${ tyName };\n\n` ; } } let isFirst = true ; for ( const ty of schema . entries ( ) ) { const accessModifier = ty [ 1 ] . exported ? 'public' : 'public' ; const indent0 = '    ' . repeat ( ctx . nestLevel ) ; const indent1 = '    ' . repeat ( ctx . nestLevel + 1 ) ; if ( ty [ 1 ] . ty . kind === 'object' || ty [ 1 ] . ty . kind === 'enum' ) { if ( isFirst ) { isFirst = false ; code += '\n' ; } else { code += '\n\n' ; } code += formatCSharpCodeDocComment ( ty [ 1 ] . ty , ctx . nestLevel ) ; } if ( ty [ 1 ] . ty . kind === 'object' ) { code += `${ indent0 }${ accessModifier } class ${ ty [ 0 ] }${ ty [ 1 ] . ty . baseTypes && ty [ 1 ] . ty . baseTypes . length ? ` : ${ ty [ 1 ] . ty . baseTypes . filter ( x => x . typeName ) . map ( x => formatTypeName ( x , { ... ctx , nestLevel : ctx . nestLevel + 1 } , x . typeName as string ) ) . join ( ', ' ) }` : '' } ${ generateCSharpCodeInner ( ty [ 1 ] . ty , true , ctx ) }\n` ; } else if ( ty [ 1 ] . ty . kind === 'enum' ) { let value = 0 ; code += `${ indent0 }${ accessModifier } static class ${ ty [ 0 ] }\n${ indent0 }{\n${ ty [ 1 ] . ty . values . map ( x => `${ formatCSharpCodeDocComment ( x [ 2 ] || '' , ctx . nestLevel + 1 ) }${ indent1 }${ ( ( ) => { if ( value !== null && x [ 1 ] === value ) { value ++ ; return `public static double ${ x [ 0 ] } { get { return ${ x [ 1 ] }; } }` ; } else { if ( typeof x [ 1 ] === 'number' ) { value = x [ 1 ] + 1 ; return `public static double ${ x [ 0 ] } { get { return ${ x [ 1 ] }; } }` ; } else { return `public static string ${ x [ 0 ] } { get { return "${ escapeString ( x [ 1 ] ) }"; } }` ; } } } ) ( ) }` ) . join ( '\n\n' ) }\n${ indent0 }}\n` ; } else if ( ty [ 1 ] . ty . kind === 'never' && ty [ 1 ] . ty . passThruCodeBlock ) { } else { } } return code + '}\n' ; }