未验证 提交 77db8b1b 编写于 作者: A Andrew Hall 提交者: GitHub

Merge pull request #38007 from alrz/fix-0066

Bugfixes for ConvertSwitchStatementToExpression

Fixes #37949 (leave open for offering the fix instead of bail out)
Fixes #37950 (will be resolved on the compiler side)
Fixes #36876
Fixes #37873
Fixes #37872
Fixes #37907
Fixes #37947
Closes #36877 (duplicate of #37873)
......@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertSwitchStatementToExpression
......@@ -13,7 +14,7 @@ public async Task TestNested_01()
{
await TestInCSharp8(
@"class Program
{
{
int M(int i, int j)
{
int r;
......@@ -95,7 +96,7 @@ int M(int i, int j)
}
}",
@"class Program
{
{
int M(int i, int j)
{
var r = i switch
......@@ -125,35 +126,31 @@ int M(int i, int j)
y = 1;
break;
}
switch (i)
return i switch
{
default:
throw null;
case 1:
return j switch
{
10 => 10,
20 => 20,
30 => 30,
_ => 0,
};
case 2:
return j switch
{
10 => 10,
20 => 20,
30 => 30,
var _ => 0,
};
case 3:
return j switch
{
10 => 10,
20 => 20,
30 => 30,
var v => 0,
};
}
1 => j switch
{
10 => 10,
20 => 20,
30 => 30,
_ => 0,
},
2 => j switch
{
10 => 10,
20 => 20,
30 => 30,
var _ => 0,
},
3 => j switch
{
10 => 10,
20 => 20,
30 => 30,
var v => 0,
},
_ => throw null,
};
}
}");
}
......@@ -168,7 +165,8 @@ System.Action<int> M(int i, int j)
{
{|FixAllInDocument:switch|} (i)
{
default:
// 1
default: // 2
return () =>
{
switch (j)
......@@ -186,17 +184,80 @@ System.Action<int> M(int i, int j)
{
return i switch
{
// 1
// 2
_ => () =>
{
switch (j)
{
default:
return 3;
}
}
{
switch (j)
{
default:
return 3;
}
}
,
};
}
}");
}
[WorkItem(37907, "https://github.com/dotnet/roslyn/issues/37907")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)]
public async Task TestNested_03()
{
await TestInCSharp8(
@"using System;
class Program
{
public static void Main() { }
public DayOfWeek StatusValue() => DayOfWeek.Monday;
public short Value => 0;
public bool ValueBoolean()
{
bool value;
{|FixAllInDocument:switch|} (StatusValue())
{
case DayOfWeek.Monday:
switch (Value)
{
case 0:
value = false;
break;
case 1:
value = true;
break;
default:
throw new Exception();
}
break;
default:
throw new Exception();
}
return value;
}
}",
@"using System;
class Program
{
public static void Main() { }
public DayOfWeek StatusValue() => DayOfWeek.Monday;
public short Value => 0;
public bool ValueBoolean()
{
var value = (StatusValue()) switch
{
DayOfWeek.Monday => Value switch
{
0 => false,
1 => true,
_ => throw new Exception(),
},
_ => throw new Exception(),
};
return value;
}
}");
}
}
......
......@@ -588,7 +588,7 @@ int M(int i)
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)]
public async Task TestTrivia()
public async Task TestTrivia_01()
{
await TestInCSharp8(
@"class Program
......@@ -599,7 +599,8 @@ int M(int i)
[||]switch (i) // trailing switch
{
// leading label
case 1:
case 1: // trailing label
// leading body
return 4; // trailing body
case 2:
return 5;
......@@ -619,12 +620,54 @@ int M(int i)
return i switch // trailing switch
{
// leading label
1 => 4, // trailing body
// trailing label
1 => 4,// leading body
// trailing body
2 => 5,
3 => 6,
// leading next statement
_ => throw null, // leading next statement
_ => throw null,// leading next statement
};
}
}");
}
[WorkItem(37873, "https://github.com/dotnet/roslyn/issues/37873")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)]
public async Task TestTrivia_02()
{
await TestInCSharp8(
@"class Program
{
static int GetValue(int input)
{
[||]switch (input)
{
case 1:
// this little piggy went to market
return 42;
case 2:
// this little piggy stayed home
return 50;
case 3:
// this little piggy had roast beef
return 79;
default:
// this little piggy had none
return 80;
}
}
}",
@"class Program
{
static int GetValue(int input)
{
return input switch
{
1 => 42,// this little piggy went to market
2 => 50,// this little piggy stayed home
3 => 79,// this little piggy had roast beef
_ => 80,// this little piggy had none
};
}
}");
......@@ -696,6 +739,240 @@ void M(int i)
_ => throw null,
};
}
}");
}
[WorkItem(37947, "https://github.com/dotnet/roslyn/issues/37947")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)]
public async Task TestMultiLabelWithDefault()
{
await TestInCSharp8(
@"using System;
class Program
{
public static string FromDay(DayOfWeek dayOfWeek)
{
[||]switch (dayOfWeek)
{
case DayOfWeek.Monday:
return ""Monday"";
case DayOfWeek.Friday:
default:
return ""Other"";
}
}
}",
@"using System;
class Program
{
public static string FromDay(DayOfWeek dayOfWeek)
{
return dayOfWeek switch
{
DayOfWeek.Monday => ""Monday"",
_ => ""Other"",
};
}
}");
}
[WorkItem(37949, "https://github.com/dotnet/roslyn/issues/37949")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)]
public async Task TestMissingOnUseInNextStatement()
{
await TestMissingAsync(
@"using System;
class Program
{
public static void Throw(int index)
{
string name = """";
[||]switch (index)
{
case 0: name = ""1""; break;
case 1: name = ""2""; break;
}
throw new Exception(name);
}
}");
}
[WorkItem(36876, "https://github.com/dotnet/roslyn/issues/36876")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)]
public async Task TestDeclarationInOuterScope()
{
await TestInCSharp8(
@"using System;
using System.IO;
class Program
{
static SeekOrigin origin;
static long offset;
static long position;
static long length;
public static void Test()
{
long target;
try
{
[||]switch (origin)
{
case SeekOrigin.Begin:
target = offset;
break;
case SeekOrigin.Current:
target = checked(offset + position);
break;
case SeekOrigin.End:
target = checked(offset + length);
break;
default:
throw new ArgumentOutOfRangeException(nameof(origin));
}
}
catch (OverflowException)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
if (target < 0)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
}
}",
@"using System;
using System.IO;
class Program
{
static SeekOrigin origin;
static long offset;
static long position;
static long length;
public static void Test()
{
long target;
try
{
target = origin switch
{
SeekOrigin.Begin => offset,
SeekOrigin.Current => checked(offset + position),
SeekOrigin.End => checked(offset + length),
_ => throw new ArgumentOutOfRangeException(nameof(origin)),
};
}
catch (OverflowException)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
if (target < 0)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
}
}");
}
[WorkItem(37872, "https://github.com/dotnet/roslyn/issues/37872")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)]
public async Task TestMissingOnDirectives()
{
await TestMissingAsync(
@"class Program
{
static void Main() { }
static int GetValue(int input)
{
[||]switch (input)
{
case 1:
return 42;
case 2:
#if PLATFORM_UNIX
return 50;
#else
return 51;
#endif
case 3:
return 79;
default:
return 80;
}
}
}");
}
[WorkItem(37950, "https://github.com/dotnet/roslyn/issues/37950")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)]
public async Task TestShouldNotCastNullOnNullableValueType_ReturnStatement()
{
await TestInCSharp8(
@"class Program
{
public static bool? GetBool(string name)
{
[||]switch (name)
{
case ""a"": return true;
case ""b"": return false;
default: return null;
}
}
}",
@"class Program
{
public static bool? GetBool(string name)
{
return name switch
{
""a"" => true,
""b"" => false,
_ => null,
};
}
}");
}
[WorkItem(37950, "https://github.com/dotnet/roslyn/issues/37950")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)]
public async Task TestShouldNotCastNullOnNullableValueType_Assignment()
{
await TestInCSharp8(
@"class Program
{
public static void Test(string name)
{
bool? result;
[||]switch (name)
{
case ""a"": result = true; break;
case ""b"": result = false; break;
default: result = null; break;
}
}
}",
@"class Program
{
public static void Test(string name)
{
var result = name switch
{
""a"" => true,
""b"" => false,
_ => null,
};
}
}");
}
}
......
......@@ -4,7 +4,6 @@
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
......@@ -17,6 +16,7 @@ internal sealed partial class ConvertSwitchStatementToExpressionCodeFixProvider
private sealed class Rewriter : CSharpSyntaxVisitor<ExpressionSyntax>
{
private ExpressionSyntax _assignmentTargetOpt;
private readonly bool _isAllThrowStatements;
private Rewriter(bool isAllThrowStatements)
......@@ -25,8 +25,8 @@ private Rewriter(bool isAllThrowStatements)
}
public static StatementSyntax Rewrite(
SwitchStatementSyntax switchStatement, SemanticModel semanticModel, SyntaxEditor editor,
SyntaxKind nodeToGenerate, bool shouldMoveNextStatementToSwitchExpression)
SwitchStatementSyntax switchStatement,
SyntaxKind nodeToGenerate, bool shouldMoveNextStatementToSwitchExpression, bool generateDeclaration)
{
var rewriter = new Rewriter(isAllThrowStatements: nodeToGenerate == SyntaxKind.ThrowStatement);
......@@ -34,86 +34,11 @@ private Rewriter(bool isAllThrowStatements)
var switchExpression = rewriter.RewriteSwitchStatement(switchStatement,
allowMoveNextStatementToSwitchExpression: shouldMoveNextStatementToSwitchExpression);
// Only on simple assignments we attempt to remove variable declarators.
var isSimpleAssignment = nodeToGenerate == SyntaxKind.SimpleAssignmentExpression;
var generateDeclaration = isSimpleAssignment && rewriter.TryRemoveVariableDeclarators(switchStatement, semanticModel, editor);
// Generate the final statement to wrap the switch expression, e.g. a "return" or an assignment.
return rewriter.GetFinalStatement(switchExpression,
switchStatement.SwitchKeyword.LeadingTrivia, nodeToGenerate, generateDeclaration);
}
private bool TryRemoveVariableDeclarators(SwitchStatementSyntax switchStatement, SemanticModel semanticModel, SyntaxEditor editor)
{
Debug.Assert(_assignmentTargetOpt != null);
// Try to remove variable declarator only if it's a simple identifier.
if (!_assignmentTargetOpt.IsKind(SyntaxKind.IdentifierName))
{
return false;
}
var symbol = semanticModel.GetSymbolInfo(_assignmentTargetOpt).Symbol;
if (symbol == null)
{
return false;
}
if (symbol.Kind != SymbolKind.Local)
{
return false;
}
var syntaxReferences = symbol.DeclaringSyntaxReferences;
if (syntaxReferences.Length != 1)
{
return false;
}
if (!(syntaxReferences[0].GetSyntax() is VariableDeclaratorSyntax declarator))
{
return false;
}
if (declarator.Initializer != null)
{
return false;
}
var symbolName = symbol.Name;
var declaratorSpanStart = declarator.SpanStart;
var switchStatementSpanStart = switchStatement.SpanStart;
// Check for uses before the switch expression.
foreach (var descendentNode in declarator.GetAncestor<BlockSyntax>().DescendantNodes())
{
var nodeSpanStart = descendentNode.SpanStart;
if (nodeSpanStart <= declaratorSpanStart)
{
// We haven't yet reached the declarator node.
continue;
}
if (nodeSpanStart >= switchStatementSpanStart)
{
// We've reached the switch statement.
break;
}
if (descendentNode.IsKind(SyntaxKind.IdentifierName, out IdentifierNameSyntax identifierName) &&
identifierName.Identifier.ValueText == symbolName &&
symbol.Equals(semanticModel.GetSymbolInfo(identifierName).Symbol))
{
// The variable is being used outside the switch statement.
return false;
}
}
// Safe to remove declarator node.
editor.RemoveNode(symbol.DeclaringSyntaxReferences[0].GetSyntax());
return true;
}
private StatementSyntax GetFinalStatement(
ExpressionSyntax switchExpression,
SyntaxTriviaList leadingTrivia,
......@@ -169,10 +94,8 @@ private StatementSyntax GenerateVariableDeclaration(ExpressionSyntax switchExpre
private SwitchExpressionArmSyntax GetSwitchExpressionArm(SwitchSectionSyntax node)
{
Debug.Assert(node.Labels.Count == 1);
return SwitchExpressionArm(
pattern: GetPattern(node.Labels[0], out var whenClauseOpt),
pattern: GetPattern(SingleOrDefaultSwitchLabel(node.Labels), out var whenClauseOpt),
whenClause: whenClauseOpt,
expression: RewriteStatements(node.Statements));
}
......@@ -201,11 +124,7 @@ private static PatternSyntax GetPattern(SwitchLabelSyntax switchLabel, out WhenC
public override ExpressionSyntax VisitAssignmentExpression(AssignmentExpressionSyntax node)
{
if (_assignmentTargetOpt == null)
{
_assignmentTargetOpt = node.Left;
}
_assignmentTargetOpt ??= node.Left;
return node.Right;
}
......@@ -221,29 +140,32 @@ public override ExpressionSyntax VisitSwitchStatement(SwitchStatementSyntax node
return RewriteSwitchStatement(node);
}
private static SwitchLabelSyntax SingleOrDefaultSwitchLabel(SyntaxList<SwitchLabelSyntax> labels)
{
return labels.Count == 1
? labels[0]
: labels.First(x => x.IsKind(SyntaxKind.DefaultSwitchLabel));
}
private ExpressionSyntax RewriteSwitchStatement(SwitchStatementSyntax node, bool allowMoveNextStatementToSwitchExpression = true)
{
var switchArms = node.Sections
// The default label must come last in the switch expression.
.OrderBy(section => section.Labels[0].IsKind(SyntaxKind.DefaultSwitchLabel))
.OrderBy(section => SingleOrDefaultSwitchLabel(section.Labels).IsKind(SyntaxKind.DefaultSwitchLabel))
.Select(s =>
(leadingTrivia: s.Labels[0].GetFirstToken().LeadingTrivia,
trailingTrivia: s.Statements[0].GetLastToken().TrailingTrivia,
(tokensForLeadingTrivia: new[] { s.Labels[0].GetFirstToken(), s.Labels[0].GetLastToken() },
tokensForTrailingTrivia: new[] { s.Statements[0].GetFirstToken(), s.Statements[0].GetLastToken() },
armExpression: GetSwitchExpressionArm(s)))
.ToList();
// This is possibly false only on the top-level switch statement.
// On nested nodes, if there's a subsequent statement, it is most definitely a
// "return" or "throw" which is already validated in the analysis phase.
if (allowMoveNextStatementToSwitchExpression)
{
var nextStatement = node.GetNextStatement();
if (nextStatement != null)
if (nextStatement.IsKind(SyntaxKind.ThrowStatement, SyntaxKind.ReturnStatement))
{
Debug.Assert(nextStatement.IsKind(SyntaxKind.ThrowStatement, SyntaxKind.ReturnStatement));
switchArms.Add(
(nextStatement.GetFirstToken().LeadingTrivia,
nextStatement.GetLastToken().TrailingTrivia,
(tokensForLeadingTrivia: new[] { nextStatement.GetFirstToken() },
tokensForTrailingTrivia: new[] { nextStatement.GetLastToken() },
SwitchExpressionArm(DiscardPattern(), Visit(nextStatement))));
}
}
......@@ -253,8 +175,8 @@ private ExpressionSyntax RewriteSwitchStatement(SwitchStatementSyntax node, bool
Token(leading: default, SyntaxKind.SwitchKeyword, node.CloseParenToken.TrailingTrivia),
Token(SyntaxKind.OpenBraceToken),
SeparatedList(
switchArms.Select(t => t.armExpression.WithLeadingTrivia(t.leadingTrivia)),
switchArms.Select(t => Token(leading: default, SyntaxKind.CommaToken, t.trailingTrivia))),
switchArms.Select(t => t.armExpression.WithLeadingTrivia(t.tokensForLeadingTrivia.GetTrivia().FilterComments(addElasticMarker: false))),
switchArms.Select(t => Token(SyntaxKind.CommaToken).WithTrailingTrivia(t.tokensForTrailingTrivia.GetTrivia().FilterComments(addElasticMarker: true)))),
Token(SyntaxKind.CloseBraceToken));
}
......
......@@ -15,8 +15,8 @@
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.ConvertSwitchStatementToExpression
{
......@@ -38,32 +38,39 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
return Task.CompletedTask;
}
protected override async Task FixAllAsync(Document document, ImmutableArray<Diagnostic> diagnostics, SyntaxEditor editor, CancellationToken cancellationToken)
protected override Task FixAllAsync(Document document, ImmutableArray<Diagnostic> diagnostics, SyntaxEditor editor, CancellationToken cancellationToken)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
using var spansDisposer = ArrayBuilder<TextSpan>.GetInstance(diagnostics.Length, out var spans);
foreach (var diagnostic in diagnostics)
{
cancellationToken.ThrowIfCancellationRequested();
var span = diagnostic.AdditionalLocations[0].SourceSpan;
if (spans.Any((s, nodeSpan) => s.Contains(nodeSpan), span))
var switchLocation = diagnostic.AdditionalLocations[0];
if (spans.Any((s, nodeSpan) => s.Contains(nodeSpan), switchLocation.SourceSpan))
{
// Skip nested switch expressions in case of a fix-all operation.
continue;
}
spans.Add(span);
spans.Add(switchLocation.SourceSpan);
var properties = diagnostic.Properties;
var nodeToGenerate = (SyntaxKind)int.Parse(properties[Constants.NodeToGenerateKey]);
var shouldRemoveNextStatement = bool.Parse(properties[Constants.ShouldRemoveNextStatementKey]);
var switchStatement = (SwitchStatementSyntax)editor.OriginalRoot.FindNode(span);
editor.ReplaceNode(switchStatement,
Rewriter.Rewrite(switchStatement, semanticModel, editor,
nodeToGenerate, shouldMoveNextStatementToSwitchExpression: shouldRemoveNextStatement)
.WithAdditionalAnnotations(Formatter.Annotation));
var declaratorToRemoveLocationOpt = diagnostic.AdditionalLocations.ElementAtOrDefault(1);
var switchStatement = (SwitchStatementSyntax)switchLocation.FindNode(cancellationToken);
var switchExpression = Rewriter.Rewrite(switchStatement, nodeToGenerate,
shouldMoveNextStatementToSwitchExpression: shouldRemoveNextStatement,
generateDeclaration: declaratorToRemoveLocationOpt is object);
editor.ReplaceNode(switchStatement, switchExpression.WithAdditionalAnnotations(Formatter.Annotation));
if (declaratorToRemoveLocationOpt is object)
{
editor.RemoveNode(declaratorToRemoveLocationOpt.FindNode(cancellationToken));
}
if (shouldRemoveNextStatement)
{
......@@ -73,6 +80,8 @@ protected override async Task FixAllAsync(Document document, ImmutableArray<Diag
editor.RemoveNode(nextStatement);
}
}
return Task.CompletedTask;
}
private sealed class MyCodeAction : CodeAction.DocumentChangeAction
......
......@@ -4,6 +4,7 @@
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.CSharp.ConvertSwitchStatementToExpression
{
......@@ -17,9 +18,70 @@ private Analyzer()
{
}
public static SyntaxKind Analyze(SwitchStatementSyntax node, out bool shouldRemoveNextStatement)
public static (SyntaxKind nodeToGenerate, VariableDeclaratorSyntax declaratorToRemoveOpt) Analyze(
SwitchStatementSyntax node,
SemanticModel semanticModel,
out bool shouldRemoveNextStatement)
{
return new Analyzer().AnalyzeSwitchStatement(node, out shouldRemoveNextStatement);
var analyzer = new Analyzer();
var nodeToGenerate = analyzer.AnalyzeSwitchStatement(node, out shouldRemoveNextStatement);
if (nodeToGenerate == SyntaxKind.SimpleAssignmentExpression &&
analyzer.TryGetVariableDeclaratorAndSymbol(semanticModel) is var (declarator, symbol))
{
if (shouldRemoveNextStatement &&
semanticModel.AnalyzeDataFlow(node.GetNextStatement()).DataFlowsIn.Contains(symbol))
{
// Bail out if data flows into the next statement that we want to move
// For example:
//
// string name = "";
// switch (index)
// {
// case 0: name = "0"; break;
// case 1: name = "1"; break;
// }
// throw new Exception(name);
//
return default;
}
var declaration = declarator.GetAncestor<StatementSyntax>();
if (declaration.Parent == node.Parent && declarator.Initializer is null)
{
var beforeSwitch = node.GetPreviousStatement() is StatementSyntax previousStatement
? semanticModel.AnalyzeDataFlow(declaration, previousStatement)
: semanticModel.AnalyzeDataFlow(declaration);
if (!beforeSwitch.WrittenInside.Contains(symbol))
{
// Move declarator only if it has no initializer and it's not used before switch
return (nodeToGenerate, declaratorToRemoveOpt: declarator);
}
}
}
return (nodeToGenerate, declaratorToRemoveOpt: null);
}
private (VariableDeclaratorSyntax, ISymbol)? TryGetVariableDeclaratorAndSymbol(SemanticModel semanticModel)
{
if (!_assignmentTargetOpt.IsKind(SyntaxKind.IdentifierName))
{
return null;
}
var symbol = semanticModel.GetSymbolInfo(_assignmentTargetOpt).Symbol;
if (!(symbol is { Kind: SymbolKind.Local, DeclaringSyntaxReferences: { Length: 1 } syntaxRefs }))
{
return null;
}
if (!(syntaxRefs[0].GetSyntax() is VariableDeclaratorSyntax declarator))
{
return null;
}
return (declarator, symbol);
}
private static bool IsDefaultSwitchLabel(SwitchLabelSyntax node)
......@@ -57,31 +119,31 @@ public override SyntaxKind VisitSwitchStatement(SwitchStatementSyntax node)
private SyntaxKind AnalyzeSwitchStatement(SwitchStatementSyntax switchStatement, out bool shouldRemoveNextStatement)
{
shouldRemoveNextStatement = false;
// Fail if the switch statement is empty or any of sections have more than one "case" label.
// Once we have "or" patterns, we can relax this to accept multi-case sections.
var sections = switchStatement.Sections;
if (sections.Count == 0 || sections.Any(section => section.Labels.Count != 1))
if (sections.Count == 0 || !sections.All(s => s.Labels.Count == 1 || s.Labels.Any(x => x.IsKind(SyntaxKind.DefaultSwitchLabel))))
{
shouldRemoveNextStatement = false;
return default;
}
// If there's no "default" case, we look at the next statement.
// For instance, it could be a "return" statement which we'll use
// as the default case in the switch expression.
var nextStatement = AnalyzeNextStatement(switchStatement, ref shouldRemoveNextStatement);
var nextStatement = AnalyzeNextStatement(switchStatement, out shouldRemoveNextStatement);
// We do need to intersect the next statement analysis result to catch possible
// arm kind mismatch, e.g. a "return" after a non-exhaustive assignment switch.
return Aggregate(nextStatement, sections, (result, section) => Intersect(result, AnalyzeSwitchSection(section)));
}
private SyntaxKind AnalyzeNextStatement(SwitchStatementSyntax switchStatement, ref bool shouldRemoveNextStatement)
private SyntaxKind AnalyzeNextStatement(SwitchStatementSyntax switchStatement, out bool shouldRemoveNextStatement)
{
if (switchStatement.Sections.Any(section => IsDefaultSwitchLabel(section.Labels[0])))
if (switchStatement.Sections.Any(section => section.Labels.Count > 1 || IsDefaultSwitchLabel(section.Labels[0])))
{
// Throw can be overridden by other section bodies, therefore it has no effect on the result.
shouldRemoveNextStatement = false;
return SyntaxKind.ThrowStatement;
}
......@@ -122,7 +184,7 @@ private SyntaxKind AnalyzeSwitchSection(SwitchSectionSyntax section)
switch (section.Statements.Count)
{
case 1:
case 2 when section.Statements[1].IsKind(SyntaxKind.BreakStatement):
case 2 when section.Statements[1].IsKind(SyntaxKind.BreakStatement) || section.Statements[0].IsKind(SyntaxKind.SwitchStatement):
return Visit(section.Statements[0]);
default:
return default;
......
......@@ -7,6 +7,7 @@
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
namespace Microsoft.CodeAnalysis.CSharp.ConvertSwitchStatementToExpression
{
......@@ -30,6 +31,11 @@ protected override void InitializeWorker(AnalysisContext context)
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
{
var switchStatement = context.Node;
if (switchStatement.ContainsDirectives)
{
return;
}
var syntaxTree = switchStatement.SyntaxTree;
if (((CSharpParseOptions)syntaxTree.Options).LanguageVersion < LanguageVersion.CSharp8)
......@@ -57,17 +63,25 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
return;
}
var nodeToGenerate = Analyzer.Analyze((SwitchStatementSyntax)switchStatement, out var shouldRemoveNextStatement);
var (nodeToGenerate, declaratorToRemoveOpt) =
Analyzer.Analyze(
(SwitchStatementSyntax)switchStatement,
context.SemanticModel,
out var shouldRemoveNextStatement);
if (nodeToGenerate == default)
{
return;
}
var additionalLocations = ArrayBuilder<Location>.GetInstance();
additionalLocations.Add(switchStatement.GetLocation());
additionalLocations.AddOptional(declaratorToRemoveOpt?.GetLocation());
context.ReportDiagnostic(DiagnosticHelper.Create(Descriptor,
// Report the diagnostic on the "switch" keyword.
location: switchStatement.GetFirstToken().GetLocation(),
effectiveSeverity: styleOption.Notification.Severity,
additionalLocations: new[] { switchStatement.GetLocation() },
additionalLocations: additionalLocations.ToArrayAndFree(),
properties: ImmutableDictionary<string, string>.Empty
.Add(Constants.NodeToGenerateKey, ((int)nodeToGenerate).ToString(CultureInfo.InvariantCulture))
.Add(Constants.ShouldRemoveNextStatementKey, shouldRemoveNextStatement.ToString(CultureInfo.InvariantCulture))));
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册