Skip to content

fix(wrap): correctly detect single balanced group (#549)#699

Open
MukundaKatta wants to merge 1 commit into
unjs:mainfrom
MukundaKatta:fix/wrap-single-group-detection
Open

fix(wrap): correctly detect single balanced group (#549)#699
MukundaKatta wants to merge 1 commit into
unjs:mainfrom
MukundaKatta:fix/wrap-single-group-detection

Conversation

@MukundaKatta

@MukundaKatta MukundaKatta commented Apr 15, 2026

Copy link
Copy Markdown

Summary

Fixes #549 — `maybe()` / `optionally()` incorrectly handled named captures.

`wrap()` decided whether a value was already grouped via:

```ts
const NO_WRAP_RE = /^(?:(.*)|\?.)$/
```

That check only looks at the first and last characters. A concatenation such as `(?:[-_.])?(?\d+)` starts with `(` and ends with `)` but is really two adjacent atoms, so `wrap()` skipped grouping it. The final `?` from `maybe` then bound to the trailing named capture alone:

```
/(?:beta|dev)(?:[-.])?(?\d+)?/gi // before
/(?:beta|dev)(?:(?:[-
.])?(?\d+))?/gi // after
```

Replaced the regex with a small balance-aware scan (`isSingleGroup`) that returns true only when the value is a single parenthesised group spanning from the first `(` to the last `)`. Character classes are skipped so literal `[(]` / `[)]` don't unbalance the count; escaped characters are also skipped.

Test plan

  • Added a regression case in `test/inputs.test.ts` using the exact pattern from the issue.
  • `pnpm test` — 105/105 tests pass (inline snapshots for existing `maybe` / `oneOrMore` cases like `maybe(exactly('foo').groupedAs('groupName'))` → `(?foo)?` unchanged).
  • `pnpm lint` clean.
  • Manual repro from the issue now matches `Beta` in `-Beta.zip` under both patterns.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed pattern wrapping behavior for complex regular expressions containing concatenations and parenthesized groups.
  • Tests

    • Added regression test for optional group wrapping with named captures.

…njs#549)

`wrap()` previously used `/^(?:\(.*\)|\\?.)$/` to decide whether a
value was "already grouped". That only checks the leading `(` and
trailing `)`, so a concatenation like `(?:[\-_.])?(?<number>\d+)`
(which starts with `(` and ends with `)` but is really two atoms)
was treated as already-grouped. The resulting `maybe(...)` output
`(?:[\-_.])?(?<number>\d+)?` only made the trailing named capture
optional.

Replace the regex with a small balance-aware scan that returns true
only when the string is a single parenthesised group spanning from
the first `(` to the last `)`. Character classes are skipped so a
literal `[(]` doesn't unbalance the counter.

Fixes unjs#549.
@MukundaKatta MukundaKatta requested a review from danielroe as a code owner April 15, 2026 03:20
@vercel

vercel Bot commented Apr 15, 2026

Copy link
Copy Markdown

@MukundaKatta is attempting to deploy a commit to the Nuxt Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f87f7d1d-2046-4fc7-9451-70a1ce37c078

📥 Commits

Reviewing files that changed from the base of the PR and between 5805a8c and c28a46f.

📒 Files selected for processing (2)
  • src/core/wrap.ts
  • test/inputs.test.ts

📝 Walkthrough

Walkthrough

This pull request fixes the wrap() function to correctly handle inputs that start with ( and end with ). It replaces a single regex-based check with a two-part predicate that includes a new isSingleGroup() helper to verify whether a string is truly a single balanced parenthesized group. This resolves incorrect wrapping behavior in maybe()/optionally() when named capture groups are present.

Changes

Cohort / File(s) Summary
Core wrap logic
src/core/wrap.ts
Replaced regex-based "don't wrap" check (`/^(?:(.*)
Regression test
test/inputs.test.ts
Added test case for maybe() with a named capture group (via .as('number')), verifying that the entire concatenation wraps into a single optional non-capturing group rather than applying optionality to individual components.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A hop, a skip, a fix so neat,
Now maybe() groups are quite complete,
No more fragments left alone,
One wrapped unit—the bug is gone!
Magic regexps dance as one. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: correcting single balanced group detection in the wrap function to resolve the maybe()/optionally() issue.
Linked Issues check ✅ Passed The PR directly addresses issue #549 by replacing the flawed regex-based check with a balance-aware isSingleGroup predicate that correctly detects when content is a single parenthesized group, fixing the incorrect wrapping of maybe() with named captures.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the wrap function logic and adding a regression test; no unrelated modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

maybe()/optionally() incorrectly handles named capture

1 participant