.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / linter / code-path-analysis / code-path-state.js
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
12 const CodePathSegment = require("./code-path-segment"),
13     ForkContext = require("./fork-context");
14
15 //------------------------------------------------------------------------------
16 // Helpers
17 //------------------------------------------------------------------------------
18
19 /**
20  * Adds given segments into the `dest` array.
21  * If the `others` array does not includes the given segments, adds to the `all`
22  * array as well.
23  *
24  * This adds only reachable and used segments.
25  * @param {CodePathSegment[]} dest A destination array (`returnedSegments` or `thrownSegments`).
26  * @param {CodePathSegment[]} others Another destination array (`returnedSegments` or `thrownSegments`).
27  * @param {CodePathSegment[]} all The unified destination array (`finalSegments`).
28  * @param {CodePathSegment[]} segments Segments to add.
29  * @returns {void}
30  */
31 function addToReturnedOrThrown(dest, others, all, segments) {
32     for (let i = 0; i < segments.length; ++i) {
33         const segment = segments[i];
34
35         dest.push(segment);
36         if (others.indexOf(segment) === -1) {
37             all.push(segment);
38         }
39     }
40 }
41
42 /**
43  * Gets a loop-context for a `continue` statement.
44  * @param {CodePathState} state A state to get.
45  * @param {string} label The label of a `continue` statement.
46  * @returns {LoopContext} A loop-context for a `continue` statement.
47  */
48 function getContinueContext(state, label) {
49     if (!label) {
50         return state.loopContext;
51     }
52
53     let context = state.loopContext;
54
55     while (context) {
56         if (context.label === label) {
57             return context;
58         }
59         context = context.upper;
60     }
61
62     /* istanbul ignore next: foolproof (syntax error) */
63     return null;
64 }
65
66 /**
67  * Gets a context for a `break` statement.
68  * @param {CodePathState} state A state to get.
69  * @param {string} label The label of a `break` statement.
70  * @returns {LoopContext|SwitchContext} A context for a `break` statement.
71  */
72 function getBreakContext(state, label) {
73     let context = state.breakContext;
74
75     while (context) {
76         if (label ? context.label === label : context.breakable) {
77             return context;
78         }
79         context = context.upper;
80     }
81
82     /* istanbul ignore next: foolproof (syntax error) */
83     return null;
84 }
85
86 /**
87  * Gets a context for a `return` statement.
88  * @param {CodePathState} state A state to get.
89  * @returns {TryContext|CodePathState} A context for a `return` statement.
90  */
91 function getReturnContext(state) {
92     let context = state.tryContext;
93
94     while (context) {
95         if (context.hasFinalizer && context.position !== "finally") {
96             return context;
97         }
98         context = context.upper;
99     }
100
101     return state;
102 }
103
104 /**
105  * Gets a context for a `throw` statement.
106  * @param {CodePathState} state A state to get.
107  * @returns {TryContext|CodePathState} A context for a `throw` statement.
108  */
109 function getThrowContext(state) {
110     let context = state.tryContext;
111
112     while (context) {
113         if (context.position === "try" ||
114             (context.hasFinalizer && context.position === "catch")
115         ) {
116             return context;
117         }
118         context = context.upper;
119     }
120
121     return state;
122 }
123
124 /**
125  * Removes a given element from a given array.
126  * @param {any[]} xs An array to remove the specific element.
127  * @param {any} x An element to be removed.
128  * @returns {void}
129  */
130 function remove(xs, x) {
131     xs.splice(xs.indexOf(x), 1);
132 }
133
134 /**
135  * Disconnect given segments.
136  *
137  * This is used in a process for switch statements.
138  * If there is the "default" chunk before other cases, the order is different
139  * between node's and running's.
140  * @param {CodePathSegment[]} prevSegments Forward segments to disconnect.
141  * @param {CodePathSegment[]} nextSegments Backward segments to disconnect.
142  * @returns {void}
143  */
144 function removeConnection(prevSegments, nextSegments) {
145     for (let i = 0; i < prevSegments.length; ++i) {
146         const prevSegment = prevSegments[i];
147         const nextSegment = nextSegments[i];
148
149         remove(prevSegment.nextSegments, nextSegment);
150         remove(prevSegment.allNextSegments, nextSegment);
151         remove(nextSegment.prevSegments, prevSegment);
152         remove(nextSegment.allPrevSegments, prevSegment);
153     }
154 }
155
156 /**
157  * Creates looping path.
158  * @param {CodePathState} state The instance.
159  * @param {CodePathSegment[]} unflattenedFromSegments Segments which are source.
160  * @param {CodePathSegment[]} unflattenedToSegments Segments which are destination.
161  * @returns {void}
162  */
163 function makeLooped(state, unflattenedFromSegments, unflattenedToSegments) {
164     const fromSegments = CodePathSegment.flattenUnusedSegments(unflattenedFromSegments);
165     const toSegments = CodePathSegment.flattenUnusedSegments(unflattenedToSegments);
166
167     const end = Math.min(fromSegments.length, toSegments.length);
168
169     for (let i = 0; i < end; ++i) {
170         const fromSegment = fromSegments[i];
171         const toSegment = toSegments[i];
172
173         if (toSegment.reachable) {
174             fromSegment.nextSegments.push(toSegment);
175         }
176         if (fromSegment.reachable) {
177             toSegment.prevSegments.push(fromSegment);
178         }
179         fromSegment.allNextSegments.push(toSegment);
180         toSegment.allPrevSegments.push(fromSegment);
181
182         if (toSegment.allPrevSegments.length >= 2) {
183             CodePathSegment.markPrevSegmentAsLooped(toSegment, fromSegment);
184         }
185
186         state.notifyLooped(fromSegment, toSegment);
187     }
188 }
189
190 /**
191  * Finalizes segments of `test` chunk of a ForStatement.
192  *
193  * - Adds `false` paths to paths which are leaving from the loop.
194  * - Sets `true` paths to paths which go to the body.
195  * @param {LoopContext} context A loop context to modify.
196  * @param {ChoiceContext} choiceContext A choice context of this loop.
197  * @param {CodePathSegment[]} head The current head paths.
198  * @returns {void}
199  */
200 function finalizeTestSegmentsOfFor(context, choiceContext, head) {
201     if (!choiceContext.processed) {
202         choiceContext.trueForkContext.add(head);
203         choiceContext.falseForkContext.add(head);
204         choiceContext.qqForkContext.add(head);
205     }
206
207     if (context.test !== true) {
208         context.brokenForkContext.addAll(choiceContext.falseForkContext);
209     }
210     context.endOfTestSegments = choiceContext.trueForkContext.makeNext(0, -1);
211 }
212
213 //------------------------------------------------------------------------------
214 // Public Interface
215 //------------------------------------------------------------------------------
216
217 /**
218  * A class which manages state to analyze code paths.
219  */
220 class CodePathState {
221
222     // eslint-disable-next-line jsdoc/require-description
223     /**
224      * @param {IdGenerator} idGenerator An id generator to generate id for code
225      *   path segments.
226      * @param {Function} onLooped A callback function to notify looping.
227      */
228     constructor(idGenerator, onLooped) {
229         this.idGenerator = idGenerator;
230         this.notifyLooped = onLooped;
231         this.forkContext = ForkContext.newRoot(idGenerator);
232         this.choiceContext = null;
233         this.switchContext = null;
234         this.tryContext = null;
235         this.loopContext = null;
236         this.breakContext = null;
237         this.chainContext = null;
238
239         this.currentSegments = [];
240         this.initialSegment = this.forkContext.head[0];
241
242         // returnedSegments and thrownSegments push elements into finalSegments also.
243         const final = this.finalSegments = [];
244         const returned = this.returnedForkContext = [];
245         const thrown = this.thrownForkContext = [];
246
247         returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final);
248         thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final);
249     }
250
251     /**
252      * The head segments.
253      * @type {CodePathSegment[]}
254      */
255     get headSegments() {
256         return this.forkContext.head;
257     }
258
259     /**
260      * The parent forking context.
261      * This is used for the root of new forks.
262      * @type {ForkContext}
263      */
264     get parentForkContext() {
265         const current = this.forkContext;
266
267         return current && current.upper;
268     }
269
270     /**
271      * Creates and stacks new forking context.
272      * @param {boolean} forkLeavingPath A flag which shows being in a
273      *   "finally" block.
274      * @returns {ForkContext} The created context.
275      */
276     pushForkContext(forkLeavingPath) {
277         this.forkContext = ForkContext.newEmpty(
278             this.forkContext,
279             forkLeavingPath
280         );
281
282         return this.forkContext;
283     }
284
285     /**
286      * Pops and merges the last forking context.
287      * @returns {ForkContext} The last context.
288      */
289     popForkContext() {
290         const lastContext = this.forkContext;
291
292         this.forkContext = lastContext.upper;
293         this.forkContext.replaceHead(lastContext.makeNext(0, -1));
294
295         return lastContext;
296     }
297
298     /**
299      * Creates a new path.
300      * @returns {void}
301      */
302     forkPath() {
303         this.forkContext.add(this.parentForkContext.makeNext(-1, -1));
304     }
305
306     /**
307      * Creates a bypass path.
308      * This is used for such as IfStatement which does not have "else" chunk.
309      * @returns {void}
310      */
311     forkBypassPath() {
312         this.forkContext.add(this.parentForkContext.head);
313     }
314
315     //--------------------------------------------------------------------------
316     // ConditionalExpression, LogicalExpression, IfStatement
317     //--------------------------------------------------------------------------
318
319     /**
320      * Creates a context for ConditionalExpression, LogicalExpression, AssignmentExpression (logical assignments only),
321      * IfStatement, WhileStatement, DoWhileStatement, or ForStatement.
322      *
323      * LogicalExpressions have cases that it goes different paths between the
324      * `true` case and the `false` case.
325      *
326      * For Example:
327      *
328      *     if (a || b) {
329      *         foo();
330      *     } else {
331      *         bar();
332      *     }
333      *
334      * In this case, `b` is evaluated always in the code path of the `else`
335      * block, but it's not so in the code path of the `if` block.
336      * So there are 3 paths.
337      *
338      *     a -> foo();
339      *     a -> b -> foo();
340      *     a -> b -> bar();
341      * @param {string} kind A kind string.
342      *   If the new context is LogicalExpression's or AssignmentExpression's, this is `"&&"` or `"||"` or `"??"`.
343      *   If it's IfStatement's or ConditionalExpression's, this is `"test"`.
344      *   Otherwise, this is `"loop"`.
345      * @param {boolean} isForkingAsResult A flag that shows that goes different
346      *   paths between `true` and `false`.
347      * @returns {void}
348      */
349     pushChoiceContext(kind, isForkingAsResult) {
350         this.choiceContext = {
351             upper: this.choiceContext,
352             kind,
353             isForkingAsResult,
354             trueForkContext: ForkContext.newEmpty(this.forkContext),
355             falseForkContext: ForkContext.newEmpty(this.forkContext),
356             qqForkContext: ForkContext.newEmpty(this.forkContext),
357             processed: false
358         };
359     }
360
361     /**
362      * Pops the last choice context and finalizes it.
363      * @returns {ChoiceContext} The popped context.
364      */
365     popChoiceContext() {
366         const context = this.choiceContext;
367
368         this.choiceContext = context.upper;
369
370         const forkContext = this.forkContext;
371         const headSegments = forkContext.head;
372
373         switch (context.kind) {
374             case "&&":
375             case "||":
376             case "??":
377
378                 /*
379                  * If any result were not transferred from child contexts,
380                  * this sets the head segments to both cases.
381                  * The head segments are the path of the right-hand operand.
382                  */
383                 if (!context.processed) {
384                     context.trueForkContext.add(headSegments);
385                     context.falseForkContext.add(headSegments);
386                     context.qqForkContext.add(headSegments);
387                 }
388
389                 /*
390                  * Transfers results to upper context if this context is in
391                  * test chunk.
392                  */
393                 if (context.isForkingAsResult) {
394                     const parentContext = this.choiceContext;
395
396                     parentContext.trueForkContext.addAll(context.trueForkContext);
397                     parentContext.falseForkContext.addAll(context.falseForkContext);
398                     parentContext.qqForkContext.addAll(context.qqForkContext);
399                     parentContext.processed = true;
400
401                     return context;
402                 }
403
404                 break;
405
406             case "test":
407                 if (!context.processed) {
408
409                     /*
410                      * The head segments are the path of the `if` block here.
411                      * Updates the `true` path with the end of the `if` block.
412                      */
413                     context.trueForkContext.clear();
414                     context.trueForkContext.add(headSegments);
415                 } else {
416
417                     /*
418                      * The head segments are the path of the `else` block here.
419                      * Updates the `false` path with the end of the `else`
420                      * block.
421                      */
422                     context.falseForkContext.clear();
423                     context.falseForkContext.add(headSegments);
424                 }
425
426                 break;
427
428             case "loop":
429
430                 /*
431                  * Loops are addressed in popLoopContext().
432                  * This is called from popLoopContext().
433                  */
434                 return context;
435
436             /* istanbul ignore next */
437             default:
438                 throw new Error("unreachable");
439         }
440
441         // Merges all paths.
442         const prevForkContext = context.trueForkContext;
443
444         prevForkContext.addAll(context.falseForkContext);
445         forkContext.replaceHead(prevForkContext.makeNext(0, -1));
446
447         return context;
448     }
449
450     /**
451      * Makes a code path segment of the right-hand operand of a logical
452      * expression.
453      * @returns {void}
454      */
455     makeLogicalRight() {
456         const context = this.choiceContext;
457         const forkContext = this.forkContext;
458
459         if (context.processed) {
460
461             /*
462              * This got segments already from the child choice context.
463              * Creates the next path from own true/false fork context.
464              */
465             let prevForkContext;
466
467             switch (context.kind) {
468                 case "&&": // if true then go to the right-hand side.
469                     prevForkContext = context.trueForkContext;
470                     break;
471                 case "||": // if false then go to the right-hand side.
472                     prevForkContext = context.falseForkContext;
473                     break;
474                 case "??": // Both true/false can short-circuit, so needs the third path to go to the right-hand side. That's qqForkContext.
475                     prevForkContext = context.qqForkContext;
476                     break;
477                 default:
478                     throw new Error("unreachable");
479             }
480
481             forkContext.replaceHead(prevForkContext.makeNext(0, -1));
482             prevForkContext.clear();
483             context.processed = false;
484         } else {
485
486             /*
487              * This did not get segments from the child choice context.
488              * So addresses the head segments.
489              * The head segments are the path of the left-hand operand.
490              */
491             switch (context.kind) {
492                 case "&&": // the false path can short-circuit.
493                     context.falseForkContext.add(forkContext.head);
494                     break;
495                 case "||": // the true path can short-circuit.
496                     context.trueForkContext.add(forkContext.head);
497                     break;
498                 case "??": // both can short-circuit.
499                     context.trueForkContext.add(forkContext.head);
500                     context.falseForkContext.add(forkContext.head);
501                     break;
502                 default:
503                     throw new Error("unreachable");
504             }
505
506             forkContext.replaceHead(forkContext.makeNext(-1, -1));
507         }
508     }
509
510     /**
511      * Makes a code path segment of the `if` block.
512      * @returns {void}
513      */
514     makeIfConsequent() {
515         const context = this.choiceContext;
516         const forkContext = this.forkContext;
517
518         /*
519          * If any result were not transferred from child contexts,
520          * this sets the head segments to both cases.
521          * The head segments are the path of the test expression.
522          */
523         if (!context.processed) {
524             context.trueForkContext.add(forkContext.head);
525             context.falseForkContext.add(forkContext.head);
526             context.qqForkContext.add(forkContext.head);
527         }
528
529         context.processed = false;
530
531         // Creates new path from the `true` case.
532         forkContext.replaceHead(
533             context.trueForkContext.makeNext(0, -1)
534         );
535     }
536
537     /**
538      * Makes a code path segment of the `else` block.
539      * @returns {void}
540      */
541     makeIfAlternate() {
542         const context = this.choiceContext;
543         const forkContext = this.forkContext;
544
545         /*
546          * The head segments are the path of the `if` block.
547          * Updates the `true` path with the end of the `if` block.
548          */
549         context.trueForkContext.clear();
550         context.trueForkContext.add(forkContext.head);
551         context.processed = true;
552
553         // Creates new path from the `false` case.
554         forkContext.replaceHead(
555             context.falseForkContext.makeNext(0, -1)
556         );
557     }
558
559     //--------------------------------------------------------------------------
560     // ChainExpression
561     //--------------------------------------------------------------------------
562
563     /**
564      * Push a new `ChainExpression` context to the stack.
565      * This method is called on entering to each `ChainExpression` node.
566      * This context is used to count forking in the optional chain then merge them on the exiting from the `ChainExpression` node.
567      * @returns {void}
568      */
569     pushChainContext() {
570         this.chainContext = {
571             upper: this.chainContext,
572             countChoiceContexts: 0
573         };
574     }
575
576     /**
577      * Pop a `ChainExpression` context from the stack.
578      * This method is called on exiting from each `ChainExpression` node.
579      * This merges all forks of the last optional chaining.
580      * @returns {void}
581      */
582     popChainContext() {
583         const context = this.chainContext;
584
585         this.chainContext = context.upper;
586
587         // pop all choice contexts of this.
588         for (let i = context.countChoiceContexts; i > 0; --i) {
589             this.popChoiceContext();
590         }
591     }
592
593     /**
594      * Create a choice context for optional access.
595      * This method is called on entering to each `(Call|Member)Expression[optional=true]` node.
596      * This creates a choice context as similar to `LogicalExpression[operator="??"]` node.
597      * @returns {void}
598      */
599     makeOptionalNode() {
600         if (this.chainContext) {
601             this.chainContext.countChoiceContexts += 1;
602             this.pushChoiceContext("??", false);
603         }
604     }
605
606     /**
607      * Create a fork.
608      * This method is called on entering to the `arguments|property` property of each `(Call|Member)Expression` node.
609      * @returns {void}
610      */
611     makeOptionalRight() {
612         if (this.chainContext) {
613             this.makeLogicalRight();
614         }
615     }
616
617     //--------------------------------------------------------------------------
618     // SwitchStatement
619     //--------------------------------------------------------------------------
620
621     /**
622      * Creates a context object of SwitchStatement and stacks it.
623      * @param {boolean} hasCase `true` if the switch statement has one or more
624      *   case parts.
625      * @param {string|null} label The label text.
626      * @returns {void}
627      */
628     pushSwitchContext(hasCase, label) {
629         this.switchContext = {
630             upper: this.switchContext,
631             hasCase,
632             defaultSegments: null,
633             defaultBodySegments: null,
634             foundDefault: false,
635             lastIsDefault: false,
636             countForks: 0
637         };
638
639         this.pushBreakContext(true, label);
640     }
641
642     /**
643      * Pops the last context of SwitchStatement and finalizes it.
644      *
645      * - Disposes all forking stack for `case` and `default`.
646      * - Creates the next code path segment from `context.brokenForkContext`.
647      * - If the last `SwitchCase` node is not a `default` part, creates a path
648      *   to the `default` body.
649      * @returns {void}
650      */
651     popSwitchContext() {
652         const context = this.switchContext;
653
654         this.switchContext = context.upper;
655
656         const forkContext = this.forkContext;
657         const brokenForkContext = this.popBreakContext().brokenForkContext;
658
659         if (context.countForks === 0) {
660
661             /*
662              * When there is only one `default` chunk and there is one or more
663              * `break` statements, even if forks are nothing, it needs to merge
664              * those.
665              */
666             if (!brokenForkContext.empty) {
667                 brokenForkContext.add(forkContext.makeNext(-1, -1));
668                 forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
669             }
670
671             return;
672         }
673
674         const lastSegments = forkContext.head;
675
676         this.forkBypassPath();
677         const lastCaseSegments = forkContext.head;
678
679         /*
680          * `brokenForkContext` is used to make the next segment.
681          * It must add the last segment into `brokenForkContext`.
682          */
683         brokenForkContext.add(lastSegments);
684
685         /*
686          * A path which is failed in all case test should be connected to path
687          * of `default` chunk.
688          */
689         if (!context.lastIsDefault) {
690             if (context.defaultBodySegments) {
691
692                 /*
693                  * Remove a link from `default` label to its chunk.
694                  * It's false route.
695                  */
696                 removeConnection(context.defaultSegments, context.defaultBodySegments);
697                 makeLooped(this, lastCaseSegments, context.defaultBodySegments);
698             } else {
699
700                 /*
701                  * It handles the last case body as broken if `default` chunk
702                  * does not exist.
703                  */
704                 brokenForkContext.add(lastCaseSegments);
705             }
706         }
707
708         // Pops the segment context stack until the entry segment.
709         for (let i = 0; i < context.countForks; ++i) {
710             this.forkContext = this.forkContext.upper;
711         }
712
713         /*
714          * Creates a path from all brokenForkContext paths.
715          * This is a path after switch statement.
716          */
717         this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
718     }
719
720     /**
721      * Makes a code path segment for a `SwitchCase` node.
722      * @param {boolean} isEmpty `true` if the body is empty.
723      * @param {boolean} isDefault `true` if the body is the default case.
724      * @returns {void}
725      */
726     makeSwitchCaseBody(isEmpty, isDefault) {
727         const context = this.switchContext;
728
729         if (!context.hasCase) {
730             return;
731         }
732
733         /*
734          * Merge forks.
735          * The parent fork context has two segments.
736          * Those are from the current case and the body of the previous case.
737          */
738         const parentForkContext = this.forkContext;
739         const forkContext = this.pushForkContext();
740
741         forkContext.add(parentForkContext.makeNext(0, -1));
742
743         /*
744          * Save `default` chunk info.
745          * If the `default` label is not at the last, we must make a path from
746          * the last `case` to the `default` chunk.
747          */
748         if (isDefault) {
749             context.defaultSegments = parentForkContext.head;
750             if (isEmpty) {
751                 context.foundDefault = true;
752             } else {
753                 context.defaultBodySegments = forkContext.head;
754             }
755         } else {
756             if (!isEmpty && context.foundDefault) {
757                 context.foundDefault = false;
758                 context.defaultBodySegments = forkContext.head;
759             }
760         }
761
762         context.lastIsDefault = isDefault;
763         context.countForks += 1;
764     }
765
766     //--------------------------------------------------------------------------
767     // TryStatement
768     //--------------------------------------------------------------------------
769
770     /**
771      * Creates a context object of TryStatement and stacks it.
772      * @param {boolean} hasFinalizer `true` if the try statement has a
773      *   `finally` block.
774      * @returns {void}
775      */
776     pushTryContext(hasFinalizer) {
777         this.tryContext = {
778             upper: this.tryContext,
779             position: "try",
780             hasFinalizer,
781
782             returnedForkContext: hasFinalizer
783                 ? ForkContext.newEmpty(this.forkContext)
784                 : null,
785
786             thrownForkContext: ForkContext.newEmpty(this.forkContext),
787             lastOfTryIsReachable: false,
788             lastOfCatchIsReachable: false
789         };
790     }
791
792     /**
793      * Pops the last context of TryStatement and finalizes it.
794      * @returns {void}
795      */
796     popTryContext() {
797         const context = this.tryContext;
798
799         this.tryContext = context.upper;
800
801         if (context.position === "catch") {
802
803             // Merges two paths from the `try` block and `catch` block merely.
804             this.popForkContext();
805             return;
806         }
807
808         /*
809          * The following process is executed only when there is the `finally`
810          * block.
811          */
812
813         const returned = context.returnedForkContext;
814         const thrown = context.thrownForkContext;
815
816         if (returned.empty && thrown.empty) {
817             return;
818         }
819
820         // Separate head to normal paths and leaving paths.
821         const headSegments = this.forkContext.head;
822
823         this.forkContext = this.forkContext.upper;
824         const normalSegments = headSegments.slice(0, headSegments.length / 2 | 0);
825         const leavingSegments = headSegments.slice(headSegments.length / 2 | 0);
826
827         // Forwards the leaving path to upper contexts.
828         if (!returned.empty) {
829             getReturnContext(this).returnedForkContext.add(leavingSegments);
830         }
831         if (!thrown.empty) {
832             getThrowContext(this).thrownForkContext.add(leavingSegments);
833         }
834
835         // Sets the normal path as the next.
836         this.forkContext.replaceHead(normalSegments);
837
838         /*
839          * If both paths of the `try` block and the `catch` block are
840          * unreachable, the next path becomes unreachable as well.
841          */
842         if (!context.lastOfTryIsReachable && !context.lastOfCatchIsReachable) {
843             this.forkContext.makeUnreachable();
844         }
845     }
846
847     /**
848      * Makes a code path segment for a `catch` block.
849      * @returns {void}
850      */
851     makeCatchBlock() {
852         const context = this.tryContext;
853         const forkContext = this.forkContext;
854         const thrown = context.thrownForkContext;
855
856         // Update state.
857         context.position = "catch";
858         context.thrownForkContext = ForkContext.newEmpty(forkContext);
859         context.lastOfTryIsReachable = forkContext.reachable;
860
861         // Merge thrown paths.
862         thrown.add(forkContext.head);
863         const thrownSegments = thrown.makeNext(0, -1);
864
865         // Fork to a bypass and the merged thrown path.
866         this.pushForkContext();
867         this.forkBypassPath();
868         this.forkContext.add(thrownSegments);
869     }
870
871     /**
872      * Makes a code path segment for a `finally` block.
873      *
874      * In the `finally` block, parallel paths are created. The parallel paths
875      * are used as leaving-paths. The leaving-paths are paths from `return`
876      * statements and `throw` statements in a `try` block or a `catch` block.
877      * @returns {void}
878      */
879     makeFinallyBlock() {
880         const context = this.tryContext;
881         let forkContext = this.forkContext;
882         const returned = context.returnedForkContext;
883         const thrown = context.thrownForkContext;
884         const headOfLeavingSegments = forkContext.head;
885
886         // Update state.
887         if (context.position === "catch") {
888
889             // Merges two paths from the `try` block and `catch` block.
890             this.popForkContext();
891             forkContext = this.forkContext;
892
893             context.lastOfCatchIsReachable = forkContext.reachable;
894         } else {
895             context.lastOfTryIsReachable = forkContext.reachable;
896         }
897         context.position = "finally";
898
899         if (returned.empty && thrown.empty) {
900
901             // This path does not leave.
902             return;
903         }
904
905         /*
906          * Create a parallel segment from merging returned and thrown.
907          * This segment will leave at the end of this finally block.
908          */
909         const segments = forkContext.makeNext(-1, -1);
910
911         for (let i = 0; i < forkContext.count; ++i) {
912             const prevSegsOfLeavingSegment = [headOfLeavingSegments[i]];
913
914             for (let j = 0; j < returned.segmentsList.length; ++j) {
915                 prevSegsOfLeavingSegment.push(returned.segmentsList[j][i]);
916             }
917             for (let j = 0; j < thrown.segmentsList.length; ++j) {
918                 prevSegsOfLeavingSegment.push(thrown.segmentsList[j][i]);
919             }
920
921             segments.push(
922                 CodePathSegment.newNext(
923                     this.idGenerator.next(),
924                     prevSegsOfLeavingSegment
925                 )
926             );
927         }
928
929         this.pushForkContext(true);
930         this.forkContext.add(segments);
931     }
932
933     /**
934      * Makes a code path segment from the first throwable node to the `catch`
935      * block or the `finally` block.
936      * @returns {void}
937      */
938     makeFirstThrowablePathInTryBlock() {
939         const forkContext = this.forkContext;
940
941         if (!forkContext.reachable) {
942             return;
943         }
944
945         const context = getThrowContext(this);
946
947         if (context === this ||
948             context.position !== "try" ||
949             !context.thrownForkContext.empty
950         ) {
951             return;
952         }
953
954         context.thrownForkContext.add(forkContext.head);
955         forkContext.replaceHead(forkContext.makeNext(-1, -1));
956     }
957
958     //--------------------------------------------------------------------------
959     // Loop Statements
960     //--------------------------------------------------------------------------
961
962     /**
963      * Creates a context object of a loop statement and stacks it.
964      * @param {string} type The type of the node which was triggered. One of
965      *   `WhileStatement`, `DoWhileStatement`, `ForStatement`, `ForInStatement`,
966      *   and `ForStatement`.
967      * @param {string|null} label A label of the node which was triggered.
968      * @returns {void}
969      */
970     pushLoopContext(type, label) {
971         const forkContext = this.forkContext;
972         const breakContext = this.pushBreakContext(true, label);
973
974         switch (type) {
975             case "WhileStatement":
976                 this.pushChoiceContext("loop", false);
977                 this.loopContext = {
978                     upper: this.loopContext,
979                     type,
980                     label,
981                     test: void 0,
982                     continueDestSegments: null,
983                     brokenForkContext: breakContext.brokenForkContext
984                 };
985                 break;
986
987             case "DoWhileStatement":
988                 this.pushChoiceContext("loop", false);
989                 this.loopContext = {
990                     upper: this.loopContext,
991                     type,
992                     label,
993                     test: void 0,
994                     entrySegments: null,
995                     continueForkContext: ForkContext.newEmpty(forkContext),
996                     brokenForkContext: breakContext.brokenForkContext
997                 };
998                 break;
999
1000             case "ForStatement":
1001                 this.pushChoiceContext("loop", false);
1002                 this.loopContext = {
1003                     upper: this.loopContext,
1004                     type,
1005                     label,
1006                     test: void 0,
1007                     endOfInitSegments: null,
1008                     testSegments: null,
1009                     endOfTestSegments: null,
1010                     updateSegments: null,
1011                     endOfUpdateSegments: null,
1012                     continueDestSegments: null,
1013                     brokenForkContext: breakContext.brokenForkContext
1014                 };
1015                 break;
1016
1017             case "ForInStatement":
1018             case "ForOfStatement":
1019                 this.loopContext = {
1020                     upper: this.loopContext,
1021                     type,
1022                     label,
1023                     prevSegments: null,
1024                     leftSegments: null,
1025                     endOfLeftSegments: null,
1026                     continueDestSegments: null,
1027                     brokenForkContext: breakContext.brokenForkContext
1028                 };
1029                 break;
1030
1031             /* istanbul ignore next */
1032             default:
1033                 throw new Error(`unknown type: "${type}"`);
1034         }
1035     }
1036
1037     /**
1038      * Pops the last context of a loop statement and finalizes it.
1039      * @returns {void}
1040      */
1041     popLoopContext() {
1042         const context = this.loopContext;
1043
1044         this.loopContext = context.upper;
1045
1046         const forkContext = this.forkContext;
1047         const brokenForkContext = this.popBreakContext().brokenForkContext;
1048
1049         // Creates a looped path.
1050         switch (context.type) {
1051             case "WhileStatement":
1052             case "ForStatement":
1053                 this.popChoiceContext();
1054                 makeLooped(
1055                     this,
1056                     forkContext.head,
1057                     context.continueDestSegments
1058                 );
1059                 break;
1060
1061             case "DoWhileStatement": {
1062                 const choiceContext = this.popChoiceContext();
1063
1064                 if (!choiceContext.processed) {
1065                     choiceContext.trueForkContext.add(forkContext.head);
1066                     choiceContext.falseForkContext.add(forkContext.head);
1067                 }
1068                 if (context.test !== true) {
1069                     brokenForkContext.addAll(choiceContext.falseForkContext);
1070                 }
1071
1072                 // `true` paths go to looping.
1073                 const segmentsList = choiceContext.trueForkContext.segmentsList;
1074
1075                 for (let i = 0; i < segmentsList.length; ++i) {
1076                     makeLooped(
1077                         this,
1078                         segmentsList[i],
1079                         context.entrySegments
1080                     );
1081                 }
1082                 break;
1083             }
1084
1085             case "ForInStatement":
1086             case "ForOfStatement":
1087                 brokenForkContext.add(forkContext.head);
1088                 makeLooped(
1089                     this,
1090                     forkContext.head,
1091                     context.leftSegments
1092                 );
1093                 break;
1094
1095             /* istanbul ignore next */
1096             default:
1097                 throw new Error("unreachable");
1098         }
1099
1100         // Go next.
1101         if (brokenForkContext.empty) {
1102             forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
1103         } else {
1104             forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
1105         }
1106     }
1107
1108     /**
1109      * Makes a code path segment for the test part of a WhileStatement.
1110      * @param {boolean|undefined} test The test value (only when constant).
1111      * @returns {void}
1112      */
1113     makeWhileTest(test) {
1114         const context = this.loopContext;
1115         const forkContext = this.forkContext;
1116         const testSegments = forkContext.makeNext(0, -1);
1117
1118         // Update state.
1119         context.test = test;
1120         context.continueDestSegments = testSegments;
1121         forkContext.replaceHead(testSegments);
1122     }
1123
1124     /**
1125      * Makes a code path segment for the body part of a WhileStatement.
1126      * @returns {void}
1127      */
1128     makeWhileBody() {
1129         const context = this.loopContext;
1130         const choiceContext = this.choiceContext;
1131         const forkContext = this.forkContext;
1132
1133         if (!choiceContext.processed) {
1134             choiceContext.trueForkContext.add(forkContext.head);
1135             choiceContext.falseForkContext.add(forkContext.head);
1136         }
1137
1138         // Update state.
1139         if (context.test !== true) {
1140             context.brokenForkContext.addAll(choiceContext.falseForkContext);
1141         }
1142         forkContext.replaceHead(choiceContext.trueForkContext.makeNext(0, -1));
1143     }
1144
1145     /**
1146      * Makes a code path segment for the body part of a DoWhileStatement.
1147      * @returns {void}
1148      */
1149     makeDoWhileBody() {
1150         const context = this.loopContext;
1151         const forkContext = this.forkContext;
1152         const bodySegments = forkContext.makeNext(-1, -1);
1153
1154         // Update state.
1155         context.entrySegments = bodySegments;
1156         forkContext.replaceHead(bodySegments);
1157     }
1158
1159     /**
1160      * Makes a code path segment for the test part of a DoWhileStatement.
1161      * @param {boolean|undefined} test The test value (only when constant).
1162      * @returns {void}
1163      */
1164     makeDoWhileTest(test) {
1165         const context = this.loopContext;
1166         const forkContext = this.forkContext;
1167
1168         context.test = test;
1169
1170         // Creates paths of `continue` statements.
1171         if (!context.continueForkContext.empty) {
1172             context.continueForkContext.add(forkContext.head);
1173             const testSegments = context.continueForkContext.makeNext(0, -1);
1174
1175             forkContext.replaceHead(testSegments);
1176         }
1177     }
1178
1179     /**
1180      * Makes a code path segment for the test part of a ForStatement.
1181      * @param {boolean|undefined} test The test value (only when constant).
1182      * @returns {void}
1183      */
1184     makeForTest(test) {
1185         const context = this.loopContext;
1186         const forkContext = this.forkContext;
1187         const endOfInitSegments = forkContext.head;
1188         const testSegments = forkContext.makeNext(-1, -1);
1189
1190         // Update state.
1191         context.test = test;
1192         context.endOfInitSegments = endOfInitSegments;
1193         context.continueDestSegments = context.testSegments = testSegments;
1194         forkContext.replaceHead(testSegments);
1195     }
1196
1197     /**
1198      * Makes a code path segment for the update part of a ForStatement.
1199      * @returns {void}
1200      */
1201     makeForUpdate() {
1202         const context = this.loopContext;
1203         const choiceContext = this.choiceContext;
1204         const forkContext = this.forkContext;
1205
1206         // Make the next paths of the test.
1207         if (context.testSegments) {
1208             finalizeTestSegmentsOfFor(
1209                 context,
1210                 choiceContext,
1211                 forkContext.head
1212             );
1213         } else {
1214             context.endOfInitSegments = forkContext.head;
1215         }
1216
1217         // Update state.
1218         const updateSegments = forkContext.makeDisconnected(-1, -1);
1219
1220         context.continueDestSegments = context.updateSegments = updateSegments;
1221         forkContext.replaceHead(updateSegments);
1222     }
1223
1224     /**
1225      * Makes a code path segment for the body part of a ForStatement.
1226      * @returns {void}
1227      */
1228     makeForBody() {
1229         const context = this.loopContext;
1230         const choiceContext = this.choiceContext;
1231         const forkContext = this.forkContext;
1232
1233         // Update state.
1234         if (context.updateSegments) {
1235             context.endOfUpdateSegments = forkContext.head;
1236
1237             // `update` -> `test`
1238             if (context.testSegments) {
1239                 makeLooped(
1240                     this,
1241                     context.endOfUpdateSegments,
1242                     context.testSegments
1243                 );
1244             }
1245         } else if (context.testSegments) {
1246             finalizeTestSegmentsOfFor(
1247                 context,
1248                 choiceContext,
1249                 forkContext.head
1250             );
1251         } else {
1252             context.endOfInitSegments = forkContext.head;
1253         }
1254
1255         let bodySegments = context.endOfTestSegments;
1256
1257         if (!bodySegments) {
1258
1259             /*
1260              * If there is not the `test` part, the `body` path comes from the
1261              * `init` part and the `update` part.
1262              */
1263             const prevForkContext = ForkContext.newEmpty(forkContext);
1264
1265             prevForkContext.add(context.endOfInitSegments);
1266             if (context.endOfUpdateSegments) {
1267                 prevForkContext.add(context.endOfUpdateSegments);
1268             }
1269
1270             bodySegments = prevForkContext.makeNext(0, -1);
1271         }
1272         context.continueDestSegments = context.continueDestSegments || bodySegments;
1273         forkContext.replaceHead(bodySegments);
1274     }
1275
1276     /**
1277      * Makes a code path segment for the left part of a ForInStatement and a
1278      * ForOfStatement.
1279      * @returns {void}
1280      */
1281     makeForInOfLeft() {
1282         const context = this.loopContext;
1283         const forkContext = this.forkContext;
1284         const leftSegments = forkContext.makeDisconnected(-1, -1);
1285
1286         // Update state.
1287         context.prevSegments = forkContext.head;
1288         context.leftSegments = context.continueDestSegments = leftSegments;
1289         forkContext.replaceHead(leftSegments);
1290     }
1291
1292     /**
1293      * Makes a code path segment for the right part of a ForInStatement and a
1294      * ForOfStatement.
1295      * @returns {void}
1296      */
1297     makeForInOfRight() {
1298         const context = this.loopContext;
1299         const forkContext = this.forkContext;
1300         const temp = ForkContext.newEmpty(forkContext);
1301
1302         temp.add(context.prevSegments);
1303         const rightSegments = temp.makeNext(-1, -1);
1304
1305         // Update state.
1306         context.endOfLeftSegments = forkContext.head;
1307         forkContext.replaceHead(rightSegments);
1308     }
1309
1310     /**
1311      * Makes a code path segment for the body part of a ForInStatement and a
1312      * ForOfStatement.
1313      * @returns {void}
1314      */
1315     makeForInOfBody() {
1316         const context = this.loopContext;
1317         const forkContext = this.forkContext;
1318         const temp = ForkContext.newEmpty(forkContext);
1319
1320         temp.add(context.endOfLeftSegments);
1321         const bodySegments = temp.makeNext(-1, -1);
1322
1323         // Make a path: `right` -> `left`.
1324         makeLooped(this, forkContext.head, context.leftSegments);
1325
1326         // Update state.
1327         context.brokenForkContext.add(forkContext.head);
1328         forkContext.replaceHead(bodySegments);
1329     }
1330
1331     //--------------------------------------------------------------------------
1332     // Control Statements
1333     //--------------------------------------------------------------------------
1334
1335     /**
1336      * Creates new context for BreakStatement.
1337      * @param {boolean} breakable The flag to indicate it can break by
1338      *      an unlabeled BreakStatement.
1339      * @param {string|null} label The label of this context.
1340      * @returns {Object} The new context.
1341      */
1342     pushBreakContext(breakable, label) {
1343         this.breakContext = {
1344             upper: this.breakContext,
1345             breakable,
1346             label,
1347             brokenForkContext: ForkContext.newEmpty(this.forkContext)
1348         };
1349         return this.breakContext;
1350     }
1351
1352     /**
1353      * Removes the top item of the break context stack.
1354      * @returns {Object} The removed context.
1355      */
1356     popBreakContext() {
1357         const context = this.breakContext;
1358         const forkContext = this.forkContext;
1359
1360         this.breakContext = context.upper;
1361
1362         // Process this context here for other than switches and loops.
1363         if (!context.breakable) {
1364             const brokenForkContext = context.brokenForkContext;
1365
1366             if (!brokenForkContext.empty) {
1367                 brokenForkContext.add(forkContext.head);
1368                 forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
1369             }
1370         }
1371
1372         return context;
1373     }
1374
1375     /**
1376      * Makes a path for a `break` statement.
1377      *
1378      * It registers the head segment to a context of `break`.
1379      * It makes new unreachable segment, then it set the head with the segment.
1380      * @param {string} label A label of the break statement.
1381      * @returns {void}
1382      */
1383     makeBreak(label) {
1384         const forkContext = this.forkContext;
1385
1386         if (!forkContext.reachable) {
1387             return;
1388         }
1389
1390         const context = getBreakContext(this, label);
1391
1392         /* istanbul ignore else: foolproof (syntax error) */
1393         if (context) {
1394             context.brokenForkContext.add(forkContext.head);
1395         }
1396
1397         forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
1398     }
1399
1400     /**
1401      * Makes a path for a `continue` statement.
1402      *
1403      * It makes a looping path.
1404      * It makes new unreachable segment, then it set the head with the segment.
1405      * @param {string} label A label of the continue statement.
1406      * @returns {void}
1407      */
1408     makeContinue(label) {
1409         const forkContext = this.forkContext;
1410
1411         if (!forkContext.reachable) {
1412             return;
1413         }
1414
1415         const context = getContinueContext(this, label);
1416
1417         /* istanbul ignore else: foolproof (syntax error) */
1418         if (context) {
1419             if (context.continueDestSegments) {
1420                 makeLooped(this, forkContext.head, context.continueDestSegments);
1421
1422                 // If the context is a for-in/of loop, this effects a break also.
1423                 if (context.type === "ForInStatement" ||
1424                     context.type === "ForOfStatement"
1425                 ) {
1426                     context.brokenForkContext.add(forkContext.head);
1427                 }
1428             } else {
1429                 context.continueForkContext.add(forkContext.head);
1430             }
1431         }
1432         forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
1433     }
1434
1435     /**
1436      * Makes a path for a `return` statement.
1437      *
1438      * It registers the head segment to a context of `return`.
1439      * It makes new unreachable segment, then it set the head with the segment.
1440      * @returns {void}
1441      */
1442     makeReturn() {
1443         const forkContext = this.forkContext;
1444
1445         if (forkContext.reachable) {
1446             getReturnContext(this).returnedForkContext.add(forkContext.head);
1447             forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
1448         }
1449     }
1450
1451     /**
1452      * Makes a path for a `throw` statement.
1453      *
1454      * It registers the head segment to a context of `throw`.
1455      * It makes new unreachable segment, then it set the head with the segment.
1456      * @returns {void}
1457      */
1458     makeThrow() {
1459         const forkContext = this.forkContext;
1460
1461         if (forkContext.reachable) {
1462             getThrowContext(this).thrownForkContext.add(forkContext.head);
1463             forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
1464         }
1465     }
1466
1467     /**
1468      * Makes the final path.
1469      * @returns {void}
1470      */
1471     makeFinal() {
1472         const segments = this.currentSegments;
1473
1474         if (segments.length > 0 && segments[0].reachable) {
1475             this.returnedForkContext.add(segments);
1476         }
1477     }
1478 }
1479
1480 module.exports = CodePathState;