Repository navigation
fix: decode ANSI-C byte escapes as UTF-8 - #16
Merged
Merged
Conversation
commit: |
Bash treats \xHH and \NNN in $'...' as raw bytes, so $'\xc3\xa9' is "é". The decoder mapped each byte to its own code point, producing "é". Runs of byte escapes are now collected and decoded with TextDecoder. A byte outside a well-formed UTF-8 sequence has no character and becomes U+FFFD per maximal subpart, as browsers do, instead of the code point with the same number ($'\xc3' and $'\xc3\x83' both became "Ã").
mindlace
force-pushed
the
fix/ansi-c-utf8-bytes
branch
from
October 20, 2026 04:38
6278998 to
ccbda12
Compare
Pass ignoreBOM so a leading EF BB BF in a byte run decodes to U+FEFF; the default strips it, and every run is decoded on its own, so $'\xef\xbb\xbf' became empty. Append ASCII bytes directly instead of decoding them: the lexer decodes ANSI-C quotes while scanning, and a decoder call per run roughly doubled parse time for scripts full of escapes such as \033. Cover byte order marks and heredoc delimiters written with UTF-8 byte escapes.
webpro
reviewed
Oct 20, 2026
webpro
left a comment
Member
There was a problem hiding this comment.
Thanks for the fix, Ethan! Decoding byte runs as UTF-8 with U+FFFD for malformed bytes looks right, and heredoc delimiters such as $'\xc3\xa9' now close on their é line as well.
I pushed one commit on top with two changes:
TextDecoderstrips a leading byte order mark by default, and each run is decoded on its own, so$'\xef\xbb\xbf'became an empty string andsed $'1s/^\xef\xbb\xbf//'lost its BOM. The decoder now passesignoreBOM: true.- The lexer decodes ANSI-C quotes while scanning, so a decoder call per byte run roughly doubled parse time for scripts full of escapes like
\033or\x1b. ASCII bytes now skip the decoder.
It also adds tests for the byte order mark and the heredoc delimiter.
Member
|
🚀 This pull request is included in v6.0.1. See Release 6.0.1 for release notes. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bash treats
\xHHand\NNNin$'...'as raw bytes, so multi-byte UTF-8 written as escapes decodes to a single character.decodeAnsiCQuotedmapped each byte to its own code point instead:Word.valuebefore$'\xc3\xa9'c3 a9"é""é"$'\303\251'c3 a9"é""é"$'\xe2\x82\xac'e2 82 ac"â\x82¬""€"$'\xf0\x9f\x98\x80'f0 9f 98 80"ð\x9f\x98\x80""😀"Change
Runs of
\xand octal escapes are collected as bytes and decoded withTextDecoderwhen the run ends, so hex and octal can mix ($'\xc3\251').Behavior change for malformed bytes: a byte outside a well-formed UTF-8 sequence now becomes U+FFFD, following the WHATWG and browser rule (one replacement per maximal subpart). Before, it became the code point with the same number, which made it look like a real character:
$'\xc3'and$'\xc3\x83'both decoded to"Ã", though only the second isÃin any Unicode encoding. A JS string can't hold the raw byte, sovaluecan't be lossless here, but U+FFFD at least can't be mistaken for real text.$'\xc3'c3"Ã""�"$'\777'ff"ÿ""�"$'\xe2\x82'e2 82"â\x82""�"The existing
$'\777'expectation intest/word-parts.test.tsis updated to match.TextDecoderis global since Node 11, andenginesalready requires Node ≥14.Testing
test/word-parts.test.tsfor valid sequences (expected values from bash 5.3) and for malformed ones (expected values fromTextDecoder).parse()and through bash. The output matched on every case whose bytes are valid UTF-8.pnpm test:typesandpnpm lintare clean.pnpm testpasses exceptbenchmark configuration is edited in the script instead of parsed from CLI flags, which also fails onmainwithout this change.