@@ -957,3 +957,118 @@ export function getLocalizedString(params: LocalizeStringParams): string {
957957}
958958return indent + text ;
959959}
960+
961+ function decodeUCS16 ( input :string ) :number [ ] {
962+ let output :number [ ] = [ ] ;
963+ let counter :number = 0 ;
964+ let length :number = input . length ;
965+ let value :number ;
966+ let extra :number ;
967+ while ( counter < length ) {
968+ value = input . charCodeAt ( counter ++ ) ;
969+ // tslint:disable-next-line: no-bitwise
970+ if ( ( value & 0xF800 ) === 0xD800 && counter < length ) {
971+ // high surrogate, and there is a next character
972+ extra = input . charCodeAt ( counter ++ ) ;
973+ // tslint:disable-next-line: no-bitwise
974+ if ( ( extra & 0xFC00 ) === 0xDC00 ) { // low surrogate
975+ // tslint:disable-next-line: no-bitwise
976+ output . push ( ( ( value & 0x3FF ) << 10 ) + ( extra & 0x3FF ) + 0x10000 ) ;
977+ } else {
978+ output . push ( value , extra ) ;
979+ }
980+ } else {
981+ output . push ( value ) ;
982+ }
983+ }
984+ return output ;
985+ }
986+
987+ let allowedIdentifierUnicodeRanges :number [ ] [ ] = [
988+ [ 0x0030 , 0x0039 ] , // digits
989+ [ 0x0041 , 0x005A ] , // upper case letters
990+ [ 0x0061 , 0x007A ] , // lower case letters
991+ [ 0x00A8 , 0x00A8 ] , // DIARESIS
992+ [ 0x00AA , 0x00AA ] , // FEMININE ORDINAL INDICATOR
993+ [ 0x00AD , 0x00AD ] , // SOFT HYPHEN
994+ [ 0x00AF , 0x00AF ] , // MACRON
995+ [ 0x00B2 , 0x00B5 ] , // SUPERSCRIPT TWO - MICRO SIGN
996+ [ 0x00B7 , 0x00BA ] , // MIDDLE DOT - MASCULINE ORDINAL INDICATOR
997+ [ 0x00BC , 0x00BE ] , // VULGAR FRACTION ONE QUARTER - VULGAR FRACTION THREE QUARTERS
998+ [ 0x00C0 , 0x00D6 ] , // LATIN CAPITAL LETTER A WITH GRAVE - LATIN CAPITAL LETTER O WITH DIAERESIS
999+ [ 0x00D8 , 0x00F6 ] , // LATIN CAPITAL LETTER O WITH STROKE - LATIN SMALL LETTER O WITH DIAERESIS
1000+ [ 0x00F8 , 0x167F ] , // LATIN SMALL LETTER O WITH STROKE - CANADIAN SYLLABICS BLACKFOOT W
1001+ [ 0x1681 , 0x180D ] , // OGHAM LETTER BEITH - MONGOLIAN FREE VARIATION SELECTOR THREE
1002+ [ 0x180F , 0x1FFF ] , // SYRIAC LETTER BETH - GREEK DASIA
1003+ [ 0x200B , 0x200D ] , // ZERO WIDTH SPACE - ZERO WIDTH JOINER
1004+ [ 0x202A , 0x202E ] , // LEFT-TO-RIGHT EMBEDDING - RIGHT-TO-LEFT OVERRIDE
1005+ [ 0x203F , 0x2040 ] , // UNDERTIE - CHARACTER TIE
1006+ [ 0x2054 , 0x2054 ] , // INVERTED UNDERTIE
1007+ [ 0x2060 , 0x218F ] , // WORD JOINER - TURNED DIGIT THREE
1008+ [ 0x2460 , 0x24FF ] , // CIRCLED DIGIT ONE - NEGATIVE CIRCLED DIGIT ZERO
1009+ [ 0x2776 , 0x2793 ] , // DINGBAT NEGATIVE CIRCLED DIGIT ONE - DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN
1010+ [ 0x2C00 , 0x2DFF ] , // GLAGOLITIC CAPITAL LETTER AZU - COMBINING CYRILLIC LETTER IOTIFIED BIG YUS
1011+ [ 0x2E80 , 0x2FFF ] , // CJK RADICAL REPEAT - IDEOGRAPHIC DESCRIPTION CHARACTER OVERLAID
1012+ [ 0x3004 , 0x3007 ] , // JAPANESE INDUSTRIAL STANDARD SYMBOL - IDEOGRAPHIC NUMBER ZERO
1013+ [ 0x3021 , 0x302F ] , // HANGZHOU NUMERAL ONE - HANGUL DOUBLE DOT TONE MARK
1014+ [ 0x3031 , 0xD7FF ] , // VERTICAL KANA REPEAT MARK - HANGUL JONGSEONG PHIEUPH-THIEUTH
1015+ [ 0xF900 , 0xFD3D ] , // CJK COMPATIBILITY IDEOGRAPH-F900 - ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM
1016+ [ 0xFD40 , 0xFDCF ] , // ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM - ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM
1017+ [ 0xFDF0 , 0xFE44 ] , // ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM - PRESENTATION FORM FOR VERTICAL RIGHT WHITE CORNER BRACKET
1018+ [ 0xFE47 , 0xFFFD ] , // PRESENTATION FORM FOR VERTICAL LEFT SQUARE BRACKET - REPLACEMENT CHARACTER
1019+ [ 0x10000 , 0x1FFFD ] , // LINEAR B SYLLABLE B008 A - CHEESE WEDGE (U+1F9C0)
1020+ [ 0x20000 , 0x2FFFD ] , //
1021+ [ 0x30000 , 0x3FFFD ] , //
1022+ [ 0x40000 , 0x4FFFD ] , //
1023+ [ 0x50000 , 0x5FFFD ] , //
1024+ [ 0x60000 , 0x6FFFD ] , //
1025+ [ 0x70000 , 0x7FFFD ] , //
1026+ [ 0x80000 , 0x8FFFD ] , //
1027+ [ 0x90000 , 0x9FFFD ] , //
1028+ [ 0xA0000 , 0xAFFFD ] , //
1029+ [ 0xB0000 , 0xBFFFD ] , //
1030+ [ 0xC0000 , 0xCFFFD ] , //
1031+ [ 0xD0000 , 0xDFFFD ] , //
1032+ [ 0xE0000 , 0xEFFFD ] // LANGUAGE TAG (U+E0001) - VARIATION SELECTOR-256 (U+E01EF)
1033+ ] ;
1034+
1035+ let disallowedFirstCharacterIdentifierUnicodeRanges :number [ ] [ ] = [
1036+ [ 0x0030 , 0x0039 ] , // digits
1037+ [ 0x0300 , 0x036F ] , // COMBINING GRAVE ACCENT - COMBINING LATIN SMALL LETTER X
1038+ [ 0x1DC0 , 0x1DFF ] , // COMBINING DOTTED GRAVE ACCENT - COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW
1039+ [ 0x20D0 , 0x20FF ] , // COMBINING LEFT HARPOON ABOVE - COMBINING ASTERISK ABOVE
1040+ [ 0xFE20 , 0xFE2F ] , // COMBINING LIGATURE LEFT HALF - COMBINING CYRILLIC TITLO RIGHT HALF
1041+ ] ;
1042+
1043+ export function isValidIdentifier ( candidate :string ) :boolean {
1044+ if ( ! candidate ) {
1045+ return false ;
1046+ }
1047+ let decoded :number [ ] = decodeUCS16 ( candidate ) ;
1048+ if ( ! decoded || ! decoded . length ) {
1049+ return false ;
1050+ }
1051+
1052+ // Reject if first character is disallowed
1053+ for ( let i :number = 0 ; i < disallowedFirstCharacterIdentifierUnicodeRanges . length ; i ++ ) {
1054+ let disallowedCharacters :number [ ] = disallowedFirstCharacterIdentifierUnicodeRanges [ i ] ;
1055+ if ( decoded [ 0 ] >= disallowedCharacters [ 0 ] && decoded [ 0 ] <= disallowedCharacters [ 1 ] ) {
1056+ return false ;
1057+ }
1058+ }
1059+
1060+ for ( let position :number = 0 ; position < decoded . length ; position ++ ) {
1061+ let found :boolean = false ;
1062+ for ( let i :number = 0 ; i < allowedIdentifierUnicodeRanges . length ; i ++ ) {
1063+ let allowedCharacters :number [ ] = allowedIdentifierUnicodeRanges [ i ] ;
1064+ if ( decoded [ position ] >= allowedCharacters [ 0 ] && decoded [ position ] <= allowedCharacters [ 1 ] ) {
1065+ found = true ;
1066+ break ;
1067+ }
1068+ }
1069+ if ( ! found ) {
1070+ return false ;
1071+ }
1072+ }
1073+ return true ;
1074+ }