( s: unknown )
| 95 | // Non-ASCII symbols |
| 96 | return /^[\p{XIDS}_]\p{XIDC}*$/u.test(s); |
| 97 | } |
| 98 | |
| 99 | const VS16 = '\\u{FE0F}'; // Variation Selector-16, forces emoji presentation |
| 100 | const KEYCAP = '\\u{20E3}'; // Combining Enclosing Keycap |
| 101 | const ZWJ = '\\u{200D}'; // Zero Width Joiner |
| 102 | |
| 103 | const FLAG_SEQUENCE = '\\p{RI}\\p{RI}'; |
| 104 | |
| 105 | const TAG_MOD = `(?:[\\u{E0020}-\\u{E007E}]+\\u{E007F})`; |
| 106 | const EMOJI_MOD = `(?:\\p{EMod}|${VS16}${KEYCAP}?|${TAG_MOD})`; |
| 107 | // Exclude ASCII chars (#, *, 0-9) which have the Emoji property in Unicode |
| 108 | // but should not be treated as emoji symbols |
| 109 | const EMOJI_NOT_SYMBOL = `(?:(?=\\P{XIDC})(?=[^\\x23\\x2a\\x30-\\x39])\\p{Emoji})`; |
| 110 | const ZWJ_ELEMENT = `(?:${EMOJI_NOT_SYMBOL}${EMOJI_MOD}*|\\p{Emoji}${EMOJI_MOD}+|${FLAG_SEQUENCE})`; |
| 111 | const POSSIBLE_EMOJI = `(?:${ZWJ_ELEMENT})(${ZWJ}${ZWJ_ELEMENT})*`; |
| 112 | const SOME_EMOJI = new RegExp(`(?:${POSSIBLE_EMOJI})+`, 'u'); |
| 113 | export const EMOJIS = new RegExp(`^(?:${POSSIBLE_EMOJI})+$`, 'u'); |
| 114 | |
| 115 | // Examine the string and return a string indicating if it's a valid symbol, |
| 116 | // and if not, why not. |
| 117 | // Useful for debugging. In production, use `isValidSymbol()` instead. |
| 118 | export function validateSymbol( |
| 119 | s: unknown |
| 120 | ): |
| 121 | | 'valid' |
| 122 | | 'not-a-string' |
| 123 | | 'empty-string' |
| 124 | | 'expected-nfc' |
| 125 | | 'unexpected-mixed-emoji' |
| 126 | | 'unexpected-bidi-marker' |
| 127 | | 'unexpected-script' |
| 128 | | 'invalid-first-char' |
| 129 | | 'invalid-char' { |
| 130 | if (typeof s !== 'string') return 'not-a-string'; |
| 131 | |
| 132 | // console.log([...s].map((x) => x.codePointAt(0)!.toString(16)).join(' ')); |
| 133 | |
| 134 | if (s === '') return 'empty-string'; |
| 135 | |
| 136 | // MathJSON symbols are always stored in Unicode NFC canonical order. |
| 137 | // See https://unicode.org/reports/tr15/ |
| 138 | if (s.normalize() !== s) return 'expected-nfc'; |
| 139 | |
| 140 | // Does the string contain any bidi marker? |
| 141 | // See https://www.unicode.org/L2/L2022/22028-bidi-prog.pdf |
| 142 | // > For identifiers, there should be no need to allow |
| 143 | // > [bidi control characters] at all, even if formally allowed. |
| 144 | if (/[\u200E\u200F\u2066-\u2069\u202A-\u202E]/.test(s)) |
| 145 | return 'unexpected-bidi-marker'; |
no test coverage detected