@@ -56,6 +56,27 @@ function tryWithFinally(
56
56
return result ;
57
57
}
58
58
59
+ /**
60
+ * Wrapper for bindings tracked by resolution sessions
61
+ */
62
+ export interface BindingElement {
63
+ type : 'binding' ;
64
+ value : Binding ;
65
+ }
66
+
67
+ /**
68
+ * Wrapper for injections tracked by resolution sessions
69
+ */
70
+ export interface InjectionElement {
71
+ type : 'injection' ;
72
+ value : Injection ;
73
+ }
74
+
75
+ /**
76
+ * Binding or injection elements tracked by resolution sessions
77
+ */
78
+ export type ResolutionElement = BindingElement | InjectionElement ;
79
+
59
80
/**
60
81
* Object to keep states for a session to resolve bindings and their
61
82
* dependencies within a context
@@ -65,28 +86,25 @@ export class ResolutionSession {
65
86
* A stack of bindings for the current resolution session. It's used to track
66
87
* the path of dependency resolution and detect circular dependencies.
67
88
*/
68
- readonly bindings : Binding [ ] = [ ] ;
69
-
70
- /**
71
- * A stack of injections for the current resolution session.
72
- */
73
- readonly injections : Injection [ ] = [ ] ;
89
+ readonly stack : ResolutionElement [ ] = [ ] ;
74
90
75
91
/**
76
- * Take a snapshot of the ResolutionSession so that we can pass it to
77
- * `@inject.getter` without interferring with the current session
92
+ * Fork the current session so that a new one with the same stack can be used
93
+ * in parallel or future resolutions, such as multiple method arguments,
94
+ * multiple properties, or a getter function
95
+ * @param session The current session
78
96
*/
79
- clone ( ) {
97
+ static fork ( session ?: ResolutionSession ) : ResolutionSession | undefined {
98
+ if ( session === undefined ) return undefined ;
80
99
const copy = new ResolutionSession ( ) ;
81
- copy . bindings . push ( ...this . bindings ) ;
82
- copy . injections . push ( ...this . injections ) ;
100
+ copy . stack . push ( ...session . stack ) ;
83
101
return copy ;
84
102
}
85
103
86
104
/**
87
105
* Start to resolve a binding within the session
88
- * @param binding Binding
89
- * @param session Resolution session
106
+ * @param binding The current binding
107
+ * @param session The current resolution session
90
108
*/
91
109
private static enterBinding (
92
110
binding : Binding ,
@@ -117,8 +135,8 @@ export class ResolutionSession {
117
135
118
136
/**
119
137
* Push an injection into the session
120
- * @param injection Injection
121
- * @param session Resolution session
138
+ * @param injection The current injection
139
+ * @param session The current resolution session
122
140
*/
123
141
private static enterInjection (
124
142
injection : Injection ,
@@ -152,7 +170,7 @@ export class ResolutionSession {
152
170
153
171
/**
154
172
* Describe the injection for debugging purpose
155
- * @param injection
173
+ * @param injection Injection object
156
174
*/
157
175
static describeInjection ( injection ?: Injection ) {
158
176
/* istanbul ignore if */
@@ -171,7 +189,7 @@ export class ResolutionSession {
171
189
172
190
/**
173
191
* Push the injection onto the session
174
- * @param injection Injection
192
+ * @param injection Injection The current injection
175
193
*/
176
194
pushInjection ( injection : Injection ) {
177
195
/* istanbul ignore if */
@@ -181,41 +199,60 @@ export class ResolutionSession {
181
199
ResolutionSession . describeInjection ( injection ) ,
182
200
) ;
183
201
}
184
- this . injections . push ( injection ) ;
202
+ this . stack . push ( { type : ' injection' , value : injection } ) ;
185
203
/* istanbul ignore if */
186
204
if ( debugSession . enabled ) {
187
- debugSession ( 'Injection path:' , this . getInjectionPath ( ) ) ;
205
+ debugSession ( 'Resolution path:' , this . getResolutionPath ( ) ) ;
188
206
}
189
207
}
190
208
191
209
/**
192
210
* Pop the last injection
193
211
*/
194
212
popInjection ( ) {
195
- const injection = this . injections . pop ( ) ;
213
+ const top = this . stack . pop ( ) ;
214
+ if ( top === undefined || top . type !== 'injection' ) {
215
+ throw new Error ( 'The top element must be an injection' ) ;
216
+ }
217
+
218
+ const injection = top . value ;
196
219
/* istanbul ignore if */
197
220
if ( debugSession . enabled ) {
198
221
debugSession (
199
222
'Exit injection:' ,
200
223
ResolutionSession . describeInjection ( injection ) ,
201
224
) ;
202
- debugSession ( 'Injection path:' , this . getInjectionPath ( ) || '<empty>' ) ;
225
+ debugSession ( 'Resolution path:' , this . getResolutionPath ( ) || '<empty>' ) ;
203
226
}
204
227
return injection ;
205
228
}
206
229
207
230
/**
208
231
* Getter for the current injection
209
232
*/
210
- get currentInjection ( ) {
211
- return this . injections [ this . injections . length - 1 ] ;
233
+ get currentInjection ( ) : Injection | undefined {
234
+ for ( let i = this . stack . length - 1 ; i >= 0 ; i -- ) {
235
+ const element = this . stack [ i ] ;
236
+ switch ( element . type ) {
237
+ case 'injection' :
238
+ return element . value ;
239
+ }
240
+ }
241
+ return undefined ;
212
242
}
213
243
214
244
/**
215
245
* Getter for the current binding
216
246
*/
217
- get currentBinding ( ) {
218
- return this . bindings [ this . bindings . length - 1 ] ;
247
+ get currentBinding ( ) : Binding | undefined {
248
+ for ( let i = this . stack . length - 1 ; i >= 0 ; i -- ) {
249
+ const element = this . stack [ i ] ;
250
+ switch ( element . type ) {
251
+ case 'binding' :
252
+ return element . value ;
253
+ }
254
+ }
255
+ return undefined ;
219
256
}
220
257
221
258
/**
@@ -227,29 +264,33 @@ export class ResolutionSession {
227
264
if ( debugSession . enabled ) {
228
265
debugSession ( 'Enter binding:' , binding . toJSON ( ) ) ;
229
266
}
230
- if ( this . bindings . indexOf ( binding ) !== - 1 ) {
267
+ if ( this . stack . find ( i => i . type === ' binding' && i . value === binding ) ) {
231
268
throw new Error (
232
269
`Circular dependency detected on path '${ this . getBindingPath ( ) } --> ${
233
270
binding . key
234
271
} '`,
235
272
) ;
236
273
}
237
- this . bindings . push ( binding ) ;
274
+ this . stack . push ( { type : ' binding' , value : binding } ) ;
238
275
/* istanbul ignore if */
239
276
if ( debugSession . enabled ) {
240
- debugSession ( 'Binding path:' , this . getBindingPath ( ) ) ;
277
+ debugSession ( 'Resolution path:' , this . getResolutionPath ( ) ) ;
241
278
}
242
279
}
243
280
244
281
/**
245
282
* Exit the resolution of a binding
246
283
*/
247
284
popBinding ( ) {
248
- const binding = this . bindings . pop ( ) ;
285
+ const top = this . stack . pop ( ) ;
286
+ if ( top === undefined || top . type !== 'binding' ) {
287
+ throw new Error ( 'The top element must be a binding' ) ;
288
+ }
289
+ const binding = top . value ;
249
290
/* istanbul ignore if */
250
291
if ( debugSession . enabled ) {
251
292
debugSession ( 'Exit binding:' , binding && binding . toJSON ( ) ) ;
252
- debugSession ( 'Binding path:' , this . getBindingPath ( ) || '<empty>' ) ;
293
+ debugSession ( 'Resolution path:' , this . getResolutionPath ( ) || '<empty>' ) ;
253
294
}
254
295
return binding ;
255
296
}
@@ -258,15 +299,40 @@ export class ResolutionSession {
258
299
* Get the binding path as `bindingA --> bindingB --> bindingC`.
259
300
*/
260
301
getBindingPath ( ) {
261
- return this . bindings . map ( b => b . key ) . join ( ' --> ' ) ;
302
+ return this . stack
303
+ . filter ( i => i . type === 'binding' )
304
+ . map ( b => ( < Binding > b . value ) . key )
305
+ . join ( ' --> ' ) ;
262
306
}
263
307
264
308
/**
265
- * Get the injection path as `injectionA-> injectionB-> injectionC`.
309
+ * Get the injection path as `injectionA --> injectionB --> injectionC`.
266
310
*/
267
311
getInjectionPath ( ) {
268
- return this . injections
269
- . map ( i => ResolutionSession . describeInjection ( i ) ! . targetName )
312
+ return this . stack
313
+ . filter ( i => i . type === 'injection' )
314
+ . map (
315
+ i =>
316
+ ResolutionSession . describeInjection ( < Injection > i . value ) ! . targetName ,
317
+ )
270
318
. join ( ' --> ' ) ;
271
319
}
320
+
321
+ private static describe ( e : ResolutionElement ) {
322
+ switch ( e . type ) {
323
+ case 'injection' :
324
+ return '@' + ResolutionSession . describeInjection ( e . value ) ! . targetName ;
325
+ case 'binding' :
326
+ return e . value . key ;
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Get the resolution path including bindings and injections, for example:
332
+ * `bindingA --> @ClassA[0] --> bindingB --> @ClassB.prototype.prop1
333
+ * --> bindingC`.
334
+ */
335
+ getResolutionPath ( ) {
336
+ return this . stack . map ( i => ResolutionSession . describe ( i ) ) . join ( ' --> ' ) ;
337
+ }
272
338
}
0 commit comments