@@ -80,42 +80,58 @@ class CodePath {
8080 }
8181
8282 /**
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`.
8486 * @type {CodePathSegment }
8587 */
8688 get initialSegment ( ) {
8789 return this . internal . initialSegment ;
8890 }
8991
9092 /**
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`.
9397 * @type {CodePathSegment[] }
9498 */
9599 get finalSegments ( ) {
96100 return this . internal . finalSegments ;
97101 }
98102
99103 /**
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`.
103112 * @type {CodePathSegment[] }
104113 */
105114 get returnedSegments ( ) {
106115 return this . internal . returnedForkContext ;
107116 }
108117
109118 /**
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`.
111122 * @type {CodePathSegment[] }
112123 */
113124 get thrownSegments ( ) {
114125 return this . internal . thrownForkContext ;
115126 }
116127
117128 /**
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`.
119135 * @type {CodePathSegment[] }
120136 * @deprecated
121137 */
@@ -126,79 +142,123 @@ class CodePath {
126142 /**
127143 * Traverses all segments in this code path.
128144 *
129- * codePath.traverseSegments(function (segment, controller) {
145+ * codePath.traverseSegments((segment, controller) => {
130146 * // do something.
131147 * });
132148 *
133149 * This method enumerates segments in order from the head.
134150 *
135- * The `controller` object has two methods.
151+ * The `controller` argument has two methods:
136152 *
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.
142161 * @param {Function } callback A callback function.
143162 * @returns {void }
144163 */
145- traverseSegments ( options , callback ) {
164+ traverseSegments ( optionsOrCallback , callback ) {
165+
166+ // normalize the arguments into a callback and options
146167 let resolvedOptions ;
147168 let resolvedCallback ;
148169
149- if ( typeof options === "function" ) {
150- resolvedCallback = options ;
170+ if ( typeof optionsOrCallback === "function" ) {
171+ resolvedCallback = optionsOrCallback ;
151172 resolvedOptions = { } ;
152173 } else {
153- resolvedOptions = options || { } ;
174+ resolvedOptions = optionsOrCallback || { } ;
154175 resolvedCallback = callback ;
155176 }
156177
178+ // determine where to start traversing from based on the options
157179 const startSegment = resolvedOptions . first || this . internal . initialSegment ;
158180 const lastSegment = resolvedOptions . last ;
159181
160- let item = null ;
182+ // set up initial location information
183+ let record = null ;
161184 let index = 0 ;
162185 let end = 0 ;
163186 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
165192 const stack = [ [ startSegment , 0 ] ] ;
193+
194+ // tracks the last skipped segment during traversal
166195 let skippedSegment = null ;
196+
197+ // indicates if we exited early from the traversal
167198 let broken = false ;
199+
200+ /**
201+ * Maintains traversal state.
202+ */
168203 const controller = {
204+
205+ /**
206+ * Skip the following segments in this branch.
207+ * @returns {void }
208+ */
169209 skip ( ) {
170210 if ( stack . length <= 1 ) {
171211 broken = true ;
172212 } else {
173213 skippedSegment = stack [ stack . length - 2 ] [ 0 ] ;
174214 }
175215 } ,
216+
217+ /**
218+ * Stop traversal completely - do not traverse to any
219+ * other segments.
220+ * @returns {void }
221+ */
176222 break ( ) {
177223 broken = true ;
178224 }
179225 } ;
180226
181227 /**
182- * Checks a given previous segment has been visited.
228+ * Checks if a given previous segment has been visited.
183229 * @param {CodePathSegment } prevSegment A previous segment to check.
184230 * @returns {boolean } `true` if the segment has been visited.
185231 */
186232 function isVisited ( prevSegment ) {
187233 return (
188- visited [ prevSegment . id ] ||
234+ visited . has ( prevSegment ) ||
189235 segment . isLoopedPrevSegment ( prevSegment )
190236 ) ;
191237 }
192238
239+ // the traversal
193240 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 ] ;
197257
198258 if ( index === 0 ) {
199259
200260 // Skip if this segment has been visited already.
201- if ( visited [ segment . id ] ) {
261+ if ( visited . has ( segment ) ) {
202262 stack . pop ( ) ;
203263 continue ;
204264 }
@@ -212,18 +272,29 @@ class CodePath {
212272 continue ;
213273 }
214274
215- // Reset the flag of skipping if all branches have been skipped.
275+ // Reset the skipping flag if all branches have been skipped.
216276 if ( skippedSegment && segment . prevSegments . includes ( skippedSegment ) ) {
217277 skippedSegment = null ;
218278 }
219- visited [ segment . id ] = true ;
279+ visited . add ( segment ) ;
220280
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+ */
222285 if ( ! skippedSegment ) {
223286 resolvedCallback . call ( this , segment , controller ) ;
287+
288+ // exit if we're at the last segment
224289 if ( segment === lastSegment ) {
225290 controller . skip ( ) ;
226291 }
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+ */
227298 if ( broken ) {
228299 break ;
229300 }
@@ -233,12 +304,35 @@ class CodePath {
233304 // Update the stack.
234305 end = segment . nextSegments . length - 1 ;
235306 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 ;
237319 stack . push ( [ segment . nextSegments [ index ] , 0 ] ) ;
238320 } 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 ;
241329 } 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+ */
242336 stack . pop ( ) ;
243337 }
244338 }
0 commit comments