diff --git a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpOnTypeFormattingPass.cs b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpOnTypeFormattingPass.cs
index 6c8e37b2791db..67710800deaaf 100644
--- a/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpOnTypeFormattingPass.cs
+++ b/src/Razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpOnTypeFormattingPass.cs
@@ -756,12 +756,20 @@ owner.Parent is RazorDirectiveSyntax containingDirective &&
var line = context.SourceText.Lines[i];
var lineStart = line.GetFirstNonWhitespacePosition() ?? line.Start;
var lineStartSpan = new TextSpan(lineStart, 0);
- if (!ShouldFormatLine(context, lineStartSpan, allowImplicitStatements: true))
+ if (!ShouldFormatLine(context, lineStartSpan, allowImplicitStatements: true, out var owner))
{
// We don't care about this line as it lies in an area we don't want to format.
continue;
}
+ var razorDesiredIndentation = context.GetIndentationOffsetForLevel(indentations[i].IndentationLevel);
+ if (owner is CSharpTransitionSyntax { Parent: CSharpStatementSyntax })
+ {
+ // An explicit statement delimiter has no C# source mapping, so use only its Razor/HTML indentation.
+ newIndentations[i] = razorDesiredIndentation;
+ continue;
+ }
+
if (!lineStartIndentations.TryGetValue(lineStart, out var csharpDesiredIndentation))
{
// Couldn't remap. This is probably a non-C# location.
@@ -820,7 +828,6 @@ owner.Parent is RazorDirectiveSyntax containingDirective &&
}
var effectiveCSharpDesiredIndentation = csharpDesiredIndentation - minCSharpIndentation;
- var razorDesiredIndentation = context.GetIndentationOffsetForLevel(indentations[i].IndentationLevel);
if (indentations[i].StartsInHtmlContext)
{
// This is a non-C# line.
@@ -857,8 +864,8 @@ private static bool ShouldFormat(FormattingContext context, TextSpan mappingSpan
private static bool ShouldFormat(FormattingContext context, TextSpan mappingSpan, bool allowImplicitStatements, out RazorSyntaxNode? foundOwner)
=> ShouldFormat(context, mappingSpan, new ShouldFormatOptions(allowImplicitStatements, isLineRequest: false), out foundOwner);
- private static bool ShouldFormatLine(FormattingContext context, TextSpan mappingSpan, bool allowImplicitStatements)
- => ShouldFormat(context, mappingSpan, new ShouldFormatOptions(allowImplicitStatements, isLineRequest: true), out _);
+ private static bool ShouldFormatLine(FormattingContext context, TextSpan mappingSpan, bool allowImplicitStatements, out RazorSyntaxNode? foundOwner)
+ => ShouldFormat(context, mappingSpan, new ShouldFormatOptions(allowImplicitStatements, isLineRequest: true), out foundOwner);
private static bool ShouldFormat(FormattingContext context, TextSpan mappingSpan, ShouldFormatOptions options, out RazorSyntaxNode? foundOwner)
{
diff --git a/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/CodeActions/CSharpCodeActionTests.cs b/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/CodeActions/CSharpCodeActionTests.cs
index 3b6555beeefc5..3c6a92289edd3 100644
--- a/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/CodeActions/CSharpCodeActionTests.cs
+++ b/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/CodeActions/CSharpCodeActionTests.cs
@@ -45,6 +45,55 @@ void M(bool value)
var invertIfAction = Assert.Single(shownActions, action => ((RazorVSInternalCodeAction)action.Value!).Name == PredefinedCodeRefactoringProviderNames.InvertIf);
}
+ [Fact]
+ public async Task InvertIf_ExplicitStatement()
+ {
+ var input = """
+ @(booleanValue ?@
: @
)
+
+ @{
+ [||]if (true)
+ {
+ // true
+ }
+ else
+ {
+ // false
+ }
+ }
+
+ @code
+ {
+ private bool booleanValue = true;
+ }
+ """;
+
+ var expected = """
+ @(booleanValue ?@
: @
)
+
+ @{
+ if (false)
+ {
+ // false
+ }
+ else
+ {
+ // true
+ }
+ }
+
+ @code
+ {
+ private bool booleanValue = true;
+ }
+ """;
+
+ var advancedSettings = ClientSettingsManager.GetClientSettings().AdvancedSettings;
+ ClientSettingsManager.Update(advancedSettings with { ShowAllCSharpCodeActions = true });
+
+ await VerifyCodeActionAsync(input, expected, PredefinedCodeRefactoringProviderNames.InvertIf);
+ }
+
[Fact]
public async Task GenerateConstructor()
{
diff --git a/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/Formatting/DocumentFormattingTest.cs b/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/Formatting/DocumentFormattingTest.cs
index 2fc60220a105b..3915f80c9b7a2 100644
--- a/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/Formatting/DocumentFormattingTest.cs
+++ b/src/Razor/src/Razor/test/Microsoft.CodeAnalysis.Razor.CohostingShared.UnitTests/Formatting/DocumentFormattingTest.cs
@@ -7182,6 +7182,136 @@ void Method()
""");
}
+ [Fact]
+ public async Task ExplicitStatement_ShiftedOpeningBrace()
+ {
+ await RunFormattingTestAsync(
+ input: """
+ @(booleanValue ?@
: @
)
+
+ @{
+ if (false)
+ {
+ // false
+ }
+ else
+ {
+ // true
+ }
+ }
+
+ @code
+ {
+ private bool booleanValue = true;
+ }
+ """,
+ htmlFormatted: """
+ @(booleanValue ?@
+
: @
+
)
+
+ @{
+ if (false)
+ {
+ // false
+ }
+ else
+ {
+ // true
+ }
+ }
+
+ @code
+ {
+ private bool booleanValue = true;
+ }
+ """,
+ expected: """
+ @(booleanValue ?@
: @
)
+
+ @{
+ if (false)
+ {
+ // false
+ }
+ else
+ {
+ // true
+ }
+ }
+
+ @code
+ {
+ private bool booleanValue = true;
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task ExplicitStatement_AlreadyFormatted()
+ {
+ await RunFormattingTestAsync(
+ input: """
+ @(booleanValue ?@
: @
)
+
+ @{
+ if (false)
+ {
+ // false
+ }
+ else
+ {
+ // true
+ }
+ }
+
+ @code
+ {
+ private bool booleanValue = true;
+ }
+ """,
+ htmlFormatted: """
+ @(booleanValue ?@
+
: @
+
)
+
+ @{
+ if (false)
+ {
+ // false
+ }
+ else
+ {
+ // true
+ }
+ }
+
+ @code
+ {
+ private bool booleanValue = true;
+ }
+ """,
+ expected: """
+ @(booleanValue ?@
: @
)
+
+ @{
+ if (false)
+ {
+ // false
+ }
+ else
+ {
+ // true
+ }
+ }
+
+ @code
+ {
+ private bool booleanValue = true;
+ }
+ """);
+ }
+
[Fact]
public async Task Formats_NonCodeBlockDirectives()
{
diff --git a/src/Razor/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.UnitTests/Cohost/Formatting/OnTypeFormattingTest.cs b/src/Razor/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.UnitTests/Cohost/Formatting/OnTypeFormattingTest.cs
index 7580e336fcd83..ff3c4dbdba86e 100644
--- a/src/Razor/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.UnitTests/Cohost/Formatting/OnTypeFormattingTest.cs
+++ b/src/Razor/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.UnitTests/Cohost/Formatting/OnTypeFormattingTest.cs
@@ -52,6 +52,51 @@ await RunOnTypeFormattingTestAsync(
triggerCharacter: '}');
}
+ [FormattingTestFact]
+ public async Task CloseCurly_ExplicitStatement_PreservesOpeningBraceAsync()
+ {
+ await RunOnTypeFormattingTestAsync(
+ input: """
+ @(booleanValue ?@
: @
)
+
+ @{
+ if (false)
+ {
+ // false
+ }
+ else
+ {
+ // true
+ }$$
+ }
+
+ @code
+ {
+ private bool booleanValue = true;
+ }
+ """,
+ expected: """
+ @(booleanValue ?@
: @
)
+
+ @{
+ if (false)
+ {
+ // false
+ }
+ else
+ {
+ // true
+ }
+ }
+
+ @code
+ {
+ private bool booleanValue = true;
+ }
+ """,
+ triggerCharacter: '}');
+ }
+
[FormattingTestFact]
public async Task FormatsIfStatementInComponent()
{