Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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, @<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.
Expand All @@ -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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return CreateLineInfo(

This doesn't need to specify formattedLength similar to the other call?

Copy link
Member Author

Choose a reason for hiding this comment

The 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,
Expand Down Expand Up @@ -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();
}

Expand All @@ -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
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7579,6 +7579,274 @@ 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: """
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expected

Seems odd to me that the input/expected strings are the same.

Maybe this is just the way the formatting tests work as this was the case in new tests added in another PR I reviewed today.

Copy link
Member Author

Choose a reason for hiding this comment

The 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
}
)
""");

[FormattingTestFact]
public Task PartialDocument()
=> RunFormattingTestAsync(
Expand Down
Loading