Repository: microsoft/TypeScript
File: src/services/formatting/formatting.ts (TypeScript)

Code before:

    // formatting context is used by rules provider
    const formattingContext = new FormattingContext(sourceFile, requestKind, options);
    let previousRangeTriviaEnd: number;
    let previousRange: TextRangeWithKind;
    let previousParent: Node;
    let previousRangeStartLine: number;

    let lastIndentedLine: number;
    let indentationOnLastIndentedLine = Constants.Unknown;

    const edits: TextChange[] = [];

    formattingScanner.advance();

    if (formattingScanner.isOnToken()) {
        const startLine = sourceFile.getLineAndCharacterOfPosition(enclosingNode.getStart(sourceFile)).line;
        let undecoratedStartLine = startLine;
        if (hasDecorators(enclosingNode)) {
            undecoratedStartLine = sourceFile.getLineAndCharacterOfPosition(getNonDecoratorTokenPosOfNode(enclosingNode, sourceFile)).line;
        }

        processNode(enclosingNode, enclosingNode, startLine, undecoratedStartLine, initialIndentation, delta);
    }

    if (!formattingScanner.isOnToken()) {
        const indentation = SmartIndenter.nodeWillIndentChild(options, enclosingNode, /*child*/ undefined, sourceFile, /*indentByDefault*/ false)
            ? initialIndentation + options.indentSize!
            : initialIndentation;
        const leadingTrivia = formattingScanner.getCurrentLeadingTrivia();
        if (leadingTrivia) {
            indentTriviaItems(leadingTrivia, indentation, /*indentNextTokenOrTrivia*/ false,
                item => processRange(item, sourceFile.getLineAndCharacterOfPosition(item.pos), enclosingNode, enclosingNode, /*dynamicIndentation*/ undefined!));
            if (options.trimTrailingWhitespace !== false) {
                trimTrailingWhitespacesForRemainingRange(leadingTrivia);
            }
        }
    }

    if (previousRange! && formattingScanner.getTokenFullStart() >= originalRange.end) {
        // Formatting edits happen by looking at pairs of contiguous tokens (see `processPair`),
        // typically inserting or deleting whitespace between them. The recursive `processNode`
        // logic above bails out as soon as it encounters a token that is beyond the end of the
        // range we're supposed to format (or if we reach the end of the file). But this potentially
        // leaves out an edit that would occur *inside* the requested range but cannot be discovered
        // without looking at one token *beyond* the end of the range: consider the line `x = { }`
        // with a selection from the beginning of the line to the space inside the curly braces,
        // inclusive. We would expect a format-selection would delete the space (if rules apply),
        // but in order to do that, we need to process the pair ["{", "}"], but we stopped processing
        // just before getting there. This block handles this trailing edit.
        const tokenInfo =

Review comment (bug):
We really only care if there's any trivia remaining, regardless of whether we ended up on a token. This condition was causing another bug, illustrated in formatSelectionWithTrivia8.ts

Code after:

    // formatting context is used by rules provider
    const formattingContext = new FormattingContext(sourceFile, requestKind, options);
    let previousRangeTriviaEnd: number;
    let previousRange: TextRangeWithKind;
    let previousParent: Node;
    let previousRangeStartLine: number;

    let lastIndentedLine: number;
    let indentationOnLastIndentedLine = Constants.Unknown;

    const edits: TextChange[] = [];

    formattingScanner.advance();

    if (formattingScanner.isOnToken()) {
        const startLine = sourceFile.getLineAndCharacterOfPosition(enclosingNode.getStart(sourceFile)).line;
        let undecoratedStartLine = startLine;
        if (hasDecorators(enclosingNode)) {
            undecoratedStartLine = sourceFile.getLineAndCharacterOfPosition(getNonDecoratorTokenPosOfNode(enclosingNode, sourceFile)).line;
        }

        processNode(enclosingNode, enclosingNode, startLine, undecoratedStartLine, initialIndentation, delta);
    }

    // Leading trivia items get attached to and processed with the token that proceeds them. If the
    // range ends in the middle of some leading trivia, the token that proceeds them won't be in the
    // range and thus won't get processed. So we process those remaining trivia items here.
    const remainingTrivia = formattingScanner.getCurrentLeadingTrivia();
    if (remainingTrivia) {
        const indentation = SmartIndenter.nodeWillIndentChild(options, enclosingNode, /*child*/ undefined, sourceFile, /*indentByDefault*/ false)
            ? initialIndentation + options.indentSize!
            : initialIndentation;
        indentTriviaItems(remainingTrivia, indentation, /*indentNextTokenOrTrivia*/ true,
            item => {
                processRange(item, sourceFile.getLineAndCharacterOfPosition(item.pos), enclosingNode, enclosingNode, /*dynamicIndentation*/ undefined!);
                insertIndentation(item.pos, indentation, /*lineAdded*/ false);
            }
        );
        if (options.trimTrailingWhitespace !== false) {
            trimTrailingWhitespacesForRemainingRange(remainingTrivia);
        }
    }

    if (previousRange! && formattingScanner.getTokenFullStart() >= originalRange.end) {
        // Formatting edits happen by looking at pairs of contiguous tokens (see `processPair`),
        // typically inserting or deleting whitespace between them. The recursive `processNode`
        // logic above bails out as soon as it encounters a token that is beyond the end of the
        // range we're supposed to format (or if we reach the end of the file). But this potentially
        // leaves out an edit that would occur *inside* the requested range but cannot be discovered
        // without looking at one token *beyond* the end of the range: consider the line `x = { }`