diff --git a/src/server/protocol.ts b/src/server/protocol.ts
index 5418f6ccce4..9fa4c1df253 100644
--- a/src/server/protocol.ts
+++ b/src/server/protocol.ts
@@ -3089,6 +3089,7 @@ namespace ts.server.protocol {
newLineCharacter?: string;
convertTabsToSpaces?: boolean;
indentStyle?: IndentStyle | ts.IndentStyle;
+ trimTrailingWhitespace?: boolean;
}
export interface FormatCodeSettings extends EditorSettings {
diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts
index bdd56acd960..761391af6bc 100644
--- a/src/services/formatting/formatting.ts
+++ b/src/services/formatting/formatting.ts
@@ -427,7 +427,9 @@ namespace ts.formatting {
if (leadingTrivia) {
indentTriviaItems(leadingTrivia, initialIndentation, /*indentNextTokenOrTrivia*/ false,
item => processRange(item, sourceFile.getLineAndCharacterOfPosition(item.pos), enclosingNode, enclosingNode, /*dynamicIndentation*/ undefined!));
- trimTrailingWhitespacesForRemainingRange();
+ if (options.trimTrailingWhitespace !== false) {
+ trimTrailingWhitespacesForRemainingRange();
+ }
}
}
@@ -982,7 +984,7 @@ namespace ts.formatting {
const rules = getRules(formattingContext);
- let trimTrailingWhitespaces = false;
+ let trimTrailingWhitespaces = formattingContext.options.trimTrailingWhitespace !== false;
let lineAction = LineAction.None;
if (rules) {
// Apply rules in reverse order so that higher priority rules (which are first in the array)
@@ -1010,11 +1012,11 @@ 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.action & RuleAction.DeleteSpace) && rule.flags !== RuleFlags.CanDeleteNewLines;
+ trimTrailingWhitespaces = trimTrailingWhitespaces && !(rule.action & RuleAction.DeleteSpace) && rule.flags !== RuleFlags.CanDeleteNewLines;
});
}
else {
- trimTrailingWhitespaces = currentItem.kind !== SyntaxKind.EndOfFileToken;
+ trimTrailingWhitespaces = trimTrailingWhitespaces && currentItem.kind !== SyntaxKind.EndOfFileToken;
}
if (currentStartLine !== previousStartLine && trimTrailingWhitespaces) {
diff --git a/src/services/types.ts b/src/services/types.ts
index fb7e0cb8d35..d0612304d68 100644
--- a/src/services/types.ts
+++ b/src/services/types.ts
@@ -829,6 +829,7 @@ namespace ts {
newLineCharacter?: string;
convertTabsToSpaces?: boolean;
indentStyle?: IndentStyle;
+ trimTrailingWhitespace?: boolean;
}
/* @deprecated - consider using FormatCodeSettings instead */
@@ -895,6 +896,7 @@ namespace ts {
placeOpenBraceOnNewLineForFunctions: false,
placeOpenBraceOnNewLineForControlBlocks: false,
semicolons: SemicolonPreference.Ignore,
+ trimTrailingWhitespace: true
};
}
diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts
index 39ba1ef93dc..861c9f3147d 100644
--- a/tests/baselines/reference/api/tsserverlibrary.d.ts
+++ b/tests/baselines/reference/api/tsserverlibrary.d.ts
@@ -5575,6 +5575,7 @@ declare namespace ts {
newLineCharacter?: string;
convertTabsToSpaces?: boolean;
indentStyle?: IndentStyle;
+ trimTrailingWhitespace?: boolean;
}
interface FormatCodeOptions extends EditorOptions {
InsertSpaceAfterCommaDelimiter: boolean;
@@ -8572,6 +8573,7 @@ declare namespace ts.server.protocol {
newLineCharacter?: string;
convertTabsToSpaces?: boolean;
indentStyle?: IndentStyle | ts.IndentStyle;
+ trimTrailingWhitespace?: boolean;
}
interface FormatCodeSettings extends EditorSettings {
insertSpaceAfterCommaDelimiter?: boolean;
diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts
index 84f57b074d3..9274c86c821 100644
--- a/tests/baselines/reference/api/typescript.d.ts
+++ b/tests/baselines/reference/api/typescript.d.ts
@@ -5575,6 +5575,7 @@ declare namespace ts {
newLineCharacter?: string;
convertTabsToSpaces?: boolean;
indentStyle?: IndentStyle;
+ trimTrailingWhitespace?: boolean;
}
interface FormatCodeOptions extends EditorOptions {
InsertSpaceAfterCommaDelimiter: boolean;
diff --git a/tests/cases/fourslash/formatDocumentPreserveTrailingWhitespace.ts b/tests/cases/fourslash/formatDocumentPreserveTrailingWhitespace.ts
new file mode 100644
index 00000000000..e20d374e3f6
--- /dev/null
+++ b/tests/cases/fourslash/formatDocumentPreserveTrailingWhitespace.ts
@@ -0,0 +1,26 @@
+///
+
+////
+////var a;
+////var b
+////
+//////
+////function b(){
+//// while(true){
+//// }
+////}
+////
+
+format.setOption("trimTrailingWhitespace", false);
+format.document();
+
+verify.currentFileContentIs(`
+var a;
+var b
+
+//
+function b() {
+ while (true) {
+ }
+}
+`);
diff --git a/tests/cases/fourslash/formatSelectionPreserveTrailingWhitespace.ts b/tests/cases/fourslash/formatSelectionPreserveTrailingWhitespace.ts
new file mode 100644
index 00000000000..787966b495f
--- /dev/null
+++ b/tests/cases/fourslash/formatSelectionPreserveTrailingWhitespace.ts
@@ -0,0 +1,19 @@
+///
+
+////
+/////*begin*/;
+////
+/////*end*/
+////
+////
+
+format.setOption("trimTrailingWhitespace", false);
+
+format.selection('begin', 'end');
+
+verify.currentFileContentIs(`
+;
+
+
+
+`);
diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts
index beb9d0c4d84..93a31dc47ff 100644
--- a/tests/cases/fourslash/fourslash.ts
+++ b/tests/cases/fourslash/fourslash.ts
@@ -126,6 +126,7 @@ declare namespace FourSlashInterface {
newLineCharacter?: string;
convertTabsToSpaces?: boolean;
indentStyle?: IndentStyle;
+ trimTrailingWhitespace?: boolean;
}
interface FormatCodeOptions extends EditorOptions {
InsertSpaceAfterCommaDelimiter: boolean;