diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts
index 4e48a4bf4f2..53a8c2b4307 100644
--- a/src/services/formatting/formatting.ts
+++ b/src/services/formatting/formatting.ts
@@ -121,7 +121,7 @@ namespace ts.formatting {
function findOutermostParent(position: number, expectedTokenKind: SyntaxKind, sourceFile: SourceFile): Node {
let precedingToken = findPrecedingToken(position, sourceFile);
-
+
// when it is claimed that trigger character was typed at given position
// we verify that there is a token with a matching kind whose end is equal to position (because the character was just typed).
// If this condition is not hold - then trigger character was typed in some other context,
@@ -151,7 +151,7 @@ namespace ts.formatting {
return current;
}
-
+
// Returns true if node is a element in some list in parent
// i.e. parent is class declaration with the list of members and node is one of members.
function isListElement(parent: Node, node: Node): boolean {
@@ -198,7 +198,7 @@ namespace ts.formatting {
if (!errors.length) {
return rangeHasNoErrors;
}
-
+
// pick only errors that fall in range
let sorted = errors
.filter(d => rangeOverlapsWithStartEnd(originalRange, d.start, d.start + d.length))
@@ -341,6 +341,14 @@ namespace ts.formatting {
processNode(enclosingNode, enclosingNode, startLine, undecoratedStartLine, initialIndentation, delta);
}
+ if (!formattingScanner.isOnToken()) {
+ let leadingTrivia = formattingScanner.getCurrentLeadingTrivia();
+ if (leadingTrivia) {
+ processTrivia(leadingTrivia, enclosingNode, enclosingNode, undefined);
+ trimTrailingWhitespacesForRemainingRange();
+ }
+ }
+
formattingScanner.close();
return edits;
@@ -828,9 +836,7 @@ namespace ts.formatting {
}
// We need to trim trailing whitespace between the tokens if they were on different lines, and no rule was applied to put them on the same line
- trimTrailingWhitespaces =
- (rule.Operation.Action & (RuleAction.NewLine | RuleAction.Space)) &&
- rule.Flag !== RuleFlags.CanDeleteNewLines;
+ trimTrailingWhitespaces = !(rule.Operation.Action & RuleAction.Delete) && rule.Flag !== RuleFlags.CanDeleteNewLines;
}
else {
trimTrailingWhitespaces = true;
@@ -929,17 +935,41 @@ namespace ts.formatting {
continue;
}
- let pos = lineEndPosition;
- while (pos >= lineStartPosition && isWhiteSpace(sourceFile.text.charCodeAt(pos))) {
- pos--;
- }
- if (pos !== lineEndPosition) {
- Debug.assert(pos === lineStartPosition || !isWhiteSpace(sourceFile.text.charCodeAt(pos)));
- recordDelete(pos + 1, lineEndPosition - pos);
+ let whitespaceStart = getTrailingWhitespaceStartPosition(lineStartPosition, lineEndPosition);
+ if (whitespaceStart !== -1) {
+ Debug.assert(whitespaceStart === lineStartPosition || !isWhiteSpace(sourceFile.text.charCodeAt(whitespaceStart - 1)));
+ recordDelete(whitespaceStart, lineEndPosition + 1 - whitespaceStart);
}
}
}
+ /**
+ * @param start The position of the first character in range
+ * @param end The position of the last character in range
+ */
+ function getTrailingWhitespaceStartPosition(start: number, end: number) {
+ let pos = end;
+ while (pos >= start && isWhiteSpace(sourceFile.text.charCodeAt(pos))) {
+ pos--;
+ }
+ if (pos !== end) {
+ return pos + 1;
+ }
+ return -1;
+ }
+
+ /**
+ * Trimming will be done for lines after the previous range
+ */
+ function trimTrailingWhitespacesForRemainingRange() {
+ let startPosition = previousRange ? previousRange.end : originalRange.pos;
+
+ let startLine = sourceFile.getLineAndCharacterOfPosition(startPosition).line;
+ let endLine = sourceFile.getLineAndCharacterOfPosition(originalRange.end).line;
+
+ trimTrailingWhitespacesForLines(startLine, endLine + 1, previousRange);
+ }
+
function newTextChange(start: number, len: number, newText: string): TextChange {
return { span: createTextSpan(start, len), newText }
}
diff --git a/src/services/formatting/formattingScanner.ts b/src/services/formatting/formattingScanner.ts
index 58e2f304482..7a4cb0924d8 100644
--- a/src/services/formatting/formattingScanner.ts
+++ b/src/services/formatting/formattingScanner.ts
@@ -5,21 +5,22 @@
namespace ts.formatting {
const standardScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.Standard);
const jsxScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.JSX);
-
+
/**
* Scanner that is currently used for formatting
*/
let scanner: Scanner;
-
+
export interface FormattingScanner {
advance(): void;
isOnToken(): boolean;
readTokenInfo(n: Node): TokenInfo;
+ getCurrentLeadingTrivia(): TextRangeWithKind[];
lastTrailingTriviaWasNewLine(): boolean;
close(): void;
}
- const enum ScanAction{
+ const enum ScanAction {
Scan,
RescanGreaterThanToken,
RescanSlashToken,
@@ -37,19 +38,20 @@ namespace ts.formatting {
let wasNewLine: boolean = true;
let leadingTrivia: TextRangeWithKind[];
let trailingTrivia: TextRangeWithKind[];
-
+
let savedPos: number;
let lastScanAction: ScanAction;
let lastTokenInfo: TokenInfo;
return {
- advance: advance,
- readTokenInfo: readTokenInfo,
- isOnToken: isOnToken,
+ advance,
+ readTokenInfo,
+ isOnToken,
+ getCurrentLeadingTrivia: () => leadingTrivia,
lastTrailingTriviaWasNewLine: () => wasNewLine,
close: () => {
Debug.assert(scanner !== undefined);
-
+
lastTokenInfo = undefined;
scanner.setText(undefined);
scanner = undefined;
@@ -58,7 +60,7 @@ namespace ts.formatting {
function advance(): void {
Debug.assert(scanner !== undefined);
-
+
lastTokenInfo = undefined;
let isStarted = scanner.getStartPos() !== startPos;
@@ -81,7 +83,7 @@ namespace ts.formatting {
let t: SyntaxKind;
let pos = scanner.getStartPos();
-
+
// Read leading trivia and token
while (pos < endPos) {
let t = scanner.getToken();
@@ -122,10 +124,10 @@ namespace ts.formatting {
return false;
}
-
+
function shouldRescanJsxIdentifier(node: Node): boolean {
if (node.parent) {
- switch(node.parent.kind) {
+ switch (node.parent.kind) {
case SyntaxKind.JsxAttribute:
case SyntaxKind.JsxOpeningElement:
case SyntaxKind.JsxClosingElement:
@@ -133,7 +135,7 @@ namespace ts.formatting {
return node.kind === SyntaxKind.Identifier;
}
}
-
+
return false;
}
@@ -142,7 +144,7 @@ namespace ts.formatting {
}
function shouldRescanTemplateToken(container: Node): boolean {
- return container.kind === SyntaxKind.TemplateMiddle ||
+ return container.kind === SyntaxKind.TemplateMiddle ||
container.kind === SyntaxKind.TemplateTail;
}
@@ -152,11 +154,11 @@ namespace ts.formatting {
function readTokenInfo(n: Node): TokenInfo {
Debug.assert(scanner !== undefined);
-
+
if (!isOnToken()) {
// scanner is not on the token (either advance was not called yet or scanner is already past the end position)
return {
- leadingTrivia: leadingTrivia,
+ leadingTrivia,
trailingTrivia: undefined,
token: undefined
};
@@ -164,7 +166,7 @@ namespace ts.formatting {
// normally scanner returns the smallest available token
// check the kind of context node to determine if scanner should have more greedy behavior and consume more text.
- let expectedScanAction =
+ let expectedScanAction =
shouldRescanGreaterThanToken(n)
? ScanAction.RescanGreaterThanToken
: shouldRescanSlashToken(n)
@@ -226,7 +228,7 @@ namespace ts.formatting {
if (trailingTrivia) {
trailingTrivia = undefined;
}
- while(scanner.getStartPos() < endPos) {
+ while (scanner.getStartPos() < endPos) {
currentToken = scanner.scan();
if (!isTrivia(currentToken)) {
break;
@@ -261,7 +263,7 @@ namespace ts.formatting {
function isOnToken(): boolean {
Debug.assert(scanner !== undefined);
-
+
let current = (lastTokenInfo && lastTokenInfo.token.kind) || scanner.getToken();
let startPos = (lastTokenInfo && lastTokenInfo.token.pos) || scanner.getStartPos();
return startPos < endPos && current !== SyntaxKind.EndOfFileToken && !isTrivia(current);
diff --git a/tests/cases/fourslash/formatDocumentWithTrivia.ts b/tests/cases/fourslash/formatDocumentWithTrivia.ts
new file mode 100644
index 00000000000..d51677be81d
--- /dev/null
+++ b/tests/cases/fourslash/formatDocumentWithTrivia.ts
@@ -0,0 +1,51 @@
+///
+
+////
+////// whitespace below
+////
+////// whitespace above
+////
+////let x;
+////
+////// abc
+////
+////let y;
+////
+////// whitespace above again
+////
+////while (true) {
+//// while (true) {
+//// }
+////
+//// // whitespace above
+////}
+////
+////// whitespace above again
+////
+////
+
+format.document();
+
+verify.currentFileContentIs(`
+// whitespace below
+
+// whitespace above
+
+let x;
+
+// abc
+
+let y;
+
+// whitespace above again
+
+while (true) {
+ while (true) {
+ }
+
+ // whitespace above
+}
+
+// whitespace above again
+
+`);
diff --git a/tests/cases/fourslash/formatSelectionWithTrivia2.ts b/tests/cases/fourslash/formatSelectionWithTrivia2.ts
new file mode 100644
index 00000000000..06b43009a22
--- /dev/null
+++ b/tests/cases/fourslash/formatSelectionWithTrivia2.ts
@@ -0,0 +1,10 @@
+///
+
+/////*begin*/;
+////
+/////*end*/
+////
+
+format.selection('begin', 'end');
+
+verify.currentFileContentIs(";\n\n\n ");