code:
/**
 * @fileoverview Rule to flag updates of imported bindings.
 * @author Toru Nagashima <https://github.com/mysticatea>
 */

"use strict";

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

const { findVariable } = require("@eslint-community/eslint-utils");
const astUtils = require("./utils/ast-utils");

const WellKnownMutationFunctions = {
    Object: /^(?:assign|definePropert(?:y|ies)|freeze|setPrototypeOf)$/u,
    Reflect: /^(?:(?:define|delete)Property|set(?:PrototypeOf)?)$/u
};

/**
 * Check if a given node is LHS of an assignment node.
 * @param {ASTNode} node The node to check.
 * @returns {boolean} `true` if the node is LHS.
 */
function isAssignmentLeft(node) {
    const { parent } = node;

    return (
        (
            parent.type === "AssignmentExpression" &&
            parent.left === node
        ) ||

        // Destructuring assignments
        parent.type === "ArrayPattern" ||
        (
            parent.type === "Property" &&
            parent.value === node &&
            parent.parent.type === "ObjectPattern"
        ) ||
        parent.type === "RestElement" ||
        (
            parent.type === "AssignmentPattern" &&
            parent.left === node
        )
    );
}

/**
 * Check if a given node is the operand of mutation unary operator.
 * @param {ASTNode} node The node to check.
 * @returns {boolean} `true` if the node is the operand of mutation unary operator.
 */
function isOperandOfMutationUnaryOperator(node) {
    const argumentNode = node.parent.type === "ChainExpression"
        ? node.parent
        : node;
    const { parent } = argumentNode;

    return (
        (
            parent.type === "UpdateExpression" &&
            parent.argument === argumentNode
        ) ||
        (
            parent.type === "UnaryExpression" &&
            parent.operator === "delete" &&
            parent.argument === argumentNode
        )
    );
}

/**
 * Check if a given node is the iteration variable of `for-in`/`for-of` syntax.
 * @param {ASTNode} node The node to check.
 * @returns {boolean} `true` if the node is the iteration variable.
 */
function isIterationVariable(node) {
    const { parent } = node;

    return (
        (
            parent.type === "ForInStatement" &&
            parent.left === node
        ) ||
        (
            parent.type === "ForOfStatement" &&
            parent.left === node
        )
    );
}

/**
 * Check if a given node is at the first argument of a well-known mutation function.
 * - `Object.assign`
 * - `Object.defineProperty`
 * - `Object.defineProperties`
 * - `Object.freeze`
 * - `Object.setPrototypeOf`
 * - `Reflect.defineProperty`
 * - `Reflect.deleteProperty`
 * - `Reflect.set`
 * - `Reflect.setPrototypeOf`
 * @param {ASTNode} node The node to check.
 * @param {Scope} scope A `escope.Scope` object to find variable (whichever).
 * @returns {boolean} `true` if the node is at the first argument of a well-known mutation function.
 */
function isArgumentOfWellKnownMutationFunction(node, scope) {
    const { parent } = node;

    if (parent.type !== "CallExpression" || parent.arguments[0] !== node) {
        return false;
    }
    const callee = astUtils.skipChainExpression(parent.callee);

    if (
        !astUtils.isSpecificMemberAccess(callee, "Object", WellKnownMutationFunctions.Object) &&
        !astUtils.isSpecificMemberAccess(callee, "Reflect", WellKnownMutationFunctions.Reflect)
    ) {
        return false;
    }
    const variable = findVariable(scope, callee.object);

    return variable !== null && variable.scope.type === "global";
}

/**
 * Check if the identifier node is placed at to update members.
 * @param {ASTNode} id The Identifier node to check.
 * @param {Scope} scope A `escope.Scope` object to find variable (whichever).
 * @returns {boolean} `true` if the member of `id` was updated.
 */
function isMemberWrite(id, scope) {
    const { parent } = id;

    return (
        (
            parent.type === "MemberExpression" &&
            parent.object === id &&
            (
                isAssignmentLeft(parent) ||
                isOperandOfMutationUnaryOperator(parent) ||
                isIterationVariable(parent)
            )
        ) ||
        isArgumentOfWellKnownMutationFunction(id, scope)
    );
}

/**
 * Get the mutation node.
 * @param {ASTNode} id The Identifier node to get.
 * @returns {ASTNode} The mutation node.
 */
function getWriteNode(id) {
    let node = id.parent;

    while (
        node &&
        node.type !== "AssignmentExpression" &&
        node.type !== "UpdateExpression" &&
        node.type !== "UnaryExpression" &&
        node.type !== "CallExpression" &&
        node.type !== "ForInStatement" &&
        node.type !== "ForOfStatement"
    ) {
        node = node.parent;
    }

    return node || id;
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        type: "problem",

        docs: {
            description: "Disallow assigning to imported bindings",
            recommended: true,
            url: "https://eslint.org/docs/latest/rules/no-import-assign"
        },

        schema: [],

        messages: {
            readonly: "'{{name}}' is read-only.",
            readonlyMember: "The members of '{{name}}' are read-only."
        }
    },

    create(context) {
        const sourceCode = context.sourceCode;

        return {
            ImportDeclaration(node) {
                const scope = sourceCode.getScope(node);

                for (const variable of sourceCode.getDeclaredVariables(node)) {
                    const shouldCheckMembers = variable.defs.some(
                        d => d.node.type === "ImportNamespaceSpecifier"
                    );
                    let prevIdNode = null;

                    for (const reference of variable.references) {
                        const idNode = reference.identifier;

                        /*
                         * AssignmentPattern (e.g. `[a = 0] = b`) makes two write
                         * references for the same identifier. This should skip
                         * the one of the two in order to prevent redundant reports.
                         */
                        if (idNode === prevIdNode) {
                            continue;
                        }
                        prevIdNode = idNode;

                        if (reference.isWrite()) {
                            context.report({
                                node: getWriteNode(idNode),
                                messageId: "readonly",
                                data: { name: idNode.name }
                            });
                        } else if (shouldCheckMembers && isMemberWrite(idNode, scope)) {
                            context.report({
                                node: getWriteNode(idNode),
                                messageId: "readonlyMember",
                                data: { name: idNode.name }
                            });
                        }
                    }
                }
            }
        };

    }
};


code:
/**
 * @fileoverview Enforces or disallows inline comments.
 * @author Greg Cochard
 */
"use strict";

const astUtils = require("./utils/ast-utils");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        type: "suggestion",

        docs: {
            description: "Disallow inline comments after code",
            recommended: false,
            url: "https://eslint.org/docs/latest/rules/no-inline-comments"
        },

        schema: [
            {
                type: "object",
                properties: {
                    ignorePattern: {
                        type: "string"
                    }
                },
                additionalProperties: false
            }
        ],

        messages: {
            unexpectedInlineComment: "Unexpected comment inline with code."
        }
    },

    create(context) {
        const sourceCode = context.sourceCode;
        const options = context.options[0];
        let customIgnoreRegExp;

        if (options && options.ignorePattern) {
            customIgnoreRegExp = new RegExp(options.ignorePattern, "u");
        }

        /**
         * Will check that comments are not on lines starting with or ending with code
         * @param {ASTNode} node The comment node to check
         * @private
         * @returns {void}
         */
        function testCodeAroundComment(node) {

            const startLine = String(sourceCode.lines[node.loc.start.line - 1]),
                endLine = String(sourceCode.lines[node.loc.end.line - 1]),
                preamble = startLine.slice(0, node.loc.start.column).trim(),
                postamble = endLine.slice(node.loc.end.column).trim(),
                isPreambleEmpty = !preamble,
                isPostambleEmpty = !postamble;

            // Nothing on both sides
            if (isPreambleEmpty && isPostambleEmpty) {
                return;
            }

            // Matches the ignore pattern
            if (customIgnoreRegExp && customIgnoreRegExp.test(node.value)) {
                return;
            }

            // JSX Exception
            if (
                (isPreambleEmpty || preamble === "{") &&
                (isPostambleEmpty || postamble === "}")
            ) {
                const enclosingNode = sourceCode.getNodeByRangeIndex(node.range[0]);

                if (enclosingNode && enclosingNode.type === "JSXEmptyExpression") {
                    return;
                }
            }

            // Don't report ESLint directive comments
            if (astUtils.isDirectiveComment(node)) {
                return;
            }

            context.report({
                node,
                messageId: "unexpectedInlineComment"
            });
        }

        //--------------------------------------------------------------------------
        // Public
        //--------------------------------------------------------------------------

        return {
            Program() {
                sourceCode.getAllComments()
                    .filter(token => token.type !== "Shebang")
                    .forEach(testCodeAroundComment);
            }
        };
    }
};

license: mit


code:
/**
 * @fileoverview Rule to enforce declarations in program or function body root.
 * @author Brandon Mills
 */

"use strict";

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

const astUtils = require("./utils/ast-utils");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

const validParent = new Set(["Program", "StaticBlock", "ExportNamedDeclaration", "ExportDefaultDeclaration"]);
const validBlockStatementParent = new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]);

/**
 * Finds the nearest enclosing context where this rule allows declarations and returns its description.
 * @param {ASTNode} node Node to search from.
 * @returns {string} Description. One of "program", "function body", "class static block body".
 */
function getAllowedBodyDescription(node) {
    let { parent } = node;

    while (parent) {

        if (parent.type === "StaticBlock") {
            return "class static block body";
        }

        if (astUtils.isFunction(parent)) {
            return "function body";
        }

        ({ parent } = parent);
    }

    return "program";
}

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        type: "problem",

        docs: {
            description: "Disallow variable or `function` declarations in nested blocks",
            recommended: true,
            url: "https://eslint.org/docs/latest/rules/no-inner-declarations"
        },

        schema: [
            {
                enum: ["functions", "both"]
            }
        ],

        messages: {
            moveDeclToRoot: "Move {{type}} declaration to {{body}} root."
        }
    },

    create(context) {

        /**
         * Ensure that a given node is at a program or function body's root.
         * @param {ASTNode} node Declaration node to check.
         * @returns {void}
         */
        function check(node) {
            const parent = node.parent;

            if (
                parent.type === "BlockStatement" && validBlockStatementParent.has(parent.parent.type)
            ) {
                return;
            }

            if (validParent.has(parent.type)) {
                return;
            }

            context.report({
                node,
                messageId: "moveDeclToRoot",
                data: {
                    type: (node.type === "FunctionDeclaration" ? "function" : "variable"),
                    body: getAllowedBodyDescription(node)
                }
            });
        }

        return {

            FunctionDeclaration: check,
            VariableDeclaration(node) {
                if (context.options[0] === "both" && node.kind === "var") {
                    check(node);
                }
            }

        };

    }
};


code:
/**
 * @fileoverview Validate strings passed to the RegExp constructor
 * @author Michael Ficarra
 */
"use strict";

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

const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator;
const validator = new RegExpValidator();
const validFlags = /[dgimsuvy]/gu;
const undefined1 = void 0;

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        type: "problem",

        docs: {
            description: "Disallow invalid regular expression strings in `RegExp` constructors",
            recommended: true,
            url: "https://eslint.org/docs/latest/rules/no-invalid-regexp"
        },

        schema: [{
            type: "object",
            properties: {
                allowConstructorFlags: {
                    type: "array",
                    items: {
                        type: "string"
                    }
                }
            },
            additionalProperties: false
        }],

        messages: {
            regexMessage: "{{message}}."
        }
    },

    create(context) {

        const options = context.options[0];
        let allowedFlags = null;

        if (options && options.allowConstructorFlags) {
            const temp = options.allowConstructorFlags.join("").replace(validFlags, "");

            if (temp) {
                allowedFlags = new RegExp(`[${temp}]`, "giu");
            }
        }

        /**
         * Reports error with the provided message.
         * @param {ASTNode} node The node holding the invalid RegExp
         * @param {string} message The message to report.
         * @returns {void}
         */
        function report(node, message) {
            context.report({
                node,
                messageId: "regexMessage",
                data: { message }
            });
        }

        /**
         * Check if node is a string
         * @param {ASTNode} node node to evaluate
         * @returns {boolean} True if its a string
         * @private
         */
        function isString(node) {
            return node && node.type === "Literal" && typeof node.value === "string";
        }

        /**
         * Gets flags of a regular expression created by the given `RegExp()` or `new RegExp()` call
         * Examples:
         *     new RegExp(".")         // => ""
         *     new RegExp(".", "gu")   // => "gu"
         *     new RegExp(".", flags)  // => null
         * @param {ASTNode} node `CallExpression` or `NewExpression` node
         * @returns {string|null} flags if they can be determined, `null` otherwise
         * @private
         */
        function getFlags(node) {
            if (node.arguments.length < 2) {
                return "";
            }

            if (isString(node.arguments[1])) {
                return node.arguments[1].value;
            }

            return null;
        }

        /**
         * Check syntax error in a given pattern.
         * @param {string} pattern The RegExp pattern to validate.
         * @param {Object} flags The RegExp flags to validate.
         * @param {boolean} [flags.unicode] The Unicode flag.
         * @param {boolean} [flags.unicodeSets] The UnicodeSets flag.
         * @returns {string|null} The syntax error.
         */
        function validateRegExpPattern(pattern, flags) {
            try {
                validator.validatePattern(pattern, undefined1, undefined1, flags);
                return null;
            } catch (err) {
                return err.message;
            }
        }

        /**
         * Check syntax error in a given flags.
         * @param {string|null} flags The RegExp flags to validate.
         * @returns {string|null} The syntax error.
         */
        function validateRegExpFlags(flags) {
            if (!flags) {
                return null;
            }
            try {
                validator.validateFlags(flags);
            } catch {
                return `Invalid flags supplied to RegExp constructor '${flags}'`;
            }

            /*
             * `regexpp` checks the combination of `u` and `v` flags when parsing `Pattern` according to `ecma262`,
             * but this rule may check only the flag when the pattern is unidentifiable, so check it here.
             * https://tc39.es/ecma262/multipage/text-processing.html#sec-parsepattern
             */
            if (flags.includes("u") && flags.includes("v")) {
                return "Regex 'u' and 'v' flags cannot be used together";
            }
            return null;
        }

        return {
            "CallExpression, NewExpression"(node) {
                if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp") {
                    return;
                }

                let flags = getFlags(node);

                if (flags && allowedFlags) {
                    flags = flags.replace(allowedFlags, "");
                }

                let message = validateRegExpFlags(flags);

                if (message) {
                    report(node, message);
                    return;
                }

                if (!isString(node.arguments[0])) {
                    return;
                }

                const pattern = node.arguments[0].value;

                message = (

                    // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag
                    flags === null
                        ? (
                            validateRegExpPattern(pattern, { unicode: true, unicodeSets: false }) &&
                            validateRegExpPattern(pattern, { unicode: false, unicodeSets: true }) &&
                            validateRegExpPattern(pattern, { unicode: false, unicodeSets: false })
                        )
                        : validateRegExpPattern(pattern, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") })
                );

                if (message) {
                    report(node, message);
                }
            }
        };
    }
};


code:
/**
 * @fileoverview A rule to disallow `this` keywords in contexts where the value of `this` is `undefined`.
 * @author Toru Nagashima
 */

"use strict";

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

const astUtils = require("./utils/ast-utils");

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

/**
 * Determines if the given code path is a code path with lexical `this` binding.
 * That is, if `this` within the code path refers to `this` of surrounding code path.
 * @param {CodePath} codePath Code path.
 * @param {ASTNode} node Node that started the code path.
 * @returns {boolean} `true` if it is a code path with lexical `this` binding.
 */
function isCodePathWithLexicalThis(codePath, node) {
    return codePath.origin === "function" && node.type === "ArrowFunctionExpression";
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        type: "suggestion",

        docs: {
            description: "Disallow use of `this` in contexts where the value of `this` is `undefined`",
            recommended: false,
            url: "https://eslint.org/docs/latest/rules/no-invalid-this"
        },

        schema: [
            {
                type: "object",
                properties: {
                    capIsConstructor: {
                        type: "boolean",
                        default: true
                    }
                },
                additionalProperties: false
            }
        ],

        messages: {
            unexpectedThis: "Unexpected 'this'."
        }
    },

    create(context) {
        const options = context.options[0] || {};
        const capIsConstructor = options.capIsConstructor !== false;
        const stack = [],
            sourceCode = context.sourceCode;

        /**
         * Gets the current checking context.
         *
         * The return value has a flag that whether or not `this` keyword is valid.
         * The flag is initialized when got at the first time.
         * @returns {{valid: boolean}}
         *   an object which has a flag that whether or not `this` keyword is valid.
         */
        stack.getCurrent = function() {
            const current = this[this.length - 1];

            if (!current.init) {
                current.init = true;
                current.valid = !astUtils.isDefaultThisBinding(
                    current.node,
                    sourceCode,
                    { capIsConstructor }
                );
            }
            return current;
        };

        return {

            onCodePathStart(codePath, node) {
                if (isCodePathWithLexicalThis(codePath, node)) {
                    return;
                }

                if (codePath.origin === "program") {
                    const scope = sourceCode.getScope(node);
                    const features = context.languageOptions.parserOptions.ecmaFeatures || {};

                    // `this` at the top level of scripts always refers to the global object
                    stack.push({
                        init: true,
                        node,
                        valid: !(
                            node.sourceType === "module" ||
                            (features.globalReturn && scope.childScopes[0].isStrict)
                        )
                    });

                    return;
                }

                /*
                 * `init: false` means that `valid` isn't determined yet.
                 * Most functions don't use `this`, and the calculation for `valid`
                 * is relatively costly, so we'll calculate it lazily when the first
                 * `this` within the function is traversed. A special case are non-strict
                 * functions, because `this` refers to the global object and therefore is
                 * always valid, so we can set `init: true` right away.
                 */
                stack.push({
                    init: !sourceCode.getScope(node).isStrict,
                    node,
                    valid: true
                });
            },

            onCodePathEnd(codePath, node) {
                if (isCodePathWithLexicalThis(codePath, node)) {
                    return;
                }

                stack.pop();
            },

            // Reports if `this` of the current context is invalid.
            ThisExpression(node) {
                const current = stack.getCurrent();

                if (current && !current.valid) {
                    context.report({
                        node,
                        messageId: "unexpectedThis"
                    });
                }
            }
        };
    }
};


code:
/**
 * @fileoverview Rule to flag usage of __iterator__ property
 * @author Ian Christian Myers
 */

"use strict";

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

const { getStaticPropertyName } = require("./utils/ast-utils");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        type: "suggestion",

        docs: {
            description: "Disallow the use of the `__iterator__` property",
            recommended: false,
            url: "https://eslint.org/docs/latest/rules/no-iterator"
        },

        schema: [],

        messages: {
            noIterator: "Reserved name '__iterator__'."
        }
    },

    create(context) {

        return {

            MemberExpression(node) {

                if (getStaticPropertyName(node) === "__iterator__") {
                    context.report({
                        node,
                        messageId: "noIterator"
                    });
                }
            }
        };

    }
};


code:
/**
 * @fileoverview Disallow Labeled Statements
 * @author Nicholas C. Zakas
 */
"use strict";

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

const astUtils = require("./utils/ast-utils");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        type: "suggestion",

        docs: {
            description: "Disallow labeled statements",
            recommended: false,
            url: "https://eslint.org/docs/latest/rules/no-labels"
        },

        schema: [
            {
                type: "object",
                properties: {
                    allowLoop: {
                        type: "boolean",
                        default: false
                    },
                    allowSwitch: {
                        type: "boolean",
                        default: false
                    }
                },
                additionalProperties: false
            }
        ],

        messages: {
            unexpectedLabel: "Unexpected labeled statement.",
            unexpectedLabelInBreak: "Unexpected label in break statement.",
            unexpectedLabelInContinue: "Unexpected label in continue statement."
        }
    },

    create(context) {
        const options = context.options[0];
        const allowLoop = options && options.allowLoop;
        const allowSwitch = options && options.allowSwitch;
        let scopeInfo = null;

        /**
         * Gets the kind of a given node.
         * @param {ASTNode} node A node to get.
         * @returns {string} The kind of the node.
         */
        function getBodyKind(node) {
            if (astUtils.isLoop(node)) {
                return "loop";
            }
            if (node.type === "SwitchStatement") {
                return "switch";
            }
            return "other";
        }

        /**
         * Checks whether the label of a given kind is allowed or not.
         * @param {string} kind A kind to check.
         * @returns {boolean} `true` if the kind is allowed.
         */
        function isAllowed(kind) {
            switch (kind) {
                case "loop": return allowLoop;
                case "switch": return allowSwitch;
                default: return false;
            }
        }

        /**
         * Checks whether a given name is a label of a loop or not.
         * @param {string} label A name of a label to check.
         * @returns {boolean} `true` if the name is a label of a loop.
         */
        function getKind(label) {
            let info = scopeInfo;

            while (info) {
                if (info.label === label) {
                    return info.kind;
                }
                info = info.upper;
            }

            /* c8 ignore next */
            return "other";
        }

        //--------------------------------------------------------------------------
        // Public
        //--------------------------------------------------------------------------

        return {
            LabeledStatement(node) {
                scopeInfo = {
                    label: node.label.name,
                    kind: getBodyKind(node.body),
                    upper: scopeInfo
                };
            },

            "LabeledStatement:exit"(node) {
                if (!isAllowed(scopeInfo.kind)) {
                    context.report({
                        node,
                        messageId: "unexpectedLabel"
                    });
                }

                scopeInfo = scopeInfo.upper;
            },

            BreakStatement(node) {
                if (node.label && !isAllowed(getKind(node.label.name))) {
                    context.report({
                        node,
                        messageId: "unexpectedLabelInBreak"
                    });
                }
            },

            ContinueStatement(node) {
                if (node.label && !isAllowed(getKind(node.label.name))) {
                    context.report({
                        node,
                        messageId: "unexpectedLabelInContinue"
                    });
                }
            }
        };

    }
};


code:
/**
 * @fileoverview Rule to flag blocks with no reason to exist
 * @author Brandon Mills
 */

"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        type: "suggestion",

        docs: {
            description: "Disallow unnecessary nested blocks",
            recommended: false,
            url: "https://eslint.org/docs/latest/rules/no-lone-blocks"
        },

        schema: [],

        messages: {
            redundantBlock: "Block is redundant.",
            redundantNestedBlock: "Nested block is redundant."
        }
    },

    create(context) {

        // A stack of lone blocks to be checked for block-level bindings
        const loneBlocks = [];
        let ruleDef;
        const sourceCode = context.sourceCode;

        /**
         * Reports a node as invalid.
         * @param {ASTNode} node The node to be reported.
         * @returns {void}
         */
        function report(node) {
            const messageId = node.parent.type === "BlockStatement" || node.parent.type === "StaticBlock"
                ? "redundantNestedBlock"
                : "redundantBlock";

            context.report({
                node,
                messageId
            });
        }

        /**
         * Checks for any occurrence of a BlockStatement in a place where lists of statements can appear
         * @param {ASTNode} node The node to check
         * @returns {boolean} True if the node is a lone block.
         */
        function isLoneBlock(node) {
            return node.parent.type === "BlockStatement" ||
                node.parent.type === "StaticBlock" ||
                node.parent.type === "Program" ||

                // Don't report blocks in switch cases if the block is the only statement of the case.
                node.parent.type === "SwitchCase" && !(node.parent.consequent[0] === node && node.parent.consequent.length === 1);
        }

        /**
         * Checks the enclosing block of the current node for block-level bindings,
         * and "marks it" as valid if any.
         * @param {ASTNode} node The current node to check.
         * @returns {void}
         */
        function markLoneBlock(node) {
            if (loneBlocks.length === 0) {
                return;
            }

            const block = node.parent;

            if (loneBlocks[loneBlocks.length - 1] === block) {
                loneBlocks.pop();
            }
        }

        // Default rule definition: report all lone blocks
        ruleDef = {
            BlockStatement(node) {
                if (isLoneBlock(node)) {
                    report(node);
                }
            }
        };

        // ES6: report blocks without block-level bindings, or that's only child of another block
        if (context.languageOptions.ecmaVersion >= 2015) {
            ruleDef = {
                BlockStatement(node) {
                    if (isLoneBlock(node)) {
                        loneBlocks.push(node);
                    }
                },
                "BlockStatement:exit"(node) {
                    if (loneBlocks.length > 0 && loneBlocks[loneBlocks.length - 1] === node) {
                        loneBlocks.pop();
                        report(node);
                    } else if (
                        (
                            node.parent.type === "BlockStatement" ||
                            node.parent.type === "StaticBlock"
                        ) &&
                        node.parent.body.length === 1
                    ) {
                        report(node);
                    }
                }
            };

            ruleDef.VariableDeclaration = function(node) {
                if (node.kind === "let" || node.kind === "const") {
                    markLoneBlock(node);
                }
            };

            ruleDef.FunctionDeclaration = function(node) {
                if (sourceCode.getScope(node).isStrict) {
                    markLoneBlock(node);
                }
            };

            ruleDef.ClassDeclaration = markLoneBlock;
        }

        return ruleDef;
    }
};


code:
/**
 * @fileoverview Rule to disallow if as the only statement in an else block
 * @author Brandon Mills
 */
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        type: "suggestion",

        docs: {
            description: "Disallow `if` statements as the only statement in `else` blocks",
            recommended: false,
            url: "https://eslint.org/docs/latest/rules/no-lonely-if"
        },

        schema: [],
        fixable: "code",

        messages: {
            unexpectedLonelyIf: "Unexpected if as the only statement in an else block."
        }
    },

    create(context) {
        const sourceCode = context.sourceCode;

        return {
            IfStatement(node) {
                const parent = node.parent,
                    grandparent = parent.parent;

                if (parent && parent.type === "BlockStatement" &&
                        parent.body.length === 1 && grandparent &&
                        grandparent.type === "IfStatement" &&
                        parent === grandparent.alternate) {
                    context.report({
                        node,
                        messageId: "unexpectedLonelyIf",
                        fix(fixer) {
                            const openingElseCurly = sourceCode.getFirstToken(parent);
                            const closingElseCurly = sourceCode.getLastToken(parent);
                            const elseKeyword = sourceCode.getTokenBefore(openingElseCurly);
                            const tokenAfterElseBlock = sourceCode.getTokenAfter(closingElseCurly);
                            const lastIfToken = sourceCode.getLastToken(node.consequent);
                            const sourceText = sourceCode.getText();

                            if (sourceText.slice(openingElseCurly.range[1],
                                node.range[0]).trim() || sourceText.slice(node.range[1], closingElseCurly.range[0]).trim()) {

                                // Don't fix if there are any non-whitespace characters interfering (e.g. comments)
                                return null;
                            }

                            if (
                                node.consequent.type !== "BlockStatement" && lastIfToken.value !== ";" && tokenAfterElseBlock &&
                                (
                                    node.consequent.loc.end.line === tokenAfterElseBlock.loc.start.line ||
                                    /^[([/+`-]/u.test(tokenAfterElseBlock.value) ||
                                    lastIfToken.value === "++" ||
                                    lastIfToken.value === "--"
                                )
                            ) {

                                /*
                                 * If the `if` statement has no block, and is not followed by a semicolon, make sure that fixing
                                 * the issue would not change semantics due to ASI. If this would happen, don't do a fix.
                                 */
                                return null;
                            }

                            return fixer.replaceTextRange(
                                [openingElseCurly.range[0], closingElseCurly.range[1]],
                                (elseKeyword.range[1] === openingElseCurly.range[0] ? " " : "") + sourceCode.getText(node)
                            );
                        }
                    });
                }
            }
        };

    }
};


code:
/**
 * @fileoverview Rule to flag creation of function inside a loop
 * @author Ilya Volodin
 */

"use strict";

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

/**
 * Gets the containing loop node of a specified node.
 *
 * We don't need to check nested functions, so this ignores those.
 * `Scope.through` contains references of nested functions.
 * @param {ASTNode} node An AST node to get.
 * @returns {ASTNode|null} The containing loop node of the specified node, or
 *      `null`.
 */
function getContainingLoopNode(node) {
    for (let currentNode = node; currentNode.parent; currentNode = currentNode.parent) {
        const parent = currentNode.parent;

        switch (parent.type) {
            case "WhileStatement":
            case "DoWhileStatement":
                return parent;

            case "ForStatement":

                // `init` is outside of the loop.
                if (parent.init !== currentNode) {
                    return parent;
                }
                break;

            case "ForInStatement":
            case "ForOfStatement":

                // `right` is outside of the loop.
                if (parent.right !== currentNode) {
                    return parent;
                }
                break;

            case "ArrowFunctionExpression":
            case "FunctionExpression":
            case "FunctionDeclaration":

                // We don't need to check nested functions.
                return null;

            default:
                break;
        }
    }

    return null;
}

/**
 * Gets the containing loop node of a given node.
 * If the loop was nested, this returns the most outer loop.
 * @param {ASTNode} node A node to get. This is a loop node.
 * @param {ASTNode|null} excludedNode A node that the result node should not
 *      include.
 * @returns {ASTNode} The most outer loop node.
 */
function getTopLoopNode(node, excludedNode) {
    const border = excludedNode ? excludedNode.range[1] : 0;
    let retv = node;
    let containingLoopNode = node;

    while (containingLoopNode && containingLoopNode.range[0] >= border) {
        retv = containingLoopNode;
        containingLoopNode = getContainingLoopNode(containingLoopNode);
    }

    return retv;
}

/**
 * Checks whether a given reference which refers to an upper scope's variable is
 * safe or not.
 * @param {ASTNode} loopNode A containing loop node.
 * @param {eslint-scope.Reference} reference A reference to check.
 * @returns {boolean} `true` if the reference is safe or not.
 */
function isSafe(loopNode, reference) {
    const variable = reference.resolved;
    const definition = variable && variable.defs[0];
    const declaration = definition && definition.parent;
    const kind = (declaration && declaration.type === "VariableDeclaration")
        ? declaration.kind
        : "";

    // Variables which are declared by `const` is safe.
    if (kind === "const") {
        return true;
    }

    /*
     * Variables which are declared by `let` in the loop is safe.
     * It's a different instance from the next loop step's.
     */
    if (kind === "let" &&
        declaration.range[0] > loopNode.range[0] &&
        declaration.range[1] < loopNode.range[1]
    ) {
        return true;
    }

    /*
     * WriteReferences which exist after this border are unsafe because those
     * can modify the variable.
     */
    const border = getTopLoopNode(
        loopNode,
        (kind === "let") ? declaration : null
    ).range[0];

    /**
     * Checks whether a given reference is safe or not.
     * The reference is every reference of the upper scope's variable we are
     * looking now.
     *
     * It's safe if the reference matches one of the following condition.
     * - is readonly.
     * - doesn't exist inside a local function and after the border.
     * @param {eslint-scope.Reference} upperRef A reference to check.
     * @returns {boolean} `true` if the reference is safe.
     */
    function isSafeReference(upperRef) {
        const id = upperRef.identifier;

        return (
            !upperRef.isWrite() ||
            variable.scope.variableScope === upperRef.from.variableScope &&
            id.range[0] < border
        );
    }

    return Boolean(variable) && variable.references.every(isSafeReference);
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        type: "suggestion",

        docs: {
            description: "Disallow function declarations that contain unsafe references inside loop statements",
            recommended: false,
            url: "https://eslint.org/docs/latest/rules/no-loop-func"
        },

        schema: [],

        messages: {
            unsafeRefs: "Function declared in a loop contains unsafe references to variable(s) {{ varNames }}."
        }
    },

    create(context) {

        const sourceCode = context.sourceCode;

        /**
         * Reports functions which match the following condition:
         *
         * - has a loop node in ancestors.
         * - has any references which refers to an unsafe variable.
         * @param {ASTNode} node The AST node to check.
         * @returns {void}
         */
        function checkForLoops(node) {
            const loopNode = getContainingLoopNode(node);

            if (!loopNode) {
                return;
            }

            const references = sourceCode.getScope(node).through;
            const unsafeRefs = references.filter(r => r.resolved && !isSafe(loopNode, r)).map(r => r.identifier.name);

            if (unsafeRefs.length > 0) {
                context.report({
                    node,
                    messageId: "unsafeRefs",
                    data: { varNames: `'${unsafeRefs.join("', '")}'` }
                });
            }
        }

        return {
            ArrowFunctionExpression: checkForLoops,
            FunctionExpression: checkForLoops,
            FunctionDeclaration: checkForLoops
        };
    }
};


code:
/**
 * @fileoverview Rule to flag numbers that will lose significant figure precision at runtime
 * @author Jacob Moore
 */

"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        type: "problem",

        docs: {
            description: "Disallow literal numbers that lose precision",
            recommended: true,
            url: "https://eslint.org/docs/latest/rules/no-loss-of-precision"
        },
        schema: [],
        messages: {
            noLossOfPrecision: "This number literal will lose precision at runtime."
        }
    },

    create(context) {

        /**
         * Returns whether the node is number literal
         * @param {Node} node the node literal being evaluated
         * @returns {boolean} true if the node is a number literal
         */
        function isNumber(node) {
            return typeof node.value === "number";
        }

        /**
         * Gets the source code of the given number literal. Removes `_` numeric separators from the result.
         * @param {Node} node the number `Literal` node
         * @returns {string} raw source code of the literal, without numeric separators
         */
        function getRaw(node) {
            return node.raw.replace(/_/gu, "");
        }

        /**
         * Checks whether the number is  base ten
         * @param {ASTNode} node the node being evaluated
         * @returns {boolean} true if the node is in base ten
         */
        function isBaseTen(node) {
            const prefixes = ["0x", "0X", "0b", "0B", "0o", "0O"];

            return prefixes.every(prefix => !node.raw.startsWith(prefix)) &&
            !/^0[0-7]+$/u.test(node.raw);
        }

        /**
         * Checks that the user-intended non-base ten number equals the actual number after is has been converted to the Number type
         * @param {Node} node the node being evaluated
         * @returns {boolean} true if they do not match
         */
        function notBaseTenLosesPrecision(node) {
            const rawString = getRaw(node).toUpperCase();
            let base = 0;

            if (rawString.startsWith("0B")) {
                base = 2;
            } else if (rawString.startsWith("0X")) {
                base = 16;
            } else {
                base = 8;
            }

            return !rawString.endsWith(node.value.toString(base).toUpperCase());
        }

        /**
         * Adds a decimal point to the numeric string at index 1
         * @param {string} stringNumber the numeric string without any decimal point
         * @returns {string} the numeric string with a decimal point in the proper place
         */
        function addDecimalPointToNumber(stringNumber) {
            return `${stringNumber[0]}.${stringNumber.slice(1)}`;
        }

        /**
         * Returns the number stripped of leading zeros
         * @param {string} numberAsString the string representation of the number
         * @returns {string} the stripped string
         */
        function removeLeadingZeros(numberAsString) {
            for (let i = 0; i < numberAsString.length; i++) {
                if (numberAsString[i] !== "0") {
                    return numberAsString.slice(i);
                }
            }
            return numberAsString;
        }

        /**
         * Returns the number stripped of trailing zeros
         * @param {string} numberAsString the string representation of the number
         * @returns {string} the stripped string
         */
        function removeTrailingZeros(numberAsString) {
            for (let i = numberAsString.length - 1; i >= 0; i--) {
                if (numberAsString[i] !== "0") {
                    return numberAsString.slice(0, i + 1);
                }
            }
            return numberAsString;
        }

        /**
         * Converts an integer to an object containing the integer's coefficient and order of magnitude
         * @param {string} stringInteger the string representation of the integer being converted
         * @returns {Object} the object containing the integer's coefficient and order of magnitude
         */
        function normalizeInteger(stringInteger) {
            const significantDigits = removeTrailingZeros(removeLeadingZeros(stringInteger));

            return {
                magnitude: stringInteger.startsWith("0") ? stringInteger.length - 2 : stringInteger.length - 1,
                coefficient: addDecimalPointToNumber(significantDigits)
            };
        }

        /**
         *
         * Converts a float to an object containing the floats's coefficient and order of magnitude
         * @param {string} stringFloat the string representation of the float being converted
         * @returns {Object} the object containing the integer's coefficient and order of magnitude
         */
        function normalizeFloat(stringFloat) {
            const trimmedFloat = removeLeadingZeros(stringFloat);

            if (trimmedFloat.startsWith(".")) {
                const decimalDigits = trimmedFloat.slice(1);
                const significantDigits = removeLeadingZeros(decimalDigits);

                return {
                    magnitude: significantDigits.length - decimalDigits.length - 1,
                    coefficient: addDecimalPointToNumber(significantDigits)
                };

            }
            return {
                magnitude: trimmedFloat.indexOf(".") - 1,
                coefficient: addDecimalPointToNumber(trimmedFloat.replace(".", ""))

            };
        }

        /**
         * Converts a base ten number to proper scientific notation
         * @param {string} stringNumber the string representation of the base ten number to be converted
         * @returns {string} the number converted to scientific notation
         */
        function convertNumberToScientificNotation(stringNumber) {
            const splitNumber = stringNumber.replace("E", "e").split("e");
            const originalCoefficient = splitNumber[0];
            const normalizedNumber = stringNumber.includes(".") ? normalizeFloat(originalCoefficient)
                : normalizeInteger(originalCoefficient);
            const normalizedCoefficient = normalizedNumber.coefficient;
            const magnitude = splitNumber.length > 1 ? (parseInt(splitNumber[1], 10) + normalizedNumber.magnitude)
                : normalizedNumber.magnitude;

            return `${normalizedCoefficient}e${magnitude}`;
        }

        /**
         * Checks that the user-intended base ten number equals the actual number after is has been converted to the Number type
         * @param {Node} node the node being evaluated
         * @returns {boolean} true if they do not match
         */
        function baseTenLosesPrecision(node) {
            const normalizedRawNumber = convertNumberToScientificNotation(getRaw(node));
            const requestedPrecision = normalizedRawNumber.split("e")[0].replace(".", "").length;

            if (requestedPrecision > 100) {
                return true;
            }
            const storedNumber = node.value.toPrecision(requestedPrecision);
            const normalizedStoredNumber = convertNumberToScientificNotation(storedNumber);

            return normalizedRawNumber !== normalizedStoredNumber;
        }

        /**
         * Checks that the user-intended number equals the actual number after is has been converted to the Number type
         * @param {Node} node the node being evaluated
         * @returns {boolean} true if they do not match
         */
        function losesPrecision(node) {
            return isBaseTen(node) ? baseTenLosesPrecision(node) : notBaseTenLosesPrecision(node);
        }

        return {
            Literal(node) {
                if (node.value && isNumber(node) && losesPrecision(node)) {
                    context.report({
                        messageId: "noLossOfPrecision",
                        node
                    });
                }
            }
        };
    }
};


code:
/**
 * @fileoverview Rule to flag statements that use magic numbers (adapted from https://github.com/danielstjules/buddy.js)
 * @author Vincent Lemeunier
 */

"use strict";

const astUtils = require("./utils/ast-utils");

// Maximum array length by the ECMAScript Specification.
const MAX_ARRAY_LENGTH = 2 ** 32 - 1;

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/**
 * Convert the value to bigint if it's a string. Otherwise return the value as-is.
 * @param {bigint|number|string} x The value to normalize.
 * @returns {bigint|number} The normalized value.
 */
function normalizeIgnoreValue(x) {
    if (typeof x === "string") {
        return BigInt(x.slice(0, -1));
    }
    return x;
}

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        type: "suggestion",

        docs: {
            description: "Disallow magic numbers",
            recommended: false,
            url: "https://eslint.org/docs/latest/rules/no-magic-numbers"
        },

        schema: [{
            type: "object",
            properties: {
                detectObjects: {
                    type: "boolean",
                    default: false
                },
                enforceConst: {
                    type: "boolean",
                    default: false
                },
                ignore: {
                    type: "array",
                    items: {
                        anyOf: [
                            { type: "number" },
                            { type: "string", pattern: "^[+-]?(?:0|[1-9][0-9]*)n$" }
                        ]
                    },
                    uniqueItems: true
                },
                ignoreArrayIndexes: {
                    type: "boolean",
                    default: false
                },
                ignoreDefaultValues: {
                    type: "boolean",
                    default: false
                },
                ignoreClassFieldInitialValues: {
                    type: "boolean",
                    default: false
                }
            },
            additionalProperties: false
        }],

        messages: {
            useConst: "Number constants declarations must use 'const'.",
            noMagic: "No magic number: {{raw}}."
        }
    },

    create(context) {
        const config = context.options[0] || {},
            detectObjects = !!config.detectObjects,
            enforceConst = !!config.enforceConst,
            ignore = new Set((config.ignore || []).map(normalizeIgnoreValue)),
            ignoreArrayIndexes = !!config.ignoreArrayIndexes,
            ignoreDefaultValues = !!config.ignoreDefaultValues,
            ignoreClassFieldInitialValues = !!config.ignoreClassFieldInitialValues;

        const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"];

        /**
         * Returns whether the rule is configured to ignore the given value
         * @param {bigint|number} value The value to check
         * @returns {boolean} true if the value is ignored
         */
        function isIgnoredValue(value) {
            return ignore.has(value);
        }

        /**
         * Returns whether the number is a default value assignment.
         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
         * @returns {boolean} true if the number is a default value
         */
        function isDefaultValue(fullNumberNode) {
            const parent = fullNumberNode.parent;

            return parent.type === "AssignmentPattern" && parent.right === fullNumberNode;
        }

        /**
         * Returns whether the number is the initial value of a class field.
         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
         * @returns {boolean} true if the number is the initial value of a class field.
         */
        function isClassFieldInitialValue(fullNumberNode) {
            const parent = fullNumberNode.parent;

            return parent.type === "PropertyDefinition" && parent.value === fullNumberNode;
        }

        /**
         * Returns whether the given node is used as a radix within parseInt() or Number.parseInt()
         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
         * @returns {boolean} true if the node is radix
         */
        function isParseIntRadix(fullNumberNode) {
            const parent = fullNumberNode.parent;

            return parent.type === "CallExpression" && fullNumberNode === parent.arguments[1] &&
                (
                    astUtils.isSpecificId(parent.callee, "parseInt") ||
                    astUtils.isSpecificMemberAccess(parent.callee, "Number", "parseInt")
                );
        }

        /**
         * Returns whether the given node is a direct child of a JSX node.
         * In particular, it aims to detect numbers used as prop values in JSX tags.
         * Example: <input maxLength={10} />
         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
         * @returns {boolean} true if the node is a JSX number
         */
        function isJSXNumber(fullNumberNode) {
            return fullNumberNode.parent.type.indexOf("JSX") === 0;
        }

        /**
         * Returns whether the given node is used as an array index.
         * Value must coerce to a valid array index name: "0", "1", "2" ... "4294967294".
         *
         * All other values, like "-1", "2.5", or "4294967295", are just "normal" object properties,
         * which can be created and accessed on an array in addition to the array index properties,
         * but they don't affect array's length and are not considered by methods such as .map(), .forEach() etc.
         *
         * The maximum array length by the specification is 2 ** 32 - 1 = 4294967295,
         * thus the maximum valid index is 2 ** 32 - 2 = 4294967294.
         *
         * All notations are allowed, as long as the value coerces to one of "0", "1", "2" ... "4294967294".
         *
         * Valid examples:
         * a[0], a[1], a[1.2e1], a[0xAB], a[0n], a[1n]
         * a[-0] (same as a[0] because -0 coerces to "0")
         * a[-0n] (-0n evaluates to 0n)
         *
         * Invalid examples:
         * a[-1], a[-0xAB], a[-1n], a[2.5], a[1.23e1], a[12e-1]
         * a[4294967295] (above the max index, it's an access to a regular property a["4294967295"])
         * a[999999999999999999999] (even if it wasn't above the max index, it would be a["1e+21"])
         * a[1e310] (same as a["Infinity"])
         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
         * @param {bigint|number} value Value expressed by the fullNumberNode
         * @returns {boolean} true if the node is a valid array index
         */
        function isArrayIndex(fullNumberNode, value) {
            const parent = fullNumberNode.parent;

            return parent.type === "MemberExpression" && parent.property === fullNumberNode &&
                (Number.isInteger(value) || typeof value === "bigint") &&
                value >= 0 && value < MAX_ARRAY_LENGTH;
        }

        return {
            Literal(node) {
                if (!astUtils.isNumericLiteral(node)) {
                    return;
                }

                let fullNumberNode;
                let value;
                let raw;

                // Treat unary minus as a part of the number
                if (node.parent.type === "UnaryExpression" && node.parent.operator === "-") {
                    fullNumberNode = node.parent;
                    value = -node.value;
                    raw = `-${node.raw}`;
                } else {
                    fullNumberNode = node;
                    value = node.value;
                    raw = node.raw;
                }

                const parent = fullNumberNode.parent;

                // Always allow radix arguments and JSX props
                if (
                    isIgnoredValue(value) ||
                    (ignoreDefaultValues && isDefaultValue(fullNumberNode)) ||
                    (ignoreClassFieldInitialValues && isClassFieldInitialValue(fullNumberNode)) ||
                    isParseIntRadix(fullNumberNode) ||
                    isJSXNumber(fullNumberNode) ||
                    (ignoreArrayIndexes && isArrayIndex(fullNumberNode, value))
                ) {
                    return;
                }

                if (parent.type === "VariableDeclarator") {
                    if (enforceConst && parent.parent.kind !== "const") {
                        context.report({
                            node: fullNumberNode,
                            messageId: "useConst"
                        });
                    }
                } else if (
                    !okTypes.includes(parent.type) ||
                    (parent.type === "AssignmentExpression" && parent.left.type === "Identifier")
                ) {
                    context.report({
                        node: fullNumberNode,
                        messageId: "noMagic",
                        data: {
                            raw
                        }
                    });
                }
            }
        };
    }
};


code:
/**
 * @author Toru Nagashima <https://github.com/mysticatea>
 */
"use strict";

const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils");
const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp");
const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode");
const astUtils = require("./utils/ast-utils.js");
const { isValidWithUnicodeFlag } = require("./utils/regular-expressions");

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

/**
 * @typedef {import('@eslint-community/regexpp').AST.Character} Character
 * @typedef {import('@eslint-community/regexpp').AST.CharacterClassElement} CharacterClassElement
 */

/**
 * Iterate character sequences of a given nodes.
 *
 * CharacterClassRange syntax can steal a part of character sequence,
 * so this function reverts CharacterClassRange syntax and restore the sequence.
 * @param {CharacterClassElement[]} nodes The node list to iterate character sequences.
 * @returns {IterableIterator<Character[]>} The list of character sequences.
 */
function *iterateCharacterSequence(nodes) {

    /** @type {Character[]} */
    let seq = [];

    for (const node of nodes) {
        switch (node.type) {
            case "Character":
                seq.push(node);
                break;

            case "CharacterClassRange":
                seq.push(node.min);
                yield seq;
                seq = [node.max];
                break;

            case "CharacterSet":
            case "CharacterClass": // [[]] nesting character class
            case "ClassStringDisjunction": // \q{...}
            case "ExpressionCharacterClass": // [A--B]
                if (seq.length > 0) {
                    yield seq;
                    seq = [];
                }
                break;

            // no default
        }
    }

    if (seq.length > 0) {
        yield seq;
    }
}

/**
 * Checks whether the given character node is a Unicode code point escape or not.
 * @param {Character} char the character node to check.
 * @returns {boolean} `true` if the character node is a Unicode code point escape.
 */
function isUnicodeCodePointEscape(char) {
    return /^\\u\{[\da-f]+\}$/iu.test(char.raw);
}

/**
 * Each function returns `true` if it detects that kind of problem.
 * @type {Record<string, (chars: Character[]) => boolean>}
 */
const hasCharacterSequence = {
    surrogatePairWithoutUFlag(chars) {
        return chars.some((c, i) => {
            if (i === 0) {
                return false;
            }
            const c1 = chars[i - 1];

            return (
                isSurrogatePair(c1.value, c.value) &&
                !isUnicodeCodePointEscape(c1) &&
                !isUnicodeCodePointEscape(c)
            );
        });
    },

    surrogatePair(chars) {
        return chars.some((c, i) => {
            if (i === 0) {
                return false;
            }
            const c1 = chars[i - 1];

            return (
                isSurrogatePair(c1.value, c.value) &&
                (
                    isUnicodeCodePointEscape(c1) ||
                    isUnicodeCodePointEscape(c)
                )
            );
        });
    },

    combiningClass(chars) {
        return chars.some((c, i) => (
            i !== 0 &&
            isCombiningCharacter(c.value) &&
            !isCombiningCharacter(chars[i - 1].value)
        ));
    },

    emojiModifier(chars) {
        return chars.some((c, i) => (
            i !== 0 &&
            isEmojiModifier(c.value) &&
            !isEmojiModifier(chars[i - 1].value)
        ));
    },

    regionalIndicatorSymbol(chars) {
        return chars.some((c, i) => (
            i !== 0 &&
            isRegionalIndicatorSymbol(c.value) &&
            isRegionalIndicatorSymbol(chars[i - 1].value)
        ));
    },

    zwj(chars) {
        const lastIndex = chars.length - 1;

        return chars.some((c, i) => (
            i !== 0 &&
            i !== lastIndex &&
            c.value === 0x200d &&
            chars[i - 1].value !== 0x200d &&
            chars[i + 1].value !== 0x200d
        ));
    }
};

const kinds = Object.keys(hasCharacterSequence);

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        type: "problem",

        docs: {
            description: "Disallow characters which are made with multiple code points in character class syntax",
            recommended: true,
            url: "https://eslint.org/docs/latest/rules/no-misleading-character-class"
        },

        hasSuggestions: true,

        schema: [],

        messages: {
            surrogatePairWithoutUFlag: "Unexpected surrogate pair in character class. Use 'u' flag.",
            surrogatePair: "Unexpected surrogate pair in character class.",
            combiningClass: "Unexpected combined character in character class.",
            emojiModifier: "Unexpected modified Emoji in character class.",
            regionalIndicatorSymbol: "Unexpected national flag in character class.",
            zwj: "Unexpected joined character sequence in character class.",
            suggestUnicodeFlag: "Add unicode 'u' flag to regex."
        }
    },
    create(context) {
        const sourceCode = context.sourceCode;
        const parser = new RegExpParser();

        /**
         * Verify a given regular expression.
         * @param {Node} node The node to report.
         * @param {string} pattern The regular expression pattern to verify.
         * @param {string} flags The flags of the regular expression.
         * @param {Function} unicodeFixer Fixer for missing "u" flag.
         * @returns {void}
         */
        function verify(node, pattern, flags, unicodeFixer) {
            let patternNode;

            try {
                patternNode = parser.parsePattern(
                    pattern,
                    0,
                    pattern.length,
                    {
                        unicode: flags.includes("u"),
                        unicodeSets: flags.includes("v")
                    }
                );
            } catch {

                // Ignore regular expressions with syntax errors
                return;
            }

            const foundKinds = new Set();

            visitRegExpAST(patternNode, {
                onCharacterClassEnter(ccNode) {
                    for (const chars of iterateCharacterSequence(ccNode.elements)) {
                        for (const kind of kinds) {
                            if (hasCharacterSequence[kind](chars)) {
                                foundKinds.add(kind);
                            }
                        }
                    }
                }
            });

            for (const kind of foundKinds) {
                let suggest;

                if (kind === "surrogatePairWithoutUFlag") {
                    suggest = [{
                        messageId: "suggestUnicodeFlag",
                        fix: unicodeFixer
                    }];
                }

                context.report({
                    node,
                    messageId: kind,
                    suggest
                });
            }
        }

        return {
            "Literal[regex]"(node) {
                verify(node, node.regex.pattern, node.regex.flags, fixer => {
                    if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)) {
                        return null;
                    }

                    return fixer.insertTextAfter(node, "u");
                });
            },
            "Program"(node) {
                const scope = sourceCode.getScope(node);
                const tracker = new ReferenceTracker(scope);

                /*
                 * Iterate calls of RegExp.
                 * E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`,
                 *       `const {RegExp: a} = window; new a()`, etc...
                 */
                for (const { node: refNode } of tracker.iterateGlobalReferences({
                    RegExp: { [CALL]: true, [CONSTRUCT]: true }
                })) {
                    const [patternNode, flagsNode] = refNode.arguments;
                    const pattern = getStringIfConstant(patternNode, scope);
                    const flags = getStringIfConstant(flagsNode, scope);

                    if (typeof pattern === "string") {
                        verify(refNode, pattern, flags || "", fixer => {

                            if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)) {
                                return null;
                            }

                            if (refNode.arguments.length === 1) {
                                const penultimateToken = sourceCode.getLastToken(refNode, { skip: 1 }); // skip closing parenthesis

                                return fixer.insertTextAfter(
                                    penultimateToken,
                                    astUtils.isCommaToken(penultimateToken)
                                        ? ' "u",'
                                        : ', "u"'
                                );
                            }

                            if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") {
                                const range = [flagsNode.range[0], flagsNode.range[1] - 1];

                                return fixer.insertTextAfterRange(range, "u");
                            }

                            return null;
                        });
                    }
                }
            }
        };
    }
};


code:
/**
 * @fileoverview Rule to disallow mixed binary operators.
 * @author Toru Nagashima
 * @deprecated in ESLint v8.53.0
 */

"use strict";

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

const astUtils = require("./utils/ast-utils.js");

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

const ARITHMETIC_OPERATORS = ["+", "-", "*", "/", "%", "**"];
const BITWISE_OPERATORS = ["&", "|", "^", "~", "<<", ">>", ">>>"];
const COMPARISON_OPERATORS = ["==", "!=", "===", "!==", ">", ">=", "<", "<="];
const LOGICAL_OPERATORS = ["&&", "||"];
const RELATIONAL_OPERATORS = ["in", "instanceof"];
const TERNARY_OPERATOR = ["?:"];
const COALESCE_OPERATOR = ["??"];
const ALL_OPERATORS = [].concat(
    ARITHMETIC_OPERATORS,
    BITWISE_OPERATORS,
    COMPARISON_OPERATORS,
    LOGICAL_OPERATORS,
    RELATIONAL_OPERATORS,
    TERNARY_OPERATOR,
    COALESCE_OPERATOR
);
const DEFAULT_GROUPS = [
    ARITHMETIC_OPERATORS,
    BITWISE_OPERATORS,
    COMPARISON_OPERATORS,
    LOGICAL_OPERATORS,
    RELATIONAL_OPERATORS
];
const TARGET_NODE_TYPE = /^(?:Binary|Logical|Conditional)Expression$/u;

/**
 * Normalizes options.
 * @param {Object|undefined} options A options object to normalize.
 * @returns {Object} Normalized option object.
 */
function normalizeOptions(options = {}) {
    const hasGroups = options.groups && options.groups.length > 0;
    const groups = hasGroups ? options.groups : DEFAULT_GROUPS;
    const allowSamePrecedence = options.allowSamePrecedence !== false;

    return {
        groups,
        allowSamePrecedence
    };
}

/**
 * Checks whether any group which includes both given operator exists or not.
 * @param {Array<string[]>} groups A list of groups to check.
 * @param {string} left An operator.
 * @param {string} right Another operator.
 * @returns {boolean} `true` if such group existed.
 */
function includesBothInAGroup(groups, left, right) {
    return groups.some(group => group.includes(left) && group.includes(right));
}

/**
 * Checks whether the given node is a conditional expression and returns the test node else the left node.
 * @param {ASTNode} node A node which can be a BinaryExpression or a LogicalExpression node.
 * This parent node can be BinaryExpression, LogicalExpression
 *      , or a ConditionalExpression node
 * @returns {ASTNode} node the appropriate node(left or test).
 */
function getChildNode(node) {
    return node.type === "ConditionalExpression" ? node.test : node.left;
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        deprecated: true,
        replacedBy: [],
        type: "suggestion",

        docs: {
            description: "Disallow mixed binary operators",
            recommended: false,
            url: "https://eslint.org/docs/latest/rules/no-mixed-operators"
        },

        schema: [
            {
                type: "object",
                properties: {
                    groups: {
                        type: "array",
                        items: {
                            type: "array",
                            items: { enum: ALL_OPERATORS },
                            minItems: 2,
                            uniqueItems: true
                        },
                        uniqueItems: true
                    },
                    allowSamePrecedence: {
                        type: "boolean",
                        default: true
                    }
                },
                additionalProperties: false
            }
        ],

        messages: {
            unexpectedMixedOperator: "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'. Use parentheses to clarify the intended order of operations."
        }
    },

    create(context) {
        const sourceCode = context.sourceCode;
        const options = normalizeOptions(context.options[0]);

        /**
         * Checks whether a given node should be ignored by options or not.
         * @param {ASTNode} node A node to check. This is a BinaryExpression
         *      node or a LogicalExpression node. This parent node is one of
         *      them, too.
         * @returns {boolean} `true` if the node should be ignored.
         */
        function shouldIgnore(node) {
            const a = node;
            const b = node.parent;

            return (
                !includesBothInAGroup(options.groups, a.operator, b.type === "ConditionalExpression" ? "?:" : b.operator) ||
                (
                    options.allowSamePrecedence &&
                    astUtils.getPrecedence(a) === astUtils.getPrecedence(b)
                )
            );
        }

        /**
         * Checks whether the operator of a given node is mixed with parent
         * node's operator or not.
         * @param {ASTNode} node A node to check. This is a BinaryExpression
         *      node or a LogicalExpression node. This parent node is one of
         *      them, too.
         * @returns {boolean} `true` if the node was mixed.
         */
        function isMixedWithParent(node) {

            return (
                node.operator !== node.parent.operator &&
                !astUtils.isParenthesised(sourceCode, node)
            );
        }

        /**
         * Gets the operator token of a given node.
         * @param {ASTNode} node A node to check. This is a BinaryExpression
         *      node or a LogicalExpression node.
         * @returns {Token} The operator token of the node.
         */
        function getOperatorToken(node) {
            return sourceCode.getTokenAfter(getChildNode(node), astUtils.isNotClosingParenToken);
        }

        /**
         * Reports both the operator of a given node and the operator of the
         * parent node.
         * @param {ASTNode} node A node to check. This is a BinaryExpression
         *      node or a LogicalExpression node. This parent node is one of
         *      them, too.
         * @returns {void}
         */
        function reportBothOperators(node) {
            const parent = node.parent;
            const left = (getChildNode(parent) === node) ? node : parent;
            const right = (getChildNode(parent) !== node) ? node : parent;
            const data = {
                leftOperator: left.operator || "?:",
                rightOperator: right.operator || "?:"
            };

            context.report({
                node: left,
                loc: getOperatorToken(left).loc,
                messageId: "unexpectedMixedOperator",
                data
            });
            context.report({
                node: right,
                loc: getOperatorToken(right).loc,
                messageId: "unexpectedMixedOperator",
                data
            });
        }

        /**
         * Checks between the operator of this node and the operator of the
         * parent node.
         * @param {ASTNode} node A node to check.
         * @returns {void}
         */
        function check(node) {
            if (
                TARGET_NODE_TYPE.test(node.parent.type) &&
                isMixedWithParent(node) &&
                !shouldIgnore(node)
            ) {
                reportBothOperators(node);
            }
        }

        return {
            BinaryExpression: check,
            LogicalExpression: check
        };
    }
};


code:
/**
 * @fileoverview Rule to enforce grouped require statements for Node.JS
 * @author Raphael Pigulla
 * @deprecated in ESLint v7.0.0
 */

"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        deprecated: true,

        replacedBy: [],

        type: "suggestion",

        docs: {
            description: "Disallow `require` calls to be mixed with regular variable declarations",
            recommended: false,
            url: "https://eslint.org/docs/latest/rules/no-mixed-requires"
        },

        schema: [
            {
                oneOf: [
                    {
                        type: "boolean"
                    },
                    {
                        type: "object",
                        properties: {
                            grouping: {
                                type: "boolean"
                            },
                            allowCall: {
                                type: "boolean"
                            }
                        },
                        additionalProperties: false
                    }
                ]
            }
        ],

        messages: {
            noMixRequire: "Do not mix 'require' and other declarations.",
            noMixCoreModuleFileComputed: "Do not mix core, module, file and computed requires."
        }
    },

    create(context) {

        const options = context.options[0];
        let grouping = false,
            allowCall = false;

        if (typeof options === "object") {
            grouping = options.grouping;
            allowCall = options.allowCall;
        } else {
            grouping = !!options;
        }

        /**
         * Returns the list of built-in modules.
         * @returns {string[]} An array of built-in Node.js modules.
         */
        function getBuiltinModules() {

            /*
             * This list is generated using:
             * `require("repl")._builtinLibs.concat('repl').sort()`
             * This particular list is as per nodejs v0.12.2 and iojs v0.7.1
             */
            return [
                "assert", "buffer", "child_process", "cluster", "crypto",
                "dgram", "dns", "domain", "events", "fs", "http", "https",
                "net", "os", "path", "punycode", "querystring", "readline",
                "repl", "smalloc", "stream", "string_decoder", "tls", "tty",
                "url", "util", "v8", "vm", "zlib"
            ];
        }

        const BUILTIN_MODULES = getBuiltinModules();

        const DECL_REQUIRE = "require",
            DECL_UNINITIALIZED = "uninitialized",
            DECL_OTHER = "other";

        const REQ_CORE = "core",
            REQ_FILE = "file",
            REQ_MODULE = "module",
            REQ_COMPUTED = "computed";

        /**
         * Determines the type of a declaration statement.
         * @param {ASTNode} initExpression The init node of the VariableDeclarator.
         * @returns {string} The type of declaration represented by the expression.
         */
        function getDeclarationType(initExpression) {
            if (!initExpression) {

                // "var x;"
                return DECL_UNINITIALIZED;
            }

            if (initExpression.type === "CallExpression" &&
                initExpression.callee.type === "Identifier" &&
                initExpression.callee.name === "require"
            ) {

                // "var x = require('util');"
                return DECL_REQUIRE;
            }
            if (allowCall &&
                initExpression.type === "CallExpression" &&
                initExpression.callee.type === "CallExpression"
            ) {

                // "var x = require('diagnose')('sub-module');"
                return getDeclarationType(initExpression.callee);
            }
            if (initExpression.type === "MemberExpression") {

                // "var x = require('glob').Glob;"
                return getDeclarationType(initExpression.object);
            }

            // "var x = 42;"
            return DECL_OTHER;
        }

        /**
         * Determines the type of module that is loaded via require.
         * @param {ASTNode} initExpression The init node of the VariableDeclarator.
         * @returns {string} The module type.
         */
        function inferModuleType(initExpression) {
            if (initExpression.type === "MemberExpression") {

                // "var x = require('glob').Glob;"
                return inferModuleType(initExpression.object);
            }
            if (initExpression.arguments.length === 0) {

                // "var x = require();"
                return REQ_COMPUTED;
            }

            const arg = initExpression.arguments[0];

            if (arg.type !== "Literal" || typeof arg.value !== "string") {

                // "var x = require(42);"
                return REQ_COMPUTED;
            }

            if (BUILTIN_MODULES.includes(arg.value)) {

                // "var fs = require('fs');"
                return REQ_CORE;
            }
            if (/^\.{0,2}\//u.test(arg.value)) {

                // "var utils = require('./utils');"
                return REQ_FILE;
            }

            // "var async = require('async');"
            return REQ_MODULE;

        }

        /**
         * Check if the list of variable declarations is mixed, i.e. whether it
         * contains both require and other declarations.
         * @param {ASTNode} declarations The list of VariableDeclarators.
         * @returns {boolean} True if the declarations are mixed, false if not.
         */
        function isMixed(declarations) {
            const contains = {};

            declarations.forEach(declaration => {
                const type = getDeclarationType(declaration.init);

                contains[type] = true;
            });

            return !!(
                contains[DECL_REQUIRE] &&
                (contains[DECL_UNINITIALIZED] || contains[DECL_OTHER])
            );
        }

        /**
         * Check if all require declarations in the given list are of the same
         * type.
         * @param {ASTNode} declarations The list of VariableDeclarators.
         * @returns {boolean} True if the declarations are grouped, false if not.
         */
        function isGrouped(declarations) {
            const found = {};

            declarations.forEach(declaration => {
                if (getDeclarationType(declaration.init) === DECL_REQUIRE) {
                    found[inferModuleType(declaration.init)] = true;
                }
            });

            return Object.keys(found).length <= 1;
        }

        return {

            VariableDeclaration(node) {

                if (isMixed(node.declarations)) {
                    context.report({
                        node,
                        messageId: "noMixRequire"
                    });
                } else if (grouping && !isGrouped(node.declarations)) {
                    context.report({
                        node,
                        messageId: "noMixCoreModuleFileComputed"
                    });
                }
            }
        };

    }
};


code:
/**
 * @fileoverview Disallow mixed spaces and tabs for indentation
 * @author Jary Niebur
 * @deprecated in ESLint v8.53.0
 */
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        deprecated: true,
        replacedBy: [],
        type: "layout",

        docs: {
            description: "Disallow mixed spaces and tabs for indentation",
            recommended: true,
            url: "https://eslint.org/docs/latest/rules/no-mixed-spaces-and-tabs"
        },

        schema: [
            {
                enum: ["smart-tabs", true, false]
            }
        ],

        messages: {
            mixedSpacesAndTabs: "Mixed spaces and tabs."
        }
    },

    create(context) {
        const sourceCode = context.sourceCode;

        let smartTabs;

        switch (context.options[0]) {
            case true: // Support old syntax, maybe add deprecation warning here
            case "smart-tabs":
                smartTabs = true;
                break;
            default:
                smartTabs = false;
        }

        //--------------------------------------------------------------------------
        // Public
        //--------------------------------------------------------------------------

        return {

            "Program:exit"(node) {
                const lines = sourceCode.lines,
                    comments = sourceCode.getAllComments(),
                    ignoredCommentLines = new Set();

                // Add all lines except the first ones.
                comments.forEach(comment => {
                    for (let i = comment.loc.start.line + 1; i <= comment.loc.end.line; i++) {
                        ignoredCommentLines.add(i);
                    }
                });

                /*
                 * At least one space followed by a tab
                 * or the reverse before non-tab/-space
                 * characters begin.
                 */
                let regex = /^(?=( +|\t+))\1(?:\t| )/u;

                if (smartTabs) {

                    /*
                     * At least one space followed by a tab
                     * before non-tab/-space characters begin.
                     */
                    regex = /^(?=(\t*))\1(?=( +))\2\t/u;
                }

                lines.forEach((line, i) => {
                    const match = regex.exec(line);

                    if (match) {
                        const lineNumber = i + 1;
                        const loc = {
                            start: {
                                line: lineNumber,
                                column: match[0].length - 2
                            },
                            end: {
                                line: lineNumber,
                                column: match[0].length
                            }
                        };

                        if (!ignoredCommentLines.has(lineNumber)) {
                            const containingNode = sourceCode.getNodeByRangeIndex(sourceCode.getIndexFromLoc(loc.start));

                            if (!(containingNode && ["Literal", "TemplateElement"].includes(containingNode.type))) {
                                context.report({
                                    node,
                                    loc,
                                    messageId: "mixedSpacesAndTabs"
                                });
                            }
                        }
                    }
                });
            }
        };
    }
};


code:
/**
 * @fileoverview Rule to check use of chained assignment expressions
 * @author Stewart Rand
 */

"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        type: "suggestion",

        docs: {
            description: "Disallow use of chained assignment expressions",
            recommended: false,
            url: "https://eslint.org/docs/latest/rules/no-multi-assign"
        },

        schema: [{
            type: "object",
            properties: {
                ignoreNonDeclaration: {
                    type: "boolean",
                    default: false
                }
            },
            additionalProperties: false
        }],

        messages: {
            unexpectedChain: "Unexpected chained assignment."
        }
    },

    create(context) {

        //--------------------------------------------------------------------------
        // Public
        //--------------------------------------------------------------------------
        const options = context.options[0] || {
            ignoreNonDeclaration: false
        };
        const selectors = [
            "VariableDeclarator > AssignmentExpression.init",
            "PropertyDefinition > AssignmentExpression.value"
        ];

        if (!options.ignoreNonDeclaration) {
            selectors.push("AssignmentExpression > AssignmentExpression.right");
        }

        return {
            [selectors](node) {
                context.report({
                    node,
                    messageId: "unexpectedChain"
                });
            }
        };

    }
};


code:
/**
 * @fileoverview Disallow use of multiple spaces.
 * @author Nicholas C. Zakas
 * @deprecated in ESLint v8.53.0
 */

"use strict";

const astUtils = require("./utils/ast-utils");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        deprecated: true,
        replacedBy: [],
        type: "layout",

        docs: {
            description: "Disallow multiple spaces",
            recommended: false,
            url: "https://eslint.org/docs/latest/rules/no-multi-spaces"
        },

        fixable: "whitespace",

        schema: [
            {
                type: "object",
                properties: {
                    exceptions: {
                        type: "object",
                        patternProperties: {
                            "^([A-Z][a-z]*)+$": {
                                type: "boolean"
                            }
                        },
                        additionalProperties: false
                    },
                    ignoreEOLComments: {
                        type: "boolean",
                        default: false
                    }
                },
                additionalProperties: false
            }
        ],

        messages: {
            multipleSpaces: "Multiple spaces found before '{{displayValue}}'."
        }
    },

    create(context) {
        const sourceCode = context.sourceCode;
        const options = context.options[0] || {};
        const ignoreEOLComments = options.ignoreEOLComments;
        const exceptions = Object.assign({ Property: true }, options.exceptions);
        const hasExceptions = Object.keys(exceptions).some(key => exceptions[key]);

        /**
         * Formats value of given comment token for error message by truncating its length.
         * @param {Token} token comment token
         * @returns {string} formatted value
         * @private
         */
        function formatReportedCommentValue(token) {
            const valueLines = token.value.split("\n");
            const value = valueLines[0];
            const formattedValue = `${value.slice(0, 12)}...`;

            return valueLines.length === 1 && value.length <= 12 ? value : formattedValue;
        }

        //--------------------------------------------------------------------------
        // Public
        //--------------------------------------------------------------------------

        return {
            Program() {
                sourceCode.tokensAndComments.forEach((leftToken, leftIndex, tokensAndComments) => {
                    if (leftIndex === tokensAndComments.length - 1) {
                        return;
                    }
                    const rightToken = tokensAndComments[leftIndex + 1];

                    // Ignore tokens that don't have 2 spaces between them or are on different lines
                    if (
                        !sourceCode.text.slice(leftToken.range[1], rightToken.range[0]).includes("  ") ||
                        leftToken.loc.end.line < rightToken.loc.start.line
                    ) {
                        return;
                    }

                    // Ignore comments that are the last token on their line if `ignoreEOLComments` is active.
                    if (
                        ignoreEOLComments &&
                        astUtils.isCommentToken(rightToken) &&
                        (
                            leftIndex === tokensAndComments.length - 2 ||
                            rightToken.loc.end.line < tokensAndComments[leftIndex + 2].loc.start.line
                        )
                    ) {
                        return;
                    }

                    // Ignore tokens that are in a node in the "exceptions" object
                    if (hasExceptions) {
                        const parentNode = sourceCode.getNodeByRangeIndex(rightToken.range[0] - 1);

                        if (parentNode && exceptions[parentNode.type]) {
                            return;
                        }
                    }

                    let displayValue;

                    if (rightToken.type === "Block") {
                        displayValue = `/*${formatReportedCommentValue(rightToken)}*/`;
                    } else if (rightToken.type === "Line") {
                        displayValue = `//${formatReportedCommentValue(rightToken)}`;
                    } else {
                        displayValue = rightToken.value;
                    }

                    context.report({
                        node: rightToken,
                        loc: { start: leftToken.loc.end, end: rightToken.loc.start },
                        messageId: "multipleSpaces",
                        data: { displayValue },
                        fix: fixer => fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ")
                    });
                });
            }
        };

    }
};