UNPKG

7.37 kBJavaScriptView Raw
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
12const debug = require("./debug-helpers");
13
14//------------------------------------------------------------------------------
15// Helpers
16//------------------------------------------------------------------------------
17
18/**
19 * Checks whether or not a given segment is reachable.
20 *
21 * @param {CodePathSegment} segment - A segment to check.
22 * @returns {boolean} `true` if the segment is reachable.
23 */
24function isReachable(segment) {
25 return segment.reachable;
26}
27
28//------------------------------------------------------------------------------
29// Public Interface
30//------------------------------------------------------------------------------
31
32/**
33 * A code path segment.
34 */
35class CodePathSegment {
36
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 *
102 * @param {CodePathSegment} segment - A previous segment to check.
103 * @returns {boolean} `true` if the segment is coming from the end of a loop.
104 */
105 isLoopedPrevSegment(segment) {
106 return this.internal.loopedPrevSegments.indexOf(segment) !== -1;
107 }
108
109 /**
110 * Creates the root segment.
111 *
112 * @param {string} id - An identifier.
113 * @returns {CodePathSegment} The created segment.
114 */
115 static newRoot(id) {
116 return new CodePathSegment(id, [], true);
117 }
118
119 /**
120 * Creates a segment that follows given segments.
121 *
122 * @param {string} id - An identifier.
123 * @param {CodePathSegment[]} allPrevSegments - An array of the previous segments.
124 * @returns {CodePathSegment} The created segment.
125 */
126 static newNext(id, allPrevSegments) {
127 return new CodePathSegment(
128 id,
129 CodePathSegment.flattenUnusedSegments(allPrevSegments),
130 allPrevSegments.some(isReachable)
131 );
132 }
133
134 /**
135 * Creates an unreachable segment that follows given segments.
136 *
137 * @param {string} id - An identifier.
138 * @param {CodePathSegment[]} allPrevSegments - An array of the previous segments.
139 * @returns {CodePathSegment} The created segment.
140 */
141 static newUnreachable(id, allPrevSegments) {
142 const segment = new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), false);
143
144 /*
145 * In `if (a) return a; foo();` case, the unreachable segment preceded by
146 * the return statement is not used but must not be remove.
147 */
148 CodePathSegment.markUsed(segment);
149
150 return segment;
151 }
152
153 /**
154 * Creates a segment that follows given segments.
155 * This factory method does not connect with `allPrevSegments`.
156 * But this inherits `reachable` flag.
157 *
158 * @param {string} id - An identifier.
159 * @param {CodePathSegment[]} allPrevSegments - An array of the previous segments.
160 * @returns {CodePathSegment} The created segment.
161 */
162 static newDisconnected(id, allPrevSegments) {
163 return new CodePathSegment(id, [], allPrevSegments.some(isReachable));
164 }
165
166 /**
167 * Makes a given segment being used.
168 *
169 * And this function registers the segment into the previous segments as a next.
170 *
171 * @param {CodePathSegment} segment - A segment to mark.
172 * @returns {void}
173 */
174 static markUsed(segment) {
175 if (segment.internal.used) {
176 return;
177 }
178 segment.internal.used = true;
179
180 let i;
181
182 if (segment.reachable) {
183 for (i = 0; i < segment.allPrevSegments.length; ++i) {
184 const prevSegment = segment.allPrevSegments[i];
185
186 prevSegment.allNextSegments.push(segment);
187 prevSegment.nextSegments.push(segment);
188 }
189 } else {
190 for (i = 0; i < segment.allPrevSegments.length; ++i) {
191 segment.allPrevSegments[i].allNextSegments.push(segment);
192 }
193 }
194 }
195
196 /**
197 * Marks a previous segment as looped.
198 *
199 * @param {CodePathSegment} segment - A segment.
200 * @param {CodePathSegment} prevSegment - A previous segment to mark.
201 * @returns {void}
202 */
203 static markPrevSegmentAsLooped(segment, prevSegment) {
204 segment.internal.loopedPrevSegments.push(prevSegment);
205 }
206
207 /**
208 * Replaces unused segments with the previous segments of each unused segment.
209 *
210 * @param {CodePathSegment[]} segments - An array of segments to replace.
211 * @returns {CodePathSegment[]} The replaced array.
212 */
213 static flattenUnusedSegments(segments) {
214 const done = Object.create(null);
215 const retv = [];
216
217 for (let i = 0; i < segments.length; ++i) {
218 const segment = segments[i];
219
220 // Ignores duplicated.
221 if (done[segment.id]) {
222 continue;
223 }
224
225 // Use previous segments if unused.
226 if (!segment.internal.used) {
227 for (let j = 0; j < segment.allPrevSegments.length; ++j) {
228 const prevSegment = segment.allPrevSegments[j];
229
230 if (!done[prevSegment.id]) {
231 done[prevSegment.id] = true;
232 retv.push(prevSegment);
233 }
234 }
235 } else {
236 done[segment.id] = true;
237 retv.push(segment);
238 }
239 }
240
241 return retv;
242 }
243}
244
245module.exports = CodePathSegment;