@@ -2,85 +2,103 @@ import {
2
2
ArgumentNode ,
3
3
DirectiveDefinitionNode ,
4
4
DirectiveNode ,
5
+ Kind ,
5
6
ListValueNode ,
6
7
NameNode ,
8
+ ValueNode ,
7
9
} from 'graphql' ;
8
- import { isSome } from '@graphql-tools/utils' ;
9
10
import { Config } from './merge-typedefs.js' ;
10
11
11
- function directiveAlreadyExists (
12
- directivesArr : ReadonlyArray < DirectiveNode > ,
13
- otherDirective : DirectiveNode ,
14
- ) : boolean {
15
- return ! ! directivesArr . find ( directive => directive . name . value === otherDirective . name . value ) ;
16
- }
17
-
18
12
function isRepeatableDirective (
19
13
directive : DirectiveNode ,
20
14
directives ?: Record < string , DirectiveDefinitionNode > ,
15
+ repeatableLinkImports ?: Set < string > ,
21
16
) : boolean {
22
- return ! ! directives ?. [ directive . name . value ] ?. repeatable ;
17
+ return ! ! (
18
+ directives ?. [ directive . name . value ] ?. repeatable ??
19
+ repeatableLinkImports ?. has ( directive . name . value )
20
+ ) ;
23
21
}
24
22
25
23
function nameAlreadyExists ( name : NameNode , namesArr : ReadonlyArray < NameNode > ) : boolean {
26
24
return namesArr . some ( ( { value } ) => value === name . value ) ;
27
25
}
28
26
29
27
function mergeArguments ( a1 : readonly ArgumentNode [ ] , a2 : readonly ArgumentNode [ ] ) : ArgumentNode [ ] {
30
- const result : ArgumentNode [ ] = [ ... a2 ] ;
28
+ const result : ArgumentNode [ ] = [ ] ;
31
29
32
- for ( const argument of a1 ) {
30
+ for ( const argument of [ ... a2 , ... a1 ] ) {
33
31
const existingIndex = result . findIndex ( a => a . name . value === argument . name . value ) ;
34
32
35
- if ( existingIndex > - 1 ) {
33
+ if ( existingIndex === - 1 ) {
34
+ result . push ( argument ) ;
35
+ } else {
36
36
const existingArg = result [ existingIndex ] ;
37
37
38
38
if ( existingArg . value . kind === 'ListValue' ) {
39
39
const source = ( existingArg . value as any ) . values ;
40
40
const target = ( argument . value as ListValueNode ) . values ;
41
41
42
42
// merge values of two lists
43
- ( existingArg . value as any ) . values = deduplicateLists (
44
- source ,
45
- target ,
46
- ( targetVal , source ) => {
43
+ ( existingArg . value as ListValueNode ) = {
44
+ ...existingArg . value ,
45
+ values : deduplicateLists ( source , target , ( targetVal , source ) => {
47
46
const value = ( targetVal as any ) . value ;
48
47
return ! value || ! source . some ( ( sourceVal : any ) => sourceVal . value === value ) ;
49
- } ,
50
- ) ;
48
+ } ) ,
49
+ } ;
51
50
} else {
52
51
( existingArg as any ) . value = argument . value ;
53
52
}
54
- } else {
55
- result . push ( argument ) ;
56
53
}
57
54
}
58
55
59
56
return result ;
60
57
}
61
58
62
- function deduplicateDirectives (
63
- directives : ReadonlyArray < DirectiveNode > ,
64
- definitions ?: Record < string , DirectiveDefinitionNode > ,
65
- ) : DirectiveNode [ ] {
66
- return directives
67
- . map ( ( directive , i , all ) => {
68
- const firstAt = all . findIndex ( d => d . name . value === directive . name . value ) ;
69
-
70
- if ( firstAt !== i && ! isRepeatableDirective ( directive , definitions ) ) {
71
- const dup = all [ firstAt ] ;
72
-
73
- ( directive as any ) . arguments = mergeArguments (
74
- directive . arguments as any ,
75
- dup . arguments as any ,
59
+ const matchValues = ( a : ValueNode , b : ValueNode ) : boolean => {
60
+ if ( a . kind === b . kind ) {
61
+ switch ( a . kind ) {
62
+ case Kind . LIST :
63
+ return (
64
+ a . values . length === ( b as typeof a ) . values . length &&
65
+ a . values . every ( aVal => ( b as typeof a ) . values . find ( bVal => matchValues ( aVal , bVal ) ) )
76
66
) ;
77
- return null ;
78
- }
79
-
80
- return directive ;
81
- } )
82
- . filter ( isSome ) ;
83
- }
67
+ case Kind . VARIABLE :
68
+ case Kind . NULL :
69
+ return true ;
70
+ case Kind . OBJECT :
71
+ return (
72
+ a . fields . length === ( b as typeof a ) . fields . length &&
73
+ a . fields . every ( aField =>
74
+ ( b as typeof a ) . fields . find (
75
+ bField =>
76
+ aField . name . value === bField . name . value && matchValues ( aField . value , bField . value ) ,
77
+ ) ,
78
+ )
79
+ ) ;
80
+ default :
81
+ return a . value === ( b as typeof a ) . value ;
82
+ }
83
+ }
84
+ return false ;
85
+ } ;
86
+
87
+ const matchArguments = ( a : ArgumentNode , b : ArgumentNode ) : boolean =>
88
+ a . name . value === b . name . value && a . value . kind === b . value . kind && matchValues ( a . value , b . value ) ;
89
+
90
+ /**
91
+ * Check if a directive is an exact match of another directive based on their
92
+ * arguments.
93
+ */
94
+ const matchDirectives = ( a : DirectiveNode , b : DirectiveNode ) : boolean => {
95
+ const matched =
96
+ a . name . value === b . name . value &&
97
+ ( a . arguments === b . arguments ||
98
+ ( a . arguments ?. length === b . arguments ?. length &&
99
+ a . arguments ?. every ( argA => b . arguments ?. find ( argB => matchArguments ( argA , argB ) ) ) ) ) ;
100
+ return ! ! matched ;
101
+ } ;
84
102
85
103
export function mergeDirectives (
86
104
d1 : ReadonlyArray < DirectiveNode > = [ ] ,
@@ -91,21 +109,32 @@ export function mergeDirectives(
91
109
const reverseOrder : boolean | undefined = config && config . reverseDirectives ;
92
110
const asNext = reverseOrder ? d1 : d2 ;
93
111
const asFirst = reverseOrder ? d2 : d1 ;
94
- const result = deduplicateDirectives ( [ ...asNext ] , directives ) ;
95
-
96
- for ( const directive of asFirst ) {
97
- if (
98
- directiveAlreadyExists ( result , directive ) &&
99
- ! isRepeatableDirective ( directive , directives )
100
- ) {
101
- const existingDirectiveIndex = result . findIndex ( d => d . name . value === directive . name . value ) ;
102
- const existingDirective = result [ existingDirectiveIndex ] ;
103
- ( result [ existingDirectiveIndex ] as any ) . arguments = mergeArguments (
104
- directive . arguments || [ ] ,
105
- existingDirective . arguments || [ ] ,
106
- ) ;
112
+ const result : DirectiveNode [ ] = [ ] ;
113
+ for ( const directive of [ ...asNext , ...asFirst ] ) {
114
+ if ( isRepeatableDirective ( directive , directives , config ?. repeatableLinkImports ) ) {
115
+ // look for repeated, identical directives that come before this instance
116
+ // if those exist, return null so that this directive gets removed.
117
+ const exactDuplicate = result . find ( d => matchDirectives ( directive , d ) ) ;
118
+ if ( ! exactDuplicate ) {
119
+ result . push ( directive ) ;
120
+ }
107
121
} else {
108
- result . push ( directive ) ;
122
+ const firstAt = result . findIndex ( d => d . name . value === directive . name . value ) ;
123
+ if ( firstAt === - 1 ) {
124
+ // if did not find a directive with this name on the result set already
125
+ result . push ( directive ) ;
126
+ } else {
127
+ // if not repeatable and found directive with the same name already in the result set,
128
+ // then merge the arguments of the existing directive and the new directive
129
+ const mergedArguments = mergeArguments (
130
+ directive . arguments ?? [ ] ,
131
+ result [ firstAt ] . arguments ?? [ ] ,
132
+ ) ;
133
+ result [ firstAt ] = {
134
+ ...result [ firstAt ] ,
135
+ arguments : mergedArguments . length === 0 ? undefined : mergedArguments ,
136
+ } ;
137
+ }
109
138
}
110
139
}
111
140
0 commit comments