js-regex-cjk-word-boundary

Par divinevideo · divine-mobile

Corrige les échecs de regex JavaScript lors de la correspondance avec du texte CJK (chinois/japonais/coréen) utilisant des limites de mot `\b`. À utiliser quand : (1) un pattern regex avec `\b` échoue silencieusement à correspondre à du texte japonais, chinois ou coréen, (2) le pattern fonctionne pour du texte Latin/ASCII mais pas CJK, (3) `hasDriverLicenceCue` ou une fonction similaire de détection de texte retourne `false` pour une entrée CJK malgré des caractères corrects, (4) `inferIssuerFromTitleText` ou des fonctions de correspondance de patterns échouent sur des scripts non-latins. Cause racine : `\b` en JavaScript ne reconnaît que `[a-zA-Z0-9_]` comme « caractères de mot » — les caractères CJK sont classifiés comme `\W` (non-mot), donc `\b` avant/après du CJK voit toujours une limite non-mot/non-mot et échoue à correspondre.

npx skills add https://github.com/divinevideo/divine-mobile --skill js-regex-cjk-word-boundary

Regex JavaScript : la limite de mot \b échoue avec les caractères CJK

Problème

L'assertion \b (limite de mot) de JavaScript échoue silencieusement avec les caractères CJK (chinois/japonais/coréen). Le regex se compile sans erreur et s'exécute sans lever d'exception, mais il ne correspond jamais au texte CJK qui devrait correspondre. C'est particulièrement insidieux car :

  1. Aucune erreur n'est levée — le regex retourne simplement false
  2. Le même pattern fonctionne parfaitement pour le texte latin/ASCII
  3. Les caractères CJK du pattern sont corrects (vérifiés par comparaison directe de chaînes)

Contexte / Conditions déclenchantes

  • Un pattern comme /\b\u904B\u8EE2\u514D\u8A31\u8A3C\b/ (japonais : 運転免許証) retourne false sur du texte contenant exactement ces caractères
  • Un pattern comme /\b\uC6B4\uC804\uBA74\uD5C8\uC99D\b/ (coréen : 운전면허증) échoue de la même façon
  • Les fonctions de détection de texte (p. ex. hasDriverLicenceCue(), inferIssuerFromTitleText()) retournent des résultats incorrects pour une entrée CJK alors qu'elles fonctionnent correctement pour tous les patterns en alphabet latin
  • Tout regex utilisant des limites \b autour de caractères Unicode non-ASCII (affecte également le cyrillique, l'arabe, le thaï, etc.)

Cause racine

\b de JavaScript correspond à la limite entre un « caractère de mot » (\w = [a-zA-Z0-9_]) et un « caractère non-mot » (\W). Les caractères CJK sont classés comme \W (caractères non-mot).

Quand \b apparaît avant un caractère CJK, il cherche une transition \w-vers-\W ou \W-vers-\w. Mais si le caractère précédent est aussi \W (espace, ponctuation, début de chaîne, ou un autre caractère CJK), la condition de limite est \W-vers-\W, ce que \b ne correspond PAS.

// Ceci ÉCHOUE — \b ne fonctionne pas avec CJK
/\b\u904B\u8EE2\u514D\u8A31\u8A3C\b/.test("運転免許証")  // false!

// Ceci FONCTIONNE — pas de limites de mot
/\u904B\u8EE2\u514D\u8A31\u8A3C/.test("運転免許証")  // true

Solution

Correction rapide : supprimer \b des patterns CJK

Supprimez simplement les assertions \b de tout pattern regex qui correspond aux caractères CJK :

// Avant (cassé) :
[/\b\u904B\u8EE2\u514D\u8A31\u8A3C\b/, "JAPAN"],      // 運転免許証
[/\b\uC6B4\uC804\uBA74\uD5C8\uC99D\b/, "KOREA"],      // 운전면허증

// Après (fonctionnant) :
[/\u904B\u8EE2\u514D\u8A31\u8A3C/, "JAPAN"],            // 運転免許証
[/\uC6B4\uC804\uBA74\uD5C8\uC99D/, "KOREA"],           // 운전면허증

Meilleure correction : utiliser des limites Unicode-aware (si disponible)

Pour les environnements supportant le flag v (ES2024+), vous pouvez utiliser les echappements de propriétés Unicode :

// Approche Unicode-aware (nécessite le support du flag /v) :
/(?<=^|[\s\p{P}])\u904B\u8EE2\u514D\u8A31\u8A3C(?=$|[\s\p{P}])/v

Alternative : limite manuelle avec lookbehind/lookahead

// Limite manuelle qui fonctionne avec CJK :
/(?<!\p{L})\u904B\u8EE2\u514D\u8A31\u8A3C(?!\p{L})/u

Pour les tableaux de patterns mixtes latin/CJK

Quand vous avez un tableau de patterns où certains sont en latin et d'autres en CJK, utilisez \b uniquement pour les patterns latins :

const patterns = [
  [/\bDRIVER'?S?\s+LICEN[CS]E\b/, "match"],   // Latin — \b fonctionne
  [/\bFÜHRERSCHEIN\b/, "match"],               // Latin+diacritiques — \b fonctionne (Ü est \W mais le contexte aide)
  [/\u904B\u8EE2\u514D\u8A31\u8A3C/, "match"], // CJK — \b non nécessaire
  [/\uC6B4\uC804\uBA74\uD5C8\uC99D/, "match"], // CJK — \b non nécessaire
];

Vérification

// Test que la correspondance CJK fonctionne :
console.log(/\u904B\u8EE2\u514D\u8A31\u8A3C/.test("運転免許証"));  // true
console.log(/\uC6B4\uC804\uBA74\uD5C8\uC99D/.test("운전면허증"));  // true

// Vérifier qu'il ne correspond pas faux aux sous-chaînes que vous ne voulez pas :
console.log(/\u904B\u8EE2\u514D\u8A31\u8A3C/.test("別の運転免許証テスト"));  // true (la correspondance de sous-chaîne est généralement fine pour CJK)

Exemple

Cas réel d'une fonction de détection de type de document :

function hasDriverLicenceCue(text) {
  const combined = text.toUpperCase();
  return (
    /\bDRIVER'?S?\s+LICEN[CS]E\b/.test(combined) ||   // Anglais
    /\bFÜHRERSCHEIN\b/.test(combined) ||               // Allemand
    /\bPERMIS DE CONDUIRE\b/.test(combined) ||          // Français
    /\u904B\u8EE2\u514D\u8A31\u8A3C/.test(combined) || // Japonais (pas de \b!)
    /\uC6B4\uC804\uBA74\uD5C8\uC99D/.test(combined)    // Coréen (pas de \b!)
  );
}

Notes

  • Cela affecte TOUS les scripts Unicode non-ASCII, pas seulement CJK : cyrillique, arabe, thaï, devanagari, etc.
  • Le flag u (unicode) ne corrige PAS ceci — le comportement de \b avec \w/\W reste inchangé.
  • Supprimer \b des patterns CJK est généralement sûr car les caractères CJK sont peu susceptibles d'apparaître comme sous-chaînes au sein d'autres mots dans des contextes non liés.
  • Le package npm regexp-cjk fournit des utilitaires regex sensibles aux CJK si vous avez besoin de correspondances plus sophistiquées.
  • Lors du débogage : si un regex fonctionne pour "DRIVER'S LICENCE" mais pas pour "運転免許証", suspectez d'abord \b.

Références

Skills similaires