import { indent , indentMultiline } from '@graphql-codegen/visitor-plugin-common' ; import { NameNode , StringValueNode } from 'graphql' ; function isStringValueNode ( node ) { return node && typeof node === 'object' && node . kind === 'StringValue' ; } const reservedKeywords = [ '_COLUMN_' , '_FILE_' , '_FUNCTION_' , '_LINE_' , 'as' , 'associativity' , 'break' , 'case' , 'Class' , 'continue' , 'convenience' , 'default' , 'deinit' , 'didSet' , 'do' , 'dynamic' , 'dynamicType' , 'else' , 'Enum' , 'extension' , 'fallthrough' , 'false' , 'final' , 'for' , 'Func' , 'get' , 'if' , 'import' , 'in' , 'infix' , 'Init' , 'inout' , 'internal' , 'is' , 'lazy' , 'left' , 'Let' , 'mutating' , 'nil' , 'none' , 'nonmutating' , 'operator' , 'optional' , 'override' , 'postfix' , 'precedence' , 'prefix' , 'private' , 'protocol' , 'Protocol' , 'public' , 'required' , 'return' , 'right' , 'self' , 'Self' , 'set' , 'static' , 'struct' , 'subscript' , 'super' , 'switch' , 'Type' , 'typealias' , 'true' , 'unknown' , 'unowned' , 'var' , 'weak' , 'where' , 'while' , 'willSet' , ] ; export function escapeKeywords ( keyword ) { if ( reservedKeywords . includes ( keyword ) ) { return `\`${ keyword }\`` ; } return keyword ; } function transformComment ( comment , indentLevel = 0 ) { if ( isStringValueNode ( comment ) ) { comment = comment . value ; } if ( ! comment || comment === '' ) { return '' ; } const lines = comment . split ( '\n' ) ; return lines . map ( ( line , index ) => { const isLast = lines . length === index + 1 ; const isFirst = index === 0 ; if ( isFirst && isLast ) { return indent ( `// ${ comment } \n` , indentLevel ) ; } line = line . split ( '*/' ) . join ( '*\\/' ) ; return indent ( `${ isFirst ? '/*' : '' } * ${ line }${ isLast ? '\n */\n' : '' }` , indentLevel ) ; } ) . join ( '\n' ) ; } export enum ListType { ARRAY = 'ARRAY' , LIST = 'LIST' } export type Access = 'private' | 'public' | 'internal' | 'DEFAULT' ; export type VariableFlags = { isList ? : boolean ; listType ? : ListType ; variable ? : boolean ; isEnum ? : boolean ; } ; export type StructFlags = VariableFlags & { optional ? : boolean ; static ? : boolean ; isListNullable ? : boolean ; handleListNullabilityTransparently ? : boolean ; } ; export type PropertyFlags = StructFlags ; export type MethodFlags = { static ? : boolean ; } ; export type DeclarationFlag = { final ? : boolean ; } ; export type Kind = 'class' | 'struct' | 'extension' | 'enum' ; export type VariableDeclaration = { value : string | undefined ; name : string ; type : string ; flags : VariableFlags ; } ; export type StructProperty = VariableDeclaration & { access : Access ; comment ? : string ; getter ? : string ; setter ? : string ; flags : PropertyFlags ; } ; export type MethodArgument = VariableDeclaration & { flags : PropertyFlags ; } ; export type StructMethod = { args : MethodArgument [ ] ; implementation : string ; name : string ; access : Access ; returnType : string | null ; flags : MethodFlags ; comment : string ; } ; export class SwiftDeclarationBlock { _name : string = '' ; _kind : Kind = 'struct' ; _protocols : string [ ] = [ ] ; _access : Access = 'DEFAULT' ; _properties : StructProperty [ ] = [ ] ; _methods : StructMethod [ ] = [ ] ; _comment : string = '' ; _block : string [ ] = [ ] ; _enumValues : { [ name ] : string ; } = { } ; _flags : DeclarationFlag = { final : false } ; access ( access ) { this . _access = access ; return this ; } final ( isFinal = true ) { this . _flags . final = isFinal ; return this ; } withComment ( comment ) { if ( comment ) { this . _comment = transformComment ( comment , 0 ) ; } return this ; } withName ( name ) { this . _name = typeof name === 'object' ? ( name as NameNode ) . value : name ; return this ; } withBlock ( block ) { this . _block = [ block ] ; return this ; } appendBlock ( block ) { this . _block . push ( block ) ; return this ; } asKind ( kind ) { this . _kind = kind ; return this ; } addProperty ( name , type , value ? , access = 'public' , flags = { } , comment ? , getter ? , setter ? ) { this . _properties . push ( { name , type , value , access , flags : { optional : false , ... flags , } , comment , getter , setter , } ) ; return this ; } withProtocols ( protocols ) { this . _protocols = protocols ; return this ; } addEnumValue ( name , value ? ) { if ( this . _kind !== 'enum' ) { throw new Error ( `Can not add enum values for block type ${ this . _kind }` ) ; } this . _enumValues [ name ] = value || name ; return this ; } addClassMethod ( name , returnType , implementation , args = [ ] , access = 'public' , flags = { } , comment = '' ) { this . _methods . push ( { name , returnType , implementation : implementation , args , access , flags , comment : transformComment ( comment ) , } ) ; return this ; } public get string ( ) : string { if ( this . _kind === 'enum' ) { return this . generateEnumStr ( ) ; } return this . generateStructOrExtensionStr ( ) ; } private generateEnumStr ( ) { const declarationHead = this . mergeSections ( [ this . _comment , this . getAccessStr ( ) , this . _kind , `${ escapeKeywords ( this . _name ) }${ this . _protocols . length ? `: ${ this . _protocols . join ( ', ' ) }` : '' }` , '{' , ] , false , ' ' ) ; const enumValues = this . mergeSections ( Object . entries ( this . _enumValues ) . map ( ( [ name , val ] ) => [ 'case' , escapeKeywords ( name ) , ... ( name !== val ? [ '=' , `"${ val }"` ] : [ ] ) ] . join ( ' ' ) ) , false ) ; const declarationFoot = '}' ; return this . mergeSections ( [ declarationHead , indentMultiline ( enumValues ) , declarationFoot ] , false ) ; } private generateStructOrExtensionStr ( ) { const properties = this . mergeSections ( [ ... this . _properties . map ( prop => this . generatePropertiesStr ( prop ) ) ] , false ) ; const methods = this . mergeSections ( this . _methods . map ( method => { const argsStr = this . generateArgsStr ( method . args ) ; const argWithParenthesis = argsStr . length ? [ '(' , indentMultiline ( argsStr ) . trim ( ) , ')' ] . join ( '' ) : '()' ; const methodHeader = this . mergeSections ( [ method . access === 'DEFAULT' ? '' : method . access , method . flags . static ? 'static' : '' , [ 'init' , 'deinit' ] . includes ( method . name ) ? '' : 'func' , `${ escapeKeywords ( method . name ) }${ argWithParenthesis }` , method . returnType ? `-> ${ method . returnType }` : '' , '{' , ] , false , ' ' ) ; const methodFooter = '}' ; return this . mergeSections ( [ method . comment , methodHeader , indentMultiline ( method . implementation ) , methodFooter ] , false ) ; } ) , false ) ; const declarationHead = this . mergeSections ( [ this . _flags . final ? 'final' : '' , this . getAccessStr ( ) , this . _kind , `${ escapeKeywords ( this . _name ) }${ this . _protocols . length ? `: ${ this . _protocols . join ( ', ' ) }` : '' }` , '{' , ] , false , ' ' ) ; const declarationBody = indentMultiline ( this . mergeSections ( [ ... this . _block , properties , methods ] ) ) ; const declarationFoot = '}' ; return this . mergeSections ( [ this . _comment , declarationHead , declarationBody , declarationFoot ] , false ) ; } private generateArgsStr ( args ) { const res = args . reduce ( ( acc , arg ) => { const type = arg . flags . isList ? this . getListType ( arg ) : escapeKeywords ( arg . type ) ; if ( arg . flags . handleListNullabilityTransparently ) { const isArgOptional = arg . flags . isList ? arg . flags . isListNullable : arg . flags . optional ; const val = arg . value ? arg . value : isArgOptional ? 'nil' : arg . flags . isList ? '[]' : null ; acc . push ( [ escapeKeywords ( arg . name ) , ': ' , type , isArgOptional ? '?' : '' , val ? ` = ${ val }` : '' ] . join ( '' ) ) ; } else { const val = arg . value ? arg . value : arg . flags . isList ? '[]' : arg . flags . optional ? 'nil' : null ; acc . push ( [ escapeKeywords ( arg . name ) , ': ' , type , arg . flags . optional ? '?' : '' , val ? ` = ${ val }` : '' ] . join ( '' ) ) ; } return acc ; } , [ ] ) ; return res . length > 1 ? indentMultiline ( res . join ( ',\n' ) ) : res . join ( ',' ) ; } private generatePropertiesStr ( prop ) { let propertyTypeName = prop . flags . isList ? this . getListType ( prop ) : prop . type ; let propertyType = propertyTypeName ? `: ${ propertyTypeName }${ prop . flags . optional ? '?' : '' }` : '' ; if ( prop . flags . handleListNullabilityTransparently && prop . flags . isList ) { propertyType = propertyTypeName ? `: ${ propertyTypeName }${ prop . flags . isListNullable ? '?' : '' }` : '' ; } let resultArr = [ prop . access === 'DEFAULT' ? '' : prop . access , prop . flags . static ? 'static' : '' , prop . flags . variable ? 'var' : 'let' , `${ escapeKeywords ( prop . name ) }${ propertyType }` , ] ; const getterStr = prop . getter ? `{\n${ indentMultiline ( prop . getter ) } \n}` : null ; const setterStr = prop . setter ? `{\n${ indentMultiline ( prop . setter ) } \n}` : null ; let getterSetterStr = '' ; if ( setterStr ) { getterSetterStr = this . mergeSections ( [ '{' , indentMultiline ( `set: ${ setterStr }` ) , getterStr ? indentMultiline ( `get: ${ getterStr }` ) : '' , '}' ] , false ) ; } else if ( getterStr ) { getterSetterStr = indentMultiline ( getterStr ) ; } resultArr . push ( getterSetterStr ) ; if ( prop . value ) { resultArr . push ( '=' ) ; resultArr . push ( prop . value ) ; } const propDeclaration = resultArr . filter ( r => ! ! r ) . join ( ' ' ) ; return this . mergeSections ( [ prop . comment ? `${ transformComment ( prop . comment ) }` : '' , propDeclaration ] , false ) ; } private getAccessStr ( ) { return this . _access === 'DEFAULT' ? '' : this . _access ; } mergeSections ( sections , insertNewLine = true , joinStr = '\n' ) { return sections . filter ( section => ! ! section ) . map ( section => ( insertNewLine ? `${ section }\n` : section ) ) . join ( joinStr ) . trim ( ) ; } private getListType ( typeDeclaration ) { let listMemberType = `${ escapeKeywords ( typeDeclaration . type ) }` ; if ( typeDeclaration . flags . handleListNullabilityTransparently ) { listMemberType = typeDeclaration . flags . optional ? `${ escapeKeywords ( typeDeclaration . type ) }?` : `${ escapeKeywords ( typeDeclaration . type ) }` ; } if ( typeDeclaration . flags . listType === ListType . LIST ) { return `List<${ listMemberType }>` ; } return `[${ listMemberType }]` ; } }