Skip to content

A global scan drops the trailing empty match: "a".match(/a*/g) is ["a"], node gives ["a",""] #9430

Description

@proggeramlug
"a".match(/a*/g)              // node ["a",""]        perry ["a"]
"ab".match(/b*/g)             // node ["","b",""]     perry ["","b"]
"a".replace(/a*/g, "<>")      // node "<><>"          perry "<>"

Same for matchAll. No flags beyond g are involved.

When the previous match ends at the end of the haystack, the scan stops instead of taking the final zero-width match that ECMAScript's RegExpExec loop produces there.

This is what makes test_parity_regex_replace_fn_lookahead diverge from node today, and it is why the /gms rows for terminator-final subjects had to be excluded from #9427's #9408 fixture — with a comment in the file pointing here.

Found while fixing #9408/#9409; independent of both.

Activity

  1. proggeramlug commented on Sep 2, 2026

    @proggeramlug
    ContributorAuthor

    Fixed by PR #9437: the shared global_scan.rs loop implements ECMAScript's keep-empty-match-then-advance rule for the two Rust-iterator lanes (regress already had it and is the pinned control). Wider than filed: the rule fires at EVERY match-end position, not just the trailing one. test_gap_9430_global_scan_trailing_empty.ts (64 diff lines before). Verified on main 0a1c137d90 with a fresh build: fixture byte-identical to node --experimental-strip-types (stdout, stderr, and exit code).
    The third independent root cause found nearby — split on the fancy-regex lane never running @@split, widened by #9427 — is #9438.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions