@@ -147,61 +147,85 @@ namespace ts.refactor.convertStringOrTemplateLiteral {
147147 }
148148 } ;
149149
150- function concatConsecutiveString ( index : number , nodes : readonly Expression [ ] ) : [ number , string , number [ ] ] {
150+ function escapeStringForTemplate ( s : string ) {
151+ // Escaping for $s in strings that are to be used in template strings
152+ // Naive implementation: replace \x by itself and otherwise $ by \$.
153+ // But to complicate it a bit, this should work for raw strings too.
154+ // And another bit: escape the $ in the replacement for JS's .replace().
155+ return s . replace ( / \\ .| \$ / g, m => m === "$" ? "\\\$" : m ) ;
156+ // Finally, a less-backslash-happy version can work too, doing only ${ instead of all $s:
157+ // s.replace(/\\.|\${/g, m => m === "${" ? "\\\${" : m);
158+ // but `\$${foo}` is likely more clear than the more-confusing-but-still-working `$${foo}`.
159+ }
160+
161+ function getRawTextOfTemplate ( node : TemplateHead | TemplateMiddle | TemplateTail ) {
162+ // in these cases the right side is ${
163+ const rightShaving = isTemplateHead ( node ) || isTemplateMiddle ( node ) ? - 2 : - 1 ;
164+ return getTextOfNode ( node ) . slice ( 1 , rightShaving ) ;
165+ }
166+
167+ function concatConsecutiveString ( index : number , nodes : readonly Expression [ ] ) : [ nextIndex : number , text : string , rawText : string , usedIndexes : number [ ] ] {
151168 const indexes = [ ] ;
152- let text = "" ;
169+ let text = "" , rawText = "" ;
153170 while ( index < nodes . length ) {
154171 const node = nodes [ index ] ;
155172 if ( isStringLiteralLike ( node ) ) { // includes isNoSubstitutionTemplateLiteral(node)
156- text = text + node . text ;
173+ text += escapeStringForTemplate ( node . text ) ;
174+ rawText += escapeStringForTemplate ( getTextOfNode ( node ) . slice ( 1 , - 1 ) ) ;
157175 indexes . push ( index ) ;
158176 index ++ ;
159177 }
160178 else if ( isTemplateExpression ( node ) ) {
161- text = text + node . head . text ;
179+ text += node . head . text ;
180+ rawText += getRawTextOfTemplate ( node . head ) ;
162181 break ;
163182 }
164183 else {
165184 break ;
166185 }
167186 }
168- return [ index , text , indexes ] ;
187+ return [ index , text , rawText , indexes ] ;
169188 }
170189
171190 function nodesToTemplate ( { nodes, operators } : { nodes : readonly Expression [ ] , operators : Token < BinaryOperator > [ ] } , file : SourceFile ) {
172191 const copyOperatorComments = copyTrailingOperatorComments ( operators , file ) ;
173192 const copyCommentFromStringLiterals = copyCommentFromMultiNode ( nodes , file , copyOperatorComments ) ;
174- const [ begin , headText , headIndexes ] = concatConsecutiveString ( 0 , nodes ) ;
193+ const [ begin , headText , rawHeadText , headIndexes ] = concatConsecutiveString ( 0 , nodes ) ;
175194
176195 if ( begin === nodes . length ) {
177- const noSubstitutionTemplateLiteral = factory . createNoSubstitutionTemplateLiteral ( headText ) ;
196+ const noSubstitutionTemplateLiteral = factory . createNoSubstitutionTemplateLiteral ( headText , rawHeadText ) ;
178197 copyCommentFromStringLiterals ( headIndexes , noSubstitutionTemplateLiteral ) ;
179198 return noSubstitutionTemplateLiteral ;
180199 }
181200
182201 const templateSpans : TemplateSpan [ ] = [ ] ;
183- const templateHead = factory . createTemplateHead ( headText ) ;
202+ const templateHead = factory . createTemplateHead ( headText , rawHeadText ) ;
184203 copyCommentFromStringLiterals ( headIndexes , templateHead ) ;
185204
186205 for ( let i = begin ; i < nodes . length ; i ++ ) {
187206 const currentNode = getExpressionFromParenthesesOrExpression ( nodes [ i ] ) ;
188207 copyOperatorComments ( i , currentNode ) ;
189208
190- const [ newIndex , subsequentText , stringIndexes ] = concatConsecutiveString ( i + 1 , nodes ) ;
209+ const [ newIndex , subsequentText , rawSubsequentText , stringIndexes ] = concatConsecutiveString ( i + 1 , nodes ) ;
191210 i = newIndex - 1 ;
192211 const isLast = i === nodes . length - 1 ;
193212
194213 if ( isTemplateExpression ( currentNode ) ) {
195214 const spans = map ( currentNode . templateSpans , ( span , index ) => {
196215 copyExpressionComments ( span ) ;
197- const nextSpan = currentNode . templateSpans [ index + 1 ] ;
198- const text = span . literal . text + ( nextSpan ? "" : subsequentText ) ;
199- return factory . createTemplateSpan ( span . expression , isLast ? factory . createTemplateTail ( text ) : factory . createTemplateMiddle ( text ) ) ;
216+ const isLastSpan = index === currentNode . templateSpans . length - 1 ;
217+ const text = span . literal . text + ( isLastSpan ? subsequentText : "" ) ;
218+ const rawText = getRawTextOfTemplate ( span . literal ) + ( isLastSpan ? rawSubsequentText : "" ) ;
219+ return factory . createTemplateSpan ( span . expression , isLast
220+ ? factory . createTemplateTail ( text , rawText )
221+ : factory . createTemplateMiddle ( text , rawText ) ) ;
200222 } ) ;
201223 templateSpans . push ( ...spans ) ;
202224 }
203225 else {
204- const templatePart = isLast ? factory . createTemplateTail ( subsequentText ) : factory . createTemplateMiddle ( subsequentText ) ;
226+ const templatePart = isLast
227+ ? factory . createTemplateTail ( subsequentText , rawSubsequentText )
228+ : factory . createTemplateMiddle ( subsequentText , rawSubsequentText ) ;
205229 copyCommentFromStringLiterals ( stringIndexes , templatePart ) ;
206230 templateSpans . push ( factory . createTemplateSpan ( currentNode , templatePart ) ) ;
207231 }
0 commit comments