@@ -80,42 +80,58 @@ class CodePath {
80
80
}
81
81
82
82
/**
83
- * The initial code path segment.
83
+ * The initial code path segment. This is the segment that is at the head
84
+ * of the code path.
85
+ * This is a passthrough to the underlying `CodePathState`.
84
86
* @type {CodePathSegment }
85
87
*/
86
88
get initialSegment ( ) {
87
89
return this . internal . initialSegment ;
88
90
}
89
91
90
92
/**
91
- * Final code path segments.
92
- * This array is a mix of `returnedSegments` and `thrownSegments`.
93
+ * Final code path segments. These are the terminal (tail) segments in the
94
+ * code path, which is the combination of `returnedSegments` and `thrownSegments`.
95
+ * All segments in this array are reachable.
96
+ * This is a passthrough to the underlying `CodePathState`.
93
97
* @type {CodePathSegment[] }
94
98
*/
95
99
get finalSegments ( ) {
96
100
return this . internal . finalSegments ;
97
101
}
98
102
99
103
/**
100
- * Final code path segments which is with `return` statements.
101
- * This array contains the last path segment if it's reachable.
102
- * Since the reachable last path returns `undefined`.
104
+ * Final code path segments that represent normal completion of the code path.
105
+ * For functions, this means both explicit `return` statements and implicit returns,
106
+ * such as the last reachable segment in a function that does not have an
107
+ * explicit `return` as this implicitly returns `undefined`. For scripts,
108
+ * modules, class field initializers, and class static blocks, this means
109
+ * all lines of code have been executed.
110
+ * These segments are also present in `finalSegments`.
111
+ * This is a passthrough to the underlying `CodePathState`.
103
112
* @type {CodePathSegment[] }
104
113
*/
105
114
get returnedSegments ( ) {
106
115
return this . internal . returnedForkContext ;
107
116
}
108
117
109
118
/**
110
- * Final code path segments which is with `throw` statements.
119
+ * Final code path segments that represent `throw` statements.
120
+ * This is a passthrough to the underlying `CodePathState`.
121
+ * These segments are also present in `finalSegments`.
111
122
* @type {CodePathSegment[] }
112
123
*/
113
124
get thrownSegments ( ) {
114
125
return this . internal . thrownForkContext ;
115
126
}
116
127
117
128
/**
118
- * Current code path segments.
129
+ * Tracks the traversal of the code path through each segment. This array
130
+ * starts empty and segments are added or removed as the code path is
131
+ * traversed. This array always ends up empty at the end of a code path
132
+ * traversal. The `CodePathState` uses this to track its progress through
133
+ * the code path.
134
+ * This is a passthrough to the underlying `CodePathState`.
119
135
* @type {CodePathSegment[] }
120
136
* @deprecated
121
137
*/
@@ -126,79 +142,123 @@ class CodePath {
126
142
/**
127
143
* Traverses all segments in this code path.
128
144
*
129
- * codePath.traverseSegments(function (segment, controller) {
145
+ * codePath.traverseSegments((segment, controller) => {
130
146
* // do something.
131
147
* });
132
148
*
133
149
* This method enumerates segments in order from the head.
134
150
*
135
- * The `controller` object has two methods.
151
+ * The `controller` argument has two methods:
136
152
*
137
- * - `controller.skip()` - Skip the following segments in this branch.
138
- * - `controller.break()` - Skip all following segments.
139
- * @param {Object } [options] Omittable.
140
- * @param {CodePathSegment } [options.first] The first segment to traverse.
141
- * @param {CodePathSegment } [options.last] The last segment to traverse.
153
+ * - `skip()` - skips the following segments in this branch
154
+ * - `break()` - skips all following segments in the traversal
155
+ *
156
+ * A note on the parameters: the `options` argument is optional. This means
157
+ * the first argument might be an options object or the callback function.
158
+ * @param {Object } [optionsOrCallback] Optional first and last segments to traverse.
159
+ * @param {CodePathSegment } [optionsOrCallback.first] The first segment to traverse.
160
+ * @param {CodePathSegment } [optionsOrCallback.last] The last segment to traverse.
142
161
* @param {Function } callback A callback function.
143
162
* @returns {void }
144
163
*/
145
- traverseSegments ( options , callback ) {
164
+ traverseSegments ( optionsOrCallback , callback ) {
165
+
166
+ // normalize the arguments into a callback and options
146
167
let resolvedOptions ;
147
168
let resolvedCallback ;
148
169
149
- if ( typeof options === "function" ) {
150
- resolvedCallback = options ;
170
+ if ( typeof optionsOrCallback === "function" ) {
171
+ resolvedCallback = optionsOrCallback ;
151
172
resolvedOptions = { } ;
152
173
} else {
153
- resolvedOptions = options || { } ;
174
+ resolvedOptions = optionsOrCallback || { } ;
154
175
resolvedCallback = callback ;
155
176
}
156
177
178
+ // determine where to start traversing from based on the options
157
179
const startSegment = resolvedOptions . first || this . internal . initialSegment ;
158
180
const lastSegment = resolvedOptions . last ;
159
181
160
- let item = null ;
182
+ // set up initial location information
183
+ let record = null ;
161
184
let index = 0 ;
162
185
let end = 0 ;
163
186
let segment = null ;
164
- const visited = Object . create ( null ) ;
187
+
188
+ // segments that have already been visited during traversal
189
+ const visited = new Set ( ) ;
190
+
191
+ // tracks the traversal steps
165
192
const stack = [ [ startSegment , 0 ] ] ;
193
+
194
+ // tracks the last skipped segment during traversal
166
195
let skippedSegment = null ;
196
+
197
+ // indicates if we exited early from the traversal
167
198
let broken = false ;
199
+
200
+ /**
201
+ * Maintains traversal state.
202
+ */
168
203
const controller = {
204
+
205
+ /**
206
+ * Skip the following segments in this branch.
207
+ * @returns {void }
208
+ */
169
209
skip ( ) {
170
210
if ( stack . length <= 1 ) {
171
211
broken = true ;
172
212
} else {
173
213
skippedSegment = stack [ stack . length - 2 ] [ 0 ] ;
174
214
}
175
215
} ,
216
+
217
+ /**
218
+ * Stop traversal completely - do not traverse to any
219
+ * other segments.
220
+ * @returns {void }
221
+ */
176
222
break ( ) {
177
223
broken = true ;
178
224
}
179
225
} ;
180
226
181
227
/**
182
- * Checks a given previous segment has been visited.
228
+ * Checks if a given previous segment has been visited.
183
229
* @param {CodePathSegment } prevSegment A previous segment to check.
184
230
* @returns {boolean } `true` if the segment has been visited.
185
231
*/
186
232
function isVisited ( prevSegment ) {
187
233
return (
188
- visited [ prevSegment . id ] ||
234
+ visited . has ( prevSegment ) ||
189
235
segment . isLoopedPrevSegment ( prevSegment )
190
236
) ;
191
237
}
192
238
239
+ // the traversal
193
240
while ( stack . length > 0 ) {
194
- item = stack [ stack . length - 1 ] ;
195
- segment = item [ 0 ] ;
196
- index = item [ 1 ] ;
241
+
242
+ /*
243
+ * This isn't a pure stack. We use the top record all the time
244
+ * but don't always pop it off. The record is popped only if
245
+ * one of the following is true:
246
+ *
247
+ * 1) We have already visited the segment.
248
+ * 2) We have not visited *all* of the previous segments.
249
+ * 3) We have traversed past the available next segments.
250
+ *
251
+ * Otherwise, we just read the value and sometimes modify the
252
+ * record as we traverse.
253
+ */
254
+ record = stack [ stack . length - 1 ] ;
255
+ segment = record [ 0 ] ;
256
+ index = record [ 1 ] ;
197
257
198
258
if ( index === 0 ) {
199
259
200
260
// Skip if this segment has been visited already.
201
- if ( visited [ segment . id ] ) {
261
+ if ( visited . has ( segment ) ) {
202
262
stack . pop ( ) ;
203
263
continue ;
204
264
}
@@ -212,18 +272,29 @@ class CodePath {
212
272
continue ;
213
273
}
214
274
215
- // Reset the flag of skipping if all branches have been skipped.
275
+ // Reset the skipping flag if all branches have been skipped.
216
276
if ( skippedSegment && segment . prevSegments . includes ( skippedSegment ) ) {
217
277
skippedSegment = null ;
218
278
}
219
- visited [ segment . id ] = true ;
279
+ visited . add ( segment ) ;
220
280
221
- // Call the callback when the first time.
281
+ /*
282
+ * If the most recent segment hasn't been skipped, then we call
283
+ * the callback, passing in the segment and the controller.
284
+ */
222
285
if ( ! skippedSegment ) {
223
286
resolvedCallback . call ( this , segment , controller ) ;
287
+
288
+ // exit if we're at the last segment
224
289
if ( segment === lastSegment ) {
225
290
controller . skip ( ) ;
226
291
}
292
+
293
+ /*
294
+ * If the previous statement was executed, or if the callback
295
+ * called a method on the controller, we might need to exit the
296
+ * loop, so check for that and break accordingly.
297
+ */
227
298
if ( broken ) {
228
299
break ;
229
300
}
@@ -233,12 +304,35 @@ class CodePath {
233
304
// Update the stack.
234
305
end = segment . nextSegments . length - 1 ;
235
306
if ( index < end ) {
236
- item [ 1 ] += 1 ;
307
+
308
+ /*
309
+ * If we haven't yet visited all of the next segments, update
310
+ * the current top record on the stack to the next index to visit
311
+ * and then push a record for the current segment on top.
312
+ *
313
+ * Setting the current top record's index lets us know how many
314
+ * times we've been here and ensures that the segment won't be
315
+ * reprocessed (because we only process segments with an index
316
+ * of 0).
317
+ */
318
+ record [ 1 ] += 1 ;
237
319
stack . push ( [ segment . nextSegments [ index ] , 0 ] ) ;
238
320
} else if ( index === end ) {
239
- item [ 0 ] = segment . nextSegments [ index ] ;
240
- item [ 1 ] = 0 ;
321
+
322
+ /*
323
+ * If we are at the last next segment, then reset the top record
324
+ * in the stack to next segment and set its index to 0 so it will
325
+ * be processed next.
326
+ */
327
+ record [ 0 ] = segment . nextSegments [ index ] ;
328
+ record [ 1 ] = 0 ;
241
329
} else {
330
+
331
+ /*
332
+ * If index > end, that means we have no more segments that need
333
+ * processing. So, we pop that record off of the stack in order to
334
+ * continue traversing at the next level up.
335
+ */
242
336
stack . pop ( ) ;
243
337
}
244
338
}
0 commit comments