diff --git a/ChangeLog.md b/ChangeLog.md index fbb395e715..0d668ec561 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Replace type declaration's empty braces with semicolon ([RCS1251](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1251) ([PR](https://github.com/dotnet/roslynator/pull/1323)) + ## [4.7.0] - 2023-12-03 ### Added diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/RecordDeclarationCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/RemoveUnnecessaryBracesCodeFixProvider.cs similarity index 58% rename from src/Analyzers.CodeFixes/CSharp/CodeFixes/RecordDeclarationCodeFixProvider.cs rename to src/Analyzers.CodeFixes/CSharp/CodeFixes/RemoveUnnecessaryBracesCodeFixProvider.cs index 28c7c831e8..a346892a34 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/RecordDeclarationCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/RemoveUnnecessaryBracesCodeFixProvider.cs @@ -13,9 +13,9 @@ namespace Roslynator.CSharp.CodeFixes; -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(RecordDeclarationCodeFixProvider))] +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(RemoveUnnecessaryBracesCodeFixProvider))] [Shared] -public class RecordDeclarationCodeFixProvider : BaseCodeFixProvider +public class RemoveUnnecessaryBracesCodeFixProvider : BaseCodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds { @@ -29,7 +29,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) if (!TryFindFirstAncestorOrSelf( root, context.Span, - out RecordDeclarationSyntax recordDeclaration)) + out TypeDeclarationSyntax typeDeclaration)) { return; } @@ -41,21 +41,13 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) "Remove unnecessary braces", ct => { - RecordDeclarationSyntax newRecordDeclaration = recordDeclaration.Update( - recordDeclaration.AttributeLists, - recordDeclaration.Modifiers, - recordDeclaration.Keyword, - recordDeclaration.Identifier, - recordDeclaration.TypeParameterList, - recordDeclaration.ParameterList.WithoutTrailingTrivia(), - recordDeclaration.BaseList, - recordDeclaration.ConstraintClauses, - default, - default, - default, - Token(SyntaxKind.SemicolonToken)); + TypeDeclarationSyntax newTypeDeclaration = typeDeclaration + .WithOpenBraceToken(default) + .WithCloseBraceToken(default) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) + .WithTrailingTrivia(typeDeclaration.GetTrailingTrivia()); - return document.ReplaceNodeAsync(recordDeclaration, newRecordDeclaration, ct); + return document.ReplaceNodeAsync(typeDeclaration, newTypeDeclaration, ct); }, GetEquivalenceKey(diagnostic)); diff --git a/src/Analyzers/CSharp/Analysis/RemoveUnnecessaryBracesAnalyzer.cs b/src/Analyzers/CSharp/Analysis/RemoveUnnecessaryBracesAnalyzer.cs index 54b3c4a3d6..dc1cf09c95 100644 --- a/src/Analyzers/CSharp/Analysis/RemoveUnnecessaryBracesAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/RemoveUnnecessaryBracesAnalyzer.cs @@ -28,7 +28,19 @@ public override void Initialize(AnalysisContext context) { base.Initialize(context); - context.RegisterSyntaxNodeAction(f => AnalyzeRecordDeclaration(f), SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration); + context.RegisterSyntaxNodeAction(c => AnalyzeRecordDeclaration(c), SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration); + + context.RegisterCompilationStartAction(startContext => + { + if ((int)((CSharpCompilation)startContext.Compilation).LanguageVersion >= 1200) + { + startContext.RegisterSyntaxNodeAction( + c => AnalyzeTypeDeclaration(c), + SyntaxKind.ClassDeclaration, + SyntaxKind.StructDeclaration, + SyntaxKind.InterfaceDeclaration); + } + }); } private static void AnalyzeRecordDeclaration(SyntaxNodeAnalysisContext context) @@ -38,24 +50,40 @@ private static void AnalyzeRecordDeclaration(SyntaxNodeAnalysisContext context) if (!recordDeclaration.Members.Any() && recordDeclaration.ParameterList is not null) { - SyntaxToken openBrace = recordDeclaration.OpenBraceToken; + Analyze(context, recordDeclaration); + } + } + + private static void Analyze(SyntaxNodeAnalysisContext context, TypeDeclarationSyntax typeDeclaration) + { + SyntaxToken openBrace = typeDeclaration.OpenBraceToken; + + if (!openBrace.IsKind(SyntaxKind.None)) + { + SyntaxToken closeBrace = typeDeclaration.CloseBraceToken; - if (!openBrace.IsKind(SyntaxKind.None)) + if (!closeBrace.IsKind(SyntaxKind.None) + && openBrace.TrailingTrivia.IsEmptyOrWhitespace() + && closeBrace.LeadingTrivia.IsEmptyOrWhitespace() + && typeDeclaration.ParameterList?.CloseParenToken.TrailingTrivia.IsEmptyOrWhitespace() != false) { - SyntaxToken closeBrace = recordDeclaration.CloseBraceToken; - - if (!closeBrace.IsKind(SyntaxKind.None) - && openBrace.TrailingTrivia.IsEmptyOrWhitespace() - && closeBrace.LeadingTrivia.IsEmptyOrWhitespace() - && recordDeclaration.ParameterList?.CloseParenToken.TrailingTrivia.IsEmptyOrWhitespace() != false) - { - DiagnosticHelpers.ReportDiagnostic( - context, - DiagnosticRules.RemoveUnnecessaryBraces, - openBrace.GetLocation(), - additionalLocations: new Location[] { closeBrace.GetLocation() }); - } + DiagnosticHelpers.ReportDiagnostic( + context, + DiagnosticRules.RemoveUnnecessaryBraces, + openBrace.GetLocation(), + additionalLocations: new Location[] { closeBrace.GetLocation() }); } } } + + private static void AnalyzeTypeDeclaration(SyntaxNodeAnalysisContext context) + { + var typeDeclaration = (TypeDeclarationSyntax)context.Node; + + if (!typeDeclaration.Members.Any() + && typeDeclaration.ParameterList is null) + { + Analyze(context, typeDeclaration); + } + } } diff --git a/src/Tests/Analyzers.Tests/RCS1251RemoveUnnecessaryBracesTests.cs b/src/Tests/Analyzers.Tests/RCS1251RemoveUnnecessaryBracesTests.cs index 751021f181..9a231acaff 100644 --- a/src/Tests/Analyzers.Tests/RCS1251RemoveUnnecessaryBracesTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1251RemoveUnnecessaryBracesTests.cs @@ -8,8 +8,7 @@ namespace Roslynator.CSharp.Analysis.Tests; -//TODO: remove double diagnostic (https://github.com/dotnet/roslyn/issues/53136) -public class RCS1251RemoveUnnecessaryBracesTests : AbstractCSharpDiagnosticVerifier +public class RCS1251RemoveUnnecessaryBracesTests : AbstractCSharpDiagnosticVerifier { public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.RemoveUnnecessaryBraces; @@ -24,14 +23,14 @@ record R(string Value) } } -namespace System.Runtime.CompilerServices { internal static class IsExternalInit {} } +namespace System.Runtime.CompilerServices { internal static class IsExternalInit; } ", @" namespace N { record R(string Value); } -namespace System.Runtime.CompilerServices { internal static class IsExternalInit {} } +namespace System.Runtime.CompilerServices { internal static class IsExternalInit; } "); } @@ -46,17 +45,84 @@ record struct R(string Value) } } -namespace System.Runtime.CompilerServices { internal static class IsExternalInit {} } +namespace System.Runtime.CompilerServices { internal static class IsExternalInit; } ", @" namespace N { record struct R(string Value); } -namespace System.Runtime.CompilerServices { internal static class IsExternalInit {} } +namespace System.Runtime.CompilerServices { internal static class IsExternalInit; } "); } + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveUnnecessaryBraces)] + public async Task Test_Class() + { + await VerifyDiagnosticAndFixAsync(@" +namespace N +{ + class C + [|{|] + } +} +", @" +namespace N +{ + class C; +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveUnnecessaryBraces)] + public async Task Test_Struct() + { + await VerifyDiagnosticAndFixAsync(@" +namespace N +{ + struct C + [|{|] + } +} +", @" +namespace N +{ + struct C; +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveUnnecessaryBraces)] + public async Task Test_Interface() + { + await VerifyDiagnosticAndFixAsync(@" +namespace N +{ + interface C + [|{|] + } +} +", @" +namespace N +{ + interface C; +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveUnnecessaryBraces)] + public async Task Test_Class_CSharp11() + { + await VerifyNoDiagnosticAsync(@" +namespace N +{ + class C + { + } +} +", options: WellKnownCSharpTestOptions.Default_CSharp11); + } + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveUnnecessaryBraces)] public async Task Test_NoDiagnostic() {