@@ -64,9 +64,24 @@ namespace ts.refactor.convertStringOrTemplateLiteral {
64
64
65
65
function getEditsForToTemplateLiteral ( context : RefactorContext , node : Node ) {
66
66
const maybeBinary = getParentBinaryExpression ( node ) ;
67
- const arrayOfNodes = transformTreeToArray ( maybeBinary ) ;
68
- const templateLiteral = nodesToTemplate ( arrayOfNodes ) ;
69
- return textChanges . ChangeTracker . with ( context , t => t . replaceNode ( context . file , maybeBinary , templateLiteral ) ) ;
67
+ const file = context . file ;
68
+
69
+ const templateLiteral = nodesToTemplate ( treeToArray ( maybeBinary ) , file ) ;
70
+ const trailingCommentRanges = getTrailingCommentRanges ( file . text , maybeBinary . end ) ;
71
+
72
+ if ( trailingCommentRanges ) {
73
+ const lastComment = trailingCommentRanges [ trailingCommentRanges . length - 1 ] ;
74
+ const trailingRange = { pos : trailingCommentRanges [ 0 ] . pos , end : lastComment . end } ;
75
+
76
+ return textChanges . ChangeTracker . with ( context , t => {
77
+ t . deleteRange ( file , trailingRange ) ;
78
+ t . replaceNode ( file , maybeBinary , templateLiteral ) ;
79
+ } ) ;
80
+ }
81
+ else {
82
+ return textChanges . ChangeTracker . with ( context , t => t . replaceNode ( file , maybeBinary , templateLiteral ) ) ;
83
+ }
84
+
70
85
}
71
86
72
87
const templateSpanToExpressions = ( file : SourceFile ) => ( templateSpan : TemplateSpan ) : Expression [ ] => {
@@ -132,59 +147,97 @@ namespace ts.refactor.convertStringOrTemplateLiteral {
132
147
return containsString && areOperatorsValid ;
133
148
}
134
149
135
- function transformTreeToArray ( node : Node ) : ReadonlyArray < Expression > {
136
- return treeToArray ( node ) . nodes ;
137
- }
138
-
139
- function treeToArray ( node : Node ) : { nodes : ReadonlyArray < Expression > , containsString : boolean , areOperatorsValid : boolean } {
150
+ function treeToArray ( node : Node ) : { nodes : ReadonlyArray < Expression > , operators : Token < BinaryOperator > [ ] , containsString : boolean , areOperatorsValid : boolean } {
140
151
if ( isBinaryExpression ( node ) ) {
141
- const { nodes : leftNodes , containsString : leftHasString , areOperatorsValid : leftOperatorValid } = treeToArray ( node . left ) ;
152
+ const { nodes : leftNodes , operators : leftOperator , containsString : leftHasString , areOperatorsValid : leftOperatorValid } = treeToArray ( node . left ) ;
142
153
const { nodes : rightNodes , containsString : rightHasString , areOperatorsValid : rightOperatorValid } = treeToArray ( node . right ) ;
143
154
144
155
if ( ! leftHasString && ! rightHasString ) {
145
- return { nodes : [ node ] , containsString : false , areOperatorsValid : true } ;
156
+ return { nodes : [ node ] , operators : [ ] , containsString : false , areOperatorsValid : true } ;
146
157
}
147
158
148
159
const currentOperatorValid = node . operatorToken . kind === SyntaxKind . PlusToken ;
149
160
const areOperatorsValid = leftOperatorValid && currentOperatorValid && rightOperatorValid ;
161
+ leftOperator . push ( node . operatorToken ) ;
150
162
151
- return { nodes : leftNodes . concat ( rightNodes ) , containsString : true , areOperatorsValid } ;
163
+ return { nodes : leftNodes . concat ( rightNodes ) , operators : leftOperator , containsString : true , areOperatorsValid } ;
152
164
}
153
165
154
- return { nodes : [ node as Expression ] , containsString : isStringLiteral ( node ) , areOperatorsValid : true } ;
166
+ return { nodes : [ node as Expression ] , operators : [ ] , containsString : isStringLiteral ( node ) , areOperatorsValid : true } ;
155
167
}
156
168
157
- function concatConsecutiveString ( index : number , nodes : ReadonlyArray < Expression > ) : [ number , string ] {
169
+ const copyTrailingOperatorComments = ( operators : Token < BinaryOperator > [ ] , file : SourceFile ) => ( index : number , targetNode : Node ) => {
170
+ if ( index < operators . length ) {
171
+ copyTrailingComments ( operators [ index ] , targetNode , file , SyntaxKind . MultiLineCommentTrivia , /* hasTrailingNewLine */ false ) ;
172
+ }
173
+ } ;
174
+
175
+ const copyCommentFromMultiNode = ( nodes : ReadonlyArray < Expression > , file : SourceFile , copyOperatorComments : ( index : number , targetNode : Node ) => void ) =>
176
+ ( indexes : number [ ] , targetNode : Node ) => {
177
+ while ( indexes . length > 0 ) {
178
+ const index = indexes . shift ( ) ! ;
179
+ copyTrailingComments ( nodes [ index ] , targetNode , file , SyntaxKind . MultiLineCommentTrivia , /* hasTrailingNewLine */ false ) ;
180
+ copyOperatorComments ( index , targetNode ) ;
181
+ }
182
+ } ;
183
+
184
+ function concatConsecutiveString ( index : number , nodes : ReadonlyArray < Expression > ) : [ number , string , number [ ] ] {
158
185
let text = "" ;
186
+ const indexes = [ ] ;
159
187
160
188
while ( index < nodes . length && isStringLiteral ( nodes [ index ] ) ) {
161
189
text = text + decodeRawString ( nodes [ index ] . getText ( ) ) ;
190
+ indexes . push ( index ) ;
162
191
index ++ ;
163
192
}
164
193
165
194
text = escapeText ( text ) ;
166
- return [ index , text ] ;
195
+ return [ index , text , indexes ] ;
167
196
}
168
197
169
- function nodesToTemplate ( nodes : ReadonlyArray < Expression > ) {
198
+ function nodesToTemplate ( { nodes, operators} : { nodes : ReadonlyArray < Expression > , operators : Token < BinaryOperator > [ ] } , file : SourceFile ) {
199
+ const copyOperatorComments = copyTrailingOperatorComments ( operators , file ) ;
200
+ const copyCommentFromStringLiterals = copyCommentFromMultiNode ( nodes , file , copyOperatorComments ) ;
201
+
170
202
const templateSpans : TemplateSpan [ ] = [ ] ;
171
- const [ begin , headText ] = concatConsecutiveString ( 0 , nodes ) ;
172
- const templateHead = createTemplateHead ( headText ) ;
203
+ const [ begin , headText , headIndexes ] = concatConsecutiveString ( 0 , nodes ) ;
173
204
174
- if ( begin === nodes . length ) return createNoSubstitutionTemplateLiteral ( headText ) ;
205
+ if ( begin === nodes . length ) {
206
+ const noSubstitutionTemplateLiteral = createNoSubstitutionTemplateLiteral ( headText ) ;
207
+ copyCommentFromStringLiterals ( headIndexes , noSubstitutionTemplateLiteral ) ;
208
+ return noSubstitutionTemplateLiteral ;
209
+ }
210
+
211
+ const templateHead = createTemplateHead ( headText ) ;
212
+ copyCommentFromStringLiterals ( headIndexes , templateHead ) ;
175
213
176
214
for ( let i = begin ; i < nodes . length ; i ++ ) {
177
- const expression = isParenthesizedExpression ( nodes [ i ] ) ? ( nodes [ i ] as ParenthesizedExpression ) . expression : nodes [ i ] ;
178
- const [ newIndex , subsequentText ] = concatConsecutiveString ( i + 1 , nodes ) ;
215
+ let currentNode = nodes [ i ] ;
216
+
217
+ if ( isParenthesizedExpression ( currentNode ) ) {
218
+ copyCommentsWhenParenthesized ( currentNode ) ;
219
+ currentNode = currentNode . expression ;
220
+ }
221
+
222
+ copyOperatorComments ( i , currentNode ) ;
223
+ const [ newIndex , subsequentText , stringIndexes ] = concatConsecutiveString ( i + 1 , nodes ) ;
179
224
i = newIndex - 1 ;
180
225
181
226
const templatePart = i === nodes . length - 1 ? createTemplateTail ( subsequentText ) : createTemplateMiddle ( subsequentText ) ;
182
- templateSpans . push ( createTemplateSpan ( expression , templatePart ) ) ;
227
+ copyCommentFromStringLiterals ( stringIndexes , templatePart ) ;
228
+
229
+ templateSpans . push ( createTemplateSpan ( currentNode , templatePart ) ) ;
183
230
}
184
231
185
232
return createTemplateExpression ( templateHead , templateSpans ) ;
186
233
}
187
234
235
+ function copyCommentsWhenParenthesized ( node : ParenthesizedExpression ) {
236
+ const file = node . getSourceFile ( ) ;
237
+ copyTrailingComments ( node , node . expression , file , SyntaxKind . MultiLineCommentTrivia , /* hasTrailingNewLine */ false ) ;
238
+ copyTrailingAsLeadingComments ( node . expression , node . expression , file , SyntaxKind . MultiLineCommentTrivia , /* hasTrailingNewLine */ false ) ;
239
+ }
240
+
188
241
const hexToUnicode = ( _match : string , grp : string ) => String . fromCharCode ( parseInt ( grp , 16 ) ) ;
189
242
const octalToUnicode = ( _match : string , grp : string ) => String . fromCharCode ( parseInt ( grp , 8 ) ) ;
190
243
0 commit comments