diff --git a/ChangeLog.md b/ChangeLog.md index 8250f35a9a..d182a2d73b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix analyzer [RCS1250](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1250) ([PR](https://github.com/dotnet/roslynator/pull/1652) by @aihnatiuk) - Fix analyzer [RCS1260](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1260) ([PR](https://github.com/dotnet/roslynator/pull/1668)) - Fix analyzer [RCS1105](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1105) ([PR](https://github.com/dotnet/roslynator/pull/1669)) +- Fix analyzer [RCS1260](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1260) ([PR](https://github.com/dotnet/roslynator/pull/1672)) ### Changed diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/AddOrRemoveTrailingCommaCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/AddOrRemoveTrailingCommaCodeFixProvider.cs index 5ac46f2749..96f1ef8609 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/AddOrRemoveTrailingCommaCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/AddOrRemoveTrailingCommaCodeFixProvider.cs @@ -31,15 +31,25 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) root, context.Span, out SyntaxNode node, - predicate: f => f.IsKind( - SyntaxKind.ArrayInitializerExpression, - SyntaxKind.ObjectInitializerExpression, - SyntaxKind.CollectionInitializerExpression, - SyntaxKind.EnumDeclaration, + predicate: f => + { + switch (f.Kind()) + { + case SyntaxKind.ArrayInitializerExpression: + case SyntaxKind.ObjectInitializerExpression: + case SyntaxKind.CollectionInitializerExpression: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.AnonymousObjectCreationExpression: + case SyntaxKind.SwitchExpression: + case SyntaxKind.PropertyPatternClause: #if ROSLYN_4_7 - SyntaxKind.CollectionExpression, + case SyntaxKind.CollectionExpression: #endif - SyntaxKind.AnonymousObjectCreationExpression))) + return true; + default: + return false; + } + })) { return; } @@ -72,8 +82,58 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix(codeAction, diagnostic); } } + else if (node is SwitchExpressionSyntax switchExpression) + { + SeparatedSyntaxList arms = switchExpression.Arms; + + int count = arms.Count; + + if (count == arms.SeparatorCount) + { + CodeAction codeAction = CodeAction.Create( + "Remove comma", + ct => RemoveTrailingComma(document, arms.GetSeparator(count - 1), ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + else + { + CodeAction codeAction = CodeAction.Create( + "Add comma", + ct => AddTrailingComma(document, arms.Last(), ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + } + else if (node is PropertyPatternClauseSyntax propertyPatternClause) + { + SeparatedSyntaxList subpatterns = propertyPatternClause.Subpatterns; + + int count = subpatterns.Count; + + if (count == subpatterns.SeparatorCount) + { + CodeAction codeAction = CodeAction.Create( + "Remove comma", + ct => RemoveTrailingComma(document, subpatterns.GetSeparator(count - 1), ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + else + { + CodeAction codeAction = CodeAction.Create( + "Add comma", + ct => AddTrailingComma(document, subpatterns.Last(), ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + } #if ROSLYN_4_7 - if (node is CollectionExpressionSyntax collectionExpression) + else if (node is CollectionExpressionSyntax collectionExpression) { SeparatedSyntaxList elements = collectionExpression.Elements; diff --git a/src/Analyzers/CSharp/Analysis/AddOrRemoveTrailingCommaAnalyzer.cs b/src/Analyzers/CSharp/Analysis/AddOrRemoveTrailingCommaAnalyzer.cs index 9ab9fc3e9a..0e39cece2e 100644 --- a/src/Analyzers/CSharp/Analysis/AddOrRemoveTrailingCommaAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/AddOrRemoveTrailingCommaAnalyzer.cs @@ -32,6 +32,8 @@ public override void Initialize(AnalysisContext context) context.RegisterSyntaxNodeAction(f => AnalyzeEnumDeclaration(f), SyntaxKind.EnumDeclaration); context.RegisterSyntaxNodeAction(f => AnalyzeAnonymousObjectCreationExpression(f), SyntaxKind.AnonymousObjectCreationExpression); + context.RegisterSyntaxNodeAction(f => AnalyzeSwitchExpression(f), SyntaxKind.SwitchExpression); + context.RegisterSyntaxNodeAction(f => AnalyzePropertyPatternClause(f), SyntaxKind.PropertyPatternClause); #if ROSLYN_4_7 context.RegisterSyntaxNodeAction(f => AnalyzeCollectionExpression(f), SyntaxKind.CollectionExpression); #endif @@ -172,6 +174,92 @@ private static void AnalyzeAnonymousObjectCreationExpression(SyntaxNodeAnalysisC } } + private static void AnalyzeSwitchExpression(SyntaxNodeAnalysisContext context) + { + TrailingCommaStyle style = context.GetTrailingCommaStyle(); + + if (style == TrailingCommaStyle.None) + return; + + var objectCreation = (SwitchExpressionSyntax)context.Node; + + SeparatedSyntaxList arms = objectCreation.Arms; + + if (!arms.Any()) + return; + + int count = arms.Count; + int separatorCount = arms.SeparatorCount; + + if (count == separatorCount) + { + if (style == TrailingCommaStyle.Omit) + { + ReportRemove(context, arms.GetSeparator(count - 1)); + } + else if (style == TrailingCommaStyle.OmitWhenSingleLine + && arms.IsSingleLine(cancellationToken: context.CancellationToken)) + { + ReportRemove(context, arms.GetSeparator(count - 1)); + } + } + else if (separatorCount == count - 1) + { + if (style == TrailingCommaStyle.Include) + { + ReportAdd(context, arms.Last()); + } + else if (style == TrailingCommaStyle.OmitWhenSingleLine + && !arms.IsSingleLine(cancellationToken: context.CancellationToken)) + { + ReportAdd(context, arms.Last()); + } + } + } + + private static void AnalyzePropertyPatternClause(SyntaxNodeAnalysisContext context) + { + TrailingCommaStyle style = context.GetTrailingCommaStyle(); + + if (style == TrailingCommaStyle.None) + return; + + var objectCreation = (PropertyPatternClauseSyntax)context.Node; + + SeparatedSyntaxList subpatterns = objectCreation.Subpatterns; + + if (!subpatterns.Any()) + return; + + int count = subpatterns.Count; + int separatorCount = subpatterns.SeparatorCount; + + if (count == separatorCount) + { + if (style == TrailingCommaStyle.Omit) + { + ReportRemove(context, subpatterns.GetSeparator(count - 1)); + } + else if (style == TrailingCommaStyle.OmitWhenSingleLine + && subpatterns.IsSingleLine(cancellationToken: context.CancellationToken)) + { + ReportRemove(context, subpatterns.GetSeparator(count - 1)); + } + } + else if (separatorCount == count - 1) + { + if (style == TrailingCommaStyle.Include) + { + ReportAdd(context, subpatterns.Last()); + } + else if (style == TrailingCommaStyle.OmitWhenSingleLine + && !subpatterns.IsSingleLine(cancellationToken: context.CancellationToken)) + { + ReportAdd(context, subpatterns.Last()); + } + } + } + #if ROSLYN_4_7 private static void AnalyzeCollectionExpression(SyntaxNodeAnalysisContext context) { diff --git a/src/Tests/Analyzers.Tests/RCS1260AddOrRemoveTrailingCommaTests.cs b/src/Tests/Analyzers.Tests/RCS1260AddOrRemoveTrailingCommaTests.cs index f479191234..3c23e93902 100644 --- a/src/Tests/Analyzers.Tests/RCS1260AddOrRemoveTrailingCommaTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1260AddOrRemoveTrailingCommaTests.cs @@ -330,6 +330,28 @@ void M() """, options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_OmitWhenSingleLine)); } + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddOrRemoveTrailingComma)] + public async Task Test_CollectionExpression_Include() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + void M() + { + int[] x = [1, 2, 3[||]]; + } +} +""", """ + class C + { + void M() + { + int[] x = [1, 2, 3,]; + } + } + """, options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_Include)); + } + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddOrRemoveTrailingComma)] public async Task Test_CollectionExpression_Omit() { @@ -373,4 +395,128 @@ void M() } """, options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_OmitWhenSingleLine)); } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddOrRemoveTrailingComma)] + public async Task Test_SwitchExpression_Include() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + void M(int p) + { + var x = p switch + { + 1 => "foo", + _ => "bar"[||] + }; + } +} +""", """ +class C +{ + void M(int p) + { + var x = p switch + { + 1 => "foo", + _ => "bar", + }; + } +} +""", options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_Include)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddOrRemoveTrailingComma)] + public async Task Test_SwitchExpression_Omit() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + void M(int p) + { + var x = p switch + { + 1 => "foo", + _ => "bar"[|,|] + }; + } +} +""", """ +class C +{ + void M(int p) + { + var x = p switch + { + 1 => "foo", + _ => "bar" + }; + } +} +""", options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_Omit)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddOrRemoveTrailingComma)] + public async Task Test_PatternMatching_Include() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + public int P1 { get; set; } + public int P2 { get; set; } + + void M(C p) + { + if (p is { P1: 1, P2: 2[||] }) + { + } + } +} +""", """ +class C +{ + public int P1 { get; set; } + public int P2 { get; set; } + + void M(C p) + { + if (p is { P1: 1, P2: 2, }) + { + } + } +} +""", options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_Include)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddOrRemoveTrailingComma)] + public async Task Test_PatternMatching_Omit() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + public int P1 { get; set; } + public int P2 { get; set; } + + void M(C p) + { + if (p is { P1: 1, P2: 2[|,|] }) + { + } + } +} +""", """ + class C + { + public int P1 { get; set; } + public int P2 { get; set; } + + void M(C p) + { + if (p is { P1: 1, P2: 2 }) + { + } + } + } + """, options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_Omit)); + } }