|                                                                                                                                                                                                                                                                       |  | /** * @fileoverview The CodePathSegment class. * @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. * * Each segment is arranged in a series of linked lists (implemented by arrays) * that keep track of the previous and next segments in a code path. In this way, * you can navigate between all segments in any code path so long as you have a * reference to any segment in that code path. * * When first created, the segment is in a detached state, meaning that it knows the * segments that came before it but those segments don't know that this new segment * follows it. Only when `CodePathSegment#markUsed()` is called on a segment does it * officially become part of the code path by updating the previous segments to know * that this new segment follows. */class CodePathSegment {
    /**     * Creates a new instance.     * @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 reachable segments.         * @type {CodePathSegment[]}         */        this.nextSegments = [];
        /**         * An array of the previous reachable segments.         * @type {CodePathSegment[]}         */        this.prevSegments = allPrevSegments.filter(isReachable);
        /**         * An array of all next segments including reachable and unreachable.         * @type {CodePathSegment[]}         */        this.allNextSegments = [];
        /**         * An array of all previous segments including reachable and unreachable.         * @type {CodePathSegment[]}         */        this.allPrevSegments = allPrevSegments;
        /**         * A flag which shows this is reachable.         * @type {boolean}         */        this.reachable = reachable;
        // Internal data.
        Object.defineProperty(this, "internal", {            value: {
                // determines if the segment has been attached to the code path
                used: false,
                // array of previous segments coming from the end of a loop
                loopedPrevSegments: []            }        });
        /* c8 ignore start */        if (debug.enabled) {            this.internal.nodes = [];        }/* c8 ignore stop */    }
    /**     * 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.includes(segment);    }
    /**     * Creates the root segment.     * @param {string} id An identifier.     * @returns {CodePathSegment} The created segment.     */    static newRoot(id) {        return new CodePathSegment(id, [], true);    }
    /**     * Creates a new segment and appends it after the given segments.     * @param {string} id An identifier.     * @param {CodePathSegment[]} allPrevSegments An array of the previous segments     *      to append to.     * @returns {CodePathSegment} The created segment.     */    static newNext(id, allPrevSegments) {        return new CodePathSegment(            id,            CodePathSegment.flattenUnusedSegments(allPrevSegments),            allPrevSegments.some(isReachable)        );    }
    /**     * Creates an unreachable segment and appends it after the 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 removed.         */        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));    }
    /**     * Marks a given segment as 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) {
            /*             * If the segment is reachable, then it's officially part of the             * code path. This loops through all previous segments to update             * their list of next segments. Because the segment is reachable,             * it's added to both `nextSegments` and `allNextSegments`.             */            for (i = 0; i < segment.allPrevSegments.length; ++i) {                const prevSegment = segment.allPrevSegments[i];
                prevSegment.allNextSegments.push(segment);                prevSegment.nextSegments.push(segment);            }        } else {
            /*             * If the segment is not reachable, then it's not officially part of the             * code path. This loops through all previous segments to update             * their list of next segments. Because the segment is not reachable,             * it's added only to `allNextSegments`.             */            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);    }
    /**     * Creates a new array based on an array of segments. If any segment in the     * array is unused, then it is replaced by all of its previous segments.     * All used segments are returned as-is without replacement.     * @param {CodePathSegment[]} segments The array of segments to flatten.     * @returns {CodePathSegment[]} The flattened array.     */    static flattenUnusedSegments(segments) {        const done = new Set();
        for (let i = 0; i < segments.length; ++i) {            const segment = segments[i];
            // Ignores duplicated.
            if (done.has(segment)) {                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.has(prevSegment)) {                        done.add(prevSegment);                    }                }            } else {                done.add(segment);            }        }
        return [...done];    }}
module.exports = CodePathSegment;
 |