import { DirectiveDefinitionNode , EnumTypeDefinitionNode , EnumValueDefinitionNode , FieldDefinitionNode , GraphQLSchema , InputObjectTypeDefinitionNode , InputValueDefinitionNode , InterfaceTypeDefinitionNode , ListTypeNode , NamedTypeNode , NameNode , NonNullTypeNode , ObjectTypeDefinitionNode , ScalarTypeDefinitionNode , UnionTypeDefinitionNode , StringValueNode , isEnumType , DirectiveNode , Kind , GraphQLEnumType , } from 'graphql' ; import { BaseVisitor , ParsedConfig , RawConfig } from './base-visitor' ; import { DEFAULT_SCALARS } from './scalars' ; import { normalizeDeclarationKind } from './declaration-kinds' ; import { EnumValuesMap , NormalizedScalarsMap , DeclarationKindConfig , DeclarationKind , ParsedEnumValuesMap , DirectiveArgumentAndInputFieldMappings , ParsedDirectiveArgumentAndInputFieldMappings , } from './types' ; import { transformComment , DeclarationBlock , DeclarationBlockConfig , indent , wrapWithSingleQuotes , getConfigValue , buildScalarsFromConfig , } from './utils' ; import { OperationVariablesToObject } from './variables-to-object' ; import { parseEnumValues } from './enum-values' ; import { transformDirectiveArgumentAndInputFieldMappings } from './mappers' ; export interface ParsedTypesConfig extends ParsedConfig { enumValues : ParsedEnumValuesMap ; declarationKind : DeclarationKindConfig ; addUnderscoreToArgsType : boolean ; onlyOperationTypes : boolean ; enumPrefix : boolean ; fieldWrapperValue : string ; wrapFieldDefinitions : boolean ; entireFieldWrapperValue : string ; wrapEntireDefinitions : boolean ; ignoreEnumValuesFromSchema : boolean ; directiveArgumentAndInputFieldMappings : ParsedDirectiveArgumentAndInputFieldMappings ; } export interface RawTypesConfig extends RawConfig { description exampleMarkdown addUnderscoreToArgsType ? : boolean ; description exampleMarkdown enumValues ? : EnumValuesMap ; description exampleMarkdown declarationKind ? : DeclarationKind | DeclarationKindConfig ; default description exampleMarkdown enumPrefix ? : boolean ; description default exampleMarkdown fieldWrapperValue ? : string ; description default exampleMarkdown wrapFieldDefinitions ? : boolean ; description default exampleMarkdown onlyOperationTypes ? : boolean ; description default exampleMarkdown ignoreEnumValuesFromSchema ? : boolean ; name type boolean description default example wrapEntireFieldDefinitions ? : boolean ; name type string description default example entireFieldWrapperValue ? : string ; description exampleMarkdown directiveArgumentAndInputFieldMappings ? : DirectiveArgumentAndInputFieldMappings ; description exampleMarkdown directiveArgumentAndInputFieldMappingTypeSuffix ? : string ; } export class BaseTypesVisitor < TRawConfig extends RawTypesConfig = RawTypesConfig , TPluginConfig extends ParsedTypesConfig = ParsedTypesConfig > extends BaseVisitor < TRawConfig , TPluginConfig > { protected _argumentsTransformer : OperationVariablesToObject ; constructor ( protected _schema , rawConfig , additionalConfig , defaultScalars = DEFAULT_SCALARS ) { super ( rawConfig , { enumPrefix : getConfigValue ( rawConfig . enumPrefix , true ) , onlyOperationTypes : getConfigValue ( rawConfig . onlyOperationTypes , false ) , addUnderscoreToArgsType : getConfigValue ( rawConfig . addUnderscoreToArgsType , false ) , enumValues : parseEnumValues ( { schema : _schema , mapOrStr : rawConfig . enumValues , ignoreEnumValuesFromSchema : rawConfig . ignoreEnumValuesFromSchema , } ) , declarationKind : normalizeDeclarationKind ( rawConfig . declarationKind ) , scalars : buildScalarsFromConfig ( _schema , rawConfig , defaultScalars ) , fieldWrapperValue : getConfigValue ( rawConfig . fieldWrapperValue , 'T' ) , wrapFieldDefinitions : getConfigValue ( rawConfig . wrapFieldDefinitions , false ) , entireFieldWrapperValue : getConfigValue ( rawConfig . entireFieldWrapperValue , 'T' ) , wrapEntireDefinitions : getConfigValue ( rawConfig . wrapEntireFieldDefinitions , false ) , ignoreEnumValuesFromSchema : getConfigValue ( rawConfig . ignoreEnumValuesFromSchema , false ) , directiveArgumentAndInputFieldMappings : transformDirectiveArgumentAndInputFieldMappings ( rawConfig . directiveArgumentAndInputFieldMappings ?? { } , rawConfig . directiveArgumentAndInputFieldMappingTypeSuffix ) , ... additionalConfig , } ) ; this . _argumentsTransformer = new OperationVariablesToObject ( this . scalars , this . convertName ) ; } protected getExportPrefix ( ) { return 'export ' ; } public getFieldWrapperValue ( ) { if ( this . config . fieldWrapperValue ) { return `${ this . getExportPrefix ( ) }type FieldWrapper<T> = ${ this . config . fieldWrapperValue };` ; } return '' ; } public getEntireFieldWrapperValue ( ) { if ( this . config . entireFieldWrapperValue ) { return `${ this . getExportPrefix ( ) }type EntireFieldWrapper<T> = ${ this . config . entireFieldWrapperValue };` ; } return '' ; } public getScalarsImports ( ) { return Object . keys ( this . config . scalars ) . map ( enumName => { const mappedValue = this . config . scalars [ enumName ] ; if ( mappedValue . isExternal ) { return this . _buildTypeImport ( mappedValue . import , mappedValue . source , mappedValue . default ) ; } return null ; } ) . filter ( a => a ) ; } public getDirectiveArgumentAndInputFieldMappingsImports ( ) { return Object . keys ( this . config . directiveArgumentAndInputFieldMappings ) . map ( directive => { const mappedValue = this . config . directiveArgumentAndInputFieldMappings [ directive ] ; if ( mappedValue . isExternal ) { return this . _buildTypeImport ( mappedValue . import , mappedValue . source , mappedValue . default ) ; } return null ; } ) . filter ( a => a ) ; } public get scalarsDefinition ( ) : string { const allScalars = Object . keys ( this . config . scalars ) . map ( scalarName => { const scalarValue = this . config . scalars [ scalarName ] . type ; const scalarType = this . _schema . getType ( scalarName ) ; const comment = scalarType && scalarType . astNode && scalarType . description ? transformComment ( scalarType . description , 1 ) : '' ; const { scalar } = this . _parsedConfig . declarationKind ; return comment + indent ( `${ scalarName }: ${ scalarValue }${ this . getPunctuation ( scalar ) }` ) ; } ) ; return new DeclarationBlock ( this . _declarationBlockConfig ) . export ( ) . asKind ( this . _parsedConfig . declarationKind . scalar ) . withName ( 'Scalars' ) . withComment ( 'All built-in and custom scalars, mapped to their actual values' ) . withBlock ( allScalars . join ( '\n' ) ) . string ; } public get directiveArgumentAndInputFieldMappingsDefinition ( ) : string { const directiveEntries = Object . entries ( this . config . directiveArgumentAndInputFieldMappings ) ; if ( directiveEntries . length === 0 ) { return '' ; } const allDirectives = [ ] ; for ( const [ directiveName , parsedMapper ] of directiveEntries ) { const directiveType = this . _schema . getDirective ( directiveName ) ; const comment = directiveType ?. astNode && directiveType . description ? transformComment ( directiveType . description , 1 ) : '' ; const { directive } = this . _parsedConfig . declarationKind ; allDirectives . push ( comment + indent ( `${ directiveName }: ${ parsedMapper . type }${ this . getPunctuation ( directive ) }` ) ) ; } return new DeclarationBlock ( this . _declarationBlockConfig ) . export ( ) . asKind ( this . _parsedConfig . declarationKind . directive ) . withName ( 'DirectiveArgumentAndInputFieldMappings' ) . withComment ( 'Type overrides using directives' ) . withBlock ( allDirectives . join ( '\n' ) ) . string ; } setDeclarationBlockConfig ( config ) { this . _declarationBlockConfig = config ; } setArgumentsTransformer ( argumentsTransfomer ) { this . _argumentsTransformer = argumentsTransfomer ; } NonNullType ( node ) { const asString = node . type as any as string ; return asString ; } getInputObjectDeclarationBlock ( node ) { return new DeclarationBlock ( this . _declarationBlockConfig ) . export ( ) . asKind ( this . _parsedConfig . declarationKind . input ) . withName ( this . convertName ( node ) ) . withComment ( node . description as any as string ) . withBlock ( node . fields . join ( '\n' ) ) ; } InputObjectTypeDefinition ( node ) { return this . getInputObjectDeclarationBlock ( node ) . string ; } InputValueDefinition ( node ) { const comment = transformComment ( node . description as any as string , 1 ) ; const { input } = this . _parsedConfig . declarationKind ; let type = node . type as any as string ; if ( node . directives && this . config . directiveArgumentAndInputFieldMappings ) { type = this . _getDirectiveOverrideType ( node . directives ) || type ; } return comment + indent ( `${ node . name }: ${ type }${ this . getPunctuation ( input ) }` ) ; } Name ( node ) { return node . value ; } FieldDefinition ( node ) { const typeString = node . type as any as string ; const { type } = this . _parsedConfig . declarationKind ; const comment = this . getFieldComment ( node ) ; return comment + indent ( `${ node . name }: ${ typeString }${ this . getPunctuation ( type ) }` ) ; } UnionTypeDefinition ( node , key , parent ) { if ( this . config . onlyOperationTypes ) return '' ; const originalNode = parent [ key ] as UnionTypeDefinitionNode ; const possibleTypes = originalNode . types . map ( t => ( this . scalars [ t . name . value ] ? this . _getScalar ( t . name . value ) : this . convertName ( t ) ) ) . join ( ' | ' ) ; return new DeclarationBlock ( this . _declarationBlockConfig ) . export ( ) . asKind ( 'type' ) . withName ( this . convertName ( node ) ) . withComment ( node . description as any as string ) . withContent ( possibleTypes ) . string ; } protected mergeInterfaces ( interfaces , hasOtherFields ) { return interfaces . join ( ' & ' ) + ( interfaces . length && hasOtherFields ? ' & ' : '' ) ; } appendInterfacesAndFieldsToBlock ( block , interfaces , fields ) { block . withContent ( this . mergeInterfaces ( interfaces , fields . length > 0 ) ) ; block . withBlock ( this . mergeAllFields ( fields , interfaces . length > 0 ) ) ; } getObjectTypeDeclarationBlock ( node , originalNode ) { const optionalTypename = this . config . nonOptionalTypename ? '__typename' : '__typename?' ; const { type , interface : interfacesType } = this . _parsedConfig . declarationKind ; const allFields = [ ... ( this . config . addTypename ? [ indent ( `${ this . config . immutableTypes ? 'readonly ' : '' }${ optionalTypename }: '${ node . name }'${ this . getPunctuation ( type ) }` ) , ] : [ ] ) , ... node . fields , ] as string [ ] ; const interfacesNames = originalNode . interfaces ? originalNode . interfaces . map ( i => this . convertName ( i ) ) : [ ] ; const declarationBlock = new DeclarationBlock ( this . _declarationBlockConfig ) . export ( ) . asKind ( type ) . withName ( this . convertName ( node ) ) . withComment ( node . description as any as string ) ; if ( type === 'interface' || type === 'class' ) { if ( interfacesNames . length > 0 ) { const keyword = interfacesType === 'interface' && type === 'class' ? 'implements' : 'extends' ; declarationBlock . withContent ( `${ keyword } ` + interfacesNames . join ( ', ' ) + ( allFields . length > 0 ? ' ' : ' {}' ) ) ; } declarationBlock . withBlock ( this . mergeAllFields ( allFields , false ) ) ; } else { this . appendInterfacesAndFieldsToBlock ( declarationBlock , interfacesNames , allFields ) ; } return declarationBlock ; } getFieldComment ( node ) { let commentText = node . description as any ; const deprecationDirective = node . directives . find ( ( v ) => v . name === 'deprecated' ) ; if ( deprecationDirective ) { const deprecationReason = this . getDeprecationReason ( deprecationDirective ) ; commentText = `${ commentText ? `${ commentText }\n` : '' }@deprecated ${ deprecationReason }` ; } const comment = transformComment ( commentText , 1 ) ; return comment ; } protected mergeAllFields ( allFields , _hasInterfaces ) { return allFields . join ( '\n' ) ; } ObjectTypeDefinition ( node , key , parent ) { if ( this . config . onlyOperationTypes ) return '' ; const originalNode = parent [ key ] as ObjectTypeDefinitionNode ; return [ this . getObjectTypeDeclarationBlock ( node , originalNode ) . string , this . buildArgumentsBlock ( originalNode ) ] . filter ( f => f ) . join ( '\n\n' ) ; } getInterfaceTypeDeclarationBlock ( node , _originalNode ) { const declarationBlock = new DeclarationBlock ( this . _declarationBlockConfig ) . export ( ) . asKind ( this . _parsedConfig . declarationKind . interface ) . withName ( this . convertName ( node ) ) . withComment ( node . description as any as string ) ; return declarationBlock . withBlock ( node . fields . join ( '\n' ) ) ; } InterfaceTypeDefinition ( node , key , parent ) { if ( this . config . onlyOperationTypes ) return '' ; const originalNode = parent [ key ] as InterfaceTypeDefinitionNode ; return [ this . getInterfaceTypeDeclarationBlock ( node , originalNode ) . string , this . buildArgumentsBlock ( originalNode ) ] . filter ( f => f ) . join ( '\n\n' ) ; } ScalarTypeDefinition ( _node ) { return '' ; } protected _buildTypeImport ( identifier , source , asDefault = false ) { const { useTypeImports } = this . config ; if ( asDefault ) { if ( useTypeImports ) { return `import type { default as ${ identifier } } from '${ source }';` ; } return `import ${ identifier } from '${ source }';` ; } return `import${ useTypeImports ? ' type' : '' } { ${ identifier } } from '${ source }';` ; } protected handleEnumValueMapper ( typeIdentifier , importIdentifier , sourceIdentifier , sourceFile ) { const importStatement = this . _buildTypeImport ( importIdentifier || sourceIdentifier , sourceFile ) ; if ( importIdentifier !== sourceIdentifier || sourceIdentifier !== typeIdentifier ) { return [ importStatement , `import ${ typeIdentifier } = ${ sourceIdentifier };` ] ; } return [ importStatement ] ; } public getEnumsImports ( ) { return Object . keys ( this . config . enumValues ) . flatMap ( enumName => { const mappedValue = this . config . enumValues [ enumName ] ; if ( mappedValue . sourceFile ) { if ( mappedValue . isDefault ) { return [ this . _buildTypeImport ( mappedValue . typeIdentifier , mappedValue . sourceFile , true ) ] ; } return this . handleEnumValueMapper ( mappedValue . typeIdentifier , mappedValue . importIdentifier , mappedValue . sourceIdentifier , mappedValue . sourceFile ) ; } return [ ] ; } ) . filter ( Boolean ) ; } EnumTypeDefinition ( node ) { const enumName = node . name as any as string ; if ( this . config . enumValues [ enumName ] && this . config . enumValues [ enumName ] . sourceFile ) { return null ; } return new DeclarationBlock ( this . _declarationBlockConfig ) . export ( ) . asKind ( 'enum' ) . withName ( this . convertName ( node , { useTypesPrefix : this . config . enumPrefix } ) ) . withComment ( node . description as any as string ) . withBlock ( this . buildEnumValuesBlock ( enumName , node . values ) ) . string ; } StringValue ( node ) { return node . value ; } protected makeValidEnumIdentifier ( identifier ) { if ( /^[0-9]/ . exec ( identifier ) ) { return wrapWithSingleQuotes ( identifier , true ) ; } return identifier ; } protected buildEnumValuesBlock ( typeName , values ) { const schemaEnumType = this . _schema ? ( this . _schema . getType ( typeName ) as GraphQLEnumType ) : undefined ; return values . map ( enumOption => { const optionName = this . makeValidEnumIdentifier ( this . convertName ( enumOption , { useTypesPrefix : false , transformUnderscore : true } ) ) ; const comment = transformComment ( enumOption . description as any as string , 1 ) ; const schemaEnumValue = schemaEnumType && ! this . config . ignoreEnumValuesFromSchema ? schemaEnumType . getValue ( enumOption . name as any ) . value : undefined ; let enumValue = typeof schemaEnumValue !== 'undefined' ? schemaEnumValue : ( enumOption . name as any ) ; if ( this . config . enumValues [ typeName ] && this . config . enumValues [ typeName ] . mappedValues && typeof this . config . enumValues [ typeName ] . mappedValues [ enumValue ] !== 'undefined' ) { enumValue = this . config . enumValues [ typeName ] . mappedValues [ enumValue ] ; } return ( comment + indent ( `${ optionName }${ this . _declarationBlockConfig . enumNameValueSeparator } ${ wrapWithSingleQuotes ( enumValue , typeof schemaEnumValue !== 'undefined' ) }` ) ) ; } ) . join ( ',\n' ) ; } DirectiveDefinition ( _node ) { return '' ; } getArgumentsObjectDeclarationBlock ( node , name , field ) { return new DeclarationBlock ( this . _declarationBlockConfig ) . export ( ) . asKind ( this . _parsedConfig . declarationKind . arguments ) . withName ( this . convertName ( name ) ) . withComment ( node . description ) . withBlock ( this . _argumentsTransformer . transform < InputValueDefinitionNode > ( field . arguments ) ) ; } getArgumentsObjectTypeDefinition ( node , name , field ) { return this . getArgumentsObjectDeclarationBlock ( node , name , field ) . string ; } protected buildArgumentsBlock ( node ) { const fieldsWithArguments = node . fields . filter ( field => field . arguments && field . arguments . length > 0 ) || [ ] ; return fieldsWithArguments . map ( field => { const name = node . name . value + ( this . config . addUnderscoreToArgsType ? '_' : '' ) + this . convertName ( field , { useTypesPrefix : false , useTypesSuffix : false , } ) + 'Args' ; return this . getArgumentsObjectTypeDefinition ( node , name , field ) ; } ) . join ( '\n\n' ) ; } protected _getScalar ( name ) { return `Scalars['${ name }']` ; } protected _getDirectiveArgumentNadInputFieldMapping ( name ) { return `DirectiveArgumentAndInputFieldMappings['${ name }']` ; } protected _getDirectiveOverrideType ( directives ) { const type = directives . map ( directive => { const directiveName = directive . name as any as string ; if ( this . config . directiveArgumentAndInputFieldMappings [ directiveName ] ) { return this . _getDirectiveArgumentNadInputFieldMapping ( directiveName ) ; } return null ; } ) . reverse ( ) . find ( a => ! ! a ) ; return type || null ; } protected _getTypeForNode ( node ) { const typeAsString = node . name as any as string ; if ( this . scalars [ typeAsString ] ) { return this . _getScalar ( typeAsString ) ; } else if ( this . config . enumValues [ typeAsString ] ) { return this . config . enumValues [ typeAsString ] . typeIdentifier ; } const schemaType = this . _schema . getType ( node . name as any ) ; if ( schemaType && isEnumType ( schemaType ) ) { return this . convertName ( node , { useTypesPrefix : this . config . enumPrefix } ) ; } return this . convertName ( node ) ; } NamedType ( node , key , parent , path , ancestors ) { const currentVisitContext = this . getVisitorKindContextFromAncestors ( ancestors ) ; const isVisitingInputType = currentVisitContext . includes ( Kind . INPUT_OBJECT_TYPE_DEFINITION ) ; const typeToUse = this . _getTypeForNode ( node ) ; if ( ! isVisitingInputType && this . config . fieldWrapperValue && this . config . wrapFieldDefinitions ) { return `FieldWrapper<${ typeToUse }>` ; } return typeToUse ; } ListType ( node , key , parent , path , ancestors ) { const asString = node . type as any as string ; return this . wrapWithListType ( asString ) ; } SchemaDefinition ( ) { return null ; } protected getDeprecationReason ( directive ) { if ( ( directive . name as any ) === 'deprecated' ) { const hasArguments = directive . arguments . length > 0 ; let reason = 'Field no longer supported' ; if ( hasArguments ) { reason = directive . arguments [ 0 ] . value as any ; } return reason ; } } protected wrapWithListType ( str ) { return `Array<${ str }>` ; } }