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 = [];
95 this.internal.exitNodes = [];
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.
104 isLoopedPrevSegment(segment) {
105 return this.internal.loopedPrevSegments.indexOf(segment) !== -1;
109 * Creates the root segment.
110 * @param {string} id An identifier.
111 * @returns {CodePathSegment} The created segment.
114 return new CodePathSegment(id, [], true);
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.
123 static newNext(id, allPrevSegments) {
124 return new CodePathSegment(
126 CodePathSegment.flattenUnusedSegments(allPrevSegments),
127 allPrevSegments.some(isReachable)
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.
137 static newUnreachable(id, allPrevSegments) {
138 const segment = new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), false);
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.
144 CodePathSegment.markUsed(segment);
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.
157 static newDisconnected(id, allPrevSegments) {
158 return new CodePathSegment(id, [], allPrevSegments.some(isReachable));
162 * Makes a given segment being used.
164 * And this function registers the segment into the previous segments as a next.
165 * @param {CodePathSegment} segment A segment to mark.
168 static markUsed(segment) {
169 if (segment.internal.used) {
172 segment.internal.used = true;
176 if (segment.reachable) {
177 for (i = 0; i < segment.allPrevSegments.length; ++i) {
178 const prevSegment = segment.allPrevSegments[i];
180 prevSegment.allNextSegments.push(segment);
181 prevSegment.nextSegments.push(segment);
184 for (i = 0; i < segment.allPrevSegments.length; ++i) {
185 segment.allPrevSegments[i].allNextSegments.push(segment);
191 * Marks a previous segment as looped.
192 * @param {CodePathSegment} segment A segment.
193 * @param {CodePathSegment} prevSegment A previous segment to mark.
196 static markPrevSegmentAsLooped(segment, prevSegment) {
197 segment.internal.loopedPrevSegments.push(prevSegment);
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.
205 static flattenUnusedSegments(segments) {
206 const done = Object.create(null);
209 for (let i = 0; i < segments.length; ++i) {
210 const segment = segments[i];
212 // Ignores duplicated.
213 if (done[segment.id]) {
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];
222 if (!done[prevSegment.id]) {
223 done[prevSegment.id] = true;
224 retv.push(prevSegment);
228 done[segment.id] = true;
237 module.exports = CodePathSegment;