You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
7.1 KiB
236 lines
7.1 KiB
/** |
|
* @fileoverview A class of the code path segment. |
|
* @author Toru Nagashima |
|
*/ |
|
|
|
"use strict"; |
|
|
|
//------------------------------------------------------------------------------ |
|
// Requirements |
|
//------------------------------------------------------------------------------ |
|
|
|
const debug = require("./debug-helpers"); |
|
|
|
//------------------------------------------------------------------------------ |
|
// Helpers |
|
//------------------------------------------------------------------------------ |
|
|
|
/** |
|
* Checks whether or not a given segment is reachable. |
|
* @param {CodePathSegment} segment A segment to check. |
|
* @returns {boolean} `true` if the segment is reachable. |
|
*/ |
|
function isReachable(segment) { |
|
return segment.reachable; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Public Interface |
|
//------------------------------------------------------------------------------ |
|
|
|
/** |
|
* A code path segment. |
|
*/ |
|
class CodePathSegment { |
|
|
|
// eslint-disable-next-line jsdoc/require-description |
|
/** |
|
* @param {string} id An identifier. |
|
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments. |
|
* This array includes unreachable segments. |
|
* @param {boolean} reachable A flag which shows this is reachable. |
|
*/ |
|
constructor(id, allPrevSegments, reachable) { |
|
|
|
/** |
|
* The identifier of this code path. |
|
* Rules use it to store additional information of each rule. |
|
* @type {string} |
|
*/ |
|
this.id = id; |
|
|
|
/** |
|
* An array of the next segments. |
|
* @type {CodePathSegment[]} |
|
*/ |
|
this.nextSegments = []; |
|
|
|
/** |
|
* An array of the previous segments. |
|
* @type {CodePathSegment[]} |
|
*/ |
|
this.prevSegments = allPrevSegments.filter(isReachable); |
|
|
|
/** |
|
* An array of the next segments. |
|
* This array includes unreachable segments. |
|
* @type {CodePathSegment[]} |
|
*/ |
|
this.allNextSegments = []; |
|
|
|
/** |
|
* An array of the previous segments. |
|
* This array includes unreachable segments. |
|
* @type {CodePathSegment[]} |
|
*/ |
|
this.allPrevSegments = allPrevSegments; |
|
|
|
/** |
|
* A flag which shows this is reachable. |
|
* @type {boolean} |
|
*/ |
|
this.reachable = reachable; |
|
|
|
// Internal data. |
|
Object.defineProperty(this, "internal", { |
|
value: { |
|
used: false, |
|
loopedPrevSegments: [] |
|
} |
|
}); |
|
|
|
/* istanbul ignore if */ |
|
if (debug.enabled) { |
|
this.internal.nodes = []; |
|
} |
|
} |
|
|
|
/** |
|
* Checks a given previous segment is coming from the end of a loop. |
|
* @param {CodePathSegment} segment A previous segment to check. |
|
* @returns {boolean} `true` if the segment is coming from the end of a loop. |
|
*/ |
|
isLoopedPrevSegment(segment) { |
|
return this.internal.loopedPrevSegments.indexOf(segment) !== -1; |
|
} |
|
|
|
/** |
|
* Creates the root segment. |
|
* @param {string} id An identifier. |
|
* @returns {CodePathSegment} The created segment. |
|
*/ |
|
static newRoot(id) { |
|
return new CodePathSegment(id, [], true); |
|
} |
|
|
|
/** |
|
* Creates a segment that follows given segments. |
|
* @param {string} id An identifier. |
|
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments. |
|
* @returns {CodePathSegment} The created segment. |
|
*/ |
|
static newNext(id, allPrevSegments) { |
|
return new CodePathSegment( |
|
id, |
|
CodePathSegment.flattenUnusedSegments(allPrevSegments), |
|
allPrevSegments.some(isReachable) |
|
); |
|
} |
|
|
|
/** |
|
* Creates an unreachable segment that follows given segments. |
|
* @param {string} id An identifier. |
|
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments. |
|
* @returns {CodePathSegment} The created segment. |
|
*/ |
|
static newUnreachable(id, allPrevSegments) { |
|
const segment = new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), false); |
|
|
|
/* |
|
* In `if (a) return a; foo();` case, the unreachable segment preceded by |
|
* the return statement is not used but must not be remove. |
|
*/ |
|
CodePathSegment.markUsed(segment); |
|
|
|
return segment; |
|
} |
|
|
|
/** |
|
* Creates a segment that follows given segments. |
|
* This factory method does not connect with `allPrevSegments`. |
|
* But this inherits `reachable` flag. |
|
* @param {string} id An identifier. |
|
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments. |
|
* @returns {CodePathSegment} The created segment. |
|
*/ |
|
static newDisconnected(id, allPrevSegments) { |
|
return new CodePathSegment(id, [], allPrevSegments.some(isReachable)); |
|
} |
|
|
|
/** |
|
* Makes a given segment being used. |
|
* |
|
* And this function registers the segment into the previous segments as a next. |
|
* @param {CodePathSegment} segment A segment to mark. |
|
* @returns {void} |
|
*/ |
|
static markUsed(segment) { |
|
if (segment.internal.used) { |
|
return; |
|
} |
|
segment.internal.used = true; |
|
|
|
let i; |
|
|
|
if (segment.reachable) { |
|
for (i = 0; i < segment.allPrevSegments.length; ++i) { |
|
const prevSegment = segment.allPrevSegments[i]; |
|
|
|
prevSegment.allNextSegments.push(segment); |
|
prevSegment.nextSegments.push(segment); |
|
} |
|
} else { |
|
for (i = 0; i < segment.allPrevSegments.length; ++i) { |
|
segment.allPrevSegments[i].allNextSegments.push(segment); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Marks a previous segment as looped. |
|
* @param {CodePathSegment} segment A segment. |
|
* @param {CodePathSegment} prevSegment A previous segment to mark. |
|
* @returns {void} |
|
*/ |
|
static markPrevSegmentAsLooped(segment, prevSegment) { |
|
segment.internal.loopedPrevSegments.push(prevSegment); |
|
} |
|
|
|
/** |
|
* Replaces unused segments with the previous segments of each unused segment. |
|
* @param {CodePathSegment[]} segments An array of segments to replace. |
|
* @returns {CodePathSegment[]} The replaced array. |
|
*/ |
|
static flattenUnusedSegments(segments) { |
|
const done = Object.create(null); |
|
const retv = []; |
|
|
|
for (let i = 0; i < segments.length; ++i) { |
|
const segment = segments[i]; |
|
|
|
// Ignores duplicated. |
|
if (done[segment.id]) { |
|
continue; |
|
} |
|
|
|
// Use previous segments if unused. |
|
if (!segment.internal.used) { |
|
for (let j = 0; j < segment.allPrevSegments.length; ++j) { |
|
const prevSegment = segment.allPrevSegments[j]; |
|
|
|
if (!done[prevSegment.id]) { |
|
done[prevSegment.id] = true; |
|
retv.push(prevSegment); |
|
} |
|
} |
|
} else { |
|
done[segment.id] = true; |
|
retv.push(segment); |
|
} |
|
} |
|
|
|
return retv; |
|
} |
|
} |
|
|
|
module.exports = CodePathSegment;
|
|
|