"😀".match(/./g).length // node 2 perry 1
"😀".split("").length // node 2 perry 1
/^.$/.test("😀") // node false perry true
Without the u flag, . matches a single UTF-16 code unit, so an astral character is two matches; String.prototype.split("") likewise splits into code units.
The string representation is correct — "😀".length is 2 and charCodeAt returns the surrogates — so this is the iterators disagreeing with the representation, not a storage issue.
Reachability
9 split("") sites in the cc bundle, and cc does emoji-width work constantly (terminal rendering, truncation, column math). A width calculation that sees one unit where node sees two mis-renders or mis-truncates.
Note
The u-flagged forms are correct, so a fix must keep /u code-point semantics while making the non-u path code-unit based — the regression test should assert both flags side by side, since fixing one direction and breaking the other would pass a single-flag test.
Found by a differential stress-test of claude-code under perry.
Without the
uflag,.matches a single UTF-16 code unit, so an astral character is two matches;String.prototype.split("")likewise splits into code units.The string representation is correct —
"😀".lengthis 2 andcharCodeAtreturns the surrogates — so this is the iterators disagreeing with the representation, not a storage issue.Reachability
9
split("")sites in the cc bundle, and cc does emoji-width work constantly (terminal rendering, truncation, column math). A width calculation that sees one unit where node sees two mis-renders or mis-truncates.Note
The
u-flagged forms are correct, so a fix must keep/ucode-point semantics while making the non-upath code-unit based — the regression test should assert both flags side by side, since fixing one direction and breaking the other would pass a single-flag test.Found by a differential stress-test of claude-code under perry.