diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index bae2977c44ae8..a42abbbc60909 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -324,6 +324,7 @@ namespace FourSlash { InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, + InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, PlaceOpenBraceOnNewLineForFunctions: false, PlaceOpenBraceOnNewLineForControlBlocks: false, }; diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index e48d61920177f..092450e526c16 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1580,6 +1580,7 @@ namespace ts.server { InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, + InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, PlaceOpenBraceOnNewLineForFunctions: false, PlaceOpenBraceOnNewLineForControlBlocks: false, }); diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts index b4916025af2aa..6e132f03465c9 100644 --- a/src/services/formatting/rules.ts +++ b/src/services/formatting/rules.ts @@ -225,6 +225,12 @@ namespace ts.formatting { public NoSpaceBeforeTemplateMiddleAndTail: Rule; public SpaceBeforeTemplateMiddleAndTail: Rule; + // No space after { and before } in JSX expression + public NoSpaceAfterOpenBraceInJsxExpression: Rule; + public SpaceAfterOpenBraceInJsxExpression: Rule; + public NoSpaceBeforeCloseBraceInJsxExpression: Rule; + public SpaceBeforeCloseBraceInJsxExpression: Rule; + constructor() { /// /// Common Rules @@ -316,7 +322,7 @@ namespace ts.formatting { // Add a space between statements. All keywords except (do,else,case) has open/close parens after them. // So, we have a rule to add a space for [),Any], [do,Any], [else,Any], and [case,Any] - this.SpaceBetweenStatements = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.CloseParenToken, SyntaxKind.DoKeyword, SyntaxKind.ElseKeyword, SyntaxKind.CaseKeyword]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsNotForContext), RuleAction.Space)); + this.SpaceBetweenStatements = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.CloseParenToken, SyntaxKind.DoKeyword, SyntaxKind.ElseKeyword, SyntaxKind.CaseKeyword]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.isNonJsxElementContext, Rules.IsNotForContext), RuleAction.Space)); // This low-pri rule takes care of "try {" and "finally {" in case the rule SpaceBeforeOpenBraceInControl didn't execute on FormatOnEnter. this.SpaceAfterTryFinally = new Rule(RuleDescriptor.create2(Shared.TokenRange.FromTokens([SyntaxKind.TryKeyword, SyntaxKind.FinallyKeyword]), SyntaxKind.OpenBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); @@ -444,8 +450,8 @@ namespace ts.formatting { /// // Insert space after comma delimiter - this.SpaceAfterComma = new Rule(RuleDescriptor.create3(SyntaxKind.CommaToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsNextTokenNotCloseBracket), RuleAction.Space)); - this.NoSpaceAfterComma = new Rule(RuleDescriptor.create3(SyntaxKind.CommaToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); + this.SpaceAfterComma = new Rule(RuleDescriptor.create3(SyntaxKind.CommaToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.isNonJsxElementContext, Rules.IsNextTokenNotCloseBracket), RuleAction.Space)); + this.NoSpaceAfterComma = new Rule(RuleDescriptor.create3(SyntaxKind.CommaToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.isNonJsxElementContext), RuleAction.Delete)); // Insert space before and after binary operators this.SpaceBeforeBinaryOperator = new Rule(RuleDescriptor.create4(Shared.TokenRange.Any, Shared.TokenRange.BinaryOperators), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space)); @@ -491,6 +497,12 @@ namespace ts.formatting { this.NoSpaceBeforeTemplateMiddleAndTail = new Rule(RuleDescriptor.create4(Shared.TokenRange.Any, Shared.TokenRange.FromTokens([SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail])), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); this.SpaceBeforeTemplateMiddleAndTail = new Rule(RuleDescriptor.create4(Shared.TokenRange.Any, Shared.TokenRange.FromTokens([SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail])), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); + // No space after { and before } in JSX expression + this.NoSpaceAfterOpenBraceInJsxExpression = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBraceToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.isJsxExpressionContext), RuleAction.Delete)); + this.SpaceAfterOpenBraceInJsxExpression = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBraceToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.isJsxExpressionContext), RuleAction.Space)); + this.NoSpaceBeforeCloseBraceInJsxExpression = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.isJsxExpressionContext), RuleAction.Delete)); + this.SpaceBeforeCloseBraceInJsxExpression = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.isJsxExpressionContext), RuleAction.Space)); + // Insert space after function keyword for anonymous functions this.SpaceAfterAnonymousFunctionKeyword = new Rule(RuleDescriptor.create1(SyntaxKind.FunctionKeyword, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsFunctionDeclContext), RuleAction.Space)); this.NoSpaceAfterAnonymousFunctionKeyword = new Rule(RuleDescriptor.create1(SyntaxKind.FunctionKeyword, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsFunctionDeclContext), RuleAction.Delete)); @@ -723,6 +735,14 @@ namespace ts.formatting { return context.TokensAreOnSameLine() && context.contextNode.kind !== SyntaxKind.JsxText; } + static isNonJsxElementContext(context: FormattingContext): boolean { + return context.contextNode.kind !== SyntaxKind.JsxElement; + } + + static isJsxExpressionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.JsxExpression; + } + static IsNotBeforeBlockInFunctionDeclarationContext(context: FormattingContext): boolean { return !Rules.IsFunctionDeclContext(context) && !Rules.IsBeforeBlockContext(context); } diff --git a/src/services/formatting/rulesProvider.ts b/src/services/formatting/rulesProvider.ts index d672a401d89d8..1be0f9e912d2e 100644 --- a/src/services/formatting/rulesProvider.ts +++ b/src/services/formatting/rulesProvider.ts @@ -90,6 +90,15 @@ namespace ts.formatting { rules.push(this.globalRules.NoSpaceBeforeTemplateMiddleAndTail); } + if (options.InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces) { + rules.push(this.globalRules.SpaceAfterOpenBraceInJsxExpression); + rules.push(this.globalRules.SpaceBeforeCloseBraceInJsxExpression); + } + else { + rules.push(this.globalRules.NoSpaceAfterOpenBraceInJsxExpression); + rules.push(this.globalRules.NoSpaceBeforeCloseBraceInJsxExpression); + } + if (options.InsertSpaceAfterSemicolonInForStatements) { rules.push(this.globalRules.SpaceAfterSemicolonInFor); } diff --git a/src/services/services.ts b/src/services/services.ts index a17d9feed2485..529ce8a7bf7d5 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1267,6 +1267,7 @@ namespace ts { InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean; InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: boolean; InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: boolean; + InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; PlaceOpenBraceOnNewLineForFunctions: boolean; PlaceOpenBraceOnNewLineForControlBlocks: boolean; [s: string]: boolean | number | string | undefined; diff --git a/tests/cases/fourslash/formattingJsxElements.ts b/tests/cases/fourslash/formattingJsxElements.ts index fbe4bb5d29ed4..e07149960dba9 100644 --- a/tests/cases/fourslash/formattingJsxElements.ts +++ b/tests/cases/fourslash/formattingJsxElements.ts @@ -9,7 +9,7 @@ //// //// ) ////} -//// +//// ////function foo1() { //// return ( ////
@@ -45,8 +45,8 @@ //// class3= {/*5*/ //// }/>/*6*/ //// ) -////} -//// +////} +//// ////(function () { //// return
/*grandchildJsxElementAutoformat*/ /////*containedClosingTagAutoformat*/ -//// +////; +//// +////
,{integer}
;/*commaInJsxElement*/ +////
, {integer}
;/*commaInJsxElement2*/ +////);/*closingParenInJsxElement*/ +////) ;/*closingParenInJsxElement2*/ +////;/*jsxExpressionSpaces*/ +////;/*jsxExpressionSpaces2*/ format.document(); goTo.marker("autoformat"); @@ -114,7 +121,7 @@ verify.indentationIs(12); goTo.marker("danglingBracketAutoformat") // TODO: verify.currentLineContentIs(" >"); -verify.currentLineContentIs(" >"); +verify.currentLineContentIs(" >"); goTo.marker("closingTagAutoformat"); verify.currentLineContentIs("
"); @@ -125,4 +132,17 @@ verify.indentationIs(8); goTo.marker("grandchildJsxElementAutoformat"); verify.currentLineContentIs(" "); goTo.marker("containedClosingTagAutoformat"); -verify.currentLineContentIs(" "); \ No newline at end of file +verify.currentLineContentIs(" "); + +goTo.marker("commaInJsxElement"); +verify.currentLineContentIs("
,{integer}
;"); +goTo.marker("commaInJsxElement2"); +verify.currentLineContentIs("
, {integer}
;"); +goTo.marker("closingParenInJsxElement"); +verify.currentLineContentIs(");"); +goTo.marker("closingParenInJsxElement2"); +verify.currentLineContentIs(") ;"); +goTo.marker("jsxExpressionSpaces"); +verify.currentLineContentIs(";"); +goTo.marker("jsxExpressionSpaces2"); +verify.currentLineContentIs(";"); \ No newline at end of file diff --git a/tests/cases/fourslash/formattingOptionsChangeJsx.ts b/tests/cases/fourslash/formattingOptionsChangeJsx.ts new file mode 100644 index 0000000000000..a3c7e28a1ef7e --- /dev/null +++ b/tests/cases/fourslash/formattingOptionsChangeJsx.ts @@ -0,0 +1,32 @@ +/// + +//@Filename: file.tsx +/////*InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces*/; + +runTest("InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces", ";", ";"); + + +function runTest(propertyName: string, expectedStringWhenTrue: string, expectedStringWhenFalse: string) { + // Go to the correct file + goTo.marker(propertyName); + + // Set the option to false first + format.setOption(propertyName, false); + + // Format + format.document(); + + // Verify + goTo.marker(propertyName); + verify.currentLineContentIs(expectedStringWhenFalse); + + // Set the option to true + format.setOption(propertyName, true); + + // Format + format.document(); + + // Verify + goTo.marker(propertyName); + verify.currentLineContentIs(expectedStringWhenTrue); +} \ No newline at end of file