code:
/**
 * @fileoverview Helpers to debug for code path analysis.
 * @author Toru Nagashima
 */

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const debug = require("debug")("eslint:code-path");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
 * Gets id of a given segment.
 * @param {CodePathSegment} segment A segment to get.
 * @returns {string} Id of the segment.
 */
/* c8 ignore next */
function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc -- Ignoring
    return segment.id + (segment.reachable ? "" : "!");
}

/**
 * Get string for the given node and operation.
 * @param {ASTNode} node The node to convert.
 * @param {"enter" | "exit" | undefined} label The operation label.
 * @returns {string} The string representation.
 */
function nodeToString(node, label) {
    const suffix = label ? `:${label}` : "";

    switch (node.type) {
        case "Identifier": return `${node.type}${suffix} (${node.name})`;
        case "Literal": return `${node.type}${suffix} (${node.value})`;
        default: return `${node.type}${suffix}`;
    }
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

module.exports = {

    /**
     * A flag that debug dumping is enabled or not.
     * @type {boolean}
     */
    enabled: debug.enabled,

    /**
     * Dumps given objects.
     * @param {...any} args objects to dump.
     * @returns {void}
     */
    dump: debug,

    /**
     * Dumps the current analyzing state.
     * @param {ASTNode} node A node to dump.
     * @param {CodePathState} state A state to dump.
     * @param {boolean} leaving A flag whether or not it's leaving
     * @returns {void}
     */
    dumpState: !debug.enabled ? debug : /* c8 ignore next */ function(node, state, leaving) {
        for (let i = 0; i < state.currentSegments.length; ++i) {
            const segInternal = state.currentSegments[i].internal;

            if (leaving) {
                const last = segInternal.nodes.length - 1;

                if (last >= 0 && segInternal.nodes[last] === nodeToString(node, "enter")) {
                    segInternal.nodes[last] = nodeToString(node, void 0);
                } else {
                    segInternal.nodes.push(nodeToString(node, "exit"));
                }
            } else {
                segInternal.nodes.push(nodeToString(node, "enter"));
            }
        }

        debug([
            `${state.currentSegments.map(getId).join(",")})`,
            `${node.type}${leaving ? ":exit" : ""}`
        ].join(" "));
    },

    /**
     * Dumps a DOT code of a given code path.
     * The DOT code can be visualized with Graphvis.
     * @param {CodePath} codePath A code path to dump.
     * @returns {void}
     * @see http://www.graphviz.org
     * @see http://www.webgraphviz.com
     */
    dumpDot: !debug.enabled ? debug : /* c8 ignore next */ function(codePath) {
        let text =
            "\n" +
            "digraph {\n" +
            "node[shape=box,style=\"rounded,filled\",fillcolor=white];\n" +
            "initial[label=\"\",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];\n";

        if (codePath.returnedSegments.length > 0) {
            text += "final[label=\"\",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];\n";
        }
        if (codePath.thrownSegments.length > 0) {
            text += "thrown[label=\"✘\",shape=circle,width=0.3,height=0.3,fixedsize=true];\n";
        }

        const traceMap = Object.create(null);
        const arrows = this.makeDotArrows(codePath, traceMap);

        for (const id in traceMap) { // eslint-disable-line guard-for-in -- Want ability to traverse prototype
            const segment = traceMap[id];

            text += `${id}[`;

            if (segment.reachable) {
                text += "label=\"";
            } else {
                text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<<unreachable>>\\n";
            }

            if (segment.internal.nodes.length > 0) {
                text += segment.internal.nodes.join("\\n");
            } else {
                text += "????";
            }

            text += "\"];\n";
        }

        text += `${arrows}\n`;
        text += "}";
        debug("DOT", text);
    },

    /**
     * Makes a DOT code of a given code path.
     * The DOT code can be visualized with Graphvis.
     * @param {CodePath} codePath A code path to make DOT.
     * @param {Object} traceMap Optional. A map to check whether or not segments had been done.
     * @returns {string} A DOT code of the code path.
     */
    makeDotArrows(codePath, traceMap) {
        const stack = [[codePath.initialSegment, 0]];
        const done = traceMap || Object.create(null);
        let lastId = codePath.initialSegment.id;
        let text = `initial->${codePath.initialSegment.id}`;

        while (stack.length > 0) {
            const item = stack.pop();
            const segment = item[0];
            const index = item[1];

            if (done[segment.id] && index === 0) {
                continue;
            }
            done[segment.id] = segment;

            const nextSegment = segment.allNextSegments[index];

            if (!nextSegment) {
                continue;
            }

            if (lastId === segment.id) {
                text += `->${nextSegment.id}`;
            } else {
                text += `;\n${segment.id}->${nextSegment.id}`;
            }
            lastId = nextSegment.id;

            stack.unshift([segment, 1 + index]);
            stack.push([nextSegment, 0]);
        }

        codePath.returnedSegments.forEach(finalSegment => {
            if (lastId === finalSegment.id) {
                text += "->final";
            } else {
                text += `;\n${finalSegment.id}->final`;
            }
            lastId = null;
        });

        codePath.thrownSegments.forEach(finalSegment => {
            if (lastId === finalSegment.id) {
                text += "->thrown";
            } else {
                text += `;\n${finalSegment.id}->thrown`;
            }
            lastId = null;
        });

        return `${text};`;
    }
};


code:
/**
 * @fileoverview A class to operate forking.
 *
 * This is state of forking.
 * This has a fork list and manages it.
 *
 * @author Toru Nagashima
 */

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const assert = require("assert"),
    CodePathSegment = require("./code-path-segment");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
 * Determines whether or not a given segment is reachable.
 * @param {CodePathSegment} segment The segment to check.
 * @returns {boolean} `true` if the segment is reachable.
 */
function isReachable(segment) {
    return segment.reachable;
}

/**
 * Creates a new segment for each fork in the given context and appends it
 * to the end of the specified range of segments. Ultimately, this ends up calling
 * `new CodePathSegment()` for each of the forks using the `create` argument
 * as a wrapper around special behavior.
 *
 * The `startIndex` and `endIndex` arguments specify a range of segments in
 * `context` that should become `allPrevSegments` for the newly created
 * `CodePathSegment` objects.
 *
 * When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and
 * `end` is `-1`, this creates two new segments, `[g, h]`. This `g` is appended to
 * the end of the path from `a`, `c`, and `e`. This `h` is appended to the end of
 * `b`, `d`, and `f`.
 * @param {ForkContext} context An instance from which the previous segments
 *      will be obtained.
 * @param {number} startIndex The index of the first segment in the context
 *      that should be specified as previous segments for the newly created segments.
 * @param {number} endIndex The index of the last segment in the context
 *      that should be specified as previous segments for the newly created segments.
 * @param {Function} create A function that creates new `CodePathSegment`
 *      instances in a particular way. See the `CodePathSegment.new*` methods.
 * @returns {Array<CodePathSegment>} An array of the newly-created segments.
 */
function createSegments(context, startIndex, endIndex, create) {

    /** @type {Array<Array<CodePathSegment>>} */
    const list = context.segmentsList;

    /*
     * Both `startIndex` and `endIndex` work the same way: if the number is zero
     * or more, then the number is used as-is. If the number is negative,
     * then that number is added to the length of the segments list to
     * determine the index to use. That means -1 for either argument
     * is the last element, -2 is the second to last, and so on.
     *
     * So if `startIndex` is 0, `endIndex` is -1, and `list.length` is 3, the
     * effective `startIndex` is 0 and the effective `endIndex` is 2, so this function
     * will include items at indices 0, 1, and 2.
     *
     * Therefore, if `startIndex` is -1 and `endIndex` is -1, that means we'll only
     * be using the last segment in `list`.
     */
    const normalizedBegin = startIndex >= 0 ? startIndex : list.length + startIndex;
    const normalizedEnd = endIndex >= 0 ? endIndex : list.length + endIndex;

    /** @type {Array<CodePathSegment>} */
    const segments = [];

    for (let i = 0; i < context.count; ++i) {

        // this is passed into `new CodePathSegment` to add to code path.
        const allPrevSegments = [];

        for (let j = normalizedBegin; j <= normalizedEnd; ++j) {
            allPrevSegments.push(list[j][i]);
        }

        // note: `create` is just a wrapper that augments `new CodePathSegment`.
        segments.push(create(context.idGenerator.next(), allPrevSegments));
    }

    return segments;
}

/**
 * Inside of a `finally` block we end up with two parallel paths. If the code path
 * exits by a control statement (such as `break` or `continue`) from the `finally`
 * block, then we need to merge the remaining parallel paths back into one.
 * @param {ForkContext} context The fork context to work on.
 * @param {Array<CodePathSegment>} segments Segments to merge.
 * @returns {Array<CodePathSegment>} The merged segments.
 */
function mergeExtraSegments(context, segments) {
    let currentSegments = segments;

    /*
     * We need to ensure that the array returned from this function contains no more
     * than the number of segments that the context allows. `context.count` indicates
     * how many items should be in the returned array to ensure that the new segment
     * entries will line up with the already existing segment entries.
     */
    while (currentSegments.length > context.count) {
        const merged = [];

        /*
         * Because `context.count` is a factor of 2 inside of a `finally` block,
         * we can divide the segment count by 2 to merge the paths together.
         * This loops through each segment in the list and creates a new `CodePathSegment`
         * that has the segment and the segment two slots away as previous segments.
         *
         * If `currentSegments` is [a,b,c,d], this will create new segments e and f, such
         * that:
         *
         * When `i` is 0:
         * a->e
         * c->e
         *
         * When `i` is 1:
         * b->f
         * d->f
         */
        for (let i = 0, length = Math.floor(currentSegments.length / 2); i < length; ++i) {
            merged.push(CodePathSegment.newNext(
                context.idGenerator.next(),
                [currentSegments[i], currentSegments[i + length]]
            ));
        }

        /*
         * Go through the loop condition one more time to see if we have the
         * number of segments for the context. If not, we'll keep merging paths
         * of the merged segments until we get there.
         */
        currentSegments = merged;
    }

    return currentSegments;
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

/**
 * Manages the forking of code paths.
 */
class ForkContext {

    /**
     * Creates a new instance.
     * @param {IdGenerator} idGenerator An identifier generator for segments.
     * @param {ForkContext|null} upper The preceding fork context.
     * @param {number} count The number of parallel segments in each element
     *      of `segmentsList`.
     */
    constructor(idGenerator, upper, count) {

        /**
         * The ID generator that will generate segment IDs for any new
         * segments that are created.
         * @type {IdGenerator}
         */
        this.idGenerator = idGenerator;

        /**
         * The preceding fork context.
         * @type {ForkContext|null}
         */
        this.upper = upper;

        /**
         * The number of elements in each element of `segmentsList`. In most
         * cases, this is 1 but can be 2 when there is a `finally` present,
         * which forks the code path outside of normal flow. In the case of nested
         * `finally` blocks, this can be a multiple of 2.
         * @type {number}
         */
        this.count = count;

        /**
         * The segments within this context. Each element in this array has
         * `count` elements that represent one step in each fork. For example,
         * when `segmentsList` is `[[a, b], [c, d], [e, f]]`, there is one path
         * a->c->e and one path b->d->f, and `count` is 2 because each element
         * is an array with two elements.
         * @type {Array<Array<CodePathSegment>>}
         */
        this.segmentsList = [];
    }

    /**
     * The segments that begin this fork context.
     * @type {Array<CodePathSegment>}
     */
    get head() {
        const list = this.segmentsList;

        return list.length === 0 ? [] : list[list.length - 1];
    }

    /**
     * Indicates if the context contains no segments.
     * @type {boolean}
     */
    get empty() {
        return this.segmentsList.length === 0;
    }

    /**
     * Indicates if there are any segments that are reachable.
     * @type {boolean}
     */
    get reachable() {
        const segments = this.head;

        return segments.length > 0 && segments.some(isReachable);
    }

    /**
     * Creates new segments in this context and appends them to the end of the
     * already existing `CodePathSegment`s specified by `startIndex` and
     * `endIndex`.
     * @param {number} startIndex The index of the first segment in the context
     *      that should be specified as previous segments for the newly created segments.
     * @param {number} endIndex The index of the last segment in the context
     *      that should be specified as previous segments for the newly created segments.
     * @returns {Array<CodePathSegment>} An array of the newly created segments.
     */
    makeNext(startIndex, endIndex) {
        return createSegments(this, startIndex, endIndex, CodePathSegment.newNext);
    }

    /**
     * Creates new unreachable segments in this context and appends them to the end of the
     * already existing `CodePathSegment`s specified by `startIndex` and
     * `endIndex`.
     * @param {number} startIndex The index of the first segment in the context
     *      that should be specified as previous segments for the newly created segments.
     * @param {number} endIndex The index of the last segment in the context
     *      that should be specified as previous segments for the newly created segments.
     * @returns {Array<CodePathSegment>} An array of the newly created segments.
     */
    makeUnreachable(startIndex, endIndex) {
        return createSegments(this, startIndex, endIndex, CodePathSegment.newUnreachable);
    }

    /**
     * Creates new segments in this context and does not append them to the end
     *  of the already existing `CodePathSegment`s specified by `startIndex` and
     * `endIndex`. The `startIndex` and `endIndex` are only used to determine if
     * the new segments should be reachable. If any of the segments in this range
     * are reachable then the new segments are also reachable; otherwise, the new
     * segments are unreachable.
     * @param {number} startIndex The index of the first segment in the context
     *      that should be considered for reachability.
     * @param {number} endIndex The index of the last segment in the context
     *      that should be considered for reachability.
     * @returns {Array<CodePathSegment>} An array of the newly created segments.
     */
    makeDisconnected(startIndex, endIndex) {
        return createSegments(this, startIndex, endIndex, CodePathSegment.newDisconnected);
    }

    /**
     * Adds segments to the head of this context.
     * @param {Array<CodePathSegment>} segments The segments to add.
     * @returns {void}
     */
    add(segments) {
        assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
        this.segmentsList.push(mergeExtraSegments(this, segments));
    }

    /**
     * Replaces the head segments with the given segments.
     * The current head segments are removed.
     * @param {Array<CodePathSegment>} replacementHeadSegments The new head segments.
     * @returns {void}
     */
    replaceHead(replacementHeadSegments) {
        assert(
            replacementHeadSegments.length >= this.count,
            `${replacementHeadSegments.length} >= ${this.count}`
        );
        this.segmentsList.splice(-1, 1, mergeExtraSegments(this, replacementHeadSegments));
    }

    /**
     * Adds all segments of a given fork context into this context.
     * @param {ForkContext} otherForkContext The fork context to add from.
     * @returns {void}
     */
    addAll(otherForkContext) {
        assert(otherForkContext.count === this.count);
        this.segmentsList.push(...otherForkContext.segmentsList);
    }

    /**
     * Clears all segments in this context.
     * @returns {void}
     */
    clear() {
        this.segmentsList = [];
    }

    /**
     * Creates a new root context, meaning that there are no parent
     * fork contexts.
     * @param {IdGenerator} idGenerator An identifier generator for segments.
     * @returns {ForkContext} New fork context.
     */
    static newRoot(idGenerator) {
        const context = new ForkContext(idGenerator, null, 1);

        context.add([CodePathSegment.newRoot(idGenerator.next())]);

        return context;
    }

    /**
     * Creates an empty fork context preceded by a given context.
     * @param {ForkContext} parentContext The parent fork context.
     * @param {boolean} shouldForkLeavingPath Indicates that we are inside of
     *      a `finally` block and should therefore fork the path that leaves
     *      `finally`.
     * @returns {ForkContext} New fork context.
     */
    static newEmpty(parentContext, shouldForkLeavingPath) {
        return new ForkContext(
            parentContext.idGenerator,
            parentContext,
            (shouldForkLeavingPath ? 2 : 1) * parentContext.count
        );
    }
}

module.exports = ForkContext;

license: mit


code:
/**
 * @fileoverview A class of identifiers generator for code path segments.
 *
 * Each rule uses the identifier of code path segments to store additional
 * information of the code path.
 *
 * @author Toru Nagashima
 */

"use strict";

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

/**
 * A generator for unique ids.
 */
class IdGenerator {

    /**
     * @param {string} prefix Optional. A prefix of generated ids.
     */
    constructor(prefix) {
        this.prefix = String(prefix);
        this.n = 0;
    }

    /**
     * Generates id.
     * @returns {string} A generated id.
     */
    next() {
        this.n = 1 + this.n | 0;

        /* c8 ignore start */
        if (this.n < 0) {
            this.n = 1;
        }/* c8 ignore stop */

        return this.prefix + this.n;
    }
}

module.exports = IdGenerator;


code:
/**
 * @fileoverview Config Comment Parser
 * @author Nicholas C. Zakas
 */

/* eslint class-methods-use-this: off -- Methods desired on instance */
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const levn = require("levn"),
    {
        Legacy: {
            ConfigOps
        }
    } = require("@eslint/eslintrc/universal"),
    {
        directivesPattern
    } = require("../shared/directives");

const debug = require("debug")("eslint:config-comment-parser");

//------------------------------------------------------------------------------
// Typedefs
//------------------------------------------------------------------------------

/** @typedef {import("../shared/types").LintMessage} LintMessage */

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

/**
 * Object to parse ESLint configuration comments inside JavaScript files.
 * @name ConfigCommentParser
 */
module.exports = class ConfigCommentParser {

    /**
     * Parses a list of "name:string_value" or/and "name" options divided by comma or
     * whitespace. Used for "global" and "exported" comments.
     * @param {string} string The string to parse.
     * @param {Comment} comment The comment node which has the string.
     * @returns {Object} Result map object of names and string values, or null values if no value was provided
     */
    parseStringConfig(string, comment) {
        debug("Parsing String config");

        const items = {};

        // Collapse whitespace around `:` and `,` to make parsing easier
        const trimmedString = string.replace(/\s*([:,])\s*/gu, "$1");

        trimmedString.split(/\s|,+/u).forEach(name => {
            if (!name) {
                return;
            }

            // value defaults to null (if not provided), e.g: "foo" => ["foo", null]
            const [key, value = null] = name.split(":");

            items[key] = { value, comment };
        });
        return items;
    }

    /**
     * Parses a JSON-like config.
     * @param {string} string The string to parse.
     * @param {Object} location Start line and column of comments for potential error message.
     * @returns {({success: true, config: Object}|{success: false, error: LintMessage})} Result map object
     */
    parseJsonConfig(string, location) {
        debug("Parsing JSON config");

        let items = {};

        // Parses a JSON-like comment by the same way as parsing CLI option.
        try {
            items = levn.parse("Object", string) || {};

            // Some tests say that it should ignore invalid comments such as `/*eslint no-alert:abc*/`.
            // Also, commaless notations have invalid severity:
            //     "no-alert: 2 no-console: 2" --> {"no-alert": "2 no-console: 2"}
            // Should ignore that case as well.
            if (ConfigOps.isEverySeverityValid(items)) {
                return {
                    success: true,
                    config: items
                };
            }
        } catch {

            debug("Levn parsing failed; falling back to manual parsing.");

            // ignore to parse the string by a fallback.
        }

        /*
         * Optionator cannot parse commaless notations.
         * But we are supporting that. So this is a fallback for that.
         */
        items = {};
        const normalizedString = string.replace(/([-a-zA-Z0-9/]+):/gu, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/u, "$1,");

        try {
            items = JSON.parse(`{${normalizedString}}`);
        } catch (ex) {
            debug("Manual parsing failed.");

            return {
                success: false,
                error: {
                    ruleId: null,
                    fatal: true,
                    severity: 2,
                    message: `Failed to parse JSON from '${normalizedString}': ${ex.message}`,
                    line: location.start.line,
                    column: location.start.column + 1,
                    nodeType: null
                }
            };

        }

        return {
            success: true,
            config: items
        };
    }

    /**
     * Parses a config of values separated by comma.
     * @param {string} string The string to parse.
     * @returns {Object} Result map of values and true values
     */
    parseListConfig(string) {
        debug("Parsing list config");

        const items = {};

        string.split(",").forEach(name => {
            const trimmedName = name.trim().replace(/^(?<quote>['"]?)(?<ruleId>.*)\k<quote>$/us, "$<ruleId>");

            if (trimmedName) {
                items[trimmedName] = true;
            }
        });
        return items;
    }

    /**
     * Extract the directive and the justification from a given directive comment and trim them.
     * @param {string} value The comment text to extract.
     * @returns {{directivePart: string, justificationPart: string}} The extracted directive and justification.
     */
    extractDirectiveComment(value) {
        const match = /\s-{2,}\s/u.exec(value);

        if (!match) {
            return { directivePart: value.trim(), justificationPart: "" };
        }

        const directive = value.slice(0, match.index).trim();
        const justification = value.slice(match.index + match[0].length).trim();

        return { directivePart: directive, justificationPart: justification };
    }

    /**
     * Parses a directive comment into directive text and value.
     * @param {Comment} comment The comment node with the directive to be parsed.
     * @returns {{directiveText: string, directiveValue: string}} The directive text and value.
     */
    parseDirective(comment) {
        const { directivePart } = this.extractDirectiveComment(comment.value);
        const match = directivesPattern.exec(directivePart);
        const directiveText = match[1];
        const directiveValue = directivePart.slice(match.index + directiveText.length);

        return { directiveText, directiveValue };
    }
};


code:
"use strict";

const { Linter } = require("./linter");
const interpolate = require("./interpolate");
const SourceCodeFixer = require("./source-code-fixer");

module.exports = {
    Linter,

    // For testers.
    SourceCodeFixer,
    interpolate
};


code:
/**
 * @fileoverview Interpolate keys from an object into a string with {{ }} markers.
 * @author Jed Fox
 */

"use strict";

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

module.exports = (text, data) => {
    if (!data) {
        return text;
    }

    // Substitution content for any {{ }} markers.
    return text.replace(/\{\{([^{}]+?)\}\}/gu, (fullMatch, termWithWhitespace) => {
        const term = termWithWhitespace.trim();

        if (term in data) {
            return data[term];
        }

        // Preserve old behavior: If parameter name not provided, don't replace it.
        return fullMatch;
    });
};