Repository: eslint/eslint
File: lib/rules/prefer-named-capture-group.js (JavaScript)

Code before:
// Helpers
//------------------------------------------------------------------------------

const parser = new regexpp.RegExpParser();

/**
 * Creates fixer suggestions for the regex, if statically determinable.
 * @param {number} groupStart Starting index of the regex group.
 * @param {string} pattern The regular expression pattern to be checked.
 * @param {string} rawText Source text of the regexNode.
 * @param {ASTNode} regexNode AST node which contains the regular expression.
 * @returns {Array<SuggestionResult>} Fixer suggestions for the regex, if statically determinable.
 */
function suggestIfPossible(groupStart, pattern, rawText, regexNode) {
    if (regexNode.type === "Literal") {
        if (typeof regexNode.value === "string" && rawText.includes("\\")) {
            return null;
        }
    } else if (
        regexNode.type === "TemplateLiteral" &&
        (regexNode.expressions.length || rawText.slice(1, -1) !== pattern)
    ) {
        return null;
    }

    const start = regexNode.range[0] + rawText.indexOf("(", groupStart) + 1;

    return [
        {
            fix(fixer) {
                return fixer.insertTextBeforeRange(
                    [start, start],
                    `?<temp${((pattern.match(/temp\d+/gu) || []).length || 0) + 1}>`
                );
            },
            messageId: "addGroupName"
        },
        {
            fix(fixer) {
                return fixer.insertTextBeforeRange(
                    [start, start],
                    "?:"
                );
            },
            messageId: "addNonCapture"
        }
    ];
}

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

Review comment (bug):
Can we now remove `indexOf`? I think we can now calculate `start` directly without searching for `(` that could mask incorrect calculations that might go unnoticed in our tests but become observable in some cases.

Code after:
// Helpers
//------------------------------------------------------------------------------

const parser = new regexpp.RegExpParser();

/**
 * Creates fixer suggestions for the regex, if statically determinable.
 * @param {number} groupStart Starting index of the regex group.
 * @param {string} pattern The regular expression pattern to be checked.
 * @param {string} rawText Source text of the regexNode.
 * @param {ASTNode} regexNode AST node which contains the regular expression.
 * @returns {Array<SuggestionResult>} Fixer suggestions for the regex, if statically determinable.
 */
function suggestIfPossible(groupStart, pattern, rawText, regexNode) {
    switch (regexNode.type) {
        case "Literal":
            if (typeof regexNode.value === "string" && rawText.includes("\\")) {
                return null;
            }
            break;
        case "TemplateLiteral":
            if (regexNode.expressions.length || rawText.slice(1, -1) !== pattern) {
                return null;
            }
            break;
        default:
            return null;
    }

    const start = regexNode.range[0] + groupStart + 2;

    return [
        {
            fix(fixer) {
                const existingTemps = pattern.match(/temp\d+/gu) || [];
                const highestTempCount = existingTemps.reduce(
                    (previous, next) =>
                        Math.max(previous, Number(next.slice("temp".length))),
                    0
                );

                return fixer.insertTextBeforeRange(
                    [start, start],
                    `?<temp${highestTempCount + 1}>`
                );
            },
            messageId: "addGroupName"
        },
        {
            fix(fixer) {
                return fixer.insertTextBeforeRange(