import { indent , indentMultiline } from '@graphql-codegen/visitor-plugin-common' ; import { StringValueNode } from 'graphql' ; function isStringValueNode ( node ) { return node && typeof node === 'object' && node . kind === 'StringValue' ; } function transformComment ( comment , indentLevel = 0 ) { if ( ! comment || comment === '' ) { return '' ; } if ( isStringValueNode ( comment ) ) { comment = comment . value ; } comment = comment . split ( '*/' ) . join ( '*\\/' ) ; 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 ) ; } return indent ( `${ isFirst ? '/*' : '' } * ${ line }${ isLast ? '\n */\n' : '' }` , indentLevel ) ; } ) . join ( '\n' ) ; } export type Access = 'private' | 'public' | 'DEFAULT' | 'protected' ; export type VariableFlag = { readonly ? : boolean ; static ? : boolean ; } ; export type VariableDeclaration = { name : string ; type : string ; flags ? : VariableFlag ; value ? : string ; } ; export type PropertyFlag = VariableFlag & { optional ? : boolean ; } ; export type Property = VariableDeclaration & { access : Access ; flags : PropertyFlag ; } ; export type MethodArguments = VariableDeclaration ; export type MethodFlag = { static ? : boolean ; } ; export type Method = { name : string ; args : MethodArguments [ ] ; access : Access ; returnType : string | null ; flags : MethodFlag ; implmentation : string | null ; comment : string ; } ; export type EnumValues = { [ name ] : string ; } ; export type DeclarationKind = 'class' | 'enum' | 'interface' | 'type' ; export type DeclarationFlags = { isDeclaration ? : boolean ; shouldExport ? : boolean ; } ; export class TypeScriptDeclarationBlock { protected _name : string = '' ; protected _kind : DeclarationKind = 'class' ; protected _extends : string [ ] = [ ] ; protected _properties : Property [ ] = [ ] ; protected _methods : Method [ ] = [ ] ; protected _flags : DeclarationFlags = { isDeclaration : false , shouldExport : false , } ; protected _comments : string = '' ; protected _block : string = '' ; protected _enumValues : EnumValues = { } ; public withName ( name ) { this . _name = name ; return this ; } public export ( shouldExport = true ) { this . _flags . shouldExport = shouldExport ; return this ; } public withComment ( comment ) { this . _comments = transformComment ( comment ) ; return this ; } public withBlock ( block ) { this . _block = block ; return this ; } public asKind ( kind ) { this . _kind = kind ; return this ; } public withFlag ( flags ) { this . _flags = { ... this . _flags , ... flags } ; return this ; } public addEnumValue ( name , value ? ) { if ( this . _kind !== 'enum' ) { throw new Error ( 'Can not add enum values for non enum kind' ) ; } this . _enumValues [ name ] = value !== undefined ? value : name ; } public withEnumValues ( values ) { if ( Array . isArray ( values ) ) { values . forEach ( val => this . addEnumValue ( val ) ) ; } else { Object . entries ( values ) . forEach ( ( [ name , val ] ) => this . addEnumValue ( name , val ) ) ; } return this ; } public addProperty ( name , type , value ? , access = 'DEFAULT' , flags = { } ) { if ( this . _kind === 'enum' ) { throw new Error ( 'Can not add property to enum kind' ) ; } this . _properties . push ( { name , type , flags , access , value , } ) ; } public addClassMethod ( name , returnType , implmentation , args = [ ] , access = 'DEFAULT' , flags = { } , comment = '' ) { if ( this . _kind === 'enum' ) { throw new Error ( 'Can not add method to enum kind' ) ; } this . _methods . push ( { name , returnType , implmentation , args , flags , access , comment : transformComment ( comment ) , } ) ; } public get string ( ) : string { switch ( this . _kind ) { case 'interface' : return this . generateInterface ( ) ; case 'class' : return this . generateClass ( ) ; case 'enum' : return this . generateEnum ( ) ; case 'type' : return this . generateType ( ) ; } } protected generateEnum ( ) { if ( this . _kind !== 'enum' ) { throw new Error ( `generateEnum called for non enum kind(${ this . _kind })` ) ; } const header = [ 'export' , 'enum' , this . _name ] . join ( ' ' ) ; const body = Object . entries ( this . _enumValues ) . map ( ( [ name , value ] ) => `${ name } = "${ value }"` ) . join ( ',\n' ) ; return [ `${ header } {` , indentMultiline ( body ) , '}' ] . join ( '\n' ) ; } protected generateClass ( ) { const header = [ this . _flags . shouldExport ? 'export' : '' , this . _flags . isDeclaration ? 'declare' : '' , 'class' , this . _name , '{' , ] ; if ( this . _extends . length ) { header . push ( [ 'extends' , this . _extends . join ( ', ' ) ] . join ( ' ' ) ) ; } const body = [ this . generateProperties ( ) , this . generateMethods ( ) ] ; return [ `${ header . filter ( h => h ) . join ( ' ' ) }` , indentMultiline ( body . join ( '\n' ) ) , '}' ] . join ( '\n' ) ; } protected generateProperties ( ) { const props = this . _properties . map ( prop => { const result = [ ] ; if ( prop . access !== 'DEFAULT' ) { result . push ( prop . access ) ; } if ( prop . flags && prop . flags . readonly ) { result . push ( 'readonly' ) ; } result . push ( this . generatePropertyName ( prop ) ) ; if ( prop . value ) { result . push ( ` = ${ prop . type };` ) ; } return result . join ( ' ' ) ; } ) ; return props . map ( propDeclaration => `${ propDeclaration };` ) . join ( '\n' ) ; } protected generateMethods ( ) { const methods = this . _methods . map ( method => { const methodAccessAndName = [ ] ; if ( method . access !== 'DEFAULT' ) { methodAccessAndName . push ( method . access ) ; } if ( method . flags . static ) { methodAccessAndName . push ( 'static' ) ; } methodAccessAndName . push ( method . name ) ; const args = method . args . map ( arg => { return `${ arg . name }${ arg . type ? `: ${ arg . type }` : '' }` ; } ) . join ( ', ' ) ; const returnType = method . returnType ? `: ${ method . returnType }` : '' ; const methodHeaderStr = `${ methodAccessAndName . join ( ' ' ) }(${ args })${ returnType }` ; if ( this . _flags . isDeclaration ) { return `${ methodHeaderStr };` ; } return [ `${ methodHeaderStr }` , '{' , method . implmentation ? indentMultiline ( method . implmentation ) : '' , '}' ] . join ( '\n' ) ; } ) ; return methods . join ( '\n' ) ; } protected generateInterface ( ) { throw new Error ( 'Not implemented yet' ) ; } protected generateType ( ) { const header = [ this . _flags . shouldExport ? 'export' : '' , this . _flags . isDeclaration ? 'declare' : '' , 'type' , this . _name , '= {' , ] ; if ( this . _extends . length ) { header . push ( [ 'extends' , this . _extends . join ( ', ' ) ] . join ( ' ' ) ) ; } const body = [ this . generateProperties ( ) ] ; return [ `${ header . filter ( h => h ) . join ( ' ' ) }` , indentMultiline ( body . join ( '\n' ) ) , '}' ] . join ( '\n' ) ; } protected generatePropertyName ( property ) { let propertyName = property . name ; if ( property . flags . optional ) { propertyName = `${ propertyName }?` ; } return property . type ? `${ propertyName }: ${ property . type }` : propertyName ; } }