Un controllo della larghezza diceva che la stringa era sicura da tagliare. Ha diviso un kanji a metà.
Un nome è stato inserito in una tabella del terminale ed è uscito corrotto. Il cognome era 𠮷田.
Il primo carattere non è il comune 吉. È 𠮷 (U+20BB7). Si tratta di una forma rara utilizzata nei veri cognomi giapponesi. La tabella ha troncato la cella per adattarla a una colonna, lasciando un carattere corrotto.
Il bug risiedeva in una singola riga di codice. Era un'ottimizzazione che decideva che una stringa era sicura da tagliare tramite indice.
Una stringa JavaScript ha tre lunghezze diverse: • Code unit (.length): "𠮷".length è 2. • Code point: [..."𠮷"].length è 1. • Larghezza di visualizzazione: 𠮷 occupa 2 colonne.
Per il testo standard in inglese, questi numeri sono tutti uguali. Questa coincidenza fa apparire il codice sicuro.
Il carattere 𠮷 infrange questa regola. Ha 2 code unit perché è una coppia surrogata (surrogate pair). Occupa 2 colonne perché è un carattere largo. I numeri coincidono (2 = 2), ma per motivi diversi.
La libreria cli-table3 utilizzava una fast path: Se la lunghezza della code unit è uguale alla larghezza di visualizzazione, allora taglia la stringa per indice.
Questo ha funzionato per anni perché i caratteri giapponesi comuni come 漢 hanno una lunghezza di 1 e una larghezza di 2. Non hanno mai attivato la fast path.
La fast path viene attivata solo per caratteri rari come 𠮷 o le emoji. Questi caratteri hanno una lunghezza di 2 e una larghezza di 2. Il codice pensa che siano semplici caratteri a unità singola. Li taglia a metà tramite indice. Ciò lascia un surrogato isolato. Ecco perché il terminale mostra un quadratino corrotto.
Per risolvere il problema, è necessario:
- Proteggere la fast path per escludere le coppie surrogate.
- Effettuare il trimming tramite code point invece che tramite code unit.
L'uso di Array.from(str) aiuta perché itera per code point. Questo assicura di non tagliare mai un carattere a metà.
La lezione è semplice: non misurare mai con un'unità e tagliare con un'altra. Se misuri la larghezza di visualizzazione o i code point, devi tagliare utilizzando le stesse unità.
Testa il tuo codice con un carattere CJK Extension B o un'emoji. L'ASCII non rivelerà mai questo bug.
Comunità di apprendimento opzionale: https://greymoth-jp.github.io/cjk-failure-corpus/
