Skip to content

Commit b7355e3

Browse files
authored
Fix trailing formatting edit when range ends mid-token (microsoft#50082)
1 parent c9586f3 commit b7355e3

File tree

2 files changed

+30
-1
lines changed

2 files changed

+30
-1
lines changed

src/services/formatting/formatting.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ namespace ts.formatting {
403403

404404
// formatting context is used by rules provider
405405
const formattingContext = new FormattingContext(sourceFile, requestKind, options);
406+
let previousRangeTriviaEnd: number;
406407
let previousRange: TextRangeWithKind;
407408
let previousParent: Node;
408409
let previousRangeStartLine: number;
@@ -439,12 +440,32 @@ namespace ts.formatting {
439440
}
440441

441442
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.
442453
const tokenInfo =
443454
formattingScanner.isOnEOF() ? formattingScanner.readEOFTokenRange() :
444455
formattingScanner.isOnToken() ? formattingScanner.readTokenInfo(enclosingNode).token :
445456
undefined;
446457

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.
448469
const parent = findPrecedingToken(tokenInfo.end, sourceFile, enclosingNode)?.parent || previousParent!;
449470
processPair(
450471
tokenInfo,
@@ -888,6 +909,7 @@ namespace ts.formatting {
888909
}
889910

890911
if (currentTokenInfo.trailingTrivia) {
912+
previousRangeTriviaEnd = last(currentTokenInfo.trailingTrivia).end;
891913
processTrivia(currentTokenInfo.trailingTrivia, parent, childContextNode, dynamicIndentation);
892914
}
893915

@@ -976,6 +998,7 @@ namespace ts.formatting {
976998
}
977999

9781000
previousRange = range;
1001+
previousRangeTriviaEnd = range.end;
9791002
previousParent = parent;
9801003
previousRangeStartLine = rangeStart.line;
9811004

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
//// const x = `${0}/*0*/abc/*1*/`;
4+
5+
format.selection("0", "1");
6+
verify.currentFileContentIs("const x = `${0}abc`;");

0 commit comments

Comments
 (0)