@@ -147,61 +147,85 @@ namespace ts.refactor.convertStringOrTemplateLiteral {
147
147
}
148
148
} ;
149
149
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 [ ] ] {
151
168
const indexes = [ ] ;
152
- let text = "" ;
169
+ let text = "" , rawText = "" ;
153
170
while ( index < nodes . length ) {
154
171
const node = nodes [ index ] ;
155
172
if ( isStringLiteralLike ( node ) ) { // includes isNoSubstitutionTemplateLiteral(node)
156
- text = text + node . text ;
173
+ text += escapeStringForTemplate ( node . text ) ;
174
+ rawText += escapeStringForTemplate ( getTextOfNode ( node ) . slice ( 1 , - 1 ) ) ;
157
175
indexes . push ( index ) ;
158
176
index ++ ;
159
177
}
160
178
else if ( isTemplateExpression ( node ) ) {
161
- text = text + node . head . text ;
179
+ text += node . head . text ;
180
+ rawText += getRawTextOfTemplate ( node . head ) ;
162
181
break ;
163
182
}
164
183
else {
165
184
break ;
166
185
}
167
186
}
168
- return [ index , text , indexes ] ;
187
+ return [ index , text , rawText , indexes ] ;
169
188
}
170
189
171
190
function nodesToTemplate ( { nodes, operators } : { nodes : readonly Expression [ ] , operators : Token < BinaryOperator > [ ] } , file : SourceFile ) {
172
191
const copyOperatorComments = copyTrailingOperatorComments ( operators , file ) ;
173
192
const copyCommentFromStringLiterals = copyCommentFromMultiNode ( nodes , file , copyOperatorComments ) ;
174
- const [ begin , headText , headIndexes ] = concatConsecutiveString ( 0 , nodes ) ;
193
+ const [ begin , headText , rawHeadText , headIndexes ] = concatConsecutiveString ( 0 , nodes ) ;
175
194
176
195
if ( begin === nodes . length ) {
177
- const noSubstitutionTemplateLiteral = factory . createNoSubstitutionTemplateLiteral ( headText ) ;
196
+ const noSubstitutionTemplateLiteral = factory . createNoSubstitutionTemplateLiteral ( headText , rawHeadText ) ;
178
197
copyCommentFromStringLiterals ( headIndexes , noSubstitutionTemplateLiteral ) ;
179
198
return noSubstitutionTemplateLiteral ;
180
199
}
181
200
182
201
const templateSpans : TemplateSpan [ ] = [ ] ;
183
- const templateHead = factory . createTemplateHead ( headText ) ;
202
+ const templateHead = factory . createTemplateHead ( headText , rawHeadText ) ;
184
203
copyCommentFromStringLiterals ( headIndexes , templateHead ) ;
185
204
186
205
for ( let i = begin ; i < nodes . length ; i ++ ) {
187
206
const currentNode = getExpressionFromParenthesesOrExpression ( nodes [ i ] ) ;
188
207
copyOperatorComments ( i , currentNode ) ;
189
208
190
- const [ newIndex , subsequentText , stringIndexes ] = concatConsecutiveString ( i + 1 , nodes ) ;
209
+ const [ newIndex , subsequentText , rawSubsequentText , stringIndexes ] = concatConsecutiveString ( i + 1 , nodes ) ;
191
210
i = newIndex - 1 ;
192
211
const isLast = i === nodes . length - 1 ;
193
212
194
213
if ( isTemplateExpression ( currentNode ) ) {
195
214
const spans = map ( currentNode . templateSpans , ( span , index ) => {
196
215
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 ) ) ;
200
222
} ) ;
201
223
templateSpans . push ( ...spans ) ;
202
224
}
203
225
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 ) ;
205
229
copyCommentFromStringLiterals ( stringIndexes , templatePart ) ;
206
230
templateSpans . push ( factory . createTemplateSpan ( currentNode , templatePart ) ) ;
207
231
}
0 commit comments