@@ -551,7 +551,7 @@ namespace ts {
551
551
return emitNode && emitNode . flags || 0 ;
552
552
}
553
553
554
- export function getLiteralText ( node : LiteralLikeNode , sourceFile : SourceFile , neverAsciiEscape : boolean | undefined ) {
554
+ export function getLiteralText ( node : LiteralLikeNode , sourceFile : SourceFile , neverAsciiEscape : boolean | undefined , jsxAttributeEscape : boolean ) {
555
555
// If we don't need to downlevel and we can reach the original source text using
556
556
// the node's parent reference, then simply get the text as it was originally written.
557
557
if ( ! nodeIsSynthesized ( node ) && node . parent && ! (
@@ -561,24 +561,29 @@ namespace ts {
561
561
return getSourceTextOfNodeFromSourceFile ( sourceFile , node ) ;
562
562
}
563
563
564
- // If a NoSubstitutionTemplateLiteral appears to have a substitution in it, the original text
565
- // had to include a backslash: `not \${a} substitution`.
566
- const escapeText = neverAsciiEscape || ( getEmitFlags ( node ) & EmitFlags . NoAsciiEscaping ) ? escapeString : escapeNonAsciiString ;
567
-
568
564
// If we can't reach the original source text, use the canonical form if it's a number,
569
565
// or a (possibly escaped) quoted form of the original text if it's string-like.
570
566
switch ( node . kind ) {
571
- case SyntaxKind . StringLiteral :
567
+ case SyntaxKind . StringLiteral : {
568
+ const escapeText = jsxAttributeEscape ? escapeJsxAttributeString :
569
+ neverAsciiEscape || ( getEmitFlags ( node ) & EmitFlags . NoAsciiEscaping ) ? escapeString :
570
+ escapeNonAsciiString ;
572
571
if ( ( < StringLiteral > node ) . singleQuote ) {
573
572
return "'" + escapeText ( node . text , CharacterCodes . singleQuote ) + "'" ;
574
573
}
575
574
else {
576
575
return '"' + escapeText ( node . text , CharacterCodes . doubleQuote ) + '"' ;
577
576
}
577
+ }
578
578
case SyntaxKind . NoSubstitutionTemplateLiteral :
579
579
case SyntaxKind . TemplateHead :
580
580
case SyntaxKind . TemplateMiddle :
581
- case SyntaxKind . TemplateTail :
581
+ case SyntaxKind . TemplateTail : {
582
+ // If a NoSubstitutionTemplateLiteral appears to have a substitution in it, the original text
583
+ // had to include a backslash: `not \${a} substitution`.
584
+ const escapeText = neverAsciiEscape || ( getEmitFlags ( node ) & EmitFlags . NoAsciiEscaping ) ? escapeString :
585
+ escapeNonAsciiString ;
586
+
582
587
const rawText = ( < TemplateLiteralLikeNode > node ) . rawText || escapeTemplateSubstitution ( escapeText ( node . text , CharacterCodes . backtick ) ) ;
583
588
switch ( node . kind ) {
584
589
case SyntaxKind . NoSubstitutionTemplateLiteral :
@@ -591,6 +596,7 @@ namespace ts {
591
596
return "}" + rawText + "`" ;
592
597
}
593
598
break ;
599
+ }
594
600
case SyntaxKind . NumericLiteral :
595
601
case SyntaxKind . BigIntLiteral :
596
602
case SyntaxKind . RegularExpressionLiteral :
@@ -3384,6 +3390,25 @@ namespace ts {
3384
3390
"\u0085" : "\\u0085" // nextLine
3385
3391
} ) ;
3386
3392
3393
+ function encodeUtf16EscapeSequence ( charCode : number ) : string {
3394
+ const hexCharCode = charCode . toString ( 16 ) . toUpperCase ( ) ;
3395
+ const paddedHexCode = ( "0000" + hexCharCode ) . slice ( - 4 ) ;
3396
+ return "\\u" + paddedHexCode ;
3397
+ }
3398
+
3399
+ function getReplacement ( c : string , offset : number , input : string ) {
3400
+ if ( c . charCodeAt ( 0 ) === CharacterCodes . nullCharacter ) {
3401
+ const lookAhead = input . charCodeAt ( offset + c . length ) ;
3402
+ if ( lookAhead >= CharacterCodes . _0 && lookAhead <= CharacterCodes . _9 ) {
3403
+ // If the null character is followed by digits, print as a hex escape to prevent the result from parsing as an octal (which is forbidden in strict mode)
3404
+ return "\\x00" ;
3405
+ }
3406
+ // Otherwise, keep printing a literal \0 for the null character
3407
+ return "\\0" ;
3408
+ }
3409
+ return escapedCharsMap . get ( c ) || encodeUtf16EscapeSequence ( c . charCodeAt ( 0 ) ) ;
3410
+ }
3411
+
3387
3412
/**
3388
3413
* Based heavily on the abstract 'Quote'/'QuoteJSONString' operation from ECMA-262 (24.3.2.2),
3389
3414
* but augmented for a few select characters (e.g. lineSeparator, paragraphSeparator, nextLine)
@@ -3397,6 +3422,46 @@ namespace ts {
3397
3422
return s . replace ( escapedCharsRegExp , getReplacement ) ;
3398
3423
}
3399
3424
3425
+ const nonAsciiCharacters = / [ ^ \u0000 - \u007F ] / g;
3426
+ export function escapeNonAsciiString ( s : string , quoteChar ?: CharacterCodes . doubleQuote | CharacterCodes . singleQuote | CharacterCodes . backtick ) : string {
3427
+ s = escapeString ( s , quoteChar ) ;
3428
+ // Replace non-ASCII characters with '\uNNNN' escapes if any exist.
3429
+ // Otherwise just return the original string.
3430
+ return nonAsciiCharacters . test ( s ) ?
3431
+ s . replace ( nonAsciiCharacters , c => encodeUtf16EscapeSequence ( c . charCodeAt ( 0 ) ) ) :
3432
+ s ;
3433
+ }
3434
+
3435
+ // This consists of the first 19 unprintable ASCII characters, JSX canonical escapes, lineSeparator,
3436
+ // paragraphSeparator, and nextLine. The latter three are just desirable to suppress new lines in
3437
+ // the language service. These characters should be escaped when printing, and if any characters are added,
3438
+ // the map below must be updated.
3439
+ const jsxDoubleQuoteEscapedCharsRegExp = / [ \" \u0000 - \u001f \u2028 \u2029 \u0085 ] / g;
3440
+ const jsxSingleQuoteEscapedCharsRegExp = / [ \' \u0000 - \u001f \u2028 \u2029 \u0085 ] / g;
3441
+ const jsxEscapedCharsMap = createMapFromTemplate ( {
3442
+ "\"" : """ ,
3443
+ "\'" : "'"
3444
+ } ) ;
3445
+
3446
+ function encodeJsxCharacterEntity ( charCode : number ) : string {
3447
+ const hexCharCode = charCode . toString ( 16 ) . toUpperCase ( ) ;
3448
+ return "&#x" + hexCharCode + ";" ;
3449
+ }
3450
+
3451
+ function getJsxAttributeStringReplacement ( c : string ) {
3452
+ if ( c . charCodeAt ( 0 ) === CharacterCodes . nullCharacter ) {
3453
+ return "�" ;
3454
+ }
3455
+ return jsxEscapedCharsMap . get ( c ) || encodeJsxCharacterEntity ( c . charCodeAt ( 0 ) ) ;
3456
+ }
3457
+
3458
+ export function escapeJsxAttributeString ( s : string , quoteChar ?: CharacterCodes . doubleQuote | CharacterCodes . singleQuote ) {
3459
+ const escapedCharsRegExp =
3460
+ quoteChar === CharacterCodes . singleQuote ? jsxSingleQuoteEscapedCharsRegExp :
3461
+ jsxDoubleQuoteEscapedCharsRegExp ;
3462
+ return s . replace ( escapedCharsRegExp , getJsxAttributeStringReplacement ) ;
3463
+ }
3464
+
3400
3465
/**
3401
3466
* Strip off existed surrounding single quotes, double quotes, or backticks from a given string
3402
3467
*
@@ -3416,40 +3481,11 @@ namespace ts {
3416
3481
charCode === CharacterCodes . backtick ;
3417
3482
}
3418
3483
3419
- function getReplacement ( c : string , offset : number , input : string ) {
3420
- if ( c . charCodeAt ( 0 ) === CharacterCodes . nullCharacter ) {
3421
- const lookAhead = input . charCodeAt ( offset + c . length ) ;
3422
- if ( lookAhead >= CharacterCodes . _0 && lookAhead <= CharacterCodes . _9 ) {
3423
- // If the null character is followed by digits, print as a hex escape to prevent the result from parsing as an octal (which is forbidden in strict mode)
3424
- return "\\x00" ;
3425
- }
3426
- // Otherwise, keep printing a literal \0 for the null character
3427
- return "\\0" ;
3428
- }
3429
- return escapedCharsMap . get ( c ) || get16BitUnicodeEscapeSequence ( c . charCodeAt ( 0 ) ) ;
3430
- }
3431
-
3432
3484
export function isIntrinsicJsxName ( name : __String | string ) {
3433
3485
const ch = ( name as string ) . charCodeAt ( 0 ) ;
3434
3486
return ( ch >= CharacterCodes . a && ch <= CharacterCodes . z ) || stringContains ( ( name as string ) , "-" ) ;
3435
3487
}
3436
3488
3437
- function get16BitUnicodeEscapeSequence ( charCode : number ) : string {
3438
- const hexCharCode = charCode . toString ( 16 ) . toUpperCase ( ) ;
3439
- const paddedHexCode = ( "0000" + hexCharCode ) . slice ( - 4 ) ;
3440
- return "\\u" + paddedHexCode ;
3441
- }
3442
-
3443
- const nonAsciiCharacters = / [ ^ \u0000 - \u007F ] / g;
3444
- export function escapeNonAsciiString ( s : string , quoteChar ?: CharacterCodes . doubleQuote | CharacterCodes . singleQuote | CharacterCodes . backtick ) : string {
3445
- s = escapeString ( s , quoteChar ) ;
3446
- // Replace non-ASCII characters with '\uNNNN' escapes if any exist.
3447
- // Otherwise just return the original string.
3448
- return nonAsciiCharacters . test ( s ) ?
3449
- s . replace ( nonAsciiCharacters , c => get16BitUnicodeEscapeSequence ( c . charCodeAt ( 0 ) ) ) :
3450
- s ;
3451
- }
3452
-
3453
3489
const indentStrings : string [ ] = [ "" , " " ] ;
3454
3490
export function getIndentString ( level : number ) {
3455
3491
if ( indentStrings [ level ] === undefined ) {
0 commit comments