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 * @param {CodePathSegment} segment A segment to check. 21 * @returns {boolean} `true` if the segment is reachable. 22 */ 23function isReachable(segment) { 24 return segment.reachable; 25} 26 27//------------------------------------------------------------------------------ 28// Public Interface 29//------------------------------------------------------------------------------ 30 31/** 32 * A code path segment. 33 */ 34class 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 } 96 } 97 98 /** 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. 102 */ 103 isLoopedPrevSegment(segment) { 104 return this.internal.loopedPrevSegments.indexOf(segment) !== -1; 105 } 106 107 /** 108 * Creates the root segment. 109 * @param {string} id An identifier. 110 * @returns {CodePathSegment} The created segment. 111 */ 112 static newRoot(id) { 113 return new CodePathSegment(id, [], true); 114 } 115 116 /** 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. 121 */ 122 static newNext(id, allPrevSegments) { 123 return new CodePathSegment( 124 id, 125 CodePathSegment.flattenUnusedSegments(allPrevSegments), 126 allPrevSegments.some(isReachable) 127 ); 128 } 129 130 /** 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. 135 */ 136 static newUnreachable(id, allPrevSegments) { 137 const segment = new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), false); 138 139 /* 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. 142 */ 143 CodePathSegment.markUsed(segment); 144 145 return segment; 146 } 147 148 /** 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. 155 */ 156 static newDisconnected(id, allPrevSegments) { 157 return new CodePathSegment(id, [], allPrevSegments.some(isReachable)); 158 } 159 160 /** 161 * Makes a given segment being used. 162 * 163 * And this function registers the segment into the previous segments as a next. 164 * @param {CodePathSegment} segment A segment to mark. 165 * @returns {void} 166 */ 167 static markUsed(segment) { 168 if (segment.internal.used) { 169 return; 170 } 171 segment.internal.used = true; 172 173 let i; 174 175 if (segment.reachable) { 176 for (i = 0; i < segment.allPrevSegments.length; ++i) { 177 const prevSegment = segment.allPrevSegments[i]; 178 179 prevSegment.allNextSegments.push(segment); 180 prevSegment.nextSegments.push(segment); 181 } 182 } else { 183 for (i = 0; i < segment.allPrevSegments.length; ++i) { 184 segment.allPrevSegments[i].allNextSegments.push(segment); 185 } 186 } 187 } 188 189 /** 190 * Marks a previous segment as looped. 191 * @param {CodePathSegment} segment A segment. 192 * @param {CodePathSegment} prevSegment A previous segment to mark. 193 * @returns {void} 194 */ 195 static markPrevSegmentAsLooped(segment, prevSegment) { 196 segment.internal.loopedPrevSegments.push(prevSegment); 197 } 198 199 /** 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. 203 */ 204 static flattenUnusedSegments(segments) { 205 const done = Object.create(null); 206 const retv = []; 207 208 for (let i = 0; i < segments.length; ++i) { 209 const segment = segments[i]; 210 211 // Ignores duplicated. 212 if (done[segment.id]) { 213 continue; 214 } 215 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]; 220 221 if (!done[prevSegment.id]) { 222 done[prevSegment.id] = true; 223 retv.push(prevSegment); 224 } 225 } 226 } else { 227 done[segment.id] = true; 228 retv.push(segment); 229 } 230 } 231 232 return retv; 233 } 234} 235 236module.exports = CodePathSegment; 237