diff --git a/analyzers/its/expected/akka.net/S2094-Akka-netstandard2.0.json b/analyzers/its/expected/akka.net/S2094-Akka-netstandard2.0.json index 1f32fa13081..29210fba7e8 100644 --- a/analyzers/its/expected/akka.net/S2094-Akka-netstandard2.0.json +++ b/analyzers/its/expected/akka.net/S2094-Akka-netstandard2.0.json @@ -23,24 +23,6 @@ "Message": "Remove this empty class, write its code or make it an \u0022interface\u0022.", "Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/akka.net/src/core/Akka/IO/Inet.cs#L229", "Location": "Line 229 Position 31-43" - }, - { - "Id": "S2094", - "Message": "Remove this empty class, write its code or make it an \u0022interface\u0022.", - "Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/akka.net/src/core/Akka/IO/Tcp.cs#L738", - "Location": "Line 738 Position 22-27" - }, - { - "Id": "S2094", - "Message": "Remove this empty class, write its code or make it an \u0022interface\u0022.", - "Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/akka.net/src/core/Akka/Routing/Listeners.cs#L37", - "Location": "Line 37 Position 27-42" - }, - { - "Id": "S2094", - "Message": "Remove this empty class, write its code or make it an \u0022interface\u0022.", - "Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/akka.net/src/core/Akka/Routing/RouterMsg.cs#L31", - "Location": "Line 31 Position 27-50" } ] } \ No newline at end of file diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs index b5396cd597a..302bc0457ee 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs @@ -31,6 +31,8 @@ public abstract class ClassShouldNotBeEmptyBase KnownType.System_Attribute, KnownType.System_Exception); + private static readonly IEnumerable IgnoredSuffixes = ["Command", "Event", "Message"]; + protected abstract bool IsEmptyAndNotPartial(SyntaxNode node); protected abstract TDeclarationSyntax GetIfHasDeclaredBaseClassOrInterface(SyntaxNode node); protected abstract bool HasInterfaceOrGenericBaseClass(TDeclarationSyntax declaration); @@ -51,6 +53,7 @@ protected override void Initialize(SonarAnalysisContext context) => && IsEmptyAndNotPartial(c.Node) && !HasAnyAttribute(c.Node) && !HasConditionalCompilationDirectives(c.Node) + && !ShouldIgnoreBecauseOfName(identifier) && !ShouldIgnoreBecauseOfBaseClassOrInterface(c.Node, c.SemanticModel)) { c.ReportIssue(Diagnostic.Create(Rule, identifier.GetLocation(), DeclarationTypeKeyword(c.Node))); @@ -58,6 +61,9 @@ protected override void Initialize(SonarAnalysisContext context) => }, Language.SyntaxKind.ClassAndRecordClassDeclarations); + private static bool ShouldIgnoreBecauseOfName(SyntaxToken identifier) => + IgnoredSuffixes.Any(identifier.ValueText.EndsWith); + private bool ShouldIgnoreBecauseOfBaseClassOrInterface(SyntaxNode node, SemanticModel model) => GetIfHasDeclaredBaseClassOrInterface(node) is { } declaration && (HasInterfaceOrGenericBaseClass(declaration) || ShouldIgnoreType(declaration, model)); diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/ClassShouldNotBeEmpty.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/ClassShouldNotBeEmpty.cs index 5d419cc37f4..f754380dbd5 100644 --- a/analyzers/tests/SonarAnalyzer.Test/TestCases/ClassShouldNotBeEmpty.cs +++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/ClassShouldNotBeEmpty.cs @@ -126,4 +126,16 @@ class ImplementsMarker: IMarker { } // Compliant - implements a marker struct EmptyStruct { } // Compliant - this rule only deals with classes enum EmptyEnum { } // Compliant - this rule only deals with classes + + class SomeCommand { } // Compliant, ignored because of the suffix + class SomeEvent { } // Compliant, ignored because of the suffix + class SomeMessage { } // Compliant, ignored because of the suffix + class Some_Command { } // Compliant, ignored because of the suffix + class Someevent { } // Noncompliant + class SOMEMESSAGE { } // Noncompliant + class SomeCommandHandler { } // Noncompliant + class MessageHandler { } // Noncompliant + class Command { } // Compliant, ignored because of the suffix + class Event { } // Compliant, ignored because of the suffix + class Message { } // Compliant, ignored because of the suffix }