File size: 8,812 Bytes
bc20498
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
/**
 * @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;