Skip to content

Commit 5b20f50

Browse files
Fix indentation of if statement after else on separate line (#81178)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com> Co-authored-by: Cyrus Najmabadi <cyrus.najmabadi@gmail.com>
1 parent 7630528 commit 5b20f50

File tree

4 files changed

+119
-23
lines changed

4 files changed

+119
-23
lines changed

src/Workspaces/CSharpTest/Formatting/FormattingTests.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,82 @@ void Method()
171171
}
172172
""");
173173

174+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/16328")]
175+
public Task FormatElseIfOnSeparateLines()
176+
=> AssertFormatAsync("""
177+
void Method()
178+
{
179+
if (true) { }
180+
else
181+
if (false) { }
182+
}
183+
""", """
184+
void Method()
185+
{
186+
if (true) { }
187+
else
188+
if (false) { }
189+
}
190+
""");
191+
192+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/16328")]
193+
public Task FormatElseReturnOnSeparateLines()
194+
=> AssertFormatAsync("""
195+
void Method()
196+
{
197+
if (true) { }
198+
else
199+
return;
200+
}
201+
""", """
202+
void Method()
203+
{
204+
if (true) { }
205+
else
206+
return;
207+
}
208+
""");
209+
210+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/16328")]
211+
public Task FormatElseWhileOnSeparateLines()
212+
=> AssertFormatAsync("""
213+
void Method()
214+
{
215+
if (true) { }
216+
else
217+
while (true) { }
218+
}
219+
""", """
220+
void Method()
221+
{
222+
if (true) { }
223+
else
224+
while (true) { }
225+
}
226+
""");
227+
228+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/16328")]
229+
public Task FormatElseIfOnSameLineWithExtraSpaces()
230+
=> AssertFormatAsync("""
231+
class A
232+
{
233+
void Method()
234+
{
235+
if (true) { }
236+
else if (false) { }
237+
}
238+
}
239+
""", """
240+
class A
241+
{
242+
void Method()
243+
{
244+
if (true) { }
245+
else if (false) { }
246+
}
247+
}
248+
""");
249+
174250
[Fact]
175251
public Task Format10()
176252
=> AssertFormatAsync("""

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/FormattingHelpers.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using System;
66
using System.Collections.Generic;
7+
using System.Diagnostics;
78
using System.Diagnostics.CodeAnalysis;
89
using Microsoft.CodeAnalysis.CSharp.Extensions;
910
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -531,4 +532,12 @@ public static bool IsCommaInTupleExpression(this SyntaxToken currentToken)
531532

532533
public static bool IsCommaInCollectionExpression(this SyntaxToken token)
533534
=> token.Kind() == SyntaxKind.CommaToken && token.Parent.IsKind(SyntaxKind.CollectionExpression);
535+
536+
public static bool AreOnSameLine(SyntaxToken previousToken, SyntaxToken currentToken)
537+
{
538+
Debug.Assert(previousToken != default);
539+
Debug.Assert(currentToken != default);
540+
Debug.Assert(previousToken.FullSpan.End == currentToken.FullSpan.Start);
541+
return !previousToken.TrailingTrivia.Any(SyntaxKind.EndOfLineTrivia) && !currentToken.LeadingTrivia.Any(SyntaxKind.EndOfLineTrivia);
542+
}
534543
}

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/IndentBlockFormattingRule.cs

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -292,29 +292,38 @@ private static void AddAlignmentBlockOperationRelativeToFirstTokenOnBaseTokenLin
292292

293293
private static void AddEmbeddedStatementsIndentationOperation(List<IndentBlockOperation> list, SyntaxNode node)
294294
{
295-
// increase indentation - embedded statement cases
296-
var statement = node switch
295+
// An else-if on the same line should not cause an extra indentation. Instead, the indentation will be
296+
// controlled entirely by the IfStatement.
297+
if (node is ElseClauseSyntax { ElseKeyword: var elseKeyword, Statement: IfStatementSyntax { IfKeyword: var ifKeyword } } &&
298+
FormattingHelpers.AreOnSameLine(elseKeyword, ifKeyword))
297299
{
298-
// Basic cases that want to unilaterally indent their embedded statements (unless they are blocks)
299-
IfStatementSyntax ifStatement => ifStatement.Statement,
300-
WhileStatementSyntax whileStatement => whileStatement.Statement,
301-
ForStatementSyntax forStatement => forStatement.Statement,
302-
CommonForEachStatementSyntax foreachStatement => foreachStatement.Statement,
303-
DoStatementSyntax doStatement => doStatement.Statement,
304-
LockStatementSyntax lockStatement => lockStatement.Statement,
305-
// A few special cases where if we see certain nesting of statements, we don't want to double indent.
306-
ElseClauseSyntax { Statement: not IfStatementSyntax } elseClause => elseClause.Statement,
307-
UsingStatementSyntax { Statement: not UsingStatementSyntax } usingStatement => usingStatement.Statement,
308-
FixedStatementSyntax { Statement: not FixedStatementSyntax } fixedStatement => fixedStatement.Statement,
309-
_ => null,
310-
};
311-
312-
// We never want to indent a block. It is its own indentation region.
313-
if (statement is null or BlockSyntax)
300+
return;
301+
}
302+
303+
// Handle common idiom in C# of nested usings (or nested fixed-statements) not getting extra indentation.
304+
if (node is UsingStatementSyntax { Statement: UsingStatementSyntax } ||
305+
node is FixedStatementSyntax { Statement: FixedStatementSyntax })
306+
{
307+
return;
308+
}
309+
310+
// Labels don't increase indent. Instead, the label itself is placed normally at a higher level and the content
311+
// stays at the same level as the label's container.
312+
if (node is LabeledStatementSyntax)
313+
return;
314+
315+
var embeddedStatement = node.GetEmbeddedStatement();
316+
317+
// If it's not a construct that has embedded statements, we def don't want to increase the indent.
318+
if (embeddedStatement is null)
319+
return;
320+
321+
// We also never want to indent if the embedded statement is a block. It is its own indentation region.
322+
if (embeddedStatement is BlockSyntax)
314323
return;
315324

316-
var firstToken = statement.GetFirstToken(includeZeroWidth: true);
317-
var lastToken = statement.GetLastToken(includeZeroWidth: true);
325+
var firstToken = embeddedStatement.GetFirstToken(includeZeroWidth: true);
326+
var lastToken = embeddedStatement.GetLastToken(includeZeroWidth: true);
318327

319328
if (lastToken.IsMissing)
320329
{

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/TokenBasedFormattingRule.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,12 @@ public override AbstractFormattingRule WithOptions(SyntaxFormattingOptions optio
137137
return CreateAdjustNewLinesOperation(0, AdjustNewLinesOption.PreserveLines);
138138
}
139139

140-
// else * except else if case
141-
if (previousToken.Kind() == SyntaxKind.ElseKeyword && currentToken.Kind() != SyntaxKind.IfKeyword)
140+
// else * except `else if` case on the same line.
141+
if (previousToken.Kind() == SyntaxKind.ElseKeyword)
142142
{
143-
return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines);
143+
var isElseIfOnSameLine = currentToken.Kind() == SyntaxKind.IfKeyword && FormattingHelpers.AreOnSameLine(previousToken, currentToken);
144+
if (!isElseIfOnSameLine)
145+
return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines);
144146
}
145147

146148
// , * in enum declarations

0 commit comments

Comments
 (0)