1
1
import { EventEmitter } from '@stoplight/lifecycle' ;
2
+ import type { Dictionary } from '@stoplight/types' ;
2
3
3
4
import { mergeAllOf } from '../mergers/mergeAllOf' ;
4
5
import { mergeOneOrAnyOf } from '../mergers/mergeOneOrAnyOf' ;
@@ -8,81 +9,25 @@ import type { RootNode } from '../nodes/RootNode';
8
9
import { SchemaCombinerName , SchemaNode , SchemaNodeKind } from '../nodes/types' ;
9
10
import type { SchemaFragment } from '../types' ;
10
11
import { isObjectLiteral } from '../utils/guards' ;
11
- import type { WalkingOptions } from './types' ;
12
-
13
- function * processFragment (
14
- fragment : SchemaFragment ,
15
- path : string [ ] ,
16
- walkingOptions : WalkingOptions ,
17
- processedFragments : WeakMap < SchemaFragment , SchemaNode > ,
18
- ) : IterableIterator < SchemaNode > {
19
- const processedFragment = processedFragments . get ( fragment ) ;
20
- if ( processedFragment !== void 0 ) {
21
- return yield new MirrorNode ( processedFragment ) ;
22
- }
23
-
24
- if ( '$ref' in fragment ) {
25
- if ( walkingOptions . resolveRef !== null && typeof fragment . $ref === 'string' ) {
26
- try {
27
- const seenRefs : string [ ] = [ ] ;
28
- while ( typeof fragment . $ref === 'string' ) {
29
- if ( seenRefs . includes ( fragment . $ref ) ) {
30
- return yield new ReferenceNode ( fragment , null ) ;
31
- }
32
-
33
- seenRefs . push ( fragment . $ref ) ;
34
- fragment = walkingOptions . resolveRef ( path , fragment . $ref ) ;
35
- }
36
- } catch ( ex ) {
37
- return yield new ReferenceNode ( fragment , ex . message ) ;
38
- }
39
- } else {
40
- return yield new ReferenceNode ( fragment , null ) ;
41
- }
42
- }
43
-
44
- if ( walkingOptions . mergeAllOf && SchemaCombinerName . AllOf in fragment ) {
45
- try {
46
- fragment = mergeAllOf ( fragment , path , walkingOptions ) ;
47
- } catch {
48
- //
49
- }
50
- }
51
-
52
- if ( SchemaCombinerName . OneOf in fragment || SchemaCombinerName . AnyOf in fragment ) {
53
- try {
54
- for ( const item of mergeOneOrAnyOf ( fragment , path , walkingOptions ) ) {
55
- yield new RegularNode ( item ) ;
56
- }
57
-
58
- return ;
59
- } catch {
60
- //
61
- }
62
- }
63
-
64
- yield new RegularNode ( fragment ) ;
65
- }
66
-
67
- type WalkerItem = {
68
- node : SchemaNode ;
69
- parentNode : SchemaNode | null ;
70
- } ;
71
-
72
- type WalkerSnapshot = {
73
- readonly fragment : SchemaFragment ;
74
- readonly depth : number ;
75
- readonly path : string [ ] ;
76
- } ;
77
-
78
- export class Walker extends EventEmitter < any > {
12
+ import type {
13
+ WalkerEvent ,
14
+ WalkerEventHandler ,
15
+ WalkerHookAction ,
16
+ WalkerHookHandler ,
17
+ WalkerItem ,
18
+ WalkerSnapshot ,
19
+ WalkingOptions ,
20
+ } from './types' ;
21
+
22
+ export class Walker extends EventEmitter < Dictionary < WalkerEventHandler , WalkerEvent > > {
79
23
public readonly path : string [ ] ;
80
24
public depth : number ;
81
25
82
26
protected fragment : SchemaFragment ;
83
27
protected schemaNode : RegularNode | RootNode ;
84
28
85
29
private readonly processedFragments : WeakMap < SchemaFragment , SchemaNode > ;
30
+ private readonly hooks : Partial < Dictionary < WalkerHookHandler , WalkerHookAction > > ;
86
31
87
32
constructor ( protected readonly root : RootNode , protected readonly walkingOptions : WalkingOptions ) {
88
33
super ( ) ;
@@ -92,9 +37,9 @@ export class Walker extends EventEmitter<any> {
92
37
this . fragment = root . fragment ;
93
38
this . schemaNode = root ;
94
39
this . processedFragments = new WeakMap < SchemaFragment , SchemaNode > ( ) ;
95
- }
96
40
97
- public stepIn : boolean = true ;
41
+ this . hooks = { } ;
42
+ }
98
43
99
44
public * resume ( snapshot : WalkerSnapshot ) {
100
45
this . path . splice ( 0 , this . path . length , ...snapshot . path ) ;
@@ -112,23 +57,32 @@ export class Walker extends EventEmitter<any> {
112
57
} ;
113
58
}
114
59
60
+ public hookInto ( action : WalkerHookAction , handler : WalkerHookHandler ) {
61
+ this . hooks [ action ] = handler ;
62
+ }
63
+
115
64
public * walk ( ) : IterableIterator < WalkerItem > {
116
65
const {
117
66
depth : initialDepth ,
118
67
schemaNode : initialSchemaNode ,
119
68
path : { length } ,
120
69
} = this ;
121
70
122
- for ( const schemaNode of processFragment ( this . fragment , this . path , this . walkingOptions , this . processedFragments ) ) {
71
+ for ( const schemaNode of this . processFragment ( ) ) {
72
+ super . emit ( 'newNode' , schemaNode ) ;
73
+
123
74
this . processedFragments . set ( schemaNode . fragment , schemaNode ) ;
124
75
125
76
this . fragment = schemaNode . fragment ;
126
77
this . depth = initialDepth + 1 ;
127
78
128
- super . emit ( 'enter' , schemaNode ) ;
79
+ const shouldSkipNode = this . hooks . filter ?.( schemaNode ) ;
80
+
81
+ if ( shouldSkipNode === true ) {
82
+ continue ;
83
+ }
129
84
130
85
schemaNode . parent = initialSchemaNode ;
131
- // @ts -ignore
132
86
schemaNode . subpath = this . path . slice ( initialSchemaNode . path . length ) ;
133
87
134
88
if ( 'children' in initialSchemaNode ) {
@@ -143,11 +97,12 @@ export class Walker extends EventEmitter<any> {
143
97
144
98
this . schemaNode = schemaNode ;
145
99
146
- if ( this . stepIn ) {
100
+ if ( this . hooks . stepIn ?.( schemaNode ) === false ) {
101
+ super . emit ( 'enterNode' , schemaNode ) ;
147
102
yield * this . walkNodeChildren ( ) ;
148
103
}
149
104
150
- super . emit ( 'exit' ) ;
105
+ super . emit ( 'exitNode' , schemaNode ) ;
151
106
}
152
107
153
108
this . path . length = length ;
@@ -166,7 +121,6 @@ export class Walker extends EventEmitter<any> {
166
121
path : { length } ,
167
122
} = this ;
168
123
169
- // todo: combiner
170
124
if ( schemaNode . combiners !== null ) {
171
125
for ( const combiner of schemaNode . combiners ) {
172
126
const items = fragment [ combiner ] ;
@@ -177,6 +131,7 @@ export class Walker extends EventEmitter<any> {
177
131
i ++ ;
178
132
if ( ! isObjectLiteral ( item ) ) continue ;
179
133
this . fragment = item ;
134
+ // todo: spaghetti
180
135
this . schemaNode = initialSchemaNode ;
181
136
this . depth = initialDepth ;
182
137
this . path . length = length ;
@@ -242,4 +197,48 @@ export class Walker extends EventEmitter<any> {
242
197
243
198
this . schemaNode = schemaNode ;
244
199
}
200
+
201
+ protected * processFragment ( ) : IterableIterator < SchemaNode > {
202
+ const { walkingOptions, path, processedFragments } = this ;
203
+ let { fragment } = this ;
204
+
205
+ const processedFragment = processedFragments . get ( fragment ) ;
206
+ if ( processedFragment !== void 0 ) {
207
+ return yield new MirrorNode ( processedFragment ) ;
208
+ }
209
+
210
+ if ( '$ref' in fragment ) {
211
+ if ( walkingOptions . resolveRef !== null && typeof fragment . $ref === 'string' ) {
212
+ try {
213
+ fragment = walkingOptions . resolveRef ( path , fragment . $ref ) ;
214
+ } catch ( ex ) {
215
+ return yield new ReferenceNode ( fragment , ex ?. message ?? 'Unknown resolving error' ) ;
216
+ }
217
+ } else {
218
+ return yield new ReferenceNode ( fragment , null ) ;
219
+ }
220
+ }
221
+
222
+ if ( walkingOptions . mergeAllOf && SchemaCombinerName . AllOf in fragment ) {
223
+ try {
224
+ fragment = mergeAllOf ( fragment , path , walkingOptions ) ;
225
+ } catch {
226
+ // no the end of the world - we will render raw unprocessed fragment
227
+ }
228
+ }
229
+
230
+ if ( SchemaCombinerName . OneOf in fragment || SchemaCombinerName . AnyOf in fragment ) {
231
+ try {
232
+ for ( const item of mergeOneOrAnyOf ( fragment , path , walkingOptions ) ) {
233
+ yield new RegularNode ( item ) ;
234
+ }
235
+
236
+ return ;
237
+ } catch {
238
+ // no the end of the world - we will render raw unprocessed fragment
239
+ }
240
+ }
241
+
242
+ yield new RegularNode ( fragment ) ;
243
+ }
245
244
}
0 commit comments