2 * @fileoverview A class of the code path segment.
3 * @author Toru Nagashima
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const debug = require("./debug-helpers");
14 //------------------------------------------------------------------------------
16 //------------------------------------------------------------------------------
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.
23 function isReachable(segment) {
24 return segment.reachable;
27 //------------------------------------------------------------------------------
29 //------------------------------------------------------------------------------
32 * A code path segment.
34 class CodePathSegment {
36 // eslint-disable-next-line jsdoc/require-description
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.
43 constructor(id, allPrevSegments, reachable) {
46 * The identifier of this code path.
47 * Rules use it to store additional information of each rule.
53 * An array of the next segments.
54 * @type {CodePathSegment[]}
56 this.nextSegments = [];
59 * An array of the previous segments.
60 * @type {CodePathSegment[]}
62 this.prevSegments = allPrevSegments.filter(isReachable);
65 * An array of the next segments.
66 * This array includes unreachable segments.
67 * @type {CodePathSegment[]}
69 this.allNextSegments = [];
72 * An array of the previous segments.
73 * This array includes unreachable segments.
74 * @type {CodePathSegment[]}
76 this.allPrevSegments = allPrevSegments;
79 * A flag which shows this is reachable.
82 this.reachable = reachable;
85 Object.defineProperty(this, "internal", {
88 loopedPrevSegments: []
92 /* istanbul ignore if */
94 this.internal.nodes = [];
99 * Checks a given previous segment is coming from the end of a loop.
100 * @param {CodePathSegment} segment A previous segment to check.
101 * @returns {boolean} `true` if the segment is coming from the end of a loop.
103 isLoopedPrevSegment(segment) {
104 return this.internal.loopedPrevSegments.indexOf(segment) !== -1;
108 * Creates the root segment.
109 * @param {string} id An identifier.
110 * @returns {CodePathSegment} The created segment.
113 return new CodePathSegment(id, [], true);
117 * Creates a segment that follows given segments.
118 * @param {string} id An identifier.
119 * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
120 * @returns {CodePathSegment} The created segment.
122 static newNext(id, allPrevSegments) {
123 return new CodePathSegment(
125 CodePathSegment.flattenUnusedSegments(allPrevSegments),
126 allPrevSegments.some(isReachable)
131 * Creates an unreachable segment that follows given segments.
132 * @param {string} id An identifier.
133 * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
134 * @returns {CodePathSegment} The created segment.
136 static newUnreachable(id, allPrevSegments) {
137 const segment = new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), false);
140 * In `if (a) return a; foo();` case, the unreachable segment preceded by
141 * the return statement is not used but must not be remove.
143 CodePathSegment.markUsed(segment);
149 * Creates a segment that follows given segments.
150 * This factory method does not connect with `allPrevSegments`.
151 * But this inherits `reachable` flag.
152 * @param {string} id An identifier.
153 * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
154 * @returns {CodePathSegment} The created segment.
156 static newDisconnected(id, allPrevSegments) {
157 return new CodePathSegment(id, [], allPrevSegments.some(isReachable));
161 * Makes a given segment being used.
163 * And this function registers the segment into the previous segments as a next.
164 * @param {CodePathSegment} segment A segment to mark.
167 static markUsed(segment) {
168 if (segment.internal.used) {
171 segment.internal.used = true;
175 if (segment.reachable) {
176 for (i = 0; i < segment.allPrevSegments.length; ++i) {
177 const prevSegment = segment.allPrevSegments[i];
179 prevSegment.allNextSegments.push(segment);
180 prevSegment.nextSegments.push(segment);
183 for (i = 0; i < segment.allPrevSegments.length; ++i) {
184 segment.allPrevSegments[i].allNextSegments.push(segment);
190 * Marks a previous segment as looped.
191 * @param {CodePathSegment} segment A segment.
192 * @param {CodePathSegment} prevSegment A previous segment to mark.
195 static markPrevSegmentAsLooped(segment, prevSegment) {
196 segment.internal.loopedPrevSegments.push(prevSegment);
200 * Replaces unused segments with the previous segments of each unused segment.
201 * @param {CodePathSegment[]} segments An array of segments to replace.
202 * @returns {CodePathSegment[]} The replaced array.
204 static flattenUnusedSegments(segments) {
205 const done = Object.create(null);
208 for (let i = 0; i < segments.length; ++i) {
209 const segment = segments[i];
211 // Ignores duplicated.
212 if (done[segment.id]) {
216 // Use previous segments if unused.
217 if (!segment.internal.used) {
218 for (let j = 0; j < segment.allPrevSegments.length; ++j) {
219 const prevSegment = segment.allPrevSegments[j];
221 if (!done[prevSegment.id]) {
222 done[prevSegment.id] = true;
223 retv.push(prevSegment);
227 done[segment.id] = true;
236 module.exports = CodePathSegment;