source: imaps-frontend/node_modules/eslint/lib/linter/code-path-analysis/fork-context.js@ d565449

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

Update repo after prototype presentation

  • Property mode set to 100644
File size: 12.9 KB
Line 
1/**
2 * @fileoverview A class to operate forking.
3 *
4 * This is state of forking.
5 * This has a fork list and manages it.
6 *
7 * @author Toru Nagashima
8 */
9
10"use strict";
11
12//------------------------------------------------------------------------------
13// Requirements
14//------------------------------------------------------------------------------
15
16const assert = require("assert"),
17 CodePathSegment = require("./code-path-segment");
18
19//------------------------------------------------------------------------------
20// Helpers
21//------------------------------------------------------------------------------
22
23/**
24 * Determines whether or not a given segment is reachable.
25 * @param {CodePathSegment} segment The segment to check.
26 * @returns {boolean} `true` if the segment is reachable.
27 */
28function isReachable(segment) {
29 return segment.reachable;
30}
31
32/**
33 * Creates a new segment for each fork in the given context and appends it
34 * to the end of the specified range of segments. Ultimately, this ends up calling
35 * `new CodePathSegment()` for each of the forks using the `create` argument
36 * as a wrapper around special behavior.
37 *
38 * The `startIndex` and `endIndex` arguments specify a range of segments in
39 * `context` that should become `allPrevSegments` for the newly created
40 * `CodePathSegment` objects.
41 *
42 * When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and
43 * `end` is `-1`, this creates two new segments, `[g, h]`. This `g` is appended to
44 * the end of the path from `a`, `c`, and `e`. This `h` is appended to the end of
45 * `b`, `d`, and `f`.
46 * @param {ForkContext} context An instance from which the previous segments
47 * will be obtained.
48 * @param {number} startIndex The index of the first segment in the context
49 * that should be specified as previous segments for the newly created segments.
50 * @param {number} endIndex The index of the last segment in the context
51 * that should be specified as previous segments for the newly created segments.
52 * @param {Function} create A function that creates new `CodePathSegment`
53 * instances in a particular way. See the `CodePathSegment.new*` methods.
54 * @returns {Array<CodePathSegment>} An array of the newly-created segments.
55 */
56function createSegments(context, startIndex, endIndex, create) {
57
58 /** @type {Array<Array<CodePathSegment>>} */
59 const list = context.segmentsList;
60
61 /*
62 * Both `startIndex` and `endIndex` work the same way: if the number is zero
63 * or more, then the number is used as-is. If the number is negative,
64 * then that number is added to the length of the segments list to
65 * determine the index to use. That means -1 for either argument
66 * is the last element, -2 is the second to last, and so on.
67 *
68 * So if `startIndex` is 0, `endIndex` is -1, and `list.length` is 3, the
69 * effective `startIndex` is 0 and the effective `endIndex` is 2, so this function
70 * will include items at indices 0, 1, and 2.
71 *
72 * Therefore, if `startIndex` is -1 and `endIndex` is -1, that means we'll only
73 * be using the last segment in `list`.
74 */
75 const normalizedBegin = startIndex >= 0 ? startIndex : list.length + startIndex;
76 const normalizedEnd = endIndex >= 0 ? endIndex : list.length + endIndex;
77
78 /** @type {Array<CodePathSegment>} */
79 const segments = [];
80
81 for (let i = 0; i < context.count; ++i) {
82
83 // this is passed into `new CodePathSegment` to add to code path.
84 const allPrevSegments = [];
85
86 for (let j = normalizedBegin; j <= normalizedEnd; ++j) {
87 allPrevSegments.push(list[j][i]);
88 }
89
90 // note: `create` is just a wrapper that augments `new CodePathSegment`.
91 segments.push(create(context.idGenerator.next(), allPrevSegments));
92 }
93
94 return segments;
95}
96
97/**
98 * Inside of a `finally` block we end up with two parallel paths. If the code path
99 * exits by a control statement (such as `break` or `continue`) from the `finally`
100 * block, then we need to merge the remaining parallel paths back into one.
101 * @param {ForkContext} context The fork context to work on.
102 * @param {Array<CodePathSegment>} segments Segments to merge.
103 * @returns {Array<CodePathSegment>} The merged segments.
104 */
105function mergeExtraSegments(context, segments) {
106 let currentSegments = segments;
107
108 /*
109 * We need to ensure that the array returned from this function contains no more
110 * than the number of segments that the context allows. `context.count` indicates
111 * how many items should be in the returned array to ensure that the new segment
112 * entries will line up with the already existing segment entries.
113 */
114 while (currentSegments.length > context.count) {
115 const merged = [];
116
117 /*
118 * Because `context.count` is a factor of 2 inside of a `finally` block,
119 * we can divide the segment count by 2 to merge the paths together.
120 * This loops through each segment in the list and creates a new `CodePathSegment`
121 * that has the segment and the segment two slots away as previous segments.
122 *
123 * If `currentSegments` is [a,b,c,d], this will create new segments e and f, such
124 * that:
125 *
126 * When `i` is 0:
127 * a->e
128 * c->e
129 *
130 * When `i` is 1:
131 * b->f
132 * d->f
133 */
134 for (let i = 0, length = Math.floor(currentSegments.length / 2); i < length; ++i) {
135 merged.push(CodePathSegment.newNext(
136 context.idGenerator.next(),
137 [currentSegments[i], currentSegments[i + length]]
138 ));
139 }
140
141 /*
142 * Go through the loop condition one more time to see if we have the
143 * number of segments for the context. If not, we'll keep merging paths
144 * of the merged segments until we get there.
145 */
146 currentSegments = merged;
147 }
148
149 return currentSegments;
150}
151
152//------------------------------------------------------------------------------
153// Public Interface
154//------------------------------------------------------------------------------
155
156/**
157 * Manages the forking of code paths.
158 */
159class ForkContext {
160
161 /**
162 * Creates a new instance.
163 * @param {IdGenerator} idGenerator An identifier generator for segments.
164 * @param {ForkContext|null} upper The preceding fork context.
165 * @param {number} count The number of parallel segments in each element
166 * of `segmentsList`.
167 */
168 constructor(idGenerator, upper, count) {
169
170 /**
171 * The ID generator that will generate segment IDs for any new
172 * segments that are created.
173 * @type {IdGenerator}
174 */
175 this.idGenerator = idGenerator;
176
177 /**
178 * The preceding fork context.
179 * @type {ForkContext|null}
180 */
181 this.upper = upper;
182
183 /**
184 * The number of elements in each element of `segmentsList`. In most
185 * cases, this is 1 but can be 2 when there is a `finally` present,
186 * which forks the code path outside of normal flow. In the case of nested
187 * `finally` blocks, this can be a multiple of 2.
188 * @type {number}
189 */
190 this.count = count;
191
192 /**
193 * The segments within this context. Each element in this array has
194 * `count` elements that represent one step in each fork. For example,
195 * when `segmentsList` is `[[a, b], [c, d], [e, f]]`, there is one path
196 * a->c->e and one path b->d->f, and `count` is 2 because each element
197 * is an array with two elements.
198 * @type {Array<Array<CodePathSegment>>}
199 */
200 this.segmentsList = [];
201 }
202
203 /**
204 * The segments that begin this fork context.
205 * @type {Array<CodePathSegment>}
206 */
207 get head() {
208 const list = this.segmentsList;
209
210 return list.length === 0 ? [] : list[list.length - 1];
211 }
212
213 /**
214 * Indicates if the context contains no segments.
215 * @type {boolean}
216 */
217 get empty() {
218 return this.segmentsList.length === 0;
219 }
220
221 /**
222 * Indicates if there are any segments that are reachable.
223 * @type {boolean}
224 */
225 get reachable() {
226 const segments = this.head;
227
228 return segments.length > 0 && segments.some(isReachable);
229 }
230
231 /**
232 * Creates new segments in this context and appends them to the end of the
233 * already existing `CodePathSegment`s specified by `startIndex` and
234 * `endIndex`.
235 * @param {number} startIndex The index of the first segment in the context
236 * that should be specified as previous segments for the newly created segments.
237 * @param {number} endIndex The index of the last segment in the context
238 * that should be specified as previous segments for the newly created segments.
239 * @returns {Array<CodePathSegment>} An array of the newly created segments.
240 */
241 makeNext(startIndex, endIndex) {
242 return createSegments(this, startIndex, endIndex, CodePathSegment.newNext);
243 }
244
245 /**
246 * Creates new unreachable segments in this context and appends them to the end of the
247 * already existing `CodePathSegment`s specified by `startIndex` and
248 * `endIndex`.
249 * @param {number} startIndex The index of the first segment in the context
250 * that should be specified as previous segments for the newly created segments.
251 * @param {number} endIndex The index of the last segment in the context
252 * that should be specified as previous segments for the newly created segments.
253 * @returns {Array<CodePathSegment>} An array of the newly created segments.
254 */
255 makeUnreachable(startIndex, endIndex) {
256 return createSegments(this, startIndex, endIndex, CodePathSegment.newUnreachable);
257 }
258
259 /**
260 * Creates new segments in this context and does not append them to the end
261 * of the already existing `CodePathSegment`s specified by `startIndex` and
262 * `endIndex`. The `startIndex` and `endIndex` are only used to determine if
263 * the new segments should be reachable. If any of the segments in this range
264 * are reachable then the new segments are also reachable; otherwise, the new
265 * segments are unreachable.
266 * @param {number} startIndex The index of the first segment in the context
267 * that should be considered for reachability.
268 * @param {number} endIndex The index of the last segment in the context
269 * that should be considered for reachability.
270 * @returns {Array<CodePathSegment>} An array of the newly created segments.
271 */
272 makeDisconnected(startIndex, endIndex) {
273 return createSegments(this, startIndex, endIndex, CodePathSegment.newDisconnected);
274 }
275
276 /**
277 * Adds segments to the head of this context.
278 * @param {Array<CodePathSegment>} segments The segments to add.
279 * @returns {void}
280 */
281 add(segments) {
282 assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
283 this.segmentsList.push(mergeExtraSegments(this, segments));
284 }
285
286 /**
287 * Replaces the head segments with the given segments.
288 * The current head segments are removed.
289 * @param {Array<CodePathSegment>} replacementHeadSegments The new head segments.
290 * @returns {void}
291 */
292 replaceHead(replacementHeadSegments) {
293 assert(
294 replacementHeadSegments.length >= this.count,
295 `${replacementHeadSegments.length} >= ${this.count}`
296 );
297 this.segmentsList.splice(-1, 1, mergeExtraSegments(this, replacementHeadSegments));
298 }
299
300 /**
301 * Adds all segments of a given fork context into this context.
302 * @param {ForkContext} otherForkContext The fork context to add from.
303 * @returns {void}
304 */
305 addAll(otherForkContext) {
306 assert(otherForkContext.count === this.count);
307 this.segmentsList.push(...otherForkContext.segmentsList);
308 }
309
310 /**
311 * Clears all segments in this context.
312 * @returns {void}
313 */
314 clear() {
315 this.segmentsList = [];
316 }
317
318 /**
319 * Creates a new root context, meaning that there are no parent
320 * fork contexts.
321 * @param {IdGenerator} idGenerator An identifier generator for segments.
322 * @returns {ForkContext} New fork context.
323 */
324 static newRoot(idGenerator) {
325 const context = new ForkContext(idGenerator, null, 1);
326
327 context.add([CodePathSegment.newRoot(idGenerator.next())]);
328
329 return context;
330 }
331
332 /**
333 * Creates an empty fork context preceded by a given context.
334 * @param {ForkContext} parentContext The parent fork context.
335 * @param {boolean} shouldForkLeavingPath Indicates that we are inside of
336 * a `finally` block and should therefore fork the path that leaves
337 * `finally`.
338 * @returns {ForkContext} New fork context.
339 */
340 static newEmpty(parentContext, shouldForkLeavingPath) {
341 return new ForkContext(
342 parentContext.idGenerator,
343 parentContext,
344 (shouldForkLeavingPath ? 2 : 1) * parentContext.count
345 );
346 }
347}
348
349module.exports = ForkContext;
Note: See TracBrowser for help on using the repository browser.