Regex Anchor Precedence

ID

javascript.regex_anchor_precedence

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

JavaScript

Tags

code-style

Description

Reports an alternation whose first branch starts with ^ or whose last branch ends with $, while at least one other branch of the same alternation carries no such anchor.

Rationale

Anchors bind tighter than |, so ^a|b|c$ is not "match a, b or c, anchored at both ends" — it parses as (?:^a)|b|(?:c$):

const verb = /^get|post|put/;

verb.test("get");            // true  — as intended
verb.test("unsupported-put") // true  — NOT intended: only the first branch is anchored

The layout suggests every branch is bounded; only the outermost ones actually are. In a validation regex that means the pattern accepts input it was written to reject, and the gap is invisible on review because the anchors are right there in the source.

Remediation

Wrap the alternatives in a non-capturing group so the anchors apply to the whole thing, or repeat the anchors on every branch if each was really meant to be anchored on its own:

const verb = /^(?:get|post|put)$/;
// or
const verb = /^get$|^post$|^put$/;

What is not reported

An alternation where every branch carries the same anchor on its own (a$|b$|^c$) is not reported — there is no gap between branches to be misled by. An alternation already wrapped in a group (^(?:a|b|c)$), or one whose anchored branches are each explicitly scoped in their own group ((?:^a)|b|(?:c$)), reads exactly as intended and is not reported either.

Two idioms are exempt. A branch that is nothing but the anchor — (?:,|$), (|[\\]) — is the deliberate "this or the boundary" form and has no content to anchor. The two-branch trim shape ^A|B$, as in text.replace(/^,|,$/g, ""), already anchors each branch at the edge it strips.

An alternation with no anchor at either end is out of scope.

© 2026 Xygeni. All rights reserved