source: imaps-frontend/node_modules/eslint/lib/linter/code-path-analysis/code-path-state.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 80.1 KB
RevLine 
[d565449]1/**
2 * @fileoverview A class to manage state of generating a code path.
3 * @author Toru Nagashima
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const CodePathSegment = require("./code-path-segment"),
13 ForkContext = require("./fork-context");
14
15//-----------------------------------------------------------------------------
16// Contexts
17//-----------------------------------------------------------------------------
18
19/**
20 * Represents the context in which a `break` statement can be used.
21 *
22 * A `break` statement without a label is only valid in a few places in
23 * JavaScript: any type of loop or a `switch` statement. Otherwise, `break`
24 * without a label causes a syntax error. For these contexts, `breakable` is
25 * set to `true` to indicate that a `break` without a label is valid.
26 *
27 * However, a `break` statement with a label is also valid inside of a labeled
28 * statement. For example, this is valid:
29 *
30 * a : {
31 * break a;
32 * }
33 *
34 * The `breakable` property is set false for labeled statements to indicate
35 * that `break` without a label is invalid.
36 */
37class BreakContext {
38
39 /**
40 * Creates a new instance.
41 * @param {BreakContext} upperContext The previous `BreakContext`.
42 * @param {boolean} breakable Indicates if we are inside a statement where
43 * `break` without a label will exit the statement.
44 * @param {string|null} label The label for the statement.
45 * @param {ForkContext} forkContext The current fork context.
46 */
47 constructor(upperContext, breakable, label, forkContext) {
48
49 /**
50 * The previous `BreakContext`
51 * @type {BreakContext}
52 */
53 this.upper = upperContext;
54
55 /**
56 * Indicates if we are inside a statement where `break` without a label
57 * will exit the statement.
58 * @type {boolean}
59 */
60 this.breakable = breakable;
61
62 /**
63 * The label associated with the statement.
64 * @type {string|null}
65 */
66 this.label = label;
67
68 /**
69 * The fork context for the `break`.
70 * @type {ForkContext}
71 */
72 this.brokenForkContext = ForkContext.newEmpty(forkContext);
73 }
74}
75
76/**
77 * Represents the context for `ChainExpression` nodes.
78 */
79class ChainContext {
80
81 /**
82 * Creates a new instance.
83 * @param {ChainContext} upperContext The previous `ChainContext`.
84 */
85 constructor(upperContext) {
86
87 /**
88 * The previous `ChainContext`
89 * @type {ChainContext}
90 */
91 this.upper = upperContext;
92
93 /**
94 * The number of choice contexts inside of the `ChainContext`.
95 * @type {number}
96 */
97 this.choiceContextCount = 0;
98
99 }
100}
101
102/**
103 * Represents a choice in the code path.
104 *
105 * Choices are created by logical operators such as `&&`, loops, conditionals,
106 * and `if` statements. This is the point at which the code path has a choice of
107 * which direction to go.
108 *
109 * The result of a choice might be in the left (test) expression of another choice,
110 * and in that case, may create a new fork. For example, `a || b` is a choice
111 * but does not create a new fork because the result of the expression is
112 * not used as the test expression in another expression. In this case,
113 * `isForkingAsResult` is false. In the expression `a || b || c`, the `a || b`
114 * expression appears as the test expression for `|| c`, so the
115 * result of `a || b` creates a fork because execution may or may not
116 * continue to `|| c`. `isForkingAsResult` for `a || b` in this case is true
117 * while `isForkingAsResult` for `|| c` is false. (`isForkingAsResult` is always
118 * false for `if` statements, conditional expressions, and loops.)
119 *
120 * All of the choices except one (`??`) operate on a true/false fork, meaning if
121 * true go one way and if false go the other (tracked by `trueForkContext` and
122 * `falseForkContext`). The `??` operator doesn't operate on true/false because
123 * the left expression is evaluated to be nullish or not, so only if nullish do
124 * we fork to the right expression (tracked by `nullishForkContext`).
125 */
126class ChoiceContext {
127
128 /**
129 * Creates a new instance.
130 * @param {ChoiceContext} upperContext The previous `ChoiceContext`.
131 * @param {string} kind The kind of choice. If it's a logical or assignment expression, this
132 * is `"&&"` or `"||"` or `"??"`; if it's an `if` statement or
133 * conditional expression, this is `"test"`; otherwise, this is `"loop"`.
134 * @param {boolean} isForkingAsResult Indicates if the result of the choice
135 * creates a fork.
136 * @param {ForkContext} forkContext The containing `ForkContext`.
137 */
138 constructor(upperContext, kind, isForkingAsResult, forkContext) {
139
140 /**
141 * The previous `ChoiceContext`
142 * @type {ChoiceContext}
143 */
144 this.upper = upperContext;
145
146 /**
147 * The kind of choice. If it's a logical or assignment expression, this
148 * is `"&&"` or `"||"` or `"??"`; if it's an `if` statement or
149 * conditional expression, this is `"test"`; otherwise, this is `"loop"`.
150 * @type {string}
151 */
152 this.kind = kind;
153
154 /**
155 * Indicates if the result of the choice forks the code path.
156 * @type {boolean}
157 */
158 this.isForkingAsResult = isForkingAsResult;
159
160 /**
161 * The fork context for the `true` path of the choice.
162 * @type {ForkContext}
163 */
164 this.trueForkContext = ForkContext.newEmpty(forkContext);
165
166 /**
167 * The fork context for the `false` path of the choice.
168 * @type {ForkContext}
169 */
170 this.falseForkContext = ForkContext.newEmpty(forkContext);
171
172 /**
173 * The fork context for when the choice result is `null` or `undefined`.
174 * @type {ForkContext}
175 */
176 this.nullishForkContext = ForkContext.newEmpty(forkContext);
177
178 /**
179 * Indicates if any of `trueForkContext`, `falseForkContext`, or
180 * `nullishForkContext` have been updated with segments from a child context.
181 * @type {boolean}
182 */
183 this.processed = false;
184 }
185
186}
187
188/**
189 * Base class for all loop contexts.
190 */
191class LoopContextBase {
192
193 /**
194 * Creates a new instance.
195 * @param {LoopContext|null} upperContext The previous `LoopContext`.
196 * @param {string} type The AST node's `type` for the loop.
197 * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
198 * @param {BreakContext} breakContext The context for breaking the loop.
199 */
200 constructor(upperContext, type, label, breakContext) {
201
202 /**
203 * The previous `LoopContext`.
204 * @type {LoopContext}
205 */
206 this.upper = upperContext;
207
208 /**
209 * The AST node's `type` for the loop.
210 * @type {string}
211 */
212 this.type = type;
213
214 /**
215 * The label for the loop from an enclosing `LabeledStatement`.
216 * @type {string|null}
217 */
218 this.label = label;
219
220 /**
221 * The fork context for when `break` is encountered.
222 * @type {ForkContext}
223 */
224 this.brokenForkContext = breakContext.brokenForkContext;
225 }
226}
227
228/**
229 * Represents the context for a `while` loop.
230 */
231class WhileLoopContext extends LoopContextBase {
232
233 /**
234 * Creates a new instance.
235 * @param {LoopContext|null} upperContext The previous `LoopContext`.
236 * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
237 * @param {BreakContext} breakContext The context for breaking the loop.
238 */
239 constructor(upperContext, label, breakContext) {
240 super(upperContext, "WhileStatement", label, breakContext);
241
242 /**
243 * The hardcoded literal boolean test condition for
244 * the loop. Used to catch infinite or skipped loops.
245 * @type {boolean|undefined}
246 */
247 this.test = void 0;
248
249 /**
250 * The segments representing the test condition where `continue` will
251 * jump to. The test condition will typically have just one segment but
252 * it's possible for there to be more than one.
253 * @type {Array<CodePathSegment>|null}
254 */
255 this.continueDestSegments = null;
256 }
257}
258
259/**
260 * Represents the context for a `do-while` loop.
261 */
262class DoWhileLoopContext extends LoopContextBase {
263
264 /**
265 * Creates a new instance.
266 * @param {LoopContext|null} upperContext The previous `LoopContext`.
267 * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
268 * @param {BreakContext} breakContext The context for breaking the loop.
269 * @param {ForkContext} forkContext The enclosing fork context.
270 */
271 constructor(upperContext, label, breakContext, forkContext) {
272 super(upperContext, "DoWhileStatement", label, breakContext);
273
274 /**
275 * The hardcoded literal boolean test condition for
276 * the loop. Used to catch infinite or skipped loops.
277 * @type {boolean|undefined}
278 */
279 this.test = void 0;
280
281 /**
282 * The segments at the start of the loop body. This is the only loop
283 * where the test comes at the end, so the first iteration always
284 * happens and we need a reference to the first statements.
285 * @type {Array<CodePathSegment>|null}
286 */
287 this.entrySegments = null;
288
289 /**
290 * The fork context to follow when a `continue` is found.
291 * @type {ForkContext}
292 */
293 this.continueForkContext = ForkContext.newEmpty(forkContext);
294 }
295}
296
297/**
298 * Represents the context for a `for` loop.
299 */
300class ForLoopContext extends LoopContextBase {
301
302 /**
303 * Creates a new instance.
304 * @param {LoopContext|null} upperContext The previous `LoopContext`.
305 * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
306 * @param {BreakContext} breakContext The context for breaking the loop.
307 */
308 constructor(upperContext, label, breakContext) {
309 super(upperContext, "ForStatement", label, breakContext);
310
311 /**
312 * The hardcoded literal boolean test condition for
313 * the loop. Used to catch infinite or skipped loops.
314 * @type {boolean|undefined}
315 */
316 this.test = void 0;
317
318 /**
319 * The end of the init expression. This may change during the lifetime
320 * of the instance as we traverse the loop because some loops don't have
321 * an init expression.
322 * @type {Array<CodePathSegment>|null}
323 */
324 this.endOfInitSegments = null;
325
326 /**
327 * The start of the test expression. This may change during the lifetime
328 * of the instance as we traverse the loop because some loops don't have
329 * a test expression.
330 * @type {Array<CodePathSegment>|null}
331 */
332 this.testSegments = null;
333
334 /**
335 * The end of the test expression. This may change during the lifetime
336 * of the instance as we traverse the loop because some loops don't have
337 * a test expression.
338 * @type {Array<CodePathSegment>|null}
339 */
340 this.endOfTestSegments = null;
341
342 /**
343 * The start of the update expression. This may change during the lifetime
344 * of the instance as we traverse the loop because some loops don't have
345 * an update expression.
346 * @type {Array<CodePathSegment>|null}
347 */
348 this.updateSegments = null;
349
350 /**
351 * The end of the update expresion. This may change during the lifetime
352 * of the instance as we traverse the loop because some loops don't have
353 * an update expression.
354 * @type {Array<CodePathSegment>|null}
355 */
356 this.endOfUpdateSegments = null;
357
358 /**
359 * The segments representing the test condition where `continue` will
360 * jump to. The test condition will typically have just one segment but
361 * it's possible for there to be more than one. This may change during the
362 * lifetime of the instance as we traverse the loop because some loops
363 * don't have an update expression. When there is an update expression, this
364 * will end up pointing to that expression; otherwise it will end up pointing
365 * to the test expression.
366 * @type {Array<CodePathSegment>|null}
367 */
368 this.continueDestSegments = null;
369 }
370}
371
372/**
373 * Represents the context for a `for-in` loop.
374 *
375 * Terminology:
376 * - "left" means the part of the loop to the left of the `in` keyword. For
377 * example, in `for (var x in y)`, the left is `var x`.
378 * - "right" means the part of the loop to the right of the `in` keyword. For
379 * example, in `for (var x in y)`, the right is `y`.
380 */
381class ForInLoopContext extends LoopContextBase {
382
383 /**
384 * Creates a new instance.
385 * @param {LoopContext|null} upperContext The previous `LoopContext`.
386 * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
387 * @param {BreakContext} breakContext The context for breaking the loop.
388 */
389 constructor(upperContext, label, breakContext) {
390 super(upperContext, "ForInStatement", label, breakContext);
391
392 /**
393 * The segments that came immediately before the start of the loop.
394 * This allows you to traverse backwards out of the loop into the
395 * surrounding code. This is necessary to evaluate the right expression
396 * correctly, as it must be evaluated in the same way as the left
397 * expression, but the pointer to these segments would otherwise be
398 * lost if not stored on the instance. Once the right expression has
399 * been evaluated, this property is no longer used.
400 * @type {Array<CodePathSegment>|null}
401 */
402 this.prevSegments = null;
403
404 /**
405 * Segments representing the start of everything to the left of the
406 * `in` keyword. This can be used to move forward towards
407 * `endOfLeftSegments`. `leftSegments` and `endOfLeftSegments` are
408 * effectively the head and tail of a doubly-linked list.
409 * @type {Array<CodePathSegment>|null}
410 */
411 this.leftSegments = null;
412
413 /**
414 * Segments representing the end of everything to the left of the
415 * `in` keyword. This can be used to move backward towards `leftSegments`.
416 * `leftSegments` and `endOfLeftSegments` are effectively the head
417 * and tail of a doubly-linked list.
418 * @type {Array<CodePathSegment>|null}
419 */
420 this.endOfLeftSegments = null;
421
422 /**
423 * The segments representing the left expression where `continue` will
424 * jump to. In `for-in` loops, `continue` must always re-execute the
425 * left expression each time through the loop. This contains the same
426 * segments as `leftSegments`, but is duplicated here so each loop
427 * context has the same property pointing to where `continue` should
428 * end up.
429 * @type {Array<CodePathSegment>|null}
430 */
431 this.continueDestSegments = null;
432 }
433}
434
435/**
436 * Represents the context for a `for-of` loop.
437 */
438class ForOfLoopContext extends LoopContextBase {
439
440 /**
441 * Creates a new instance.
442 * @param {LoopContext|null} upperContext The previous `LoopContext`.
443 * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`.
444 * @param {BreakContext} breakContext The context for breaking the loop.
445 */
446 constructor(upperContext, label, breakContext) {
447 super(upperContext, "ForOfStatement", label, breakContext);
448
449 /**
450 * The segments that came immediately before the start of the loop.
451 * This allows you to traverse backwards out of the loop into the
452 * surrounding code. This is necessary to evaluate the right expression
453 * correctly, as it must be evaluated in the same way as the left
454 * expression, but the pointer to these segments would otherwise be
455 * lost if not stored on the instance. Once the right expression has
456 * been evaluated, this property is no longer used.
457 * @type {Array<CodePathSegment>|null}
458 */
459 this.prevSegments = null;
460
461 /**
462 * Segments representing the start of everything to the left of the
463 * `of` keyword. This can be used to move forward towards
464 * `endOfLeftSegments`. `leftSegments` and `endOfLeftSegments` are
465 * effectively the head and tail of a doubly-linked list.
466 * @type {Array<CodePathSegment>|null}
467 */
468 this.leftSegments = null;
469
470 /**
471 * Segments representing the end of everything to the left of the
472 * `of` keyword. This can be used to move backward towards `leftSegments`.
473 * `leftSegments` and `endOfLeftSegments` are effectively the head
474 * and tail of a doubly-linked list.
475 * @type {Array<CodePathSegment>|null}
476 */
477 this.endOfLeftSegments = null;
478
479 /**
480 * The segments representing the left expression where `continue` will
481 * jump to. In `for-in` loops, `continue` must always re-execute the
482 * left expression each time through the loop. This contains the same
483 * segments as `leftSegments`, but is duplicated here so each loop
484 * context has the same property pointing to where `continue` should
485 * end up.
486 * @type {Array<CodePathSegment>|null}
487 */
488 this.continueDestSegments = null;
489 }
490}
491
492/**
493 * Represents the context for any loop.
494 * @typedef {WhileLoopContext|DoWhileLoopContext|ForLoopContext|ForInLoopContext|ForOfLoopContext} LoopContext
495 */
496
497/**
498 * Represents the context for a `switch` statement.
499 */
500class SwitchContext {
501
502 /**
503 * Creates a new instance.
504 * @param {SwitchContext} upperContext The previous context.
505 * @param {boolean} hasCase Indicates if there is at least one `case` statement.
506 * `default` doesn't count.
507 */
508 constructor(upperContext, hasCase) {
509
510 /**
511 * The previous context.
512 * @type {SwitchContext}
513 */
514 this.upper = upperContext;
515
516 /**
517 * Indicates if there is at least one `case` statement. `default` doesn't count.
518 * @type {boolean}
519 */
520 this.hasCase = hasCase;
521
522 /**
523 * The `default` keyword.
524 * @type {Array<CodePathSegment>|null}
525 */
526 this.defaultSegments = null;
527
528 /**
529 * The default case body starting segments.
530 * @type {Array<CodePathSegment>|null}
531 */
532 this.defaultBodySegments = null;
533
534 /**
535 * Indicates if a `default` case and is empty exists.
536 * @type {boolean}
537 */
538 this.foundEmptyDefault = false;
539
540 /**
541 * Indicates that a `default` exists and is the last case.
542 * @type {boolean}
543 */
544 this.lastIsDefault = false;
545
546 /**
547 * The number of fork contexts created. This is equivalent to the
548 * number of `case` statements plus a `default` statement (if present).
549 * @type {number}
550 */
551 this.forkCount = 0;
552 }
553}
554
555/**
556 * Represents the context for a `try` statement.
557 */
558class TryContext {
559
560 /**
561 * Creates a new instance.
562 * @param {TryContext} upperContext The previous context.
563 * @param {boolean} hasFinalizer Indicates if the `try` statement has a
564 * `finally` block.
565 * @param {ForkContext} forkContext The enclosing fork context.
566 */
567 constructor(upperContext, hasFinalizer, forkContext) {
568
569 /**
570 * The previous context.
571 * @type {TryContext}
572 */
573 this.upper = upperContext;
574
575 /**
576 * Indicates if the `try` statement has a `finally` block.
577 * @type {boolean}
578 */
579 this.hasFinalizer = hasFinalizer;
580
581 /**
582 * Tracks the traversal position inside of the `try` statement. This is
583 * used to help determine the context necessary to create paths because
584 * a `try` statement may or may not have `catch` or `finally` blocks,
585 * and code paths behave differently in those blocks.
586 * @type {"try"|"catch"|"finally"}
587 */
588 this.position = "try";
589
590 /**
591 * If the `try` statement has a `finally` block, this affects how a
592 * `return` statement behaves in the `try` block. Without `finally`,
593 * `return` behaves as usual and doesn't require a fork; with `finally`,
594 * `return` forks into the `finally` block, so we need a fork context
595 * to track it.
596 * @type {ForkContext|null}
597 */
598 this.returnedForkContext = hasFinalizer
599 ? ForkContext.newEmpty(forkContext)
600 : null;
601
602 /**
603 * When a `throw` occurs inside of a `try` block, the code path forks
604 * into the `catch` or `finally` blocks, and this fork context tracks
605 * that path.
606 * @type {ForkContext}
607 */
608 this.thrownForkContext = ForkContext.newEmpty(forkContext);
609
610 /**
611 * Indicates if the last segment in the `try` block is reachable.
612 * @type {boolean}
613 */
614 this.lastOfTryIsReachable = false;
615
616 /**
617 * Indicates if the last segment in the `catch` block is reachable.
618 * @type {boolean}
619 */
620 this.lastOfCatchIsReachable = false;
621 }
622}
623
624//------------------------------------------------------------------------------
625// Helpers
626//------------------------------------------------------------------------------
627
628/**
629 * Adds given segments into the `dest` array.
630 * If the `others` array does not include the given segments, adds to the `all`
631 * array as well.
632 *
633 * This adds only reachable and used segments.
634 * @param {CodePathSegment[]} dest A destination array (`returnedSegments` or `thrownSegments`).
635 * @param {CodePathSegment[]} others Another destination array (`returnedSegments` or `thrownSegments`).
636 * @param {CodePathSegment[]} all The unified destination array (`finalSegments`).
637 * @param {CodePathSegment[]} segments Segments to add.
638 * @returns {void}
639 */
640function addToReturnedOrThrown(dest, others, all, segments) {
641 for (let i = 0; i < segments.length; ++i) {
642 const segment = segments[i];
643
644 dest.push(segment);
645 if (!others.includes(segment)) {
646 all.push(segment);
647 }
648 }
649}
650
651/**
652 * Gets a loop context for a `continue` statement based on a given label.
653 * @param {CodePathState} state The state to search within.
654 * @param {string|null} label The label of a `continue` statement.
655 * @returns {LoopContext} A loop-context for a `continue` statement.
656 */
657function getContinueContext(state, label) {
658 if (!label) {
659 return state.loopContext;
660 }
661
662 let context = state.loopContext;
663
664 while (context) {
665 if (context.label === label) {
666 return context;
667 }
668 context = context.upper;
669 }
670
671 /* c8 ignore next */
672 return null;
673}
674
675/**
676 * Gets a context for a `break` statement.
677 * @param {CodePathState} state The state to search within.
678 * @param {string|null} label The label of a `break` statement.
679 * @returns {BreakContext} A context for a `break` statement.
680 */
681function getBreakContext(state, label) {
682 let context = state.breakContext;
683
684 while (context) {
685 if (label ? context.label === label : context.breakable) {
686 return context;
687 }
688 context = context.upper;
689 }
690
691 /* c8 ignore next */
692 return null;
693}
694
695/**
696 * Gets a context for a `return` statement. There is just one special case:
697 * if there is a `try` statement with a `finally` block, because that alters
698 * how `return` behaves; otherwise, this just passes through the given state.
699 * @param {CodePathState} state The state to search within
700 * @returns {TryContext|CodePathState} A context for a `return` statement.
701 */
702function getReturnContext(state) {
703 let context = state.tryContext;
704
705 while (context) {
706 if (context.hasFinalizer && context.position !== "finally") {
707 return context;
708 }
709 context = context.upper;
710 }
711
712 return state;
713}
714
715/**
716 * Gets a context for a `throw` statement. There is just one special case:
717 * if there is a `try` statement with a `finally` block and we are inside of
718 * a `catch` because that changes how `throw` behaves; otherwise, this just
719 * passes through the given state.
720 * @param {CodePathState} state The state to search within.
721 * @returns {TryContext|CodePathState} A context for a `throw` statement.
722 */
723function getThrowContext(state) {
724 let context = state.tryContext;
725
726 while (context) {
727 if (context.position === "try" ||
728 (context.hasFinalizer && context.position === "catch")
729 ) {
730 return context;
731 }
732 context = context.upper;
733 }
734
735 return state;
736}
737
738/**
739 * Removes a given value from a given array.
740 * @param {any[]} elements An array to remove the specific element.
741 * @param {any} value The value to be removed.
742 * @returns {void}
743 */
744function removeFromArray(elements, value) {
745 elements.splice(elements.indexOf(value), 1);
746}
747
748/**
749 * Disconnect given segments.
750 *
751 * This is used in a process for switch statements.
752 * If there is the "default" chunk before other cases, the order is different
753 * between node's and running's.
754 * @param {CodePathSegment[]} prevSegments Forward segments to disconnect.
755 * @param {CodePathSegment[]} nextSegments Backward segments to disconnect.
756 * @returns {void}
757 */
758function disconnectSegments(prevSegments, nextSegments) {
759 for (let i = 0; i < prevSegments.length; ++i) {
760 const prevSegment = prevSegments[i];
761 const nextSegment = nextSegments[i];
762
763 removeFromArray(prevSegment.nextSegments, nextSegment);
764 removeFromArray(prevSegment.allNextSegments, nextSegment);
765 removeFromArray(nextSegment.prevSegments, prevSegment);
766 removeFromArray(nextSegment.allPrevSegments, prevSegment);
767 }
768}
769
770/**
771 * Creates looping path between two arrays of segments, ensuring that there are
772 * paths going between matching segments in the arrays.
773 * @param {CodePathState} state The state to operate on.
774 * @param {CodePathSegment[]} unflattenedFromSegments Segments which are source.
775 * @param {CodePathSegment[]} unflattenedToSegments Segments which are destination.
776 * @returns {void}
777 */
778function makeLooped(state, unflattenedFromSegments, unflattenedToSegments) {
779
780 const fromSegments = CodePathSegment.flattenUnusedSegments(unflattenedFromSegments);
781 const toSegments = CodePathSegment.flattenUnusedSegments(unflattenedToSegments);
782 const end = Math.min(fromSegments.length, toSegments.length);
783
784 /*
785 * This loop effectively updates a doubly-linked list between two collections
786 * of segments making sure that segments in the same array indices are
787 * combined to create a path.
788 */
789 for (let i = 0; i < end; ++i) {
790
791 // get the segments in matching array indices
792 const fromSegment = fromSegments[i];
793 const toSegment = toSegments[i];
794
795 /*
796 * If the destination segment is reachable, then create a path from the
797 * source segment to the destination segment.
798 */
799 if (toSegment.reachable) {
800 fromSegment.nextSegments.push(toSegment);
801 }
802
803 /*
804 * If the source segment is reachable, then create a path from the
805 * destination segment back to the source segment.
806 */
807 if (fromSegment.reachable) {
808 toSegment.prevSegments.push(fromSegment);
809 }
810
811 /*
812 * Also update the arrays that don't care if the segments are reachable
813 * or not. This should always happen regardless of anything else.
814 */
815 fromSegment.allNextSegments.push(toSegment);
816 toSegment.allPrevSegments.push(fromSegment);
817
818 /*
819 * If the destination segment has at least two previous segments in its
820 * path then that means there was one previous segment before this iteration
821 * of the loop was executed. So, we need to mark the source segment as
822 * looped.
823 */
824 if (toSegment.allPrevSegments.length >= 2) {
825 CodePathSegment.markPrevSegmentAsLooped(toSegment, fromSegment);
826 }
827
828 // let the code path analyzer know that there's been a loop created
829 state.notifyLooped(fromSegment, toSegment);
830 }
831}
832
833/**
834 * Finalizes segments of `test` chunk of a ForStatement.
835 *
836 * - Adds `false` paths to paths which are leaving from the loop.
837 * - Sets `true` paths to paths which go to the body.
838 * @param {LoopContext} context A loop context to modify.
839 * @param {ChoiceContext} choiceContext A choice context of this loop.
840 * @param {CodePathSegment[]} head The current head paths.
841 * @returns {void}
842 */
843function finalizeTestSegmentsOfFor(context, choiceContext, head) {
844
845 /*
846 * If this choice context doesn't already contain paths from a
847 * child context, then add the current head to each potential path.
848 */
849 if (!choiceContext.processed) {
850 choiceContext.trueForkContext.add(head);
851 choiceContext.falseForkContext.add(head);
852 choiceContext.nullishForkContext.add(head);
853 }
854
855 /*
856 * If the test condition isn't a hardcoded truthy value, then `break`
857 * must follow the same path as if the test condition is false. To represent
858 * that, we append the path for when the loop test is false (represented by
859 * `falseForkContext`) to the `brokenForkContext`.
860 */
861 if (context.test !== true) {
862 context.brokenForkContext.addAll(choiceContext.falseForkContext);
863 }
864
865 context.endOfTestSegments = choiceContext.trueForkContext.makeNext(0, -1);
866}
867
868//------------------------------------------------------------------------------
869// Public Interface
870//------------------------------------------------------------------------------
871
872/**
873 * A class which manages state to analyze code paths.
874 */
875class CodePathState {
876
877 /**
878 * Creates a new instance.
879 * @param {IdGenerator} idGenerator An id generator to generate id for code
880 * path segments.
881 * @param {Function} onLooped A callback function to notify looping.
882 */
883 constructor(idGenerator, onLooped) {
884
885 /**
886 * The ID generator to use when creating new segments.
887 * @type {IdGenerator}
888 */
889 this.idGenerator = idGenerator;
890
891 /**
892 * A callback function to call when there is a loop.
893 * @type {Function}
894 */
895 this.notifyLooped = onLooped;
896
897 /**
898 * The root fork context for this state.
899 * @type {ForkContext}
900 */
901 this.forkContext = ForkContext.newRoot(idGenerator);
902
903 /**
904 * Context for logical expressions, conditional expressions, `if` statements,
905 * and loops.
906 * @type {ChoiceContext}
907 */
908 this.choiceContext = null;
909
910 /**
911 * Context for `switch` statements.
912 * @type {SwitchContext}
913 */
914 this.switchContext = null;
915
916 /**
917 * Context for `try` statements.
918 * @type {TryContext}
919 */
920 this.tryContext = null;
921
922 /**
923 * Context for loop statements.
924 * @type {LoopContext}
925 */
926 this.loopContext = null;
927
928 /**
929 * Context for `break` statements.
930 * @type {BreakContext}
931 */
932 this.breakContext = null;
933
934 /**
935 * Context for `ChainExpression` nodes.
936 * @type {ChainContext}
937 */
938 this.chainContext = null;
939
940 /**
941 * An array that tracks the current segments in the state. The array
942 * starts empty and segments are added with each `onCodePathSegmentStart`
943 * event and removed with each `onCodePathSegmentEnd` event. Effectively,
944 * this is tracking the code path segment traversal as the state is
945 * modified.
946 * @type {Array<CodePathSegment>}
947 */
948 this.currentSegments = [];
949
950 /**
951 * Tracks the starting segment for this path. This value never changes.
952 * @type {CodePathSegment}
953 */
954 this.initialSegment = this.forkContext.head[0];
955
956 /**
957 * The final segments of the code path which are either `return` or `throw`.
958 * This is a union of the segments in `returnedForkContext` and `thrownForkContext`.
959 * @type {Array<CodePathSegment>}
960 */
961 this.finalSegments = [];
962
963 /**
964 * The final segments of the code path which are `return`. These
965 * segments are also contained in `finalSegments`.
966 * @type {Array<CodePathSegment>}
967 */
968 this.returnedForkContext = [];
969
970 /**
971 * The final segments of the code path which are `throw`. These
972 * segments are also contained in `finalSegments`.
973 * @type {Array<CodePathSegment>}
974 */
975 this.thrownForkContext = [];
976
977 /*
978 * We add an `add` method so that these look more like fork contexts and
979 * can be used interchangeably when a fork context is needed to add more
980 * segments to a path.
981 *
982 * Ultimately, we want anything added to `returned` or `thrown` to also
983 * be added to `final`. We only add reachable and used segments to these
984 * arrays.
985 */
986 const final = this.finalSegments;
987 const returned = this.returnedForkContext;
988 const thrown = this.thrownForkContext;
989
990 returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final);
991 thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final);
992 }
993
994 /**
995 * A passthrough property exposing the current pointer as part of the API.
996 * @type {CodePathSegment[]}
997 */
998 get headSegments() {
999 return this.forkContext.head;
1000 }
1001
1002 /**
1003 * The parent forking context.
1004 * This is used for the root of new forks.
1005 * @type {ForkContext}
1006 */
1007 get parentForkContext() {
1008 const current = this.forkContext;
1009
1010 return current && current.upper;
1011 }
1012
1013 /**
1014 * Creates and stacks new forking context.
1015 * @param {boolean} forkLeavingPath A flag which shows being in a
1016 * "finally" block.
1017 * @returns {ForkContext} The created context.
1018 */
1019 pushForkContext(forkLeavingPath) {
1020 this.forkContext = ForkContext.newEmpty(
1021 this.forkContext,
1022 forkLeavingPath
1023 );
1024
1025 return this.forkContext;
1026 }
1027
1028 /**
1029 * Pops and merges the last forking context.
1030 * @returns {ForkContext} The last context.
1031 */
1032 popForkContext() {
1033 const lastContext = this.forkContext;
1034
1035 this.forkContext = lastContext.upper;
1036 this.forkContext.replaceHead(lastContext.makeNext(0, -1));
1037
1038 return lastContext;
1039 }
1040
1041 /**
1042 * Creates a new path.
1043 * @returns {void}
1044 */
1045 forkPath() {
1046 this.forkContext.add(this.parentForkContext.makeNext(-1, -1));
1047 }
1048
1049 /**
1050 * Creates a bypass path.
1051 * This is used for such as IfStatement which does not have "else" chunk.
1052 * @returns {void}
1053 */
1054 forkBypassPath() {
1055 this.forkContext.add(this.parentForkContext.head);
1056 }
1057
1058 //--------------------------------------------------------------------------
1059 // ConditionalExpression, LogicalExpression, IfStatement
1060 //--------------------------------------------------------------------------
1061
1062 /**
1063 * Creates a context for ConditionalExpression, LogicalExpression, AssignmentExpression (logical assignments only),
1064 * IfStatement, WhileStatement, DoWhileStatement, or ForStatement.
1065 *
1066 * LogicalExpressions have cases that it goes different paths between the
1067 * `true` case and the `false` case.
1068 *
1069 * For Example:
1070 *
1071 * if (a || b) {
1072 * foo();
1073 * } else {
1074 * bar();
1075 * }
1076 *
1077 * In this case, `b` is evaluated always in the code path of the `else`
1078 * block, but it's not so in the code path of the `if` block.
1079 * So there are 3 paths.
1080 *
1081 * a -> foo();
1082 * a -> b -> foo();
1083 * a -> b -> bar();
1084 * @param {string} kind A kind string.
1085 * If the new context is LogicalExpression's or AssignmentExpression's, this is `"&&"` or `"||"` or `"??"`.
1086 * If it's IfStatement's or ConditionalExpression's, this is `"test"`.
1087 * Otherwise, this is `"loop"`.
1088 * @param {boolean} isForkingAsResult Indicates if the result of the choice
1089 * creates a fork.
1090 * @returns {void}
1091 */
1092 pushChoiceContext(kind, isForkingAsResult) {
1093 this.choiceContext = new ChoiceContext(this.choiceContext, kind, isForkingAsResult, this.forkContext);
1094 }
1095
1096 /**
1097 * Pops the last choice context and finalizes it.
1098 * This is called upon leaving a node that represents a choice.
1099 * @throws {Error} (Unreachable.)
1100 * @returns {ChoiceContext} The popped context.
1101 */
1102 popChoiceContext() {
1103 const poppedChoiceContext = this.choiceContext;
1104 const forkContext = this.forkContext;
1105 const head = forkContext.head;
1106
1107 this.choiceContext = poppedChoiceContext.upper;
1108
1109 switch (poppedChoiceContext.kind) {
1110 case "&&":
1111 case "||":
1112 case "??":
1113
1114 /*
1115 * The `head` are the path of the right-hand operand.
1116 * If we haven't previously added segments from child contexts,
1117 * then we add these segments to all possible forks.
1118 */
1119 if (!poppedChoiceContext.processed) {
1120 poppedChoiceContext.trueForkContext.add(head);
1121 poppedChoiceContext.falseForkContext.add(head);
1122 poppedChoiceContext.nullishForkContext.add(head);
1123 }
1124
1125 /*
1126 * If this context is the left (test) expression for another choice
1127 * context, such as `a || b` in the expression `a || b || c`,
1128 * then we take the segments for this context and move them up
1129 * to the parent context.
1130 */
1131 if (poppedChoiceContext.isForkingAsResult) {
1132 const parentContext = this.choiceContext;
1133
1134 parentContext.trueForkContext.addAll(poppedChoiceContext.trueForkContext);
1135 parentContext.falseForkContext.addAll(poppedChoiceContext.falseForkContext);
1136 parentContext.nullishForkContext.addAll(poppedChoiceContext.nullishForkContext);
1137 parentContext.processed = true;
1138
1139 // Exit early so we don't collapse all paths into one.
1140 return poppedChoiceContext;
1141 }
1142
1143 break;
1144
1145 case "test":
1146 if (!poppedChoiceContext.processed) {
1147
1148 /*
1149 * The head segments are the path of the `if` block here.
1150 * Updates the `true` path with the end of the `if` block.
1151 */
1152 poppedChoiceContext.trueForkContext.clear();
1153 poppedChoiceContext.trueForkContext.add(head);
1154 } else {
1155
1156 /*
1157 * The head segments are the path of the `else` block here.
1158 * Updates the `false` path with the end of the `else`
1159 * block.
1160 */
1161 poppedChoiceContext.falseForkContext.clear();
1162 poppedChoiceContext.falseForkContext.add(head);
1163 }
1164
1165 break;
1166
1167 case "loop":
1168
1169 /*
1170 * Loops are addressed in `popLoopContext()` so just return
1171 * the context without modification.
1172 */
1173 return poppedChoiceContext;
1174
1175 /* c8 ignore next */
1176 default:
1177 throw new Error("unreachable");
1178 }
1179
1180 /*
1181 * Merge the true path with the false path to create a single path.
1182 */
1183 const combinedForkContext = poppedChoiceContext.trueForkContext;
1184
1185 combinedForkContext.addAll(poppedChoiceContext.falseForkContext);
1186 forkContext.replaceHead(combinedForkContext.makeNext(0, -1));
1187
1188 return poppedChoiceContext;
1189 }
1190
1191 /**
1192 * Creates a code path segment to represent right-hand operand of a logical
1193 * expression.
1194 * This is called in the preprocessing phase when entering a node.
1195 * @throws {Error} (Unreachable.)
1196 * @returns {void}
1197 */
1198 makeLogicalRight() {
1199 const currentChoiceContext = this.choiceContext;
1200 const forkContext = this.forkContext;
1201
1202 if (currentChoiceContext.processed) {
1203
1204 /*
1205 * This context was already assigned segments from a child
1206 * choice context. In this case, we are concerned only about
1207 * the path that does not short-circuit and so ends up on the
1208 * right-hand operand of the logical expression.
1209 */
1210 let prevForkContext;
1211
1212 switch (currentChoiceContext.kind) {
1213 case "&&": // if true then go to the right-hand side.
1214 prevForkContext = currentChoiceContext.trueForkContext;
1215 break;
1216 case "||": // if false then go to the right-hand side.
1217 prevForkContext = currentChoiceContext.falseForkContext;
1218 break;
1219 case "??": // Both true/false can short-circuit, so needs the third path to go to the right-hand side. That's nullishForkContext.
1220 prevForkContext = currentChoiceContext.nullishForkContext;
1221 break;
1222 default:
1223 throw new Error("unreachable");
1224 }
1225
1226 /*
1227 * Create the segment for the right-hand operand of the logical expression
1228 * and adjust the fork context pointer to point there. The right-hand segment
1229 * is added at the end of all segments in `prevForkContext`.
1230 */
1231 forkContext.replaceHead(prevForkContext.makeNext(0, -1));
1232
1233 /*
1234 * We no longer need this list of segments.
1235 *
1236 * Reset `processed` because we've removed the segments from the child
1237 * choice context. This allows `popChoiceContext()` to continue adding
1238 * segments later.
1239 */
1240 prevForkContext.clear();
1241 currentChoiceContext.processed = false;
1242
1243 } else {
1244
1245 /*
1246 * This choice context was not assigned segments from a child
1247 * choice context, which means that it's a terminal logical
1248 * expression.
1249 *
1250 * `head` is the segments for the left-hand operand of the
1251 * logical expression.
1252 *
1253 * Each of the fork contexts below are empty at this point. We choose
1254 * the path(s) that will short-circuit and add the segment for the
1255 * left-hand operand to it. Ultimately, this will be the only segment
1256 * in that path due to the short-circuting, so we are just seeding
1257 * these paths to start.
1258 */
1259 switch (currentChoiceContext.kind) {
1260 case "&&":
1261
1262 /*
1263 * In most contexts, when a && expression evaluates to false,
1264 * it short circuits, so we need to account for that by setting
1265 * the `falseForkContext` to the left operand.
1266 *
1267 * When a && expression is the left-hand operand for a ??
1268 * expression, such as `(a && b) ?? c`, a nullish value will
1269 * also short-circuit in a different way than a false value,
1270 * so we also set the `nullishForkContext` to the left operand.
1271 * This path is only used with a ?? expression and is thrown
1272 * away for any other type of logical expression, so it's safe
1273 * to always add.
1274 */
1275 currentChoiceContext.falseForkContext.add(forkContext.head);
1276 currentChoiceContext.nullishForkContext.add(forkContext.head);
1277 break;
1278 case "||": // the true path can short-circuit.
1279 currentChoiceContext.trueForkContext.add(forkContext.head);
1280 break;
1281 case "??": // both can short-circuit.
1282 currentChoiceContext.trueForkContext.add(forkContext.head);
1283 currentChoiceContext.falseForkContext.add(forkContext.head);
1284 break;
1285 default:
1286 throw new Error("unreachable");
1287 }
1288
1289 /*
1290 * Create the segment for the right-hand operand of the logical expression
1291 * and adjust the fork context pointer to point there.
1292 */
1293 forkContext.replaceHead(forkContext.makeNext(-1, -1));
1294 }
1295 }
1296
1297 /**
1298 * Makes a code path segment of the `if` block.
1299 * @returns {void}
1300 */
1301 makeIfConsequent() {
1302 const context = this.choiceContext;
1303 const forkContext = this.forkContext;
1304
1305 /*
1306 * If any result were not transferred from child contexts,
1307 * this sets the head segments to both cases.
1308 * The head segments are the path of the test expression.
1309 */
1310 if (!context.processed) {
1311 context.trueForkContext.add(forkContext.head);
1312 context.falseForkContext.add(forkContext.head);
1313 context.nullishForkContext.add(forkContext.head);
1314 }
1315
1316 context.processed = false;
1317
1318 // Creates new path from the `true` case.
1319 forkContext.replaceHead(
1320 context.trueForkContext.makeNext(0, -1)
1321 );
1322 }
1323
1324 /**
1325 * Makes a code path segment of the `else` block.
1326 * @returns {void}
1327 */
1328 makeIfAlternate() {
1329 const context = this.choiceContext;
1330 const forkContext = this.forkContext;
1331
1332 /*
1333 * The head segments are the path of the `if` block.
1334 * Updates the `true` path with the end of the `if` block.
1335 */
1336 context.trueForkContext.clear();
1337 context.trueForkContext.add(forkContext.head);
1338 context.processed = true;
1339
1340 // Creates new path from the `false` case.
1341 forkContext.replaceHead(
1342 context.falseForkContext.makeNext(0, -1)
1343 );
1344 }
1345
1346 //--------------------------------------------------------------------------
1347 // ChainExpression
1348 //--------------------------------------------------------------------------
1349
1350 /**
1351 * Pushes a new `ChainExpression` context to the stack. This method is
1352 * called when entering a `ChainExpression` node. A chain context is used to
1353 * count forking in the optional chain then merge them on the exiting from the
1354 * `ChainExpression` node.
1355 * @returns {void}
1356 */
1357 pushChainContext() {
1358 this.chainContext = new ChainContext(this.chainContext);
1359 }
1360
1361 /**
1362 * Pop a `ChainExpression` context from the stack. This method is called on
1363 * exiting from each `ChainExpression` node. This merges all forks of the
1364 * last optional chaining.
1365 * @returns {void}
1366 */
1367 popChainContext() {
1368 const context = this.chainContext;
1369
1370 this.chainContext = context.upper;
1371
1372 // pop all choice contexts of this.
1373 for (let i = context.choiceContextCount; i > 0; --i) {
1374 this.popChoiceContext();
1375 }
1376 }
1377
1378 /**
1379 * Create a choice context for optional access.
1380 * This method is called on entering to each `(Call|Member)Expression[optional=true]` node.
1381 * This creates a choice context as similar to `LogicalExpression[operator="??"]` node.
1382 * @returns {void}
1383 */
1384 makeOptionalNode() {
1385 if (this.chainContext) {
1386 this.chainContext.choiceContextCount += 1;
1387 this.pushChoiceContext("??", false);
1388 }
1389 }
1390
1391 /**
1392 * Create a fork.
1393 * This method is called on entering to the `arguments|property` property of each `(Call|Member)Expression` node.
1394 * @returns {void}
1395 */
1396 makeOptionalRight() {
1397 if (this.chainContext) {
1398 this.makeLogicalRight();
1399 }
1400 }
1401
1402 //--------------------------------------------------------------------------
1403 // SwitchStatement
1404 //--------------------------------------------------------------------------
1405
1406 /**
1407 * Creates a context object of SwitchStatement and stacks it.
1408 * @param {boolean} hasCase `true` if the switch statement has one or more
1409 * case parts.
1410 * @param {string|null} label The label text.
1411 * @returns {void}
1412 */
1413 pushSwitchContext(hasCase, label) {
1414 this.switchContext = new SwitchContext(this.switchContext, hasCase);
1415 this.pushBreakContext(true, label);
1416 }
1417
1418 /**
1419 * Pops the last context of SwitchStatement and finalizes it.
1420 *
1421 * - Disposes all forking stack for `case` and `default`.
1422 * - Creates the next code path segment from `context.brokenForkContext`.
1423 * - If the last `SwitchCase` node is not a `default` part, creates a path
1424 * to the `default` body.
1425 * @returns {void}
1426 */
1427 popSwitchContext() {
1428 const context = this.switchContext;
1429
1430 this.switchContext = context.upper;
1431
1432 const forkContext = this.forkContext;
1433 const brokenForkContext = this.popBreakContext().brokenForkContext;
1434
1435 if (context.forkCount === 0) {
1436
1437 /*
1438 * When there is only one `default` chunk and there is one or more
1439 * `break` statements, even if forks are nothing, it needs to merge
1440 * those.
1441 */
1442 if (!brokenForkContext.empty) {
1443 brokenForkContext.add(forkContext.makeNext(-1, -1));
1444 forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
1445 }
1446
1447 return;
1448 }
1449
1450 const lastSegments = forkContext.head;
1451
1452 this.forkBypassPath();
1453 const lastCaseSegments = forkContext.head;
1454
1455 /*
1456 * `brokenForkContext` is used to make the next segment.
1457 * It must add the last segment into `brokenForkContext`.
1458 */
1459 brokenForkContext.add(lastSegments);
1460
1461 /*
1462 * Any value that doesn't match a `case` test should flow to the default
1463 * case. That happens normally when the default case is last in the `switch`,
1464 * but if it's not, we need to rewire some of the paths to be correct.
1465 */
1466 if (!context.lastIsDefault) {
1467 if (context.defaultBodySegments) {
1468
1469 /*
1470 * There is a non-empty default case, so remove the path from the `default`
1471 * label to its body for an accurate representation.
1472 */
1473 disconnectSegments(context.defaultSegments, context.defaultBodySegments);
1474
1475 /*
1476 * Connect the path from the last non-default case to the body of the
1477 * default case.
1478 */
1479 makeLooped(this, lastCaseSegments, context.defaultBodySegments);
1480
1481 } else {
1482
1483 /*
1484 * There is no default case, so we treat this as if the last case
1485 * had a `break` in it.
1486 */
1487 brokenForkContext.add(lastCaseSegments);
1488 }
1489 }
1490
1491 // Traverse up to the original fork context for the `switch` statement
1492 for (let i = 0; i < context.forkCount; ++i) {
1493 this.forkContext = this.forkContext.upper;
1494 }
1495
1496 /*
1497 * Creates a path from all `brokenForkContext` paths.
1498 * This is a path after `switch` statement.
1499 */
1500 this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
1501 }
1502
1503 /**
1504 * Makes a code path segment for a `SwitchCase` node.
1505 * @param {boolean} isCaseBodyEmpty `true` if the body is empty.
1506 * @param {boolean} isDefaultCase `true` if the body is the default case.
1507 * @returns {void}
1508 */
1509 makeSwitchCaseBody(isCaseBodyEmpty, isDefaultCase) {
1510 const context = this.switchContext;
1511
1512 if (!context.hasCase) {
1513 return;
1514 }
1515
1516 /*
1517 * Merge forks.
1518 * The parent fork context has two segments.
1519 * Those are from the current `case` and the body of the previous case.
1520 */
1521 const parentForkContext = this.forkContext;
1522 const forkContext = this.pushForkContext();
1523
1524 forkContext.add(parentForkContext.makeNext(0, -1));
1525
1526 /*
1527 * Add information about the default case.
1528 *
1529 * The purpose of this is to identify the starting segments for the
1530 * default case to make sure there is a path there.
1531 */
1532 if (isDefaultCase) {
1533
1534 /*
1535 * This is the default case in the `switch`.
1536 *
1537 * We first save the current pointer as `defaultSegments` to point
1538 * to the `default` keyword.
1539 */
1540 context.defaultSegments = parentForkContext.head;
1541
1542 /*
1543 * If the body of the case is empty then we just set
1544 * `foundEmptyDefault` to true; otherwise, we save a reference
1545 * to the current pointer as `defaultBodySegments`.
1546 */
1547 if (isCaseBodyEmpty) {
1548 context.foundEmptyDefault = true;
1549 } else {
1550 context.defaultBodySegments = forkContext.head;
1551 }
1552
1553 } else {
1554
1555 /*
1556 * This is not the default case in the `switch`.
1557 *
1558 * If it's not empty and there is already an empty default case found,
1559 * that means the default case actually comes before this case,
1560 * and that it will fall through to this case. So, we can now
1561 * ignore the previous default case (reset `foundEmptyDefault` to false)
1562 * and set `defaultBodySegments` to the current segments because this is
1563 * effectively the new default case.
1564 */
1565 if (!isCaseBodyEmpty && context.foundEmptyDefault) {
1566 context.foundEmptyDefault = false;
1567 context.defaultBodySegments = forkContext.head;
1568 }
1569 }
1570
1571 // keep track if the default case ends up last
1572 context.lastIsDefault = isDefaultCase;
1573 context.forkCount += 1;
1574 }
1575
1576 //--------------------------------------------------------------------------
1577 // TryStatement
1578 //--------------------------------------------------------------------------
1579
1580 /**
1581 * Creates a context object of TryStatement and stacks it.
1582 * @param {boolean} hasFinalizer `true` if the try statement has a
1583 * `finally` block.
1584 * @returns {void}
1585 */
1586 pushTryContext(hasFinalizer) {
1587 this.tryContext = new TryContext(this.tryContext, hasFinalizer, this.forkContext);
1588 }
1589
1590 /**
1591 * Pops the last context of TryStatement and finalizes it.
1592 * @returns {void}
1593 */
1594 popTryContext() {
1595 const context = this.tryContext;
1596
1597 this.tryContext = context.upper;
1598
1599 /*
1600 * If we're inside the `catch` block, that means there is no `finally`,
1601 * so we can process the `try` and `catch` blocks the simple way and
1602 * merge their two paths.
1603 */
1604 if (context.position === "catch") {
1605 this.popForkContext();
1606 return;
1607 }
1608
1609 /*
1610 * The following process is executed only when there is a `finally`
1611 * block.
1612 */
1613
1614 const originalReturnedForkContext = context.returnedForkContext;
1615 const originalThrownForkContext = context.thrownForkContext;
1616
1617 // no `return` or `throw` in `try` or `catch` so there's nothing left to do
1618 if (originalReturnedForkContext.empty && originalThrownForkContext.empty) {
1619 return;
1620 }
1621
1622 /*
1623 * The following process is executed only when there is a `finally`
1624 * block and there was a `return` or `throw` in the `try` or `catch`
1625 * blocks.
1626 */
1627
1628 // Separate head to normal paths and leaving paths.
1629 const headSegments = this.forkContext.head;
1630
1631 this.forkContext = this.forkContext.upper;
1632 const normalSegments = headSegments.slice(0, headSegments.length / 2 | 0);
1633 const leavingSegments = headSegments.slice(headSegments.length / 2 | 0);
1634
1635 // Forwards the leaving path to upper contexts.
1636 if (!originalReturnedForkContext.empty) {
1637 getReturnContext(this).returnedForkContext.add(leavingSegments);
1638 }
1639 if (!originalThrownForkContext.empty) {
1640 getThrowContext(this).thrownForkContext.add(leavingSegments);
1641 }
1642
1643 // Sets the normal path as the next.
1644 this.forkContext.replaceHead(normalSegments);
1645
1646 /*
1647 * If both paths of the `try` block and the `catch` block are
1648 * unreachable, the next path becomes unreachable as well.
1649 */
1650 if (!context.lastOfTryIsReachable && !context.lastOfCatchIsReachable) {
1651 this.forkContext.makeUnreachable();
1652 }
1653 }
1654
1655 /**
1656 * Makes a code path segment for a `catch` block.
1657 * @returns {void}
1658 */
1659 makeCatchBlock() {
1660 const context = this.tryContext;
1661 const forkContext = this.forkContext;
1662 const originalThrownForkContext = context.thrownForkContext;
1663
1664 /*
1665 * We are now in a catch block so we need to update the context
1666 * with that information. This includes creating a new fork
1667 * context in case we encounter any `throw` statements here.
1668 */
1669 context.position = "catch";
1670 context.thrownForkContext = ForkContext.newEmpty(forkContext);
1671 context.lastOfTryIsReachable = forkContext.reachable;
1672
1673 // Merge the thrown paths from the `try` and `catch` blocks
1674 originalThrownForkContext.add(forkContext.head);
1675 const thrownSegments = originalThrownForkContext.makeNext(0, -1);
1676
1677 // Fork to a bypass and the merged thrown path.
1678 this.pushForkContext();
1679 this.forkBypassPath();
1680 this.forkContext.add(thrownSegments);
1681 }
1682
1683 /**
1684 * Makes a code path segment for a `finally` block.
1685 *
1686 * In the `finally` block, parallel paths are created. The parallel paths
1687 * are used as leaving-paths. The leaving-paths are paths from `return`
1688 * statements and `throw` statements in a `try` block or a `catch` block.
1689 * @returns {void}
1690 */
1691 makeFinallyBlock() {
1692 const context = this.tryContext;
1693 let forkContext = this.forkContext;
1694 const originalReturnedForkContext = context.returnedForkContext;
1695 const originalThrownForContext = context.thrownForkContext;
1696 const headOfLeavingSegments = forkContext.head;
1697
1698 // Update state.
1699 if (context.position === "catch") {
1700
1701 // Merges two paths from the `try` block and `catch` block.
1702 this.popForkContext();
1703 forkContext = this.forkContext;
1704
1705 context.lastOfCatchIsReachable = forkContext.reachable;
1706 } else {
1707 context.lastOfTryIsReachable = forkContext.reachable;
1708 }
1709
1710
1711 context.position = "finally";
1712
1713 /*
1714 * If there was no `return` or `throw` in either the `try` or `catch`
1715 * blocks, then there's no further code paths to create for `finally`.
1716 */
1717 if (originalReturnedForkContext.empty && originalThrownForContext.empty) {
1718
1719 // This path does not leave.
1720 return;
1721 }
1722
1723 /*
1724 * Create a parallel segment from merging returned and thrown.
1725 * This segment will leave at the end of this `finally` block.
1726 */
1727 const segments = forkContext.makeNext(-1, -1);
1728
1729 for (let i = 0; i < forkContext.count; ++i) {
1730 const prevSegsOfLeavingSegment = [headOfLeavingSegments[i]];
1731
1732 for (let j = 0; j < originalReturnedForkContext.segmentsList.length; ++j) {
1733 prevSegsOfLeavingSegment.push(originalReturnedForkContext.segmentsList[j][i]);
1734 }
1735 for (let j = 0; j < originalThrownForContext.segmentsList.length; ++j) {
1736 prevSegsOfLeavingSegment.push(originalThrownForContext.segmentsList[j][i]);
1737 }
1738
1739 segments.push(
1740 CodePathSegment.newNext(
1741 this.idGenerator.next(),
1742 prevSegsOfLeavingSegment
1743 )
1744 );
1745 }
1746
1747 this.pushForkContext(true);
1748 this.forkContext.add(segments);
1749 }
1750
1751 /**
1752 * Makes a code path segment from the first throwable node to the `catch`
1753 * block or the `finally` block.
1754 * @returns {void}
1755 */
1756 makeFirstThrowablePathInTryBlock() {
1757 const forkContext = this.forkContext;
1758
1759 if (!forkContext.reachable) {
1760 return;
1761 }
1762
1763 const context = getThrowContext(this);
1764
1765 if (context === this ||
1766 context.position !== "try" ||
1767 !context.thrownForkContext.empty
1768 ) {
1769 return;
1770 }
1771
1772 context.thrownForkContext.add(forkContext.head);
1773 forkContext.replaceHead(forkContext.makeNext(-1, -1));
1774 }
1775
1776 //--------------------------------------------------------------------------
1777 // Loop Statements
1778 //--------------------------------------------------------------------------
1779
1780 /**
1781 * Creates a context object of a loop statement and stacks it.
1782 * @param {string} type The type of the node which was triggered. One of
1783 * `WhileStatement`, `DoWhileStatement`, `ForStatement`, `ForInStatement`,
1784 * and `ForStatement`.
1785 * @param {string|null} label A label of the node which was triggered.
1786 * @throws {Error} (Unreachable - unknown type.)
1787 * @returns {void}
1788 */
1789 pushLoopContext(type, label) {
1790 const forkContext = this.forkContext;
1791
1792 // All loops need a path to account for `break` statements
1793 const breakContext = this.pushBreakContext(true, label);
1794
1795 switch (type) {
1796 case "WhileStatement":
1797 this.pushChoiceContext("loop", false);
1798 this.loopContext = new WhileLoopContext(this.loopContext, label, breakContext);
1799 break;
1800
1801 case "DoWhileStatement":
1802 this.pushChoiceContext("loop", false);
1803 this.loopContext = new DoWhileLoopContext(this.loopContext, label, breakContext, forkContext);
1804 break;
1805
1806 case "ForStatement":
1807 this.pushChoiceContext("loop", false);
1808 this.loopContext = new ForLoopContext(this.loopContext, label, breakContext);
1809 break;
1810
1811 case "ForInStatement":
1812 this.loopContext = new ForInLoopContext(this.loopContext, label, breakContext);
1813 break;
1814
1815 case "ForOfStatement":
1816 this.loopContext = new ForOfLoopContext(this.loopContext, label, breakContext);
1817 break;
1818
1819 /* c8 ignore next */
1820 default:
1821 throw new Error(`unknown type: "${type}"`);
1822 }
1823 }
1824
1825 /**
1826 * Pops the last context of a loop statement and finalizes it.
1827 * @throws {Error} (Unreachable - unknown type.)
1828 * @returns {void}
1829 */
1830 popLoopContext() {
1831 const context = this.loopContext;
1832
1833 this.loopContext = context.upper;
1834
1835 const forkContext = this.forkContext;
1836 const brokenForkContext = this.popBreakContext().brokenForkContext;
1837
1838 // Creates a looped path.
1839 switch (context.type) {
1840 case "WhileStatement":
1841 case "ForStatement":
1842 this.popChoiceContext();
1843
1844 /*
1845 * Creates the path from the end of the loop body up to the
1846 * location where `continue` would jump to.
1847 */
1848 makeLooped(
1849 this,
1850 forkContext.head,
1851 context.continueDestSegments
1852 );
1853 break;
1854
1855 case "DoWhileStatement": {
1856 const choiceContext = this.popChoiceContext();
1857
1858 if (!choiceContext.processed) {
1859 choiceContext.trueForkContext.add(forkContext.head);
1860 choiceContext.falseForkContext.add(forkContext.head);
1861 }
1862
1863 /*
1864 * If this isn't a hardcoded `true` condition, then `break`
1865 * should continue down the path as if the condition evaluated
1866 * to false.
1867 */
1868 if (context.test !== true) {
1869 brokenForkContext.addAll(choiceContext.falseForkContext);
1870 }
1871
1872 /*
1873 * When the condition is true, the loop continues back to the top,
1874 * so create a path from each possible true condition back to the
1875 * top of the loop.
1876 */
1877 const segmentsList = choiceContext.trueForkContext.segmentsList;
1878
1879 for (let i = 0; i < segmentsList.length; ++i) {
1880 makeLooped(
1881 this,
1882 segmentsList[i],
1883 context.entrySegments
1884 );
1885 }
1886 break;
1887 }
1888
1889 case "ForInStatement":
1890 case "ForOfStatement":
1891 brokenForkContext.add(forkContext.head);
1892
1893 /*
1894 * Creates the path from the end of the loop body up to the
1895 * left expression (left of `in` or `of`) of the loop.
1896 */
1897 makeLooped(
1898 this,
1899 forkContext.head,
1900 context.leftSegments
1901 );
1902 break;
1903
1904 /* c8 ignore next */
1905 default:
1906 throw new Error("unreachable");
1907 }
1908
1909 /*
1910 * If there wasn't a `break` statement in the loop, then we're at
1911 * the end of the loop's path, so we make an unreachable segment
1912 * to mark that.
1913 *
1914 * If there was a `break` statement, then we continue on into the
1915 * `brokenForkContext`.
1916 */
1917 if (brokenForkContext.empty) {
1918 forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
1919 } else {
1920 forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
1921 }
1922 }
1923
1924 /**
1925 * Makes a code path segment for the test part of a WhileStatement.
1926 * @param {boolean|undefined} test The test value (only when constant).
1927 * @returns {void}
1928 */
1929 makeWhileTest(test) {
1930 const context = this.loopContext;
1931 const forkContext = this.forkContext;
1932 const testSegments = forkContext.makeNext(0, -1);
1933
1934 // Update state.
1935 context.test = test;
1936 context.continueDestSegments = testSegments;
1937 forkContext.replaceHead(testSegments);
1938 }
1939
1940 /**
1941 * Makes a code path segment for the body part of a WhileStatement.
1942 * @returns {void}
1943 */
1944 makeWhileBody() {
1945 const context = this.loopContext;
1946 const choiceContext = this.choiceContext;
1947 const forkContext = this.forkContext;
1948
1949 if (!choiceContext.processed) {
1950 choiceContext.trueForkContext.add(forkContext.head);
1951 choiceContext.falseForkContext.add(forkContext.head);
1952 }
1953
1954 /*
1955 * If this isn't a hardcoded `true` condition, then `break`
1956 * should continue down the path as if the condition evaluated
1957 * to false.
1958 */
1959 if (context.test !== true) {
1960 context.brokenForkContext.addAll(choiceContext.falseForkContext);
1961 }
1962 forkContext.replaceHead(choiceContext.trueForkContext.makeNext(0, -1));
1963 }
1964
1965 /**
1966 * Makes a code path segment for the body part of a DoWhileStatement.
1967 * @returns {void}
1968 */
1969 makeDoWhileBody() {
1970 const context = this.loopContext;
1971 const forkContext = this.forkContext;
1972 const bodySegments = forkContext.makeNext(-1, -1);
1973
1974 // Update state.
1975 context.entrySegments = bodySegments;
1976 forkContext.replaceHead(bodySegments);
1977 }
1978
1979 /**
1980 * Makes a code path segment for the test part of a DoWhileStatement.
1981 * @param {boolean|undefined} test The test value (only when constant).
1982 * @returns {void}
1983 */
1984 makeDoWhileTest(test) {
1985 const context = this.loopContext;
1986 const forkContext = this.forkContext;
1987
1988 context.test = test;
1989
1990 /*
1991 * If there is a `continue` statement in the loop then `continueForkContext`
1992 * won't be empty. We wire up the path from `continue` to the loop
1993 * test condition and then continue the traversal in the root fork context.
1994 */
1995 if (!context.continueForkContext.empty) {
1996 context.continueForkContext.add(forkContext.head);
1997 const testSegments = context.continueForkContext.makeNext(0, -1);
1998
1999 forkContext.replaceHead(testSegments);
2000 }
2001 }
2002
2003 /**
2004 * Makes a code path segment for the test part of a ForStatement.
2005 * @param {boolean|undefined} test The test value (only when constant).
2006 * @returns {void}
2007 */
2008 makeForTest(test) {
2009 const context = this.loopContext;
2010 const forkContext = this.forkContext;
2011 const endOfInitSegments = forkContext.head;
2012 const testSegments = forkContext.makeNext(-1, -1);
2013
2014 /*
2015 * Update the state.
2016 *
2017 * The `continueDestSegments` are set to `testSegments` because we
2018 * don't yet know if there is an update expression in this loop. So,
2019 * from what we already know at this point, a `continue` statement
2020 * will jump back to the test expression.
2021 */
2022 context.test = test;
2023 context.endOfInitSegments = endOfInitSegments;
2024 context.continueDestSegments = context.testSegments = testSegments;
2025 forkContext.replaceHead(testSegments);
2026 }
2027
2028 /**
2029 * Makes a code path segment for the update part of a ForStatement.
2030 * @returns {void}
2031 */
2032 makeForUpdate() {
2033 const context = this.loopContext;
2034 const choiceContext = this.choiceContext;
2035 const forkContext = this.forkContext;
2036
2037 // Make the next paths of the test.
2038 if (context.testSegments) {
2039 finalizeTestSegmentsOfFor(
2040 context,
2041 choiceContext,
2042 forkContext.head
2043 );
2044 } else {
2045 context.endOfInitSegments = forkContext.head;
2046 }
2047
2048 /*
2049 * Update the state.
2050 *
2051 * The `continueDestSegments` are now set to `updateSegments` because we
2052 * know there is an update expression in this loop. So, a `continue` statement
2053 * in the loop will jump to the update expression first, and then to any
2054 * test expression the loop might have.
2055 */
2056 const updateSegments = forkContext.makeDisconnected(-1, -1);
2057
2058 context.continueDestSegments = context.updateSegments = updateSegments;
2059 forkContext.replaceHead(updateSegments);
2060 }
2061
2062 /**
2063 * Makes a code path segment for the body part of a ForStatement.
2064 * @returns {void}
2065 */
2066 makeForBody() {
2067 const context = this.loopContext;
2068 const choiceContext = this.choiceContext;
2069 const forkContext = this.forkContext;
2070
2071 /*
2072 * Determine what to do based on which part of the `for` loop are present.
2073 * 1. If there is an update expression, then `updateSegments` is not null and
2074 * we need to assign `endOfUpdateSegments`, and if there is a test
2075 * expression, we then need to create the looped path to get back to
2076 * the test condition.
2077 * 2. If there is no update expression but there is a test expression,
2078 * then we only need to update the test segment information.
2079 * 3. If there is no update expression and no test expression, then we
2080 * just save `endOfInitSegments`.
2081 */
2082 if (context.updateSegments) {
2083 context.endOfUpdateSegments = forkContext.head;
2084
2085 /*
2086 * In a `for` loop that has both an update expression and a test
2087 * condition, execution flows from the test expression into the
2088 * loop body, to the update expression, and then back to the test
2089 * expression to determine if the loop should continue.
2090 *
2091 * To account for that, we need to make a path from the end of the
2092 * update expression to the start of the test expression. This is
2093 * effectively what creates the loop in the code path.
2094 */
2095 if (context.testSegments) {
2096 makeLooped(
2097 this,
2098 context.endOfUpdateSegments,
2099 context.testSegments
2100 );
2101 }
2102 } else if (context.testSegments) {
2103 finalizeTestSegmentsOfFor(
2104 context,
2105 choiceContext,
2106 forkContext.head
2107 );
2108 } else {
2109 context.endOfInitSegments = forkContext.head;
2110 }
2111
2112 let bodySegments = context.endOfTestSegments;
2113
2114 /*
2115 * If there is a test condition, then there `endOfTestSegments` is also
2116 * the start of the loop body. If there isn't a test condition then
2117 * `bodySegments` will be null and we need to look elsewhere to find
2118 * the start of the body.
2119 *
2120 * The body starts at the end of the init expression and ends at the end
2121 * of the update expression, so we use those locations to determine the
2122 * body segments.
2123 */
2124 if (!bodySegments) {
2125
2126 const prevForkContext = ForkContext.newEmpty(forkContext);
2127
2128 prevForkContext.add(context.endOfInitSegments);
2129 if (context.endOfUpdateSegments) {
2130 prevForkContext.add(context.endOfUpdateSegments);
2131 }
2132
2133 bodySegments = prevForkContext.makeNext(0, -1);
2134 }
2135
2136 /*
2137 * If there was no test condition and no update expression, then
2138 * `continueDestSegments` will be null. In that case, a
2139 * `continue` should skip directly to the body of the loop.
2140 * Otherwise, we want to keep the current `continueDestSegments`.
2141 */
2142 context.continueDestSegments = context.continueDestSegments || bodySegments;
2143
2144 // move pointer to the body
2145 forkContext.replaceHead(bodySegments);
2146 }
2147
2148 /**
2149 * Makes a code path segment for the left part of a ForInStatement and a
2150 * ForOfStatement.
2151 * @returns {void}
2152 */
2153 makeForInOfLeft() {
2154 const context = this.loopContext;
2155 const forkContext = this.forkContext;
2156 const leftSegments = forkContext.makeDisconnected(-1, -1);
2157
2158 // Update state.
2159 context.prevSegments = forkContext.head;
2160 context.leftSegments = context.continueDestSegments = leftSegments;
2161 forkContext.replaceHead(leftSegments);
2162 }
2163
2164 /**
2165 * Makes a code path segment for the right part of a ForInStatement and a
2166 * ForOfStatement.
2167 * @returns {void}
2168 */
2169 makeForInOfRight() {
2170 const context = this.loopContext;
2171 const forkContext = this.forkContext;
2172 const temp = ForkContext.newEmpty(forkContext);
2173
2174 temp.add(context.prevSegments);
2175 const rightSegments = temp.makeNext(-1, -1);
2176
2177 // Update state.
2178 context.endOfLeftSegments = forkContext.head;
2179 forkContext.replaceHead(rightSegments);
2180 }
2181
2182 /**
2183 * Makes a code path segment for the body part of a ForInStatement and a
2184 * ForOfStatement.
2185 * @returns {void}
2186 */
2187 makeForInOfBody() {
2188 const context = this.loopContext;
2189 const forkContext = this.forkContext;
2190 const temp = ForkContext.newEmpty(forkContext);
2191
2192 temp.add(context.endOfLeftSegments);
2193 const bodySegments = temp.makeNext(-1, -1);
2194
2195 // Make a path: `right` -> `left`.
2196 makeLooped(this, forkContext.head, context.leftSegments);
2197
2198 // Update state.
2199 context.brokenForkContext.add(forkContext.head);
2200 forkContext.replaceHead(bodySegments);
2201 }
2202
2203 //--------------------------------------------------------------------------
2204 // Control Statements
2205 //--------------------------------------------------------------------------
2206
2207 /**
2208 * Creates new context in which a `break` statement can be used. This occurs inside of a loop,
2209 * labeled statement, or switch statement.
2210 * @param {boolean} breakable Indicates if we are inside a statement where
2211 * `break` without a label will exit the statement.
2212 * @param {string|null} label The label associated with the statement.
2213 * @returns {BreakContext} The new context.
2214 */
2215 pushBreakContext(breakable, label) {
2216 this.breakContext = new BreakContext(this.breakContext, breakable, label, this.forkContext);
2217 return this.breakContext;
2218 }
2219
2220 /**
2221 * Removes the top item of the break context stack.
2222 * @returns {Object} The removed context.
2223 */
2224 popBreakContext() {
2225 const context = this.breakContext;
2226 const forkContext = this.forkContext;
2227
2228 this.breakContext = context.upper;
2229
2230 // Process this context here for other than switches and loops.
2231 if (!context.breakable) {
2232 const brokenForkContext = context.brokenForkContext;
2233
2234 if (!brokenForkContext.empty) {
2235 brokenForkContext.add(forkContext.head);
2236 forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
2237 }
2238 }
2239
2240 return context;
2241 }
2242
2243 /**
2244 * Makes a path for a `break` statement.
2245 *
2246 * It registers the head segment to a context of `break`.
2247 * It makes new unreachable segment, then it set the head with the segment.
2248 * @param {string|null} label A label of the break statement.
2249 * @returns {void}
2250 */
2251 makeBreak(label) {
2252 const forkContext = this.forkContext;
2253
2254 if (!forkContext.reachable) {
2255 return;
2256 }
2257
2258 const context = getBreakContext(this, label);
2259
2260
2261 if (context) {
2262 context.brokenForkContext.add(forkContext.head);
2263 }
2264
2265 /* c8 ignore next */
2266 forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
2267 }
2268
2269 /**
2270 * Makes a path for a `continue` statement.
2271 *
2272 * It makes a looping path.
2273 * It makes new unreachable segment, then it set the head with the segment.
2274 * @param {string|null} label A label of the continue statement.
2275 * @returns {void}
2276 */
2277 makeContinue(label) {
2278 const forkContext = this.forkContext;
2279
2280 if (!forkContext.reachable) {
2281 return;
2282 }
2283
2284 const context = getContinueContext(this, label);
2285
2286 if (context) {
2287 if (context.continueDestSegments) {
2288 makeLooped(this, forkContext.head, context.continueDestSegments);
2289
2290 // If the context is a for-in/of loop, this affects a break also.
2291 if (context.type === "ForInStatement" ||
2292 context.type === "ForOfStatement"
2293 ) {
2294 context.brokenForkContext.add(forkContext.head);
2295 }
2296 } else {
2297 context.continueForkContext.add(forkContext.head);
2298 }
2299 }
2300 forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
2301 }
2302
2303 /**
2304 * Makes a path for a `return` statement.
2305 *
2306 * It registers the head segment to a context of `return`.
2307 * It makes new unreachable segment, then it set the head with the segment.
2308 * @returns {void}
2309 */
2310 makeReturn() {
2311 const forkContext = this.forkContext;
2312
2313 if (forkContext.reachable) {
2314 getReturnContext(this).returnedForkContext.add(forkContext.head);
2315 forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
2316 }
2317 }
2318
2319 /**
2320 * Makes a path for a `throw` statement.
2321 *
2322 * It registers the head segment to a context of `throw`.
2323 * It makes new unreachable segment, then it set the head with the segment.
2324 * @returns {void}
2325 */
2326 makeThrow() {
2327 const forkContext = this.forkContext;
2328
2329 if (forkContext.reachable) {
2330 getThrowContext(this).thrownForkContext.add(forkContext.head);
2331 forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
2332 }
2333 }
2334
2335 /**
2336 * Makes the final path.
2337 * @returns {void}
2338 */
2339 makeFinal() {
2340 const segments = this.currentSegments;
2341
2342 if (segments.length > 0 && segments[0].reachable) {
2343 this.returnedForkContext.add(segments);
2344 }
2345 }
2346}
2347
2348module.exports = CodePathState;
Note: See TracBrowser for help on using the repository browser.