.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / linter / code-path-analysis / code-path.js
1 /**
2  * @fileoverview A class of the code path.
3  * @author Toru Nagashima
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const CodePathState = require("./code-path-state");
13 const IdGenerator = require("./id-generator");
14
15 //------------------------------------------------------------------------------
16 // Public Interface
17 //------------------------------------------------------------------------------
18
19 /**
20  * A code path.
21  */
22 class CodePath {
23
24     // eslint-disable-next-line jsdoc/require-description
25     /**
26      * @param {string} id An identifier.
27      * @param {CodePath|null} upper The code path of the upper function scope.
28      * @param {Function} onLooped A callback function to notify looping.
29      */
30     constructor(id, upper, onLooped) {
31
32         /**
33          * The identifier of this code path.
34          * Rules use it to store additional information of each rule.
35          * @type {string}
36          */
37         this.id = id;
38
39         /**
40          * The code path of the upper function scope.
41          * @type {CodePath|null}
42          */
43         this.upper = upper;
44
45         /**
46          * The code paths of nested function scopes.
47          * @type {CodePath[]}
48          */
49         this.childCodePaths = [];
50
51         // Initializes internal state.
52         Object.defineProperty(
53             this,
54             "internal",
55             { value: new CodePathState(new IdGenerator(`${id}_`), onLooped) }
56         );
57
58         // Adds this into `childCodePaths` of `upper`.
59         if (upper) {
60             upper.childCodePaths.push(this);
61         }
62     }
63
64     /**
65      * Gets the state of a given code path.
66      * @param {CodePath} codePath A code path to get.
67      * @returns {CodePathState} The state of the code path.
68      */
69     static getState(codePath) {
70         return codePath.internal;
71     }
72
73     /**
74      * The initial code path segment.
75      * @type {CodePathSegment}
76      */
77     get initialSegment() {
78         return this.internal.initialSegment;
79     }
80
81     /**
82      * Final code path segments.
83      * This array is a mix of `returnedSegments` and `thrownSegments`.
84      * @type {CodePathSegment[]}
85      */
86     get finalSegments() {
87         return this.internal.finalSegments;
88     }
89
90     /**
91      * Final code path segments which is with `return` statements.
92      * This array contains the last path segment if it's reachable.
93      * Since the reachable last path returns `undefined`.
94      * @type {CodePathSegment[]}
95      */
96     get returnedSegments() {
97         return this.internal.returnedForkContext;
98     }
99
100     /**
101      * Final code path segments which is with `throw` statements.
102      * @type {CodePathSegment[]}
103      */
104     get thrownSegments() {
105         return this.internal.thrownForkContext;
106     }
107
108     /**
109      * Current code path segments.
110      * @type {CodePathSegment[]}
111      */
112     get currentSegments() {
113         return this.internal.currentSegments;
114     }
115
116     /**
117      * Traverses all segments in this code path.
118      *
119      *     codePath.traverseSegments(function(segment, controller) {
120      *         // do something.
121      *     });
122      *
123      * This method enumerates segments in order from the head.
124      *
125      * The `controller` object has two methods.
126      *
127      * - `controller.skip()` - Skip the following segments in this branch.
128      * - `controller.break()` - Skip all following segments.
129      * @param {Object} [options] Omittable.
130      * @param {CodePathSegment} [options.first] The first segment to traverse.
131      * @param {CodePathSegment} [options.last] The last segment to traverse.
132      * @param {Function} callback A callback function.
133      * @returns {void}
134      */
135     traverseSegments(options, callback) {
136         let resolvedOptions;
137         let resolvedCallback;
138
139         if (typeof options === "function") {
140             resolvedCallback = options;
141             resolvedOptions = {};
142         } else {
143             resolvedOptions = options || {};
144             resolvedCallback = callback;
145         }
146
147         const startSegment = resolvedOptions.first || this.internal.initialSegment;
148         const lastSegment = resolvedOptions.last;
149
150         let item = null;
151         let index = 0;
152         let end = 0;
153         let segment = null;
154         const visited = Object.create(null);
155         const stack = [[startSegment, 0]];
156         let skippedSegment = null;
157         let broken = false;
158         const controller = {
159             skip() {
160                 if (stack.length <= 1) {
161                     broken = true;
162                 } else {
163                     skippedSegment = stack[stack.length - 2][0];
164                 }
165             },
166             break() {
167                 broken = true;
168             }
169         };
170
171         /**
172          * Checks a given previous segment has been visited.
173          * @param {CodePathSegment} prevSegment A previous segment to check.
174          * @returns {boolean} `true` if the segment has been visited.
175          */
176         function isVisited(prevSegment) {
177             return (
178                 visited[prevSegment.id] ||
179                 segment.isLoopedPrevSegment(prevSegment)
180             );
181         }
182
183         while (stack.length > 0) {
184             item = stack[stack.length - 1];
185             segment = item[0];
186             index = item[1];
187
188             if (index === 0) {
189
190                 // Skip if this segment has been visited already.
191                 if (visited[segment.id]) {
192                     stack.pop();
193                     continue;
194                 }
195
196                 // Skip if all previous segments have not been visited.
197                 if (segment !== startSegment &&
198                     segment.prevSegments.length > 0 &&
199                     !segment.prevSegments.every(isVisited)
200                 ) {
201                     stack.pop();
202                     continue;
203                 }
204
205                 // Reset the flag of skipping if all branches have been skipped.
206                 if (skippedSegment && segment.prevSegments.indexOf(skippedSegment) !== -1) {
207                     skippedSegment = null;
208                 }
209                 visited[segment.id] = true;
210
211                 // Call the callback when the first time.
212                 if (!skippedSegment) {
213                     resolvedCallback.call(this, segment, controller);
214                     if (segment === lastSegment) {
215                         controller.skip();
216                     }
217                     if (broken) {
218                         break;
219                     }
220                 }
221             }
222
223             // Update the stack.
224             end = segment.nextSegments.length - 1;
225             if (index < end) {
226                 item[1] += 1;
227                 stack.push([segment.nextSegments[index], 0]);
228             } else if (index === end) {
229                 item[0] = segment.nextSegments[index];
230                 item[1] = 0;
231             } else {
232                 stack.pop();
233             }
234         }
235     }
236 }
237
238 module.exports = CodePath;