Les caractères Unicode invisibles n’occupent aucun espace visible mais font toujours partie de la chaîne. Les plus courants sont les espaces sans chasse, les césures conditionnelles, les indicateurs d’ordre des octets (BOM), les espaces insécables et les marques directionnelles. Chacun a une fonction typographique légitime, et chacun casse la recherche, la validation, l’analyse et la déduplication lorsqu’il survit à un copier-coller.
Les caractères que vous rencontrerez vraiment
| Caractère | Point de code | Fonction | Ce qu’il casse |
|---|---|---|---|
| Espace sans chasse | U+200B | Suggère un point de coupure de ligne | Recherche, comparaison de chaînes |
| Antiliant sans chasse | U+200C | Empêche les lettres de se lier | Ligatures, texte arabe et indien |
| Liant sans chasse | U+200D | Force les lettres à se lier | Séquences d’emojis, longueur du texte |
| Césure conditionnelle | U+00AD | Trait d’union affiché uniquement en cas de coupure | Recherche, texte copié |
| Espace insécable | U+00A0 | Espace qui ne passe jamais à la ligne | trim(), analyse CSV, code |
| Espace fine insécable | U+202F | Espace fine avant la ponctuation | Comme ci-dessus |
| Indicateur d’ordre des octets | U+FEFF | Marque l’encodage en début de fichier | Première colonne CSV, analyse JSON |
| Liant de mots | U+2060 | Empêche une coupure, sans largeur | Recherche |
| Marque gauche-à-droite | U+200E | Définit le sens du texte | Ordre d’affichage, trim() |
| Marque droite-à-gauche | U+200F | Définit le sens du texte | Ordre d’affichage |
| Forçage droite-à-gauche | U+202E | Force l’affichage de droite à gauche | Usurpation de noms de fichiers |
| Séparateur de ligne | U+2028 | Saut de ligne Unicode | Chaînes littérales JavaScript |
| Séparateur de paragraphe | U+2029 | Saut de paragraphe Unicode | JSON dans les anciens parseurs |
Chacun de ces caractères a une vraie fonction. Le problème n’est jamais leur existence — c’est qu’ils voyagent silencieusement entre des systèmes qui les traitent différemment.
D’où ils viennent
- Copier depuis des pages web. Les sites insèrent des espaces sans chasse pour contrôler la coupure des longues chaînes, et des césures conditionnelles pour le texte justifié. Les deux accompagnent le texte copié.
- Traitements de texte. Les espaces insécables sont insérées automatiquement pour garder un nombre avec son unité, ou un nom sur une seule ligne.
- Extraction de PDF. Copier depuis un PDF produit souvent des césures conditionnelles là où le typographe a coupé les mots entre les lignes, plus des espaces insécables issues de la justification.
- Interfaces de chat IA. La sortie est rendue en HTML, donc la copie emporte les caractères d’espacement utilisés par cette mise en forme — généralement U+00A0 et U+202F.
- Conversions d’encodage. Un indicateur d’ordre des octets apparaît lorsqu’un fichier est enregistré en UTF-8 avec BOM, fréquent sous Windows.
- Insertion délibérée. Les caractères sans chasse ont été utilisés pour marquer des documents afin de tracer les fuites, et pour faire passer du texte à travers des filtres de mots-clés.
Le schéma est constant : ils entrent quand le texte passe d’un contexte de présentation à un contexte de données.
Comment ils cassent les choses
La recherche échoue silencieusement. Une espace sans chasse à l’intérieur d’un mot signifie que sa recherche ne renvoie rien. Rien n’indique pourquoi — le mot est visiblement là.
La validation rejette une saisie correcte. Une adresse e-mail suivie d’une espace insécable échoue au contrôle de format. L’utilisateur voit une adresse correcte et un message d’erreur incompréhensible.
Les comparaisons échouent. "Ada" === "Ada\u200B" est faux. La déduplication rate des doublons évidents ; les recherches ne renvoient rien ; les jointures perdent des lignes.
trim() ne sert à rien. La plupart des implémentations de trim() ne suppriment que les espaces ASCII. Une espace insécable survit à .trim() dans de nombreux langages, c’est pourquoi « je l’ai déjà nettoyé » est une impasse si fréquente.
Le code ne compile pas. Une espace insécable là où une espace normale est attendue produit une erreur de syntaxe sur une ligne qui a l’air parfaite.
Les imports CSV se corrompent. Un indicateur d’ordre des octets se colle au premier en-tête, donc id devient \uFEFFid et la première colonne n’est silencieusement pas reconnue.
La longueur est fausse. Les liants sans chasse dans les séquences d’emojis signifient qu’un seul emoji visible peut compter plusieurs points de code, donc les limites de caractères rejettent une saisie qui semble largement conforme.
Les noms de fichiers peuvent tromper. U+202E inverse le texte affiché, donc report\u202Egnp.exe peut s’afficher comme reportexe.png. C’est un vrai problème de sécurité, pas une curiosité.
Les repérer et les supprimer
Analysez d’abord. Collez le texte dans le détecteur de caractères invisibles. Il indique quels types sont présents et en quelle quantité, puis les supprime sur demande. Voir les comptages d’abord vous dit si vous avez affaire à un caractère perdu ou à une contamination systématique.
Dans un éditeur. VS Code met en évidence la plupart des caractères invisibles par défaut via son réglage unicode-highlight. Pour une recherche-remplacement par expression régulière, ce motif attrape l’ensemble courant :
[\u200B-\u200F\u00AD\uFEFF\u2060\u202A-\u202E\u2028\u2029]
Dans le code, normalisez à la frontière — à l’arrivée des données, pas à leur utilisation :
const clean = input
.replace(/[\u200B-\u200F\u2060\uFEFF\u00AD\u202A-\u202E]/g, "")
.replace(/[\u00A0\u202F]/g, " ")
.normalize("NFC")
.trim();
Notez le traitement en deux étapes : les caractères sans chasse sont supprimés, mais les espaces insécables sont converties en espaces ordinaires — les supprimer collerait les mots entre eux.
Ne supprimez pas à l’aveugle. Les liants sans chasse sont indispensables dans les séquences d’emojis et en arabe, en persan et dans de nombreuses écritures indiennes, où leur suppression change le rendu des mots. Supprimez agressivement dans les identifiants, les clés et le code ; prudemment dans le contenu destiné aux utilisateurs.
Pour la question précise de savoir si les outils IA insèrent ces caractères délibérément, voir ChatGPT cache-t-il des caractères invisibles. L’équivalent visible mais tout aussi perturbateur est traité dans pourquoi les guillemets courbes cassent le code.
Questions fréquentes
Qu’est-ce qu’une espace sans chasse ?
U+200B, un caractère qui n’occupe aucune largeur visible mais fait toujours partie de la chaîne. Les sites web l’utilisent pour suggérer où un mot long peut être coupé. Il casse la recherche et la comparaison de chaînes car le texte semble identique alors que les octets sous-jacents diffèrent.
Pourquoi ma recherche échoue-t-elle sur un texte qui est pourtant là ?
Il y a presque certainement un caractère invisible à l’intérieur du mot — généralement une espace sans chasse ou une césure conditionnelle récupérée sur une page web ou un PDF. Le texte affiché correspond à ce que vous avez tapé, mais la chaîne sous-jacente non.
trim() supprime-t-il les espaces insécables ?
Généralement non. La plupart des implémentations de trim ne suppriment que les espaces ASCII, donc une espace insécable (U+00A0) survit. C’est pourquoi une saisie qui semble n’avoir aucune espace en début ou en fin échoue toujours à la validation après nettoyage.
Comment supprimer les caractères invisibles d’un texte ?
Collez-le dans un détecteur qui les liste et les supprime, ou utilisez une expression régulière couvrant U+200B–U+200F, U+00AD, U+FEFF, U+2060 et U+202A–U+202E. Supprimez les caractères sans chasse, mais convertissez les espaces insécables en espaces ordinaires plutôt que de les supprimer, sinon les mots se colleront.
Les caractères invisibles sont-ils parfois utiles ?
Oui. Les césures conditionnelles contrôlent les coupures de mots dans le texte justifié, les espaces insécables gardent les valeurs avec leurs unités, et les liants sans chasse sont essentiels aux séquences d’emojis ainsi qu’aux écritures arabe, persane et indiennes. Supprimez agressivement dans le code et les identifiants, prudemment dans la prose.