diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 1919a47873ede..e6258d49ecfeb 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -375,6 +375,7 @@ namespace FourSlash { insertSpaceAfterTypeAssertion: false, placeOpenBraceOnNewLineForFunctions: false, placeOpenBraceOnNewLineForControlBlocks: false, + insertSpaceBeforeTypeAnnotation: false }; // Open the first file by default diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 6ba968dd11421..c6375e466ff2c 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2488,6 +2488,7 @@ namespace ts.server.protocol { insertSpaceBeforeFunctionParenthesis?: boolean; placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } export interface CompilerOptions { diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts index 8214ffd6f2ddb..4940ff3b61852 100644 --- a/src/services/formatting/rules.ts +++ b/src/services/formatting/rules.ts @@ -47,7 +47,7 @@ namespace ts.formatting { rule("IgnoreBeforeComment", anyToken, comments, anyContext, RuleAction.Ignore), rule("IgnoreAfterLineComment", SyntaxKind.SingleLineCommentTrivia, anyToken, anyContext, RuleAction.Ignore), - rule("NoSpaceBeforeColon", anyToken, SyntaxKind.ColonToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.Delete), + rule("NotSpaceBeforeColon", anyToken, SyntaxKind.ColonToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext, isNotTypeAnnotationContext], RuleAction.Delete), rule("SpaceAfterColon", SyntaxKind.ColonToken, anyToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.Space), rule("NoSpaceBeforeQuestionMark", anyToken, SyntaxKind.QuestionToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.Delete), // insert space after '?' only when it is used in conditional operator @@ -300,6 +300,9 @@ namespace ts.formatting { rule("SpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionEnabled("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.Space), rule("NoSpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.Delete), + + rule("SpaceBeforeTypeAnnotation", anyToken, SyntaxKind.ColonToken, [isOptionEnabled("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.Space), + rule("NoSpaceBeforeTypeAnnotation", anyToken, SyntaxKind.ColonToken, [isOptionDisabledOrUndefined("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.Delete), ]; // These rules are lower in priority than user-configurable @@ -440,6 +443,19 @@ namespace ts.formatting { return !isBinaryOpContext(context); } + function isNotTypeAnnotationContext(context: FormattingContext): boolean { + return !isTypeAnnotationContext(context); + } + + function isTypeAnnotationContext(context: FormattingContext): boolean { + const contextKind = context.contextNode.kind; + return contextKind === SyntaxKind.PropertyDeclaration || + contextKind === SyntaxKind.PropertySignature || + contextKind === SyntaxKind.Parameter || + contextKind === SyntaxKind.VariableDeclaration || + isFunctionLikeKind(contextKind); + } + function isConditionalOperatorContext(context: FormattingContext): boolean { return context.contextNode.kind === SyntaxKind.ConditionalExpression; } diff --git a/src/services/types.ts b/src/services/types.ts index 7224fdbdda8d4..c5bb11a1fe310 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -571,6 +571,7 @@ namespace ts { InsertSpaceBeforeFunctionParenthesis?: boolean; PlaceOpenBraceOnNewLineForFunctions: boolean; PlaceOpenBraceOnNewLineForControlBlocks: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } export interface FormatCodeSettings extends EditorSettings { @@ -589,6 +590,7 @@ namespace ts { insertSpaceBeforeFunctionParenthesis?: boolean; placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } export interface DefinitionInfo { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index c127232c099d9..c6d6dd1300640 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4211,6 +4211,7 @@ declare namespace ts { InsertSpaceBeforeFunctionParenthesis?: boolean; PlaceOpenBraceOnNewLineForFunctions: boolean; PlaceOpenBraceOnNewLineForControlBlocks: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } interface FormatCodeSettings extends EditorSettings { insertSpaceAfterCommaDelimiter?: boolean; @@ -4228,6 +4229,7 @@ declare namespace ts { insertSpaceBeforeFunctionParenthesis?: boolean; placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } interface DefinitionInfo { fileName: string; @@ -6830,6 +6832,7 @@ declare namespace ts.server.protocol { insertSpaceBeforeFunctionParenthesis?: boolean; placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } interface CompilerOptions { allowJs?: boolean; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 62a3880827c74..fe15428482b99 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4211,6 +4211,7 @@ declare namespace ts { InsertSpaceBeforeFunctionParenthesis?: boolean; PlaceOpenBraceOnNewLineForFunctions: boolean; PlaceOpenBraceOnNewLineForControlBlocks: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } interface FormatCodeSettings extends EditorSettings { insertSpaceAfterCommaDelimiter?: boolean; @@ -4228,6 +4229,7 @@ declare namespace ts { insertSpaceBeforeFunctionParenthesis?: boolean; placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; } interface DefinitionInfo { fileName: string; diff --git a/tests/cases/fourslash/formattingOptionsChange.ts b/tests/cases/fourslash/formattingOptionsChange.ts index 0e731412ede3c..390ed426aa990 100644 --- a/tests/cases/fourslash/formattingOptionsChange.ts +++ b/tests/cases/fourslash/formattingOptionsChange.ts @@ -9,6 +9,7 @@ /////*insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets*/[1 ]; [ ]; []; [,]; /////*insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces*/`${1}`;`${ 1 }` /////*insertSpaceAfterTypeAssertion*/const bar = Thing.getFoo(); +/////*insertSpaceBeforeTypeAnnotation*/const bar : number = 1; /////*placeOpenBraceOnNewLineForFunctions*/class foo { ////} /////*placeOpenBraceOnNewLineForControlBlocks*/if (true) { @@ -26,6 +27,7 @@ runTest("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis", " ( 1 ) runTest("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets", "[ 1 ];[];[];[ , ];", "[1];[];[];[,];"); runTest("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces", "`${ 1 }`; `${ 1 }`", "`${1}`; `${1}`"); runTest("insertSpaceAfterTypeAssertion", "const bar = Thing.getFoo();", "const bar = Thing.getFoo();"); +runTest("insertSpaceBeforeTypeAnnotation", "const bar : number = 1;", "const bar: number = 1;"); runTest("placeOpenBraceOnNewLineForFunctions", "class foo", "class foo {"); runTest("placeOpenBraceOnNewLineForControlBlocks", "if (true)", "if (true) {"); runTest("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces", "{ var t = 1 }; var { a, b } = { a: 'sw', b: 'r' }; function f({ a, b }) { }", "{var t = 1}; var {a, b} = {a: 'sw', b: 'r'}; function f({a, b}) {}"); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index fbcfd13eea409..44f565108c9e6 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -97,6 +97,7 @@ declare namespace FourSlashInterface { InsertSpaceAfterTypeAssertion: boolean; PlaceOpenBraceOnNewLineForFunctions: boolean; PlaceOpenBraceOnNewLineForControlBlocks: boolean; + insertSpaceBeforeTypeAnnotation: boolean; [s: string]: boolean | number | string | undefined; } interface Range {