-
Notifications
You must be signed in to change notification settings - Fork 241
Emit the start of multiline implicit expressions the same as explicit… #12624
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
d3a6f15
385c2fe
63e4db3
b57255c
b9a8fc3
3c3052e
227b67b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -358,11 +358,11 @@ _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) | ||
| if (node.Parent.Parent is CSharpExplicitExpressionBodySyntax or CSharpImplicitExpressionBodySyntax && | ||
| _sourceText.GetLinePosition(node.Parent.Parent.EndPosition).Line == _currentLine.LineNumber) | ||
| { | ||
| isEndOfExplicitExpression = true; | ||
| end = explicitExpression.EndPosition; | ||
| end = node.Parent.Parent.EndPosition; | ||
| } | ||
| else | ||
| { | ||
|
|
@@ -384,14 +384,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, @<div></div>", 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 "@<div>" 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. | ||
|
|
@@ -408,6 +413,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( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Importantly, it can't specify a formattedLength, or the check for new lines wouldn't work. |
||
| skipPreviousLine: skipPreviousLine, | ||
| processFormatting: true, | ||
| htmlIndentLevel: htmlIndentLevel, | ||
| additionalIndentation: additionalIndentation, | ||
| checkForNewLines: true); | ||
| } | ||
|
|
||
| return CreateLineInfo( | ||
| skipPreviousLine: skipPreviousLine, | ||
| processFormatting: true, | ||
|
|
@@ -812,6 +828,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(); | ||
| } | ||
|
|
||
|
|
@@ -821,17 +848,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 | ||
|
|
@@ -880,7 +913,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); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7578,4 +7578,272 @@ public Task ObjectInitializers3() | |
| } | ||
| </div> | ||
| """); | ||
|
|
||
| [FormattingTestFact] | ||
| [WorkItem("https://github.com/dotnet/razor/issues/12622")] | ||
| public Task ObjectInitializers4() | ||
| => RunFormattingTestAsync( | ||
| input: """ | ||
| <div> | ||
| @if (true) | ||
| { | ||
| @Html.TextBox(new Test() | ||
| { | ||
| test = 5 | ||
| }) | ||
| <div></div> | ||
| } | ||
| </div> | ||
| """, | ||
| expected: """ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In a lot of cases, formatting is not just about putting things in the right place, but also not moving things if they're already in the right spot. In this case, for object initializers, Roslyn doesn't touch them at all, and so the tests verify that we correctly honour that. The old formatting engine had a bad habit of constantly indenting these sorts of things, every time the formatter ran. |
||
| <div> | ||
| @if (true) | ||
| { | ||
| @Html.TextBox(new Test() | ||
| { | ||
| test = 5 | ||
| }) | ||
| <div></div> | ||
| } | ||
| </div> | ||
| """); | ||
|
|
||
| [FormattingTestFact] | ||
| [WorkItem("https://github.com/dotnet/razor/issues/12622")] | ||
| public Task ObjectInitializers5() | ||
| => RunFormattingTestAsync( | ||
| input: """ | ||
| <div> | ||
| @if (true) | ||
| { | ||
| @Html.TextBox(new Test() { test = 5 }) | ||
| <div></div> | ||
| } | ||
| </div> | ||
| """, | ||
| expected: """ | ||
| <div> | ||
| @if (true) | ||
| { | ||
| @Html.TextBox(new Test() { test = 5 }) | ||
| <div></div> | ||
| } | ||
| </div> | ||
| """); | ||
|
|
||
| [FormattingTestFact] | ||
| [WorkItem("https://github.com/dotnet/razor/issues/12622")] | ||
| public Task ObjectInitializers6() | ||
| => RunFormattingTestAsync( | ||
| input: """ | ||
| @if (true) | ||
| { | ||
| @Html.TextBox(new Test() | ||
| { | ||
| test = 5 | ||
| }) | ||
| <div></div> | ||
| } | ||
| """, | ||
| expected: """ | ||
| @if (true) | ||
| { | ||
| @Html.TextBox(new Test() | ||
| { | ||
| test = 5 | ||
| }) | ||
| <div></div> | ||
| } | ||
| """); | ||
|
|
||
| [FormattingTestFact] | ||
| [WorkItem("https://github.com/dotnet/razor/issues/12622")] | ||
| public Task ObjectInitializers7() | ||
| => RunFormattingTestAsync( | ||
| input: """ | ||
| <div> | ||
| <div> | ||
| @Html.TextBox(new | ||
| { | ||
| test = 5, | ||
| }) | ||
| </div> | ||
| <div> | ||
| @Html.TextBox(new | ||
| { | ||
| test = 5, | ||
| }) | ||
| </div> | ||
| </div> | ||
| """, | ||
| expected: """ | ||
| <div> | ||
| <div> | ||
| @Html.TextBox(new | ||
| { | ||
| test = 5, | ||
| }) | ||
| </div> | ||
| <div> | ||
| @Html.TextBox(new | ||
| { | ||
| test = 5, | ||
| }) | ||
| </div> | ||
| </div> | ||
| """); | ||
|
|
||
| [FormattingTestFact] | ||
| [WorkItem("https://github.com/dotnet/razor/issues/12622")] | ||
| public Task ObjectInitializers8() | ||
| => RunFormattingTestAsync( | ||
| input: """ | ||
| @if (true) | ||
| { | ||
| @Html.TextBox(new Test() { | ||
| test = 5 | ||
| }) | ||
| <div></div> | ||
| } | ||
| """, | ||
| expected: """ | ||
| @if (true) | ||
| { | ||
| @Html.TextBox(new Test() | ||
| { | ||
| test = 5 | ||
| }) | ||
| <div></div> | ||
| } | ||
| """); | ||
|
|
||
| [FormattingTestFact] | ||
| [WorkItem("https://github.com/dotnet/razor/issues/12622")] | ||
| public Task ObjectInitializers9() | ||
| => RunFormattingTestAsync( | ||
| input: """ | ||
| @if (true) | ||
| { | ||
| @Html.TextBox(new Test() { | ||
| test = 5 | ||
| }) | ||
| <div></div> | ||
| } | ||
| """, | ||
| expected: """ | ||
| @if (true) | ||
| { | ||
| @Html.TextBox(new Test() { | ||
| test = 5 | ||
| }) | ||
| <div></div> | ||
| } | ||
| """, | ||
| 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 | ||
| }) | ||
| <div></div> | ||
| } | ||
| """, | ||
| expected: """ | ||
| @if (true) | ||
| { | ||
| @Html.TextBox(new Test() { | ||
| test = 5 | ||
| }) | ||
| <div></div> | ||
| } | ||
| """, | ||
| csharpSyntaxFormattingOptions: RazorCSharpSyntaxFormattingOptions.Default with | ||
| { | ||
| NewLines = RazorCSharpSyntaxFormattingOptions.Default.NewLines & ~RazorNewLinePlacement.BeforeOpenBraceInObjectCollectionArrayInitializers | ||
| }); | ||
|
|
||
| [FormattingTestFact] | ||
| [WorkItem("https://github.com/dotnet/razor/issues/12622")] | ||
| public Task ObjectInitializers11() | ||
| => RunFormattingTestAsync( | ||
| input: """ | ||
| <div> | ||
| <div> | ||
| <div> | ||
| @if (true) | ||
| { | ||
| <div> | ||
| @Html.TextBox(new | ||
| { | ||
| test = 6 | ||
| }) | ||
| </div> | ||
|
|
||
| <div></div> | ||
| } | ||
| </div> | ||
| </div> | ||
| </div> | ||
| """, | ||
| expected: """ | ||
| <div> | ||
| <div> | ||
| <div> | ||
| @if (true) | ||
| { | ||
| <div> | ||
| @Html.TextBox(new | ||
| { | ||
| test = 6 | ||
| }) | ||
| </div> | ||
|
|
||
| <div></div> | ||
| } | ||
| </div> | ||
| </div> | ||
| </div> | ||
| """); | ||
|
|
||
| [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 | ||
| } | ||
| ) | ||
| """); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.