diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpFormattingPass.CSharpDocumentGenerator.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpFormattingPass.CSharpDocumentGenerator.cs
index 19cbf36a6b5..9a6e339bd2c 100644
--- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpFormattingPass.CSharpDocumentGenerator.cs
+++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpFormattingPass.CSharpDocumentGenerator.cs
@@ -359,11 +359,12 @@ _sourceText.Lines[nodeStartLine] is { } previousLine &&
{
// A special case here is if we're inside an explicit expression body, one of the bits of content after the node will
// be the final close parens, so we need to emit that or the C# expression won't be valid, and we can't trust the formatter.
- if (node.Parent.Parent is CSharpExplicitExpressionBodySyntax explicitExpression &&
- _sourceText.GetLinePosition(explicitExpression.EndPosition).Line == _currentLine.LineNumber)
+ var potentialExplicitExpression = node.Parent.Parent;
+ if (potentialExplicitExpression is CSharpExplicitExpressionBodySyntax or CSharpImplicitExpressionBodySyntax &&
+ _sourceText.GetLinePosition(potentialExplicitExpression.EndPosition).Line == _currentLine.LineNumber)
{
isEndOfExplicitExpression = true;
- end = explicitExpression.EndPosition;
+ end = potentialExplicitExpression.EndPosition;
}
else
{
@@ -385,14 +386,19 @@ _sourceText.Lines[nodeStartLine] is { } previousLine &&
_builder.Append(';');
}
- // Append a comment at the end so whitespace isn't removed, as Roslyn thinks its the end of the line, but we know it isn't.
+ // Append a comment at the end so whitespace isn't removed, as Roslyn thinks its the end of the line, but it might not be.
// eg, given "4, 5, @
", we want Roslyn to keep the space after the last comma, because there is something after it,
// but we can't let Roslyn see the "@" that it is.
// We use a multi-line comment because Roslyn has a desire to line up "//" comments with the previous line, which we could interpret
// as Roslyn suggesting we indent some trailing Html.
- const string EndOfLineComment = " /* */";
- offsetFromEnd += EndOfLineComment.Length;
- _builder.AppendLine(EndOfLineComment);
+ if (end != _currentLine.End)
+ {
+ const string EndOfLineComment = " /* */";
+ offsetFromEnd += EndOfLineComment.Length;
+ _builder.Append(EndOfLineComment);
+ }
+
+ _builder.AppendLine();
// Final quirk: If we're inside an Html attribute, it means the Html formatter won't have formatted this line, as multi-line
// Html attributes are not valid.
@@ -409,6 +415,17 @@ _sourceText.Lines[nodeStartLine] is { } previousLine &&
additionalIndentation = new string(' ', startChar % _tabSize);
}
+ if (offsetFromEnd == 0)
+ {
+ // If we're not doing any extra emitting of our own, then we can safely check for newlines
+ return CreateLineInfo(
+ skipPreviousLine: skipPreviousLine,
+ processFormatting: true,
+ htmlIndentLevel: htmlIndentLevel,
+ additionalIndentation: additionalIndentation,
+ checkForNewLines: true);
+ }
+
return CreateLineInfo(
skipPreviousLine: skipPreviousLine,
processFormatting: true,
@@ -796,6 +813,17 @@ public override LineInfo VisitCSharpImplicitExpression(CSharpImplicitExpressionS
// so we can actually just emit these lines as a comment so the indentation is correct, and then let the code above
// handle them. Essentially, whether these are at the start or int he middle of a line is irrelevant.
+ // The exception to this is if the implicit expressions are multi-line. In that case, it's possible that the contents
+ // of this line (ie, the first line) will affect the indentation of subsequent lines. Emitting this as a comment, won't
+ // help when we emit the following lines in their original form. So lets do that for this line too. Since it's multi-line
+ // we know, by definition, there can't be more than one on this line anyway.
+
+ if (_sourceText.GetLinePositionSpan(node.Span).SpansMultipleLines())
+ {
+ var csharpCode = ((CSharpImplicitExpressionBodySyntax)node.Body).CSharpCode;
+ return VisitCSharpCodeBlock(node, csharpCode);
+ }
+
return EmitCurrentLineAsComment();
}
@@ -805,17 +833,23 @@ public override LineInfo VisitCSharpExplicitExpression(CSharpExplicitExpressionS
// of whether its at the start or in the middle of the line.
var body = (CSharpExplicitExpressionBodySyntax)node.Body;
var closeParen = body.CloseParen;
+ var csharpCode = body.CSharpCode;
if (GetLineNumber(closeParen) == GetLineNumber(node))
{
return EmitCurrentLineAsComment();
}
+ return VisitCSharpCodeBlock(node, csharpCode);
+ }
+
+ private LineInfo VisitCSharpCodeBlock(RazorSyntaxNode node, CSharpCodeBlockSyntax csharpCode)
+ {
// If this spans multiple lines however, the indentation of this line will affect the next, so we handle it in the
// same way we handle a C# literal syntax. That includes checking if the C# doesn't go to the end of the line.
// If the whole explicit expression is C#, then the children will be a single CSharpExpressionLiteral. If not, there
// will be multiple children, and the second one is not C#, so thats the one we need to exclude from the generated
// document.
- if (body.CSharpCode.Children is [_, { } secondChild, ..] &&
+ if (csharpCode.Children is [_, { } secondChild, ..] &&
GetLineNumber(secondChild) == GetLineNumber(node))
{
// Emit the whitespace, so user spacing is honoured if possible
@@ -864,7 +898,7 @@ public override LineInfo VisitCSharpExplicitExpression(CSharpExplicitExpressionS
_builder.AppendLine(_sourceText.GetSubTextString(TextSpan.FromBounds(_currentToken.Position + 1, _currentLine.End)));
return CreateLineInfo(
processFormatting: true,
- checkForNewLines: false,
+ checkForNewLines: true,
originOffset: 1,
formattedOffset: 0);
}
diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/DocumentFormattingTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/DocumentFormattingTest.cs
index c32a12dc46e..bc805a78905 100644
--- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/DocumentFormattingTest.cs
+++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/DocumentFormattingTest.cs
@@ -7579,6 +7579,274 @@ public Task ObjectInitializers3()
""");
+ [FormattingTestFact]
+ [WorkItem("https://github.com/dotnet/razor/issues/12622")]
+ public Task ObjectInitializers4()
+ => RunFormattingTestAsync(
+ input: """
+
+ @if (true)
+ {
+ @Html.TextBox(new Test()
+ {
+ test = 5
+ })
+
+ }
+
+ """,
+ expected: """
+
+ @if (true)
+ {
+ @Html.TextBox(new Test()
+ {
+ test = 5
+ })
+
+ }
+
+ """);
+
+ [FormattingTestFact]
+ [WorkItem("https://github.com/dotnet/razor/issues/12622")]
+ public Task ObjectInitializers5()
+ => RunFormattingTestAsync(
+ input: """
+
+ @if (true)
+ {
+ @Html.TextBox(new Test() { test = 5 })
+
+ }
+
+ """,
+ expected: """
+
+ @if (true)
+ {
+ @Html.TextBox(new Test() { test = 5 })
+
+ }
+
+ """);
+
+ [FormattingTestFact]
+ [WorkItem("https://github.com/dotnet/razor/issues/12622")]
+ public Task ObjectInitializers6()
+ => RunFormattingTestAsync(
+ input: """
+ @if (true)
+ {
+ @Html.TextBox(new Test()
+ {
+ test = 5
+ })
+
+ }
+ """,
+ expected: """
+ @if (true)
+ {
+ @Html.TextBox(new Test()
+ {
+ test = 5
+ })
+
+ }
+ """);
+
+ [FormattingTestFact]
+ [WorkItem("https://github.com/dotnet/razor/issues/12622")]
+ public Task ObjectInitializers7()
+ => RunFormattingTestAsync(
+ input: """
+
+
+ @Html.TextBox(new
+ {
+ test = 5,
+ })
+
+
+ @Html.TextBox(new
+ {
+ test = 5,
+ })
+
+
+ """,
+ expected: """
+
+
+ @Html.TextBox(new
+ {
+ test = 5,
+ })
+
+
+ @Html.TextBox(new
+ {
+ test = 5,
+ })
+
+
+ """);
+
+ [FormattingTestFact]
+ [WorkItem("https://github.com/dotnet/razor/issues/12622")]
+ public Task ObjectInitializers8()
+ => RunFormattingTestAsync(
+ input: """
+ @if (true)
+ {
+ @Html.TextBox(new Test() {
+ test = 5
+ })
+
+ }
+ """,
+ expected: """
+ @if (true)
+ {
+ @Html.TextBox(new Test()
+ {
+ test = 5
+ })
+
+ }
+ """);
+
+ [FormattingTestFact]
+ [WorkItem("https://github.com/dotnet/razor/issues/12622")]
+ public Task ObjectInitializers9()
+ => RunFormattingTestAsync(
+ input: """
+ @if (true)
+ {
+ @Html.TextBox(new Test() {
+ test = 5
+ })
+
+ }
+ """,
+ expected: """
+ @if (true)
+ {
+ @Html.TextBox(new Test() {
+ test = 5
+ })
+
+ }
+ """,
+ csharpSyntaxFormattingOptions: RazorCSharpSyntaxFormattingOptions.Default with
+ {
+ NewLines = RazorCSharpSyntaxFormattingOptions.Default.NewLines & ~RazorNewLinePlacement.BeforeOpenBraceInObjectCollectionArrayInitializers
+ });
+
+ [FormattingTestFact]
+ [WorkItem("https://github.com/dotnet/razor/issues/12622")]
+ public Task ObjectInitializers10()
+ => RunFormattingTestAsync(
+ input: """
+ @if (true)
+ {
+ @Html.TextBox(new Test()
+ {
+ test = 5
+ })
+
+ }
+ """,
+ expected: """
+ @if (true)
+ {
+ @Html.TextBox(new Test() {
+ test = 5
+ })
+
+ }
+ """,
+ csharpSyntaxFormattingOptions: RazorCSharpSyntaxFormattingOptions.Default with
+ {
+ NewLines = RazorCSharpSyntaxFormattingOptions.Default.NewLines & ~RazorNewLinePlacement.BeforeOpenBraceInObjectCollectionArrayInitializers
+ });
+
+ [FormattingTestFact]
+ [WorkItem("https://github.com/dotnet/razor/issues/12622")]
+ public Task ObjectInitializers11()
+ => RunFormattingTestAsync(
+ input: """
+
+
+
+ @if (true)
+ {
+
+ @Html.TextBox(new
+ {
+ test = 6
+ })
+
+
+
+ }
+
+
+
+ """,
+ expected: """
+
+
+
+ @if (true)
+ {
+
+ @Html.TextBox(new
+ {
+ test = 6
+ })
+
+
+
+ }
+
+
+
+ """);
+
+ [FormattingTestFact]
+ [WorkItem("https://github.com/dotnet/razor/issues/12631")]
+ public Task ObjectInitializers12()
+ => RunFormattingTestAsync(
+ input: """
+ @await Component.InvokeAsync("ReviewAndPublishModal",
+ new {
+ id = "ReviewPublishModal",
+ title = "Review and publish",
+ text = Model.ReviewNotes,
+ state = Model.State,
+ allowSave = allowSaveReview,
+ allowPublish = allowPublish,
+ isPublished =isCurrentPublished
+ }
+ )
+ """,
+ expected: """
+ @await Component.InvokeAsync("ReviewAndPublishModal",
+ new
+ {
+ id = "ReviewPublishModal",
+ title = "Review and publish",
+ text = Model.ReviewNotes,
+ state = Model.State,
+ allowSave = allowSaveReview,
+ allowPublish = allowPublish,
+ isPublished = isCurrentPublished
+ }
+ )
+ """);
+
[FormattingTestFact]
public Task PartialDocument()
=> RunFormattingTestAsync(