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;