Skip to content

eslint-factory: require-escaped-regexp-interpolation's isRegexEscapeReplaceCall only recognizes the single canonical global-meta [Content truncated due to length] #49282

Description

@github-actions

Rule

require-escaped-regexp-interpolation (shipped 2026-07-30, first review).

Problem

isRegexEscapeReplaceCall() in eslint-factory/src/rules/require-escaped-regexp-interpolation.ts requires the .replace() call's second argument to be the exact literal string "\$&" — i.e. it only recognizes the "escape every regex metacharacter globally" idiom (x.replace(/[.*+?^${}()|[\]\\]/g, "\$&")). Any other equally-valid, narrower inline escape is rejected as "unescaped", even when it demonstrably neutralizes the only metacharacter the value can contain.

Grounded evidence

actions/setup/js/interpolate_prompt.cjs:238-240:

const experimentName = key.substring("GH_AW_EXPERIMENTS_".length).toLowerCase(); // env-var suffix: [A-Z0-9_] only
const exprForm = `experiments.${experimentName}`;                                 // exactly one literal "."
const conditionPattern = new RegExp(`(\\{\\{#if[^}]*?)${exprForm.replace(".", "\\.")}`, "gi");

exprForm.replace(".", "\\.") deliberately escapes the single literal . that exprForm is guaranteed to contain (from the "experiments." prefix — experimentName comes from an uppercase, underscore-only env-var name and cannot itself contain regex metacharacters). This is a correct, intentional escape for this call site. isRegexEscapeReplaceCall doesn't recognize it because the replacement isn't exactly "\$&", so the rule reports unescapedInterpolation on already-escaped, safe code — a false positive in a live, in-scope production file (this file is not a .test.cjs, so it is linted).

Ask

Broaden isRegexEscapeReplaceCall to also accept the single-character/literal escape form: a .replace() call whose search argument is a string or regex literal for one specific character (or short literal), and whose replacement is a backslash followed by exactly that same literal (e.g. .replace(".", "\\."), .replace(/\./g, "\\.")). Keep the existing global-metachar-class + "\$&" form as the other accepted shape.

Acceptance criteria

  • New valid-case test: new RegExp(\...${x.replace(".", "\.")}`)(and ideally the regex-literal-search variantx.replace(/./g, "\.")`) no longer flagged.
  • Existing invalid-case tests (bare unescaped identifiers, escapeHtml, mixed escaped/unescaped) remain flagged — do not widen to accept arbitrary .replace() calls regardless of the replacement value.
  • Re-verify interpolate_prompt.cjs:240 is no longer flagged after the fix (grounding check).
  • Consider documenting the recognized .replace() shapes in the rule's require-escaped-regexp-interpolation README section once it's added (see doc-debt: this rule and 8 others shipped since the last README update are currently undocumented in eslint-factory/README.md's rule table).

Generated by 🤖 ESLint Refiner · agent · 216.4 AIC · ⌖ 6.19 AIC · ⊞ 5.1K ·

  • expires on Aug 6, 2026, 10:29 PM UTC-08:00

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions