source: imaps-frontend/node_modules/eslint/lib/linter/code-path-analysis/code-path.js

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

Update repo after prototype presentation

  • Property mode set to 100644
File size: 11.5 KB
Line 
1/**
2 * @fileoverview A class of the code path.
3 * @author Toru Nagashima
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const CodePathState = require("./code-path-state");
13const IdGenerator = require("./id-generator");
14
15//------------------------------------------------------------------------------
16// Public Interface
17//------------------------------------------------------------------------------
18
19/**
20 * A code path.
21 */
22class CodePath {
23
24 /**
25 * Creates a new instance.
26 * @param {Object} options Options for the function (see below).
27 * @param {string} options.id An identifier.
28 * @param {string} options.origin The type of code path origin.
29 * @param {CodePath|null} options.upper The code path of the upper function scope.
30 * @param {Function} options.onLooped A callback function to notify looping.
31 */
32 constructor({ id, origin, upper, onLooped }) {
33
34 /**
35 * The identifier of this code path.
36 * Rules use it to store additional information of each rule.
37 * @type {string}
38 */
39 this.id = id;
40
41 /**
42 * The reason that this code path was started. May be "program",
43 * "function", "class-field-initializer", or "class-static-block".
44 * @type {string}
45 */
46 this.origin = origin;
47
48 /**
49 * The code path of the upper function scope.
50 * @type {CodePath|null}
51 */
52 this.upper = upper;
53
54 /**
55 * The code paths of nested function scopes.
56 * @type {CodePath[]}
57 */
58 this.childCodePaths = [];
59
60 // Initializes internal state.
61 Object.defineProperty(
62 this,
63 "internal",
64 { value: new CodePathState(new IdGenerator(`${id}_`), onLooped) }
65 );
66
67 // Adds this into `childCodePaths` of `upper`.
68 if (upper) {
69 upper.childCodePaths.push(this);
70 }
71 }
72
73 /**
74 * Gets the state of a given code path.
75 * @param {CodePath} codePath A code path to get.
76 * @returns {CodePathState} The state of the code path.
77 */
78 static getState(codePath) {
79 return codePath.internal;
80 }
81
82 /**
83 * The initial code path segment. This is the segment that is at the head
84 * of the code path.
85 * This is a passthrough to the underlying `CodePathState`.
86 * @type {CodePathSegment}
87 */
88 get initialSegment() {
89 return this.internal.initialSegment;
90 }
91
92 /**
93 * Final code path segments. These are the terminal (tail) segments in the
94 * code path, which is the combination of `returnedSegments` and `thrownSegments`.
95 * All segments in this array are reachable.
96 * This is a passthrough to the underlying `CodePathState`.
97 * @type {CodePathSegment[]}
98 */
99 get finalSegments() {
100 return this.internal.finalSegments;
101 }
102
103 /**
104 * Final code path segments that represent normal completion of the code path.
105 * For functions, this means both explicit `return` statements and implicit returns,
106 * such as the last reachable segment in a function that does not have an
107 * explicit `return` as this implicitly returns `undefined`. For scripts,
108 * modules, class field initializers, and class static blocks, this means
109 * all lines of code have been executed.
110 * These segments are also present in `finalSegments`.
111 * This is a passthrough to the underlying `CodePathState`.
112 * @type {CodePathSegment[]}
113 */
114 get returnedSegments() {
115 return this.internal.returnedForkContext;
116 }
117
118 /**
119 * Final code path segments that represent `throw` statements.
120 * This is a passthrough to the underlying `CodePathState`.
121 * These segments are also present in `finalSegments`.
122 * @type {CodePathSegment[]}
123 */
124 get thrownSegments() {
125 return this.internal.thrownForkContext;
126 }
127
128 /**
129 * Tracks the traversal of the code path through each segment. This array
130 * starts empty and segments are added or removed as the code path is
131 * traversed. This array always ends up empty at the end of a code path
132 * traversal. The `CodePathState` uses this to track its progress through
133 * the code path.
134 * This is a passthrough to the underlying `CodePathState`.
135 * @type {CodePathSegment[]}
136 * @deprecated
137 */
138 get currentSegments() {
139 return this.internal.currentSegments;
140 }
141
142 /**
143 * Traverses all segments in this code path.
144 *
145 * codePath.traverseSegments((segment, controller) => {
146 * // do something.
147 * });
148 *
149 * This method enumerates segments in order from the head.
150 *
151 * The `controller` argument has two methods:
152 *
153 * - `skip()` - skips the following segments in this branch
154 * - `break()` - skips all following segments in the traversal
155 *
156 * A note on the parameters: the `options` argument is optional. This means
157 * the first argument might be an options object or the callback function.
158 * @param {Object} [optionsOrCallback] Optional first and last segments to traverse.
159 * @param {CodePathSegment} [optionsOrCallback.first] The first segment to traverse.
160 * @param {CodePathSegment} [optionsOrCallback.last] The last segment to traverse.
161 * @param {Function} callback A callback function.
162 * @returns {void}
163 */
164 traverseSegments(optionsOrCallback, callback) {
165
166 // normalize the arguments into a callback and options
167 let resolvedOptions;
168 let resolvedCallback;
169
170 if (typeof optionsOrCallback === "function") {
171 resolvedCallback = optionsOrCallback;
172 resolvedOptions = {};
173 } else {
174 resolvedOptions = optionsOrCallback || {};
175 resolvedCallback = callback;
176 }
177
178 // determine where to start traversing from based on the options
179 const startSegment = resolvedOptions.first || this.internal.initialSegment;
180 const lastSegment = resolvedOptions.last;
181
182 // set up initial location information
183 let record = null;
184 let index = 0;
185 let end = 0;
186 let segment = null;
187
188 // segments that have already been visited during traversal
189 const visited = new Set();
190
191 // tracks the traversal steps
192 const stack = [[startSegment, 0]];
193
194 // tracks the last skipped segment during traversal
195 let skippedSegment = null;
196
197 // indicates if we exited early from the traversal
198 let broken = false;
199
200 /**
201 * Maintains traversal state.
202 */
203 const controller = {
204
205 /**
206 * Skip the following segments in this branch.
207 * @returns {void}
208 */
209 skip() {
210 if (stack.length <= 1) {
211 broken = true;
212 } else {
213 skippedSegment = stack[stack.length - 2][0];
214 }
215 },
216
217 /**
218 * Stop traversal completely - do not traverse to any
219 * other segments.
220 * @returns {void}
221 */
222 break() {
223 broken = true;
224 }
225 };
226
227 /**
228 * Checks if a given previous segment has been visited.
229 * @param {CodePathSegment} prevSegment A previous segment to check.
230 * @returns {boolean} `true` if the segment has been visited.
231 */
232 function isVisited(prevSegment) {
233 return (
234 visited.has(prevSegment) ||
235 segment.isLoopedPrevSegment(prevSegment)
236 );
237 }
238
239 // the traversal
240 while (stack.length > 0) {
241
242 /*
243 * This isn't a pure stack. We use the top record all the time
244 * but don't always pop it off. The record is popped only if
245 * one of the following is true:
246 *
247 * 1) We have already visited the segment.
248 * 2) We have not visited *all* of the previous segments.
249 * 3) We have traversed past the available next segments.
250 *
251 * Otherwise, we just read the value and sometimes modify the
252 * record as we traverse.
253 */
254 record = stack[stack.length - 1];
255 segment = record[0];
256 index = record[1];
257
258 if (index === 0) {
259
260 // Skip if this segment has been visited already.
261 if (visited.has(segment)) {
262 stack.pop();
263 continue;
264 }
265
266 // Skip if all previous segments have not been visited.
267 if (segment !== startSegment &&
268 segment.prevSegments.length > 0 &&
269 !segment.prevSegments.every(isVisited)
270 ) {
271 stack.pop();
272 continue;
273 }
274
275 // Reset the skipping flag if all branches have been skipped.
276 if (skippedSegment && segment.prevSegments.includes(skippedSegment)) {
277 skippedSegment = null;
278 }
279 visited.add(segment);
280
281 /*
282 * If the most recent segment hasn't been skipped, then we call
283 * the callback, passing in the segment and the controller.
284 */
285 if (!skippedSegment) {
286 resolvedCallback.call(this, segment, controller);
287
288 // exit if we're at the last segment
289 if (segment === lastSegment) {
290 controller.skip();
291 }
292
293 /*
294 * If the previous statement was executed, or if the callback
295 * called a method on the controller, we might need to exit the
296 * loop, so check for that and break accordingly.
297 */
298 if (broken) {
299 break;
300 }
301 }
302 }
303
304 // Update the stack.
305 end = segment.nextSegments.length - 1;
306 if (index < end) {
307
308 /*
309 * If we haven't yet visited all of the next segments, update
310 * the current top record on the stack to the next index to visit
311 * and then push a record for the current segment on top.
312 *
313 * Setting the current top record's index lets us know how many
314 * times we've been here and ensures that the segment won't be
315 * reprocessed (because we only process segments with an index
316 * of 0).
317 */
318 record[1] += 1;
319 stack.push([segment.nextSegments[index], 0]);
320 } else if (index === end) {
321
322 /*
323 * If we are at the last next segment, then reset the top record
324 * in the stack to next segment and set its index to 0 so it will
325 * be processed next.
326 */
327 record[0] = segment.nextSegments[index];
328 record[1] = 0;
329 } else {
330
331 /*
332 * If index > end, that means we have no more segments that need
333 * processing. So, we pop that record off of the stack in order to
334 * continue traversing at the next level up.
335 */
336 stack.pop();
337 }
338 }
339 }
340}
341
342module.exports = CodePath;
Note: See TracBrowser for help on using the repository browser.