• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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