Skip to content

Commit bc77c9a

Browse files
nzakasfasttime
andauthored
chore: Document and refactor ForkContext (#17566)
* chore: Document and refactor ForkContext" * Update lib/linter/code-path-analysis/fork-context.js Co-authored-by: Francesco Trotta <[email protected]> * Update lib/linter/code-path-analysis/fork-context.js Co-authored-by: Francesco Trotta <[email protected]> * Update lib/linter/code-path-analysis/fork-context.js Co-authored-by: Francesco Trotta <[email protected]> * Clean up comments --------- Co-authored-by: Francesco Trotta <[email protected]>
1 parent 24e1f14 commit bc77c9a

1 file changed

Lines changed: 173 additions & 72 deletions

File tree

lib/linter/code-path-analysis/fork-context.js

Lines changed: 173 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -21,70 +21,131 @@ const assert = require("assert"),
2121
//------------------------------------------------------------------------------
2222

2323
/**
24-
* Gets whether or not a given segment is reachable.
25-
* @param {CodePathSegment} segment A segment to get.
24+
* Determines whether or not a given segment is reachable.
25+
* @param {CodePathSegment} segment The segment to check.
2626
* @returns {boolean} `true` if the segment is reachable.
2727
*/
2828
function isReachable(segment) {
2929
return segment.reachable;
3030
}
3131

3232
/**
33-
* Creates new segments from the specific range of `context.segmentsList`.
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.
3441
*
3542
* When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and
36-
* `end` is `-1`, this creates `[g, h]`. This `g` is from `a`, `c`, and `e`.
37-
* This `h` is from `b`, `d`, and `f`.
38-
* @param {ForkContext} context An instance.
39-
* @param {number} begin The first index of the previous segments.
40-
* @param {number} end The last index of the previous segments.
41-
* @param {Function} create A factory function of new segments.
42-
* @returns {CodePathSegment[]} New segments.
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.
4355
*/
44-
function makeSegments(context, begin, end, create) {
56+
function createSegments(context, startIndex, endIndex, create) {
57+
58+
/** @type {Array<Array<CodePathSegment>>} */
4559
const list = context.segmentsList;
4660

47-
const normalizedBegin = begin >= 0 ? begin : list.length + begin;
48-
const normalizedEnd = end >= 0 ? end : list.length + end;
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;
4977

78+
/** @type {Array<CodePathSegment>} */
5079
const segments = [];
5180

5281
for (let i = 0; i < context.count; ++i) {
82+
83+
// this is passed into `new CodePathSegment` to add to code path.
5384
const allPrevSegments = [];
5485

5586
for (let j = normalizedBegin; j <= normalizedEnd; ++j) {
5687
allPrevSegments.push(list[j][i]);
5788
}
5889

90+
// note: `create` is just a wrapper that augments `new CodePathSegment`.
5991
segments.push(create(context.idGenerator.next(), allPrevSegments));
6092
}
6193

6294
return segments;
6395
}
6496

6597
/**
66-
* `segments` becomes doubly in a `finally` block. Then if a code path exits by a
67-
* control statement (such as `break`, `continue`) from the `finally` block, the
68-
* destination's segments may be half of the source segments. In that case, this
69-
* merges segments.
70-
* @param {ForkContext} context An instance.
71-
* @param {CodePathSegment[]} segments Segments to merge.
72-
* @returns {CodePathSegment[]} The merged segments.
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.
73104
*/
74105
function mergeExtraSegments(context, segments) {
75106
let currentSegments = segments;
76107

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+
*/
77114
while (currentSegments.length > context.count) {
78115
const merged = [];
79116

80-
for (let i = 0, length = currentSegments.length / 2 | 0; i < length; ++i) {
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) {
81135
merged.push(CodePathSegment.newNext(
82136
context.idGenerator.next(),
83137
[currentSegments[i], currentSegments[i + length]]
84138
));
85139
}
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+
*/
86146
currentSegments = merged;
87147
}
148+
88149
return currentSegments;
89150
}
90151

@@ -93,25 +154,55 @@ function mergeExtraSegments(context, segments) {
93154
//------------------------------------------------------------------------------
94155

95156
/**
96-
* A class to manage forking.
157+
* Manages the forking of code paths.
97158
*/
98159
class ForkContext {
99160

100161
/**
162+
* Creates a new instance.
101163
* @param {IdGenerator} idGenerator An identifier generator for segments.
102-
* @param {ForkContext|null} upper An upper fork context.
103-
* @param {number} count A number of parallel 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`.
104167
*/
105168
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+
*/
106175
this.idGenerator = idGenerator;
176+
177+
/**
178+
* The preceding fork context.
179+
* @type {ForkContext|null}
180+
*/
107181
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+
*/
108190
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+
*/
109200
this.segmentsList = [];
110201
}
111202

112203
/**
113-
* The head segments.
114-
* @type {CodePathSegment[]}
204+
* The segments that begin this fork context.
205+
* @type {Array<CodePathSegment>}
115206
*/
116207
get head() {
117208
const list = this.segmentsList;
@@ -120,15 +211,15 @@ class ForkContext {
120211
}
121212

122213
/**
123-
* A flag which shows empty.
214+
* Indicates if the context contains no segments.
124215
* @type {boolean}
125216
*/
126217
get empty() {
127218
return this.segmentsList.length === 0;
128219
}
129220

130221
/**
131-
* A flag which shows reachable.
222+
* Indicates if there are any segments that are reachable.
132223
* @type {boolean}
133224
*/
134225
get reachable() {
@@ -138,75 +229,82 @@ class ForkContext {
138229
}
139230

140231
/**
141-
* Creates new segments from this context.
142-
* @param {number} begin The first index of previous segments.
143-
* @param {number} end The last index of previous segments.
144-
* @returns {CodePathSegment[]} New segments.
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.
145240
*/
146-
makeNext(begin, end) {
147-
return makeSegments(this, begin, end, CodePathSegment.newNext);
241+
makeNext(startIndex, endIndex) {
242+
return createSegments(this, startIndex, endIndex, CodePathSegment.newNext);
148243
}
149244

150245
/**
151-
* Creates new segments from this context.
152-
* The new segments is always unreachable.
153-
* @param {number} begin The first index of previous segments.
154-
* @param {number} end The last index of previous segments.
155-
* @returns {CodePathSegment[]} New segments.
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.
156254
*/
157-
makeUnreachable(begin, end) {
158-
return makeSegments(this, begin, end, CodePathSegment.newUnreachable);
255+
makeUnreachable(startIndex, endIndex) {
256+
return createSegments(this, startIndex, endIndex, CodePathSegment.newUnreachable);
159257
}
160258

161259
/**
162-
* Creates new segments from this context.
163-
* The new segments don't have connections for previous segments.
164-
* But these inherit the reachable flag from this context.
165-
* @param {number} begin The first index of previous segments.
166-
* @param {number} end The last index of previous segments.
167-
* @returns {CodePathSegment[]} New segments.
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.
168271
*/
169-
makeDisconnected(begin, end) {
170-
return makeSegments(this, begin, end, CodePathSegment.newDisconnected);
272+
makeDisconnected(startIndex, endIndex) {
273+
return createSegments(this, startIndex, endIndex, CodePathSegment.newDisconnected);
171274
}
172275

173276
/**
174-
* Adds segments into this context.
175-
* The added segments become the head.
176-
* @param {CodePathSegment[]} segments Segments to add.
277+
* Adds segments to the head of this context.
278+
* @param {Array<CodePathSegment>} segments The segments to add.
177279
* @returns {void}
178280
*/
179281
add(segments) {
180282
assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
181-
182283
this.segmentsList.push(mergeExtraSegments(this, segments));
183284
}
184285

185286
/**
186-
* Replaces the head segments with given segments.
287+
* Replaces the head segments with the given segments.
187288
* The current head segments are removed.
188-
* @param {CodePathSegment[]} segments Segments to add.
289+
* @param {Array<CodePathSegment>} replacementHeadSegments The new head segments.
189290
* @returns {void}
190291
*/
191-
replaceHead(segments) {
192-
assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
193-
194-
this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments));
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));
195298
}
196299

197300
/**
198301
* Adds all segments of a given fork context into this context.
199-
* @param {ForkContext} context A fork context to add.
302+
* @param {ForkContext} otherForkContext The fork context to add from.
200303
* @returns {void}
201304
*/
202-
addAll(context) {
203-
assert(context.count === this.count);
204-
205-
const source = context.segmentsList;
206-
207-
for (let i = 0; i < source.length; ++i) {
208-
this.segmentsList.push(source[i]);
209-
}
305+
addAll(otherForkContext) {
306+
assert(otherForkContext.count === this.count);
307+
this.segmentsList.push(...otherForkContext.segmentsList);
210308
}
211309

212310
/**
@@ -218,7 +316,8 @@ class ForkContext {
218316
}
219317

220318
/**
221-
* Creates the root fork context.
319+
* Creates a new root context, meaning that there are no parent
320+
* fork contexts.
222321
* @param {IdGenerator} idGenerator An identifier generator for segments.
223322
* @returns {ForkContext} New fork context.
224323
*/
@@ -233,14 +332,16 @@ class ForkContext {
233332
/**
234333
* Creates an empty fork context preceded by a given context.
235334
* @param {ForkContext} parentContext The parent fork context.
236-
* @param {boolean} forkLeavingPath A flag which shows inside of `finally` block.
335+
* @param {boolean} shouldForkLeavingPath Indicates that we are inside of
336+
* a `finally` block and should therefore fork the path that leaves
337+
* `finally`.
237338
* @returns {ForkContext} New fork context.
238339
*/
239-
static newEmpty(parentContext, forkLeavingPath) {
340+
static newEmpty(parentContext, shouldForkLeavingPath) {
240341
return new ForkContext(
241342
parentContext.idGenerator,
242343
parentContext,
243-
(forkLeavingPath ? 2 : 1) * parentContext.count
344+
(shouldForkLeavingPath ? 2 : 1) * parentContext.count
244345
);
245346
}
246347
}

0 commit comments

Comments
 (0)