|                                                                                                                                                                                                     |  | /** * @fileoverview Rule to count multiple spaces in regular expressions * @author Matt DuVall <http://www.mattduvall.com/>
 */
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");const regexpp = require("@eslint-community/regexpp");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const regExpParser = new regexpp.RegExpParser();const DOUBLE_SPACE = / {2}/u;
/** * 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";}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "suggestion",
        docs: {            description: "Disallow multiple spaces in regular expressions",            recommended: true,            url: "https://eslint.org/docs/latest/rules/no-regex-spaces"        },
        schema: [],        fixable: "code",
        messages: {            multipleSpaces: "Spaces are hard to count. Use {{{length}}}."        }    },
    create(context) {
        const sourceCode = context.sourceCode;
        /**         * Validate regular expression         * @param {ASTNode} nodeToReport Node to report.         * @param {string} pattern Regular expression pattern to validate.         * @param {string} rawPattern Raw representation of the pattern in the source code.         * @param {number} rawPatternStartRange Start range of the pattern in the source code.         * @param {string} flags Regular expression flags.         * @returns {void}         * @private         */        function checkRegex(nodeToReport, pattern, rawPattern, rawPatternStartRange, flags) {
            // Skip if there are no consecutive spaces in the source code, to avoid reporting e.g., RegExp(' \ ').
            if (!DOUBLE_SPACE.test(rawPattern)) {                return;            }
            const characterClassNodes = [];            let regExpAST;
            try {                regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") });            } catch {
                // Ignore regular expressions with syntax errors
                return;            }
            regexpp.visitRegExpAST(regExpAST, {                onCharacterClassEnter(ccNode) {                    characterClassNodes.push(ccNode);                }            });
            const spacesPattern = /( {2,})(?: [+*{?]|[^+*{?]|$)/gu;            let match;
            while ((match = spacesPattern.exec(pattern))) {                const { 1: { length }, index } = match;
                // Report only consecutive spaces that are not in character classes.
                if (                    characterClassNodes.every(({ start, end }) => index < start || end <= index)                ) {                    context.report({                        node: nodeToReport,                        messageId: "multipleSpaces",                        data: { length },                        fix(fixer) {                            if (pattern !== rawPattern) {                                return null;                            }                            return fixer.replaceTextRange(                                [rawPatternStartRange + index, rawPatternStartRange + index + length],                                ` {${length}}`                            );                        }                    });
                    // Report only the first occurrence of consecutive spaces
                    return;                }            }        }
        /**         * Validate regular expression literals         * @param {ASTNode} node node to validate         * @returns {void}         * @private         */        function checkLiteral(node) {            if (node.regex) {                const pattern = node.regex.pattern;                const rawPattern = node.raw.slice(1, node.raw.lastIndexOf("/"));                const rawPatternStartRange = node.range[0] + 1;                const flags = node.regex.flags;
                checkRegex(                    node,                    pattern,                    rawPattern,                    rawPatternStartRange,                    flags                );            }        }
        /**         * Validate strings passed to the RegExp constructor         * @param {ASTNode} node node to validate         * @returns {void}         * @private         */        function checkFunction(node) {            const scope = sourceCode.getScope(node);            const regExpVar = astUtils.getVariableByName(scope, "RegExp");            const shadowed = regExpVar && regExpVar.defs.length > 0;            const patternNode = node.arguments[0];
            if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(patternNode) && !shadowed) {                const pattern = patternNode.value;                const rawPattern = patternNode.raw.slice(1, -1);                const rawPatternStartRange = patternNode.range[0] + 1;                let flags;
                if (node.arguments.length < 2) {
                    // It has no flags.
                    flags = "";                } else {                    const flagsNode = node.arguments[1];
                    if (isString(flagsNode)) {                        flags = flagsNode.value;                    } else {
                        // The flags cannot be determined.
                        return;                    }                }
                checkRegex(                    node,                    pattern,                    rawPattern,                    rawPatternStartRange,                    flags                );            }        }
        return {            Literal: checkLiteral,            CallExpression: checkFunction,            NewExpression: checkFunction        };    }};
 |