]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/linter/code-path-analysis/code-path.js
0e666277094355801feda17e4f6cfe5b15d048fc
[pve-eslint.git] / 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 /**
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.
84 * @type {CodePathSegment}
85 */
86 get initialSegment() {
87 return this.internal.initialSegment;
88 }
89
90 /**
91 * Final code path segments.
92 * This array is a mix of `returnedSegments` and `thrownSegments`.
93 * @type {CodePathSegment[]}
94 */
95 get finalSegments() {
96 return this.internal.finalSegments;
97 }
98
99 /**
100 * Final code path segments which is with `return` statements.
101 * This array contains the last path segment if it's reachable.
102 * Since the reachable last path returns `undefined`.
103 * @type {CodePathSegment[]}
104 */
105 get returnedSegments() {
106 return this.internal.returnedForkContext;
107 }
108
109 /**
110 * Final code path segments which is with `throw` statements.
111 * @type {CodePathSegment[]}
112 */
113 get thrownSegments() {
114 return this.internal.thrownForkContext;
115 }
116
117 /**
118 * Current code path segments.
119 * @type {CodePathSegment[]}
120 */
121 get currentSegments() {
122 return this.internal.currentSegments;
123 }
124
125 /**
126 * Traverses all segments in this code path.
127 *
128 * codePath.traverseSegments(function(segment, controller) {
129 * // do something.
130 * });
131 *
132 * This method enumerates segments in order from the head.
133 *
134 * The `controller` object has two methods.
135 *
136 * - `controller.skip()` - Skip the following segments in this branch.
137 * - `controller.break()` - Skip all following segments.
138 * @param {Object} [options] Omittable.
139 * @param {CodePathSegment} [options.first] The first segment to traverse.
140 * @param {CodePathSegment} [options.last] The last segment to traverse.
141 * @param {Function} callback A callback function.
142 * @returns {void}
143 */
144 traverseSegments(options, callback) {
145 let resolvedOptions;
146 let resolvedCallback;
147
148 if (typeof options === "function") {
149 resolvedCallback = options;
150 resolvedOptions = {};
151 } else {
152 resolvedOptions = options || {};
153 resolvedCallback = callback;
154 }
155
156 const startSegment = resolvedOptions.first || this.internal.initialSegment;
157 const lastSegment = resolvedOptions.last;
158
159 let item = null;
160 let index = 0;
161 let end = 0;
162 let segment = null;
163 const visited = Object.create(null);
164 const stack = [[startSegment, 0]];
165 let skippedSegment = null;
166 let broken = false;
167 const controller = {
168 skip() {
169 if (stack.length <= 1) {
170 broken = true;
171 } else {
172 skippedSegment = stack[stack.length - 2][0];
173 }
174 },
175 break() {
176 broken = true;
177 }
178 };
179
180 /**
181 * Checks a given previous segment has been visited.
182 * @param {CodePathSegment} prevSegment A previous segment to check.
183 * @returns {boolean} `true` if the segment has been visited.
184 */
185 function isVisited(prevSegment) {
186 return (
187 visited[prevSegment.id] ||
188 segment.isLoopedPrevSegment(prevSegment)
189 );
190 }
191
192 while (stack.length > 0) {
193 item = stack[stack.length - 1];
194 segment = item[0];
195 index = item[1];
196
197 if (index === 0) {
198
199 // Skip if this segment has been visited already.
200 if (visited[segment.id]) {
201 stack.pop();
202 continue;
203 }
204
205 // Skip if all previous segments have not been visited.
206 if (segment !== startSegment &&
207 segment.prevSegments.length > 0 &&
208 !segment.prevSegments.every(isVisited)
209 ) {
210 stack.pop();
211 continue;
212 }
213
214 // Reset the flag of skipping if all branches have been skipped.
215 if (skippedSegment && segment.prevSegments.indexOf(skippedSegment) !== -1) {
216 skippedSegment = null;
217 }
218 visited[segment.id] = true;
219
220 // Call the callback when the first time.
221 if (!skippedSegment) {
222 resolvedCallback.call(this, segment, controller);
223 if (segment === lastSegment) {
224 controller.skip();
225 }
226 if (broken) {
227 break;
228 }
229 }
230 }
231
232 // Update the stack.
233 end = segment.nextSegments.length - 1;
234 if (index < end) {
235 item[1] += 1;
236 stack.push([segment.nextSegments[index], 0]);
237 } else if (index === end) {
238 item[0] = segment.nextSegments[index];
239 item[1] = 0;
240 } else {
241 stack.pop();
242 }
243 }
244 }
245 }
246
247 module.exports = CodePath;