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' ;
23
3
import { SiblingOperations } from './sibling-operations' ;
24
4
25
5
export type ReachableTypes = Set < string > ;
@@ -30,100 +10,52 @@ export function getReachableTypes(schema: GraphQLSchema): ReachableTypes {
30
10
// We don't want cache reachableTypes on test environment
31
11
// Otherwise reachableTypes will be same for all tests
32
12
if ( process . env . NODE_ENV !== 'test' && reachableTypesCache ) {
33
- return reachableTypesCache
13
+ return reachableTypesCache ;
34
14
}
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 ) ;
35
19
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 ;
98
24
}
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
+ } ;
104
29
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
+ } ;
108
49
109
- function collectFromImplementations ( type : GraphQLInterfaceType ) : void {
110
- schema . getPossibleTypes ( type ) . forEach ( collectFrom ) ;
111
- }
50
+ visit ( astNode , visitor ) ;
112
51
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 ) ;
119
56
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 ;
127
59
}
128
60
129
61
export type UsedFields = Record < string , Set < string > > ;
@@ -136,41 +68,30 @@ export function getUsedFields(schema: GraphQLSchema, operations: SiblingOperatio
136
68
if ( process . env . NODE_ENV !== 'test' && usedFieldsCache ) {
137
69
return usedFieldsCache ;
138
70
}
139
-
140
71
const usedFields : UsedFields = { } ;
141
-
142
- const addField = ( typeName , fieldName ) => {
143
- const fieldType = usedFields [ typeName ] ?? ( usedFields [ typeName ] = new Set ( ) ) ;
144
- fieldType . add ( fieldName ) ;
145
- } ;
146
-
147
72
const typeInfo = new TypeInfo ( schema ) ;
73
+ const allDocuments = [ ...operations . getOperations ( ) , ...operations . getFragments ( ) ] ;
148
74
149
75
const visitor = visitWithTypeInfo ( typeInfo , {
150
76
Field : {
151
- enter ( node ) {
77
+ enter ( node ) : false | void {
152
78
const fieldDef = typeInfo . getFieldDef ( ) ;
153
-
154
79
if ( ! fieldDef ) {
155
80
// skip visiting this node if field is not defined in schema
156
81
return false ;
157
82
}
158
-
159
- const parent = typeInfo . getParentType ( ) ;
83
+ const parentTypeName = typeInfo . getParentType ( ) . name ;
160
84
const fieldName = node . name . value ;
161
- addField ( parent . name , fieldName ) ;
162
85
163
- return undefined ;
86
+ usedFields [ parentTypeName ] ??= new Set ( ) ;
87
+ usedFields [ parentTypeName ] . add ( fieldName ) ;
164
88
} ,
165
89
} ,
166
90
} ) ;
167
91
168
- const allDocuments = [ ...operations . getOperations ( ) , ...operations . getFragments ( ) ] ;
169
-
170
92
for ( const { document } of allDocuments ) {
171
93
visit ( document , visitor ) ;
172
94
}
173
-
174
95
usedFieldsCache = usedFields ;
175
96
return usedFieldsCache ;
176
97
}
0 commit comments