]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/linter/code-path-analysis/code-path-segment.js
first commit
[pve-eslint.git] / eslint / lib / linter / code-path-analysis / code-path-segment.js
1 /**
2 * @fileoverview A class of the code path segment.
3 * @author Toru Nagashima
4 */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const debug = require("./debug-helpers");
13
14 //------------------------------------------------------------------------------
15 // Helpers
16 //------------------------------------------------------------------------------
17
18 /**
19 * Checks whether or not a given segment is reachable.
20 * @param {CodePathSegment} segment A segment to check.
21 * @returns {boolean} `true` if the segment is reachable.
22 */
23 function isReachable(segment) {
24 return segment.reachable;
25 }
26
27 //------------------------------------------------------------------------------
28 // Public Interface
29 //------------------------------------------------------------------------------
30
31 /**
32 * A code path segment.
33 */
34 class CodePathSegment {
35
36 // eslint-disable-next-line jsdoc/require-description
37 /**
38 * @param {string} id An identifier.
39 * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
40 * This array includes unreachable segments.
41 * @param {boolean} reachable A flag which shows this is reachable.
42 */
43 constructor(id, allPrevSegments, reachable) {
44
45 /**
46 * The identifier of this code path.
47 * Rules use it to store additional information of each rule.
48 * @type {string}
49 */
50 this.id = id;
51
52 /**
53 * An array of the next segments.
54 * @type {CodePathSegment[]}
55 */
56 this.nextSegments = [];
57
58 /**
59 * An array of the previous segments.
60 * @type {CodePathSegment[]}
61 */
62 this.prevSegments = allPrevSegments.filter(isReachable);
63
64 /**
65 * An array of the next segments.
66 * This array includes unreachable segments.
67 * @type {CodePathSegment[]}
68 */
69 this.allNextSegments = [];
70
71 /**
72 * An array of the previous segments.
73 * This array includes unreachable segments.
74 * @type {CodePathSegment[]}
75 */
76 this.allPrevSegments = allPrevSegments;
77
78 /**
79 * A flag which shows this is reachable.
80 * @type {boolean}
81 */
82 this.reachable = reachable;
83
84 // Internal data.
85 Object.defineProperty(this, "internal", {
86 value: {
87 used: false,
88 loopedPrevSegments: []
89 }
90 });
91
92 /* istanbul ignore if */
93 if (debug.enabled) {
94 this.internal.nodes = [];
95 this.internal.exitNodes = [];
96 }
97 }
98
99 /**
100 * Checks a given previous segment is coming from the end of a loop.
101 * @param {CodePathSegment} segment A previous segment to check.
102 * @returns {boolean} `true` if the segment is coming from the end of a loop.
103 */
104 isLoopedPrevSegment(segment) {
105 return this.internal.loopedPrevSegments.indexOf(segment) !== -1;
106 }
107
108 /**
109 * Creates the root segment.
110 * @param {string} id An identifier.
111 * @returns {CodePathSegment} The created segment.
112 */
113 static newRoot(id) {
114 return new CodePathSegment(id, [], true);
115 }
116
117 /**
118 * Creates a segment that follows given segments.
119 * @param {string} id An identifier.
120 * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
121 * @returns {CodePathSegment} The created segment.
122 */
123 static newNext(id, allPrevSegments) {
124 return new CodePathSegment(
125 id,
126 CodePathSegment.flattenUnusedSegments(allPrevSegments),
127 allPrevSegments.some(isReachable)
128 );
129 }
130
131 /**
132 * Creates an unreachable segment that follows given segments.
133 * @param {string} id An identifier.
134 * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
135 * @returns {CodePathSegment} The created segment.
136 */
137 static newUnreachable(id, allPrevSegments) {
138 const segment = new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), false);
139
140 /*
141 * In `if (a) return a; foo();` case, the unreachable segment preceded by
142 * the return statement is not used but must not be remove.
143 */
144 CodePathSegment.markUsed(segment);
145
146 return segment;
147 }
148
149 /**
150 * Creates a segment that follows given segments.
151 * This factory method does not connect with `allPrevSegments`.
152 * But this inherits `reachable` flag.
153 * @param {string} id An identifier.
154 * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
155 * @returns {CodePathSegment} The created segment.
156 */
157 static newDisconnected(id, allPrevSegments) {
158 return new CodePathSegment(id, [], allPrevSegments.some(isReachable));
159 }
160
161 /**
162 * Makes a given segment being used.
163 *
164 * And this function registers the segment into the previous segments as a next.
165 * @param {CodePathSegment} segment A segment to mark.
166 * @returns {void}
167 */
168 static markUsed(segment) {
169 if (segment.internal.used) {
170 return;
171 }
172 segment.internal.used = true;
173
174 let i;
175
176 if (segment.reachable) {
177 for (i = 0; i < segment.allPrevSegments.length; ++i) {
178 const prevSegment = segment.allPrevSegments[i];
179
180 prevSegment.allNextSegments.push(segment);
181 prevSegment.nextSegments.push(segment);
182 }
183 } else {
184 for (i = 0; i < segment.allPrevSegments.length; ++i) {
185 segment.allPrevSegments[i].allNextSegments.push(segment);
186 }
187 }
188 }
189
190 /**
191 * Marks a previous segment as looped.
192 * @param {CodePathSegment} segment A segment.
193 * @param {CodePathSegment} prevSegment A previous segment to mark.
194 * @returns {void}
195 */
196 static markPrevSegmentAsLooped(segment, prevSegment) {
197 segment.internal.loopedPrevSegments.push(prevSegment);
198 }
199
200 /**
201 * Replaces unused segments with the previous segments of each unused segment.
202 * @param {CodePathSegment[]} segments An array of segments to replace.
203 * @returns {CodePathSegment[]} The replaced array.
204 */
205 static flattenUnusedSegments(segments) {
206 const done = Object.create(null);
207 const retv = [];
208
209 for (let i = 0; i < segments.length; ++i) {
210 const segment = segments[i];
211
212 // Ignores duplicated.
213 if (done[segment.id]) {
214 continue;
215 }
216
217 // Use previous segments if unused.
218 if (!segment.internal.used) {
219 for (let j = 0; j < segment.allPrevSegments.length; ++j) {
220 const prevSegment = segment.allPrevSegments[j];
221
222 if (!done[prevSegment.id]) {
223 done[prevSegment.id] = true;
224 retv.push(prevSegment);
225 }
226 }
227 } else {
228 done[segment.id] = true;
229 retv.push(segment);
230 }
231 }
232
233 return retv;
234 }
235 }
236
237 module.exports = CodePathSegment;