@@ -403,6 +403,7 @@ namespace ts.formatting {
403
403
404
404
// formatting context is used by rules provider
405
405
const formattingContext = new FormattingContext ( sourceFile , requestKind , options ) ;
406
+ let previousRangeTriviaEnd : number ;
406
407
let previousRange : TextRangeWithKind ;
407
408
let previousParent : Node ;
408
409
let previousRangeStartLine : number ;
@@ -439,12 +440,32 @@ namespace ts.formatting {
439
440
}
440
441
441
442
if ( previousRange ! && formattingScanner . getStartPos ( ) >= originalRange . end ) {
443
+ // Formatting edits happen by looking at pairs of contiguous tokens (see `processPair`),
444
+ // typically inserting or deleting whitespace between them. The recursive `processNode`
445
+ // logic above bails out as soon as it encounters a token that is beyond the end of the
446
+ // range we're supposed to format (or if we reach the end of the file). But this potentially
447
+ // leaves out an edit that would occur *inside* the requested range but cannot be discovered
448
+ // without looking at one token *beyond* the end of the range: consider the line `x = { }`
449
+ // with a selection from the beginning of the line to the space inside the curly braces,
450
+ // inclusive. We would expect a format-selection would delete the space (if rules apply),
451
+ // but in order to do that, we need to process the pair ["{", "}"], but we stopped processing
452
+ // just before getting there. This block handles this trailing edit.
442
453
const tokenInfo =
443
454
formattingScanner . isOnEOF ( ) ? formattingScanner . readEOFTokenRange ( ) :
444
455
formattingScanner . isOnToken ( ) ? formattingScanner . readTokenInfo ( enclosingNode ) . token :
445
456
undefined ;
446
457
447
- if ( tokenInfo ) {
458
+ if ( tokenInfo && tokenInfo . pos === previousRangeTriviaEnd ! ) {
459
+ // We need to check that tokenInfo and previousRange are contiguous: the `originalRange`
460
+ // may have ended in the middle of a token, which means we will have stopped formatting
461
+ // on that token, leaving `previousRange` pointing to the token before it, but already
462
+ // having moved the formatting scanner (where we just got `tokenInfo`) to the next token.
463
+ // If this happens, our supposed pair [previousRange, tokenInfo] actually straddles the
464
+ // token that intersects the end of the range we're supposed to format, so the pair will
465
+ // produce bogus edits if we try to `processPair`. Recall that the point of this logic is
466
+ // to perform a trailing edit at the end of the selection range: but there can be no valid
467
+ // edit in the middle of a token where the range ended, so if we have a non-contiguous
468
+ // pair here, we're already done and we can ignore it.
448
469
const parent = findPrecedingToken ( tokenInfo . end , sourceFile , enclosingNode ) ?. parent || previousParent ! ;
449
470
processPair (
450
471
tokenInfo ,
@@ -888,6 +909,7 @@ namespace ts.formatting {
888
909
}
889
910
890
911
if ( currentTokenInfo . trailingTrivia ) {
912
+ previousRangeTriviaEnd = last ( currentTokenInfo . trailingTrivia ) . end ;
891
913
processTrivia ( currentTokenInfo . trailingTrivia , parent , childContextNode , dynamicIndentation ) ;
892
914
}
893
915
@@ -976,6 +998,7 @@ namespace ts.formatting {
976
998
}
977
999
978
1000
previousRange = range ;
1001
+ previousRangeTriviaEnd = range . end ;
979
1002
previousParent = parent ;
980
1003
previousRangeStartLine = rangeStart . line ;
981
1004
0 commit comments