1- import {
2- GraphQLSchema ,
3- GraphQLFieldMap ,
4- GraphQLInputFieldMap ,
5- GraphQLField ,
6- GraphQLInputField ,
7- GraphQLInputType ,
8- GraphQLOutputType ,
9- GraphQLNamedType ,
10- GraphQLInterfaceType ,
11- GraphQLArgument ,
12- GraphQLDirective ,
13- isObjectType ,
14- isInterfaceType ,
15- isUnionType ,
16- isInputObjectType ,
17- isListType ,
18- isNonNullType ,
19- TypeInfo ,
20- visit ,
21- visitWithTypeInfo ,
22- } from 'graphql' ;
1+ import { GraphQLSchema , TypeInfo , ASTKindToNode , Visitor , visit , visitWithTypeInfo , parse } from 'graphql' ;
2+ import { printSchemaWithDirectives } from '@graphql-tools/utils' ;
233import { SiblingOperations } from './sibling-operations' ;
244
255export type ReachableTypes = Set < string > ;
@@ -30,100 +10,52 @@ export function getReachableTypes(schema: GraphQLSchema): ReachableTypes {
3010 // We don't want cache reachableTypes on test environment
3111 // Otherwise reachableTypes will be same for all tests
3212 if ( process . env . NODE_ENV !== 'test' && reachableTypesCache ) {
33- return reachableTypesCache
13+ return reachableTypesCache ;
3414 }
15+ // 👀 `printSchemaWithDirectives` keep all custom directives and `printSchema` from `graphql` not
16+ const printedSchema = printSchemaWithDirectives ( schema ) ; // Returns a string representation of the schema
17+ const astNode = parse ( printedSchema ) ; // Transforms the string into ASTNode
18+ const cache = Object . create ( null ) ;
3519
36- const reachableTypes : ReachableTypes = new Set ( ) ;
37-
38- collectFromDirectives ( schema . getDirectives ( ) ) ;
39- collectFrom ( schema . getQueryType ( ) ) ;
40- collectFrom ( schema . getMutationType ( ) ) ;
41- collectFrom ( schema . getSubscriptionType ( ) ) ;
42-
43- reachableTypesCache = reachableTypes ;
44- return reachableTypesCache ;
45-
46- function collectFromDirectives ( directives : readonly GraphQLDirective [ ] ) : void {
47- for ( const directive of directives || [ ] ) {
48- reachableTypes . add ( directive . name ) ;
49- directive . args . forEach ( collectFromArgument ) ;
50- }
51- }
52-
53- function collectFrom ( type ?: GraphQLNamedType ) : void {
54- if ( type && shouldCollect ( type . name ) ) {
55- if ( isObjectType ( type ) ) {
56- collectFromFieldMap ( type . getFields ( ) ) ;
57- collectFromInterfaces ( type . getInterfaces ( ) ) ;
58- } else if ( isInterfaceType ( type ) ) {
59- collectFromFieldMap ( type . getFields ( ) ) ;
60- collectFromInterfaces ( type . getInterfaces ( ) ) ;
61- collectFromImplementations ( type ) ;
62- } else if ( isUnionType ( type ) ) {
63- type . getTypes ( ) . forEach ( collectFrom ) ;
64- } else if ( isInputObjectType ( type ) ) {
65- collectFromInputFieldMap ( type . getFields ( ) ) ;
66- }
67- }
68- }
69-
70- function collectFromFieldMap ( fieldMap : GraphQLFieldMap < any , any > ) : void {
71- for ( const fieldName in fieldMap ) {
72- collectFromField ( fieldMap [ fieldName ] ) ;
73- }
74- }
75-
76- function collectFromField ( field : GraphQLField < any , any > ) : void {
77- collectFromOutputType ( field . type ) ;
78- field . args . forEach ( collectFromArgument ) ;
79- }
80-
81- function collectFromArgument ( arg : GraphQLArgument ) : void {
82- collectFromInputType ( arg . type ) ;
83- }
84-
85- function collectFromInputFieldMap ( fieldMap : GraphQLInputFieldMap ) : void {
86- for ( const fieldName in fieldMap ) {
87- collectFromInputField ( fieldMap [ fieldName ] ) ;
88- }
89- }
90-
91- function collectFromInputField ( field : GraphQLInputField ) : void {
92- collectFromInputType ( field . type ) ;
93- }
94-
95- function collectFromInterfaces ( interfaces : GraphQLInterfaceType [ ] ) : void {
96- if ( interfaces ) {
97- interfaces . forEach ( collectFrom ) ;
20+ const collect = ( nodeType : any ) : void => {
21+ let node = nodeType ;
22+ while ( node . type ) {
23+ node = node . type ;
9824 }
99- }
100-
101- function collectFromOutputType ( output : GraphQLOutputType ) : void {
102- collectFrom ( schema . getType ( resolveName ( output ) ) ) ;
103- }
25+ const typeName = node . name . value ;
26+ cache [ typeName ] ??= 0 ;
27+ cache [ typeName ] += 1 ;
28+ } ;
10429
105- function collectFromInputType ( input : GraphQLInputType ) : void {
106- collectFrom ( schema . getType ( resolveName ( input ) ) ) ;
107- }
30+ const visitor : Visitor < ASTKindToNode > = {
31+ SchemaDefinition ( node ) {
32+ node . operationTypes . forEach ( collect ) ;
33+ } ,
34+ ObjectTypeDefinition ( node ) {
35+ [ node , ...node . interfaces ] . forEach ( collect ) ;
36+ } ,
37+ UnionTypeDefinition ( node ) {
38+ [ node , ...node . types ] . forEach ( collect ) ;
39+ } ,
40+ InputObjectTypeDefinition : collect ,
41+ InterfaceTypeDefinition : collect ,
42+ ScalarTypeDefinition : collect ,
43+ InputValueDefinition : collect ,
44+ DirectiveDefinition : collect ,
45+ EnumTypeDefinition : collect ,
46+ FieldDefinition : collect ,
47+ Directive : collect ,
48+ } ;
10849
109- function collectFromImplementations ( type : GraphQLInterfaceType ) : void {
110- schema . getPossibleTypes ( type ) . forEach ( collectFrom ) ;
111- }
50+ visit ( astNode , visitor ) ;
11251
113- function resolveName ( type : GraphQLOutputType | GraphQLInputType ) {
114- if ( isListType ( type ) || isNonNullType ( type ) ) {
115- return resolveName ( type . ofType ) ;
116- }
117- return type . name ;
118- }
52+ const operationTypeNames = new Set ( [ 'Query' , 'Mutation' , 'Subscription' ] ) ;
53+ const usedTypes = Object . entries ( cache )
54+ . filter ( ( [ typeName , usedCount ] ) => usedCount > 1 || operationTypeNames . has ( typeName ) )
55+ . map ( ( [ typeName ] ) => typeName ) ;
11956
120- function shouldCollect ( name : string ) : boolean {
121- if ( reachableTypes . has ( name ) ) {
122- return false ;
123- }
124- reachableTypes . add ( name ) ;
125- return true ;
126- }
57+ reachableTypesCache = new Set ( usedTypes ) ;
58+ return reachableTypesCache ;
12759}
12860
12961export type UsedFields = Record < string , Set < string > > ;
@@ -136,41 +68,30 @@ export function getUsedFields(schema: GraphQLSchema, operations: SiblingOperatio
13668 if ( process . env . NODE_ENV !== 'test' && usedFieldsCache ) {
13769 return usedFieldsCache ;
13870 }
139-
14071 const usedFields : UsedFields = { } ;
141-
142- const addField = ( typeName , fieldName ) => {
143- const fieldType = usedFields [ typeName ] ?? ( usedFields [ typeName ] = new Set ( ) ) ;
144- fieldType . add ( fieldName ) ;
145- } ;
146-
14772 const typeInfo = new TypeInfo ( schema ) ;
73+ const allDocuments = [ ...operations . getOperations ( ) , ...operations . getFragments ( ) ] ;
14874
14975 const visitor = visitWithTypeInfo ( typeInfo , {
15076 Field : {
151- enter ( node ) {
77+ enter ( node ) : false | void {
15278 const fieldDef = typeInfo . getFieldDef ( ) ;
153-
15479 if ( ! fieldDef ) {
15580 // skip visiting this node if field is not defined in schema
15681 return false ;
15782 }
158-
159- const parent = typeInfo . getParentType ( ) ;
83+ const parentTypeName = typeInfo . getParentType ( ) . name ;
16084 const fieldName = node . name . value ;
161- addField ( parent . name , fieldName ) ;
16285
163- return undefined ;
86+ usedFields [ parentTypeName ] ??= new Set ( ) ;
87+ usedFields [ parentTypeName ] . add ( fieldName ) ;
16488 } ,
16589 } ,
16690 } ) ;
16791
168- const allDocuments = [ ...operations . getOperations ( ) , ...operations . getFragments ( ) ] ;
169-
17092 for ( const { document } of allDocuments ) {
17193 visit ( document , visitor ) ;
17294 }
173-
17495 usedFieldsCache = usedFields ;
17596 return usedFieldsCache ;
17697}
0 commit comments