Skip to content

Commit cbce0b2

Browse files
committed
Fix code block brace formatting when Roslyn moves the brace up a line
1 parent 54be8f6 commit cbce0b2

File tree

3 files changed

+34
-33
lines changed

3 files changed

+34
-33
lines changed

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RazorSyntaxNodeExtensions.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,31 +85,27 @@ internal static bool IsAttributeDirective(this SyntaxNode node, [NotNullWhen(tru
8585
return false;
8686
}
8787

88-
internal static bool IsCodeDirective(this SyntaxNode node, out SyntaxToken openBraceToken)
88+
internal static bool IsCodeDirective(this SyntaxNode node)
8989
{
9090
if (IsDirective(node, ComponentCodeDirective.Directive, out var body) &&
9191
body.CSharpCode is { Children: { Count: > 0 } children } &&
92-
children.TryGetOpenBraceToken(out var openBrace))
92+
children.TryGetOpenBraceToken(out _))
9393
{
94-
openBraceToken = openBrace;
9594
return true;
9695
}
9796

98-
openBraceToken = default;
9997
return false;
10098
}
10199

102-
internal static bool IsFunctionsDirective(this SyntaxNode node, out SyntaxToken openBraceToken)
100+
internal static bool IsFunctionsDirective(this SyntaxNode node)
103101
{
104102
if (IsDirective(node, FunctionsDirective.Directive, out var body) &&
105103
body.CSharpCode is { Children: { Count: > 0 } children } &&
106-
children.TryGetOpenBraceToken(out var openBrace))
104+
children.TryGetOpenBraceToken(out _))
107105
{
108-
openBraceToken = openBrace;
109106
return true;
110107
}
111108

112-
openBraceToken = default;
113109
return false;
114110
}
115111

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/New/CSharpFormattingPass.CSharpDocumentGenerator.cs

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -695,14 +695,10 @@ public override LineInfo VisitRazorDirective(RazorDirectiveSyntax node)
695695
return VisitTypeParamDirective(typeParam, conditions);
696696
}
697697

698-
if (node.IsCodeDirective(out var openBrace))
698+
if (node.IsCodeDirective() ||
699+
node.IsFunctionsDirective())
699700
{
700-
return VisitCodeOrFunctionsDirective(openBrace);
701-
}
702-
703-
if (node.IsFunctionsDirective(out var functionsOpenBrace))
704-
{
705-
return VisitCodeOrFunctionsDirective(functionsOpenBrace);
701+
return VisitCodeOrFunctionsDirective();
706702
}
707703

708704
// All other directives that have braces are handled here
@@ -721,25 +717,17 @@ body.CSharpCode is CSharpCodeBlockSyntax code &&
721717
return EmitCurrentLineAsComment();
722718
}
723719

724-
private LineInfo VisitCodeOrFunctionsDirective(RazorSyntaxToken openBrace)
720+
private LineInfo VisitCodeOrFunctionsDirective()
725721
{
726-
// If the open brace is on the same line as the directive, then we need to ensure the contents are indented
727-
if (GetLineNumber(openBrace) == GetLineNumber(_currentToken))
728-
{
729-
// If its an @code or @functions we want to wrap the contents in a class
730-
// so that access modifiers are valid, and will be formatted as appropriate.
731-
_builder.AppendLine("class F");
732-
_builder.AppendLine("{");
733-
734-
// Roslyn might move our brace to the previous line, so we might _not_ need to skip it 🤦‍
735-
return CreateLineInfo(skipNextLineIfBrace: true);
736-
}
722+
// If its an @code or @functions we want to wrap the contents in a class so that access modifiers
723+
// on any members declared within it are valid, and will be formatted as appropriate.
724+
// We let the users content be the class name, as it will either be "@code" or "@functions", which
725+
// are both valid, and it might have an open brace after it, or that might be on the next line,
726+
// but if we just let that flow to the generated document, we don't need to do any fancy checking.
727+
_builder.Append("class ");
728+
_builder.AppendLine(_currentLine.ToString());
737729

738-
// If the braces are on different lines, then we can do nothing, unless its an @code or @functions
739-
// in which case we need to use a class. Note we don't output an open brace, as the next line of
740-
// the original file will have one.
741-
_builder.AppendLine("class F");
742-
return CreateLineInfo();
730+
return CreateLineInfo(skipNextLineIfBrace: true);
743731
}
744732

745733
private LineInfo VisitUsingDirective()

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/New/CSharpFormattingPass.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ public async Task<ImmutableArray<TextChange>> ExecuteAsync(FormattingContext con
5858
iFormatted++;
5959
}
6060

61+
if (iFormatted >= formattedCSharpText.Lines.Count)
62+
{
63+
break;
64+
}
65+
6166
var formattedLine = formattedCSharpText.Lines[iFormatted];
6267
if (lineInfo.ProcessIndentation &&
6368
formattedLine.GetFirstNonWhitespaceOffset() is { } formattedIndentation)
@@ -139,14 +144,26 @@ public async Task<ImmutableArray<TextChange>> ExecuteAsync(FormattingContext con
139144
}
140145
else if (lineInfo.SkipNextLineIfBrace)
141146
{
142-
// If the next line is a brace, we skip it, otherwise we don't. This is used to skip the opening brace of a class
147+
// If the next line is a brace, we skip it. This is used to skip the opening brace of a class
143148
// that we insert, but Roslyn settings might place on the same like as the class declaration.
144149
if (iFormatted + 1 < formattedCSharpText.Lines.Count &&
145150
formattedCSharpText.Lines[iFormatted + 1] is { Span.Length: > 0 } nextLine &&
146151
nextLine.CharAt(0) == '{')
147152
{
148153
iFormatted++;
149154
}
155+
156+
// On the other hand, we might insert the opening brace of a class, and Roslyn might collapse
157+
// it up to the previous line, so we would want to skip the next line in the original document
158+
// in that case. Fortunately its illegal to have `@code {\r\n {` in a Razor file, so there can't
159+
// be false positives here.
160+
if (iOriginal + 1 < changedText.Lines.Count &&
161+
changedText.Lines[iOriginal + 1] is { } nextOriginalLine &&
162+
nextOriginalLine.GetFirstNonWhitespaceOffset() is { } firstChar &&
163+
nextOriginalLine.CharAt(firstChar) == '{')
164+
{
165+
iOriginal++;
166+
}
150167
}
151168
}
152169

0 commit comments

Comments
 (0)