]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/linter/code-path-analysis/fork-context.js
2e872b5c0dbf5a9a779554ed858a9fb4f9484bab
[pve-eslint.git] / eslint / lib / linter / code-path-analysis / fork-context.js
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
16 const assert = require("assert"),
17 CodePathSegment = require("./code-path-segment");
18
19 //------------------------------------------------------------------------------
20 // Helpers
21 //------------------------------------------------------------------------------
22
23 /**
24 * Gets whether or not a given segment is reachable.
25 * @param {CodePathSegment} segment A segment to get.
26 * @returns {boolean} `true` if the segment is reachable.
27 */
28 function isReachable(segment) {
29 return segment.reachable;
30 }
31
32 /**
33 * Creates new segments from the specific range of `context.segmentsList`.
34 *
35 * 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 */
44 function makeSegments(context, begin, end, create) {
45 const list = context.segmentsList;
46
47 const normalizedBegin = begin >= 0 ? begin : list.length + begin;
48 const normalizedEnd = end >= 0 ? end : list.length + end;
49
50 const segments = [];
51
52 for (let i = 0; i < context.count; ++i) {
53 const allPrevSegments = [];
54
55 for (let j = normalizedBegin; j <= normalizedEnd; ++j) {
56 allPrevSegments.push(list[j][i]);
57 }
58
59 segments.push(create(context.idGenerator.next(), allPrevSegments));
60 }
61
62 return segments;
63 }
64
65 /**
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.
73 */
74 function mergeExtraSegments(context, segments) {
75 let currentSegments = segments;
76
77 while (currentSegments.length > context.count) {
78 const merged = [];
79
80 for (let i = 0, length = currentSegments.length / 2 | 0; i < length; ++i) {
81 merged.push(CodePathSegment.newNext(
82 context.idGenerator.next(),
83 [currentSegments[i], currentSegments[i + length]]
84 ));
85 }
86 currentSegments = merged;
87 }
88 return currentSegments;
89 }
90
91 //------------------------------------------------------------------------------
92 // Public Interface
93 //------------------------------------------------------------------------------
94
95 /**
96 * A class to manage forking.
97 */
98 class ForkContext {
99
100 // eslint-disable-next-line jsdoc/require-description
101 /**
102 * @param {IdGenerator} idGenerator An identifier generator for segments.
103 * @param {ForkContext|null} upper An upper fork context.
104 * @param {number} count A number of parallel segments.
105 */
106 constructor(idGenerator, upper, count) {
107 this.idGenerator = idGenerator;
108 this.upper = upper;
109 this.count = count;
110 this.segmentsList = [];
111 }
112
113 /**
114 * The head segments.
115 * @type {CodePathSegment[]}
116 */
117 get head() {
118 const list = this.segmentsList;
119
120 return list.length === 0 ? [] : list[list.length - 1];
121 }
122
123 /**
124 * A flag which shows empty.
125 * @type {boolean}
126 */
127 get empty() {
128 return this.segmentsList.length === 0;
129 }
130
131 /**
132 * A flag which shows reachable.
133 * @type {boolean}
134 */
135 get reachable() {
136 const segments = this.head;
137
138 return segments.length > 0 && segments.some(isReachable);
139 }
140
141 /**
142 * Creates new segments from this context.
143 * @param {number} begin The first index of previous segments.
144 * @param {number} end The last index of previous segments.
145 * @returns {CodePathSegment[]} New segments.
146 */
147 makeNext(begin, end) {
148 return makeSegments(this, begin, end, CodePathSegment.newNext);
149 }
150
151 /**
152 * Creates new segments from this context.
153 * The new segments is always unreachable.
154 * @param {number} begin The first index of previous segments.
155 * @param {number} end The last index of previous segments.
156 * @returns {CodePathSegment[]} New segments.
157 */
158 makeUnreachable(begin, end) {
159 return makeSegments(this, begin, end, CodePathSegment.newUnreachable);
160 }
161
162 /**
163 * Creates new segments from this context.
164 * The new segments don't have connections for previous segments.
165 * But these inherit the reachable flag from this context.
166 * @param {number} begin The first index of previous segments.
167 * @param {number} end The last index of previous segments.
168 * @returns {CodePathSegment[]} New segments.
169 */
170 makeDisconnected(begin, end) {
171 return makeSegments(this, begin, end, CodePathSegment.newDisconnected);
172 }
173
174 /**
175 * Adds segments into this context.
176 * The added segments become the head.
177 * @param {CodePathSegment[]} segments Segments to add.
178 * @returns {void}
179 */
180 add(segments) {
181 assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
182
183 this.segmentsList.push(mergeExtraSegments(this, segments));
184 }
185
186 /**
187 * Replaces the head segments with given segments.
188 * The current head segments are removed.
189 * @param {CodePathSegment[]} segments Segments to add.
190 * @returns {void}
191 */
192 replaceHead(segments) {
193 assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
194
195 this.segmentsList.splice(-1, 1, mergeExtraSegments(this, segments));
196 }
197
198 /**
199 * Adds all segments of a given fork context into this context.
200 * @param {ForkContext} context A fork context to add.
201 * @returns {void}
202 */
203 addAll(context) {
204 assert(context.count === this.count);
205
206 const source = context.segmentsList;
207
208 for (let i = 0; i < source.length; ++i) {
209 this.segmentsList.push(source[i]);
210 }
211 }
212
213 /**
214 * Clears all segments in this context.
215 * @returns {void}
216 */
217 clear() {
218 this.segmentsList = [];
219 }
220
221 /**
222 * Creates the root fork context.
223 * @param {IdGenerator} idGenerator An identifier generator for segments.
224 * @returns {ForkContext} New fork context.
225 */
226 static newRoot(idGenerator) {
227 const context = new ForkContext(idGenerator, null, 1);
228
229 context.add([CodePathSegment.newRoot(idGenerator.next())]);
230
231 return context;
232 }
233
234 /**
235 * Creates an empty fork context preceded by a given context.
236 * @param {ForkContext} parentContext The parent fork context.
237 * @param {boolean} forkLeavingPath A flag which shows inside of `finally` block.
238 * @returns {ForkContext} New fork context.
239 */
240 static newEmpty(parentContext, forkLeavingPath) {
241 return new ForkContext(
242 parentContext.idGenerator,
243 parentContext,
244 (forkLeavingPath ? 2 : 1) * parentContext.count
245 );
246 }
247 }
248
249 module.exports = ForkContext;