@@ -168,7 +168,7 @@ namespace ts.textChanges {
168
168
return { pos : getAdjustedStartPosition ( sourceFile , startNode , options ) , end : getAdjustedEndPosition ( sourceFile , endNode , options ) } ;
169
169
}
170
170
171
- function getAdjustedStartPosition ( sourceFile : SourceFile , node : Node , options : ConfigurableStart ) {
171
+ function getAdjustedStartPosition ( sourceFile : SourceFile , node : Node , options : ConfigurableStartEnd , hasTrailingComment = false ) {
172
172
const { leadingTriviaOption } = options ;
173
173
if ( leadingTriviaOption === LeadingTriviaOption . Exclude ) {
174
174
return node . getStart ( sourceFile ) ;
@@ -199,6 +199,17 @@ namespace ts.textChanges {
199
199
// when b is deleted - we delete it
200
200
return leadingTriviaOption === LeadingTriviaOption . IncludeAll ? fullStart : start ;
201
201
}
202
+
203
+ // if node has a trailing comments, use comment end position as the text has already been included.
204
+ if ( hasTrailingComment ) {
205
+ // Check first for leading comments as if the node is the first import, we want to exclude the trivia;
206
+ // otherwise we get the trailing comments.
207
+ const comment = getLeadingCommentRanges ( sourceFile . text , fullStart ) ?. [ 0 ] || getTrailingCommentRanges ( sourceFile . text , fullStart ) ?. [ 0 ] ;
208
+ if ( comment ) {
209
+ return skipTrivia ( sourceFile . text , comment . end , /*stopAfterLineBreak*/ true , /*stopAtComments*/ true ) ;
210
+ }
211
+ }
212
+
202
213
// get start position of the line following the line that contains fullstart position
203
214
// (but only if the fullstart isn't the very beginning of the file)
204
215
const nextLineStart = fullStart > 0 ? 1 : 0 ;
@@ -208,7 +219,38 @@ namespace ts.textChanges {
208
219
return getStartPositionOfLine ( getLineOfLocalPosition ( sourceFile , adjustedStartPosition ) , sourceFile ) ;
209
220
}
210
221
211
- function getAdjustedEndPosition ( sourceFile : SourceFile , node : Node , options : ConfigurableEnd ) {
222
+ /** Return the end position of a multiline comment of it is on another line; otherwise returns `undefined`; */
223
+ function getEndPositionOfMultilineTrailingComment ( sourceFile : SourceFile , node : Node , options : ConfigurableEnd ) : number | undefined {
224
+ const { end } = node ;
225
+ const { trailingTriviaOption } = options ;
226
+ if ( trailingTriviaOption === TrailingTriviaOption . Include ) {
227
+ // If the trailing comment is a multiline comment that extends to the next lines,
228
+ // return the end of the comment and track it for the next nodes to adjust.
229
+ const comments = getTrailingCommentRanges ( sourceFile . text , end ) ;
230
+ if ( comments ) {
231
+ const nodeEndLine = getLineOfLocalPosition ( sourceFile , node . end ) ;
232
+ for ( const comment of comments ) {
233
+ // Single line can break the loop as trivia will only be this line.
234
+ // Comments on subsequest lines are also ignored.
235
+ if ( comment . kind === SyntaxKind . SingleLineCommentTrivia || getLineOfLocalPosition ( sourceFile , comment . pos ) > nodeEndLine ) {
236
+ break ;
237
+ }
238
+
239
+ // Get the end line of the comment and compare against the end line of the node.
240
+ // If the comment end line position and the multiline comment extends to multiple lines,
241
+ // then is safe to return the end position.
242
+ const commentEndLine = getLineOfLocalPosition ( sourceFile , comment . end ) ;
243
+ if ( commentEndLine > nodeEndLine ) {
244
+ return skipTrivia ( sourceFile . text , comment . end , /*stopAfterLineBreak*/ true , /*stopAtComments*/ true ) ;
245
+ }
246
+ }
247
+ }
248
+ }
249
+
250
+ return undefined ;
251
+ }
252
+
253
+ function getAdjustedEndPosition ( sourceFile : SourceFile , node : Node , options : ConfigurableEnd ) : number {
212
254
const { end } = node ;
213
255
const { trailingTriviaOption } = options ;
214
256
if ( trailingTriviaOption === TrailingTriviaOption . Exclude ) {
@@ -222,6 +264,12 @@ namespace ts.textChanges {
222
264
}
223
265
return end ;
224
266
}
267
+
268
+ const multilineEndPosition = getEndPositionOfMultilineTrailingComment ( sourceFile , node , options ) ;
269
+ if ( multilineEndPosition ) {
270
+ return multilineEndPosition ;
271
+ }
272
+
225
273
const newEnd = skipTrivia ( sourceFile . text , end , /*stopAfterLineBreak*/ true ) ;
226
274
227
275
return newEnd !== end && ( trailingTriviaOption === TrailingTriviaOption . Include || isLineBreak ( sourceFile . text . charCodeAt ( newEnd - 1 ) ) )
@@ -293,6 +341,18 @@ namespace ts.textChanges {
293
341
this . deleteRange ( sourceFile , getAdjustedRange ( sourceFile , node , node , options ) ) ;
294
342
}
295
343
344
+ public deleteNodes ( sourceFile : SourceFile , nodes : readonly Node [ ] , options : ConfigurableStartEnd = { leadingTriviaOption : LeadingTriviaOption . IncludeAll } , hasTrailingComment : boolean ) : void {
345
+ // When deleting multiple nodes we need to track if the end position is including multiline trailing comments.
346
+ for ( const node of nodes ) {
347
+ const pos = getAdjustedStartPosition ( sourceFile , node , options , hasTrailingComment ) ;
348
+ const end = getAdjustedEndPosition ( sourceFile , node , options ) ;
349
+
350
+ this . deleteRange ( sourceFile , { pos, end } ) ;
351
+
352
+ hasTrailingComment = ! ! getEndPositionOfMultilineTrailingComment ( sourceFile , node , options ) ;
353
+ }
354
+ }
355
+
296
356
public deleteModifier ( sourceFile : SourceFile , modifier : Modifier ) : void {
297
357
this . deleteRange ( sourceFile , { pos : modifier . getStart ( sourceFile ) , end : skipTrivia ( sourceFile . text , modifier . end , /*stopAfterLineBreak*/ true ) } ) ;
298
358
}
@@ -337,6 +397,10 @@ namespace ts.textChanges {
337
397
this . replaceRangeWithNodes ( sourceFile , getAdjustedRange ( sourceFile , startNode , endNode , options ) , newNodes , options ) ;
338
398
}
339
399
400
+ public nodeHasTrailingComment ( sourceFile : SourceFile , oldNode : Node , configurableEnd : ConfigurableEnd = useNonAdjustedPositions ) : boolean {
401
+ return ! ! getEndPositionOfMultilineTrailingComment ( sourceFile , oldNode , configurableEnd ) ;
402
+ }
403
+
340
404
private nextCommaToken ( sourceFile : SourceFile , node : Node ) : Node | undefined {
341
405
const next = findNextToken ( node , node . parent , sourceFile ) ;
342
406
return next && next . kind === SyntaxKind . CommaToken ? next : undefined ;
@@ -1289,8 +1353,9 @@ namespace ts.textChanges {
1289
1353
case SyntaxKind . ImportEqualsDeclaration :
1290
1354
const isFirstImport = sourceFile . imports . length && node === first ( sourceFile . imports ) . parent || node === find ( sourceFile . statements , isAnyImportSyntax ) ;
1291
1355
// For first import, leave header comment in place, otherwise only delete JSDoc comments
1292
- deleteNode ( changes , sourceFile , node ,
1293
- { leadingTriviaOption : isFirstImport ? LeadingTriviaOption . Exclude : hasJSDocNodes ( node ) ? LeadingTriviaOption . JSDoc : LeadingTriviaOption . StartLine } ) ;
1356
+ deleteNode ( changes , sourceFile , node , {
1357
+ leadingTriviaOption : isFirstImport ? LeadingTriviaOption . Exclude : hasJSDocNodes ( node ) ? LeadingTriviaOption . JSDoc : LeadingTriviaOption . StartLine ,
1358
+ } ) ;
1294
1359
break ;
1295
1360
1296
1361
case SyntaxKind . BindingElement :
0 commit comments