@@ -551,7 +551,7 @@ namespace ts {
551551 return emitNode && emitNode . flags || 0 ;
552552 }
553553
554- export function getLiteralText ( node : LiteralLikeNode , sourceFile : SourceFile , neverAsciiEscape : boolean | undefined ) {
554+ export function getLiteralText ( node : LiteralLikeNode , sourceFile : SourceFile , neverAsciiEscape : boolean | undefined , jsxAttributeEscape : boolean ) {
555555 // If we don't need to downlevel and we can reach the original source text using
556556 // the node's parent reference, then simply get the text as it was originally written.
557557 if ( ! nodeIsSynthesized ( node ) && node . parent && ! (
@@ -561,24 +561,29 @@ namespace ts {
561561 return getSourceTextOfNodeFromSourceFile ( sourceFile , node ) ;
562562 }
563563
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-
568564 // If we can't reach the original source text, use the canonical form if it's a number,
569565 // or a (possibly escaped) quoted form of the original text if it's string-like.
570566 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 ;
572571 if ( ( < StringLiteral > node ) . singleQuote ) {
573572 return "'" + escapeText ( node . text , CharacterCodes . singleQuote ) + "'" ;
574573 }
575574 else {
576575 return '"' + escapeText ( node . text , CharacterCodes . doubleQuote ) + '"' ;
577576 }
577+ }
578578 case SyntaxKind . NoSubstitutionTemplateLiteral :
579579 case SyntaxKind . TemplateHead :
580580 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+
582587 const rawText = ( < TemplateLiteralLikeNode > node ) . rawText || escapeTemplateSubstitution ( escapeText ( node . text , CharacterCodes . backtick ) ) ;
583588 switch ( node . kind ) {
584589 case SyntaxKind . NoSubstitutionTemplateLiteral :
@@ -591,6 +596,7 @@ namespace ts {
591596 return "}" + rawText + "`" ;
592597 }
593598 break ;
599+ }
594600 case SyntaxKind . NumericLiteral :
595601 case SyntaxKind . BigIntLiteral :
596602 case SyntaxKind . RegularExpressionLiteral :
@@ -3384,6 +3390,25 @@ namespace ts {
33843390 "\u0085" : "\\u0085" // nextLine
33853391 } ) ;
33863392
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+
33873412 /**
33883413 * Based heavily on the abstract 'Quote'/'QuoteJSONString' operation from ECMA-262 (24.3.2.2),
33893414 * but augmented for a few select characters (e.g. lineSeparator, paragraphSeparator, nextLine)
@@ -3397,6 +3422,46 @@ namespace ts {
33973422 return s . replace ( escapedCharsRegExp , getReplacement ) ;
33983423 }
33993424
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+
34003465 /**
34013466 * Strip off existed surrounding single quotes, double quotes, or backticks from a given string
34023467 *
@@ -3416,40 +3481,11 @@ namespace ts {
34163481 charCode === CharacterCodes . backtick ;
34173482 }
34183483
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-
34323484 export function isIntrinsicJsxName ( name : __String | string ) {
34333485 const ch = ( name as string ) . charCodeAt ( 0 ) ;
34343486 return ( ch >= CharacterCodes . a && ch <= CharacterCodes . z ) || stringContains ( ( name as string ) , "-" ) ;
34353487 }
34363488
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-
34533489 const indentStrings : string [ ] = [ "" , " " ] ;
34543490 export function getIndentString ( level : number ) {
34553491 if ( indentStrings [ level ] === undefined ) {
0 commit comments