From 993271b28d48b7a5b14822d8c53ce46198cc9410 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Thu, 23 Feb 2023 17:21:54 +0100 Subject: [PATCH 1/5] Add more exceptions to test cases --- .../Rules/ClassShouldNotBeEmpty.cs | 20 +++++++++++-- .../SonarAnalyzer.Common/Helpers/KnownType.cs | 1 + .../Rules/ClassShouldNotBeEmptyBase.cs | 22 ++++++++++---- .../Rules/ClassShouldNotBeEmpty.cs | 20 +++++++++++-- .../Rules/ClassShouldNotBeEmptyTest.cs | 4 ++- .../ClassShouldNotBeEmpty.CSharp10.cs | 2 +- .../ClassShouldNotBeEmpty.CSharp9.cs | 2 +- .../ClassShouldNotBeEmpty.Inheritance.cs | 8 +++-- .../ClassShouldNotBeEmpty.Inheritance.vb | 23 +++++++++----- .../TestCases/ClassShouldNotBeEmpty.cs | 21 ++++++++++++- .../TestCases/ClassShouldNotBeEmpty.vb | 30 +++++++++++++++++-- 11 files changed, 129 insertions(+), 24 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs index 1dae0adc1d4..e6f687199a3 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs @@ -30,9 +30,25 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase x.IsKind(SyntaxKind.PartialKeyword)) && (node is ClassDeclarationSyntax || IsParameterlessRecord(node)); - protected override bool IsClassWithDeclaredBaseClass(SyntaxNode node) => node is ClassDeclarationSyntax { BaseList: not null }; + protected override bool IsClassWithDeclaredBaseClass(SyntaxNode node) => + node is ClassDeclarationSyntax { BaseList: not null }; - protected override string DeclarationTypeKeyword(SyntaxNode node) => ((TypeDeclarationSyntax)node).Keyword.ValueText; + protected override bool HasGenericBaseClassOrInterface(SyntaxNode node) => + node is ClassDeclarationSyntax { BaseList: not null } declaration + && declaration.BaseList.Types.Any(x => x.Type is GenericNameSyntax); + + protected override bool HasAnyAttribute(SyntaxNode node) => + node is TypeDeclarationSyntax { AttributeLists.Count: > 0 }; + + protected override string DeclarationTypeKeyword(SyntaxNode node) => + ((TypeDeclarationSyntax)node).Keyword.ValueText; + + protected override bool HasConditionalCompilationDirectives(SyntaxNode node) => + node.DescendantNodes(descendIntoTrivia: true).Any(x => x.IsAnyKind( + SyntaxKind.IfDirectiveTrivia, + SyntaxKind.ElifDirectiveTrivia, + SyntaxKind.ElseDirectiveTrivia, + SyntaxKind.EndIfDirectiveTrivia)); private bool IsParameterlessRecord(SyntaxNode node) => RecordDeclarationSyntaxWrapper.IsInstance(node) diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs index b0b254a4c2a..7a34af784c7 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs @@ -60,6 +60,7 @@ public sealed partial class KnownType public static readonly KnownType Microsoft_AspNetCore_Mvc_ControllerAttribute = new("Microsoft.AspNetCore.Mvc.ControllerAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_DisableRequestSizeLimitAttribute = new("Microsoft.AspNetCore.Mvc.DisableRequestSizeLimitAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_FromServicesAttribute = new("Microsoft.AspNetCore.Mvc.FromServicesAttribute"); + public static readonly KnownType Microsoft_AspNetCore_Mvc_IActionResult = new("Microsoft.AspNetCore.Mvc.IActionResult"); public static readonly KnownType Microsoft_AspNetCore_Mvc_IgnoreAntiforgeryTokenAttribute = new("Microsoft.AspNetCore.Mvc.IgnoreAntiforgeryTokenAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_NonActionAttribute = new("Microsoft.AspNetCore.Mvc.NonActionAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_NonControllerAttribute = new("Microsoft.AspNetCore.Mvc.NonControllerAttribute"); diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs index 95de276a89d..6ef20c7d917 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs @@ -27,13 +27,20 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn private static readonly ImmutableArray BaseClassesToIgnore = ImmutableArray.Create( KnownType.Microsoft_AspNetCore_Mvc_RazorPages_PageModel, + KnownType.System_Attribute, KnownType.System_Exception); + private static readonly ImmutableArray InterfacesToIgnore = ImmutableArray.Create( + KnownType.Microsoft_AspNetCore_Mvc_IActionResult); + protected abstract bool IsEmptyAndNotPartial(SyntaxNode node); protected abstract bool IsClassWithDeclaredBaseClass(SyntaxNode node); + protected abstract bool HasGenericBaseClassOrInterface(SyntaxNode node); + protected abstract bool HasAnyAttribute(SyntaxNode node); protected abstract string DeclarationTypeKeyword(SyntaxNode node); + protected abstract bool HasConditionalCompilationDirectives(SyntaxNode node); - protected override string MessageFormat => "Remove this empty {0}, or add members to it."; + protected override string MessageFormat => "Remove this empty {0}, write its code or make it an \"interface\"."; protected ClassShouldNotBeEmptyBase() : base(DiagnosticId) { } @@ -44,15 +51,20 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn { if (Language.Syntax.NodeIdentifier(c.Node) is { IsMissing: false } identifier && IsEmptyAndNotPartial(c.Node) - && !ShouldIgnoreBecauseOfBaseClass(c.Node, c.SemanticModel)) + && !HasAnyAttribute(c.Node) + && !HasConditionalCompilationDirectives(c.Node) + && !ShouldIgnoreBecauseOfBaseClassOrInterface(c.Node, c.SemanticModel)) { c.ReportIssue(Diagnostic.Create(Rule, identifier.GetLocation(), DeclarationTypeKeyword(c.Node))); } }, Language.SyntaxKind.ClassAndRecordClassDeclarations); - private bool ShouldIgnoreBecauseOfBaseClass(SyntaxNode node, SemanticModel model) => + private bool ShouldIgnoreBecauseOfBaseClassOrInterface(SyntaxNode node, SemanticModel model) => IsClassWithDeclaredBaseClass(node) - && model.GetDeclaredSymbol(node) is INamedTypeSymbol classSymbol - && (classSymbol.BaseType is { IsAbstract: true } || classSymbol.DerivesFromAny(BaseClassesToIgnore)); + && (HasGenericBaseClassOrInterface(node) + || (model.GetDeclaredSymbol(node) is INamedTypeSymbol classSymbol + && (classSymbol.BaseType is { IsAbstract: true } + || classSymbol.DerivesFromAny(BaseClassesToIgnore) + || classSymbol.ImplementsAny(InterfacesToIgnore)))); } diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs index 931ae271056..761d7bcf595 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs @@ -29,7 +29,23 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase x.IsKind(SyntaxKind.PartialKeyword)); - protected override bool IsClassWithDeclaredBaseClass(SyntaxNode node) => node is ClassBlockSyntax { Inherits.Count: > 0 }; + protected override bool IsClassWithDeclaredBaseClass(SyntaxNode node) => + node is ClassBlockSyntax { Inherits.Count: > 0 }; - protected override string DeclarationTypeKeyword(SyntaxNode node) => ((TypeBlockSyntax)node).BlockStatement.DeclarationKeyword.ValueText.ToLower(); + protected override bool HasGenericBaseClassOrInterface(SyntaxNode node) => + node is ClassBlockSyntax { Inherits.Count: > 0 } classBlock + && classBlock.Inherits.Any(x => x.Types.Any(t => t is GenericNameSyntax)); + + protected override bool HasAnyAttribute(SyntaxNode node) => + node is ClassBlockSyntax { ClassStatement.AttributeLists.Count: > 0 }; + + protected override bool HasConditionalCompilationDirectives(SyntaxNode node) => + node.DescendantNodes(descendIntoTrivia: true).Any(x => x.IsAnyKind( + SyntaxKind.IfDirectiveTrivia, + SyntaxKind.ElseIfDirectiveTrivia, + SyntaxKind.ElseDirectiveTrivia, + SyntaxKind.EndIfDirectiveTrivia)); + + protected override string DeclarationTypeKeyword(SyntaxNode node) => + ((TypeBlockSyntax)node).BlockStatement.DeclarationKeyword.ValueText.ToLower(); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs index e5c2643502a..fb7699daef8 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs @@ -45,7 +45,9 @@ public class ClassShouldNotBeEmptyTest private static readonly MetadataReference[] AdditionalReferences = new[] { - AspNetCoreMetadataReference.MicrosoftAspNetCoreRazorPages + AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions, + AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, + AspNetCoreMetadataReference.MicrosoftAspNetCoreRazorPages, }; [TestMethod] diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs index 1f2342b1612..02fd205d0ae 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs @@ -1,5 +1,5 @@  -record class EmptyRecordClass1(); // Noncompliant {{Remove this empty record, or add members to it.}} +record class EmptyRecordClass1(); // Noncompliant {{Remove this empty record, write its code or make it an "interface".}} // ^^^^^^^^^^^^^^^^^ record class EmptyRecordClass2() { }; // Noncompliant diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs index dbdd5228c73..7383f176f20 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs @@ -1,6 +1,6 @@ using System; -record EmptyRecord1(); // Noncompliant {{Remove this empty record, or add members to it.}} +record EmptyRecord1(); // Noncompliant {{Remove this empty record, write its code or make it an "interface".}} // ^^^^^^^^^^^^ record EmptyRecord2() { }; // Noncompliant diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs index c1753d38de1..3896e880e1a 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; using System; class BaseClass @@ -18,6 +19,9 @@ abstract class AbstractBaseWithoutAbstractMethods class NoImplementation: AbstractBaseWithAbstractMethods { } // Error - abstract methods should be implemented class DefaultImplementation: AbstractBaseWithoutAbstractMethods { } // Compliant - the class will use the default implementation of DefaultMethod -class EmptyPageModel: PageModel { } // Compliant - an empty PageModel can be fully functional, the C# code can be in the cshtml file class CustomException: Exception { } // Compliant - empty exception classes are allowed, the name of the class already provides information +class CustomAttribute: Attribute { } // Compliant - empty attribute classes are allowed, the name of the class already provides information + +class EmptyPageModel: PageModel { } // Compliant - an empty PageModel can be fully functional, the C# code can be in the cshtml file +class CustomActionResult: ActionResult { } // Compliant - an empty action result can still provide information by its name diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb index 60cf17b6482..c28782f9c07 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb @@ -1,4 +1,5 @@ -Imports Microsoft.AspNetCore.Mvc.RazorPages +Imports Microsoft.AspNetCore.Mvc +Imports Microsoft.AspNetCore.Mvc.RazorPages Imports System Public Class BaseClass @@ -9,7 +10,7 @@ Public Class BaseClass End Property End Class -Public Class SubClass ' Noncompliant - not derived from any special base class +Public Class SubClass ' Noncompliant - not derived from any special base class Inherits BaseClass End Class @@ -22,18 +23,26 @@ MustInherit Class AbstractBaseWithoutAbstractMethods End Sub End Class -Class NoImplementation ' Error - abstract methods should be implemented +Class NoImplementation ' Error - abstract methods should be implemented Inherits AbstractBaseWithAbstractMethods End Class -Class DefaultImplementation ' Compliant - the class will use the default implementation of DefaultMethod +Class DefaultImplementation ' Compliant - the class will use the default implementation of DefaultMethod Inherits AbstractBaseWithoutAbstractMethods End Class -Public Class EmptyPageModel ' Compliant - an empty PageModel can be fully functional, the VB code can be in the vbhtml file - Inherits PageModel +Public Class CustomException ' Compliant - empty exception classes are allowed, the name of the class already provides information + Inherits Exception End Class -Public Class CustomException ' Compliant - empty exception classes are allowed, the name of the class already provides information +Public Class CustomAttribute ' Compliant - empty attribute classes are allowed, the name of the class already provides information Inherits Exception End Class + +Public Class EmptyPageModel ' Compliant - an empty PageModel can be fully functional, the VB code can be in the vbhtml file + Inherits PageModel +End Class + +Public Class CustomActionResult ' Compliant - an empty action result can still provide information by its name + Inherits ActionResult +End Class diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs index 493dbc28ec3..940ae272e6e 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; -class Empty { } // Noncompliant {{Remove this empty class, or add members to it.}} +class Empty { } // Noncompliant {{Remove this empty class, write its code or make it an "interface".}} // ^^^^^ public class PublicEmpty { } // Noncompliant @@ -73,6 +75,13 @@ class GenericNotEmptyWithConstraints void Method(T arg) { } } +class IntegerList: List { } // Compliant - creates a more specific type without adding new members to it (similar to using the typedef keyword in C/C++) +class StringLookup: Dictionary { } +interface IIntegerSet: ISet { } + +[ComVisible(true)] +class ClassWithAttribute { } // Compliant - types with attributes are ignored + static class StaticEmpty { } // Noncompliant abstract class AbstractEmpty { } // Noncompliant @@ -90,5 +99,15 @@ struct EmptyStruct { } // Compliant - this rule only deals enum EmptyEnum { } // Compliant - this rule only deals with classes +class Conditional // Compliant - it's not empty when the given symbol is defined +{ +#if NOTDEFINED + public override string ToString() + { + return "Debug Text"; + } +#endif +} + class { } // Error diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb index d6221105cf5..2bc1ff1627e 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb @@ -1,6 +1,8 @@ Imports System +Imports System.Collections.Generic +Imports System.Runtime.InteropServices -Class Empty ' Noncompliant {{Remove this empty class, or add members to it.}} +Class Empty ' Noncompliant {{Remove this empty class, write its code or make it an "interface".}} ' ^^^^^ End Class @@ -97,6 +99,22 @@ Class GenericNotEmptyWithConstraints(Of T As Class) End Sub End Class +Class IntegerList ' Compliant - creates a more specific type without adding new members to it (similar to using the typedef keyword in C/C++) + Inherits List(Of Integer) +End Class + +Class StringLookup(Of T) + Inherits Dictionary(Of String, T) +End Class + +Interface IIntegerSet(Of T) + Inherits ISet(Of Integer) +End Interface + + +Class ClassWithAttribute ' Compliant - types with attributes are ignored +End Class + MustInherit Class AbstractEmpty ' Noncompliant End Class @@ -117,5 +135,13 @@ End Interface Structure EmptyStruct ' Compliant - this rule only deals with classes End Structure +Class Conditional ' Compliant - it's not empty when the given symbol is defined +#If NOTDEFINED Then + Public Overrides Function ToString() As String + Return "Debug Text" + End Function +#End If +End Class + Class ' Error -End Class +End Class From 4ad40426faf6af65ae531063295e3187b378fa8d Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Thu, 23 Feb 2023 17:32:59 +0100 Subject: [PATCH 2/5] Update Sarif files --- ...97-5AFD-4813-AEDC-AF33FACEADF0}-S2094.json | 2 +- .../its/expected/Net5/Net5--net5.0-S2094.json | 14 ++++----- .../its/expected/Net7/Net7--net7.0-S2094.json | 2 +- .../akka.net/Akka--netstandard2.0-S2094.json | 30 +++++++++---------- .../Akka.Benchmarks--netcoreapp3.1-S2094.json | 4 +-- .../Akka.Cluster--netstandard2.0-S2094.json | 4 +-- ...Akka.DI.TestKit--netstandard2.0-S2094.json | 8 ++--- ...kka.MultiNodeTestRunner--net471-S2094.json | 4 +-- ...kka.MultiNodeTestRunner--net5.0-S2094.json | 4 +-- ...tiNodeTestRunner--netcoreapp3.1-S2094.json | 4 +-- ...stRunner.Shared--netstandard2.0-S2094.json | 29 +++++------------- .../Akka.Remote--netstandard2.0-S2094.json | 22 +++++++------- ...ests.Performance--netcoreapp3.1-S2094.json | 2 +- .../Akka.Streams--netstandard2.0-S2094.json | 8 ++--- .../RemotePingPong--net471-S2094.json | 2 +- .../RemotePingPong--net5.0-S2094.json | 2 +- .../RemotePingPong--netcoreapp3.1-S2094.json | 2 +- ...hoService.Server--netcoreapp3.1-S2094.json | 2 +- 18 files changed, 66 insertions(+), 79 deletions(-) diff --git a/analyzers/its/expected/Ember-MM/Ember.Plugins-{9496C697-5AFD-4813-AEDC-AF33FACEADF0}-S2094.json b/analyzers/its/expected/Ember-MM/Ember.Plugins-{9496C697-5AFD-4813-AEDC-AF33FACEADF0}-S2094.json index 0ac2683eee3..45dbbc1ebc5 100644 --- a/analyzers/its/expected/Ember-MM/Ember.Plugins-{9496C697-5AFD-4813-AEDC-AF33FACEADF0}-S2094.json +++ b/analyzers/its/expected/Ember-MM/Ember.Plugins-{9496C697-5AFD-4813-AEDC-AF33FACEADF0}-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\Ember-MM\Ember.Plugins\PluginActionContext.cs", "region": { diff --git a/analyzers/its/expected/Net5/Net5--net5.0-S2094.json b/analyzers/its/expected/Net5/Net5--net5.0-S2094.json index 808fb732c6d..6410000afd1 100644 --- a/analyzers/its/expected/Net5/Net5--net5.0-S2094.json +++ b/analyzers/its/expected/Net5/Net5--net5.0-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\Net5\Net5\ModuleInitializers.cs", "region": { @@ -15,7 +15,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\Net5\Net5\ParameterValidation.cs", "region": { @@ -28,7 +28,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\Net5\Net5\S2330.cs", "region": { @@ -41,7 +41,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\Net5\Net5\S3240.cs", "region": { @@ -54,7 +54,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\Net5\Net5\S3247.cs", "region": { @@ -67,7 +67,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\Net5\Net5\S3453.cs", "region": { @@ -80,7 +80,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\Net5\Net5\StaticLambdas.cs", "region": { diff --git a/analyzers/its/expected/Net7/Net7--net7.0-S2094.json b/analyzers/its/expected/Net7/Net7--net7.0-S2094.json index 289b4af880f..9315f036e86 100644 --- a/analyzers/its/expected/Net7/Net7--net7.0-S2094.json +++ b/analyzers/its/expected/Net7/Net7--net7.0-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\Net7\Net7\features\WarningWave7.cs", "region": { diff --git a/analyzers/its/expected/akka.net/Akka--netstandard2.0-S2094.json b/analyzers/its/expected/akka.net/Akka--netstandard2.0-S2094.json index 6370b12ad44..b8fd5dbc010 100644 --- a/analyzers/its/expected/akka.net/Akka--netstandard2.0-S2094.json +++ b/analyzers/its/expected/akka.net/Akka--netstandard2.0-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka\Actor\ActorSelection.cs", "region": { @@ -15,7 +15,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka\Actor\FSM.cs", "region": { @@ -28,7 +28,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka\Event\LoggerInitialized.cs", "region": { @@ -41,7 +41,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka\Event\Logging.cs", "region": { @@ -54,7 +54,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka\IO\Dns.cs", "region": { @@ -67,7 +67,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka\IO\Inet.cs", "region": { @@ -80,7 +80,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka\IO\Tcp.cs", "region": { @@ -93,7 +93,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka\IO\Tcp.cs", "region": { @@ -106,7 +106,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka\IO\Tcp.cs", "region": { @@ -119,7 +119,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka\IO\Udp.cs", "region": { @@ -132,7 +132,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka\IO\Udp.cs", "region": { @@ -145,7 +145,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka\IO\UdpConnected.cs", "region": { @@ -158,7 +158,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka\Pattern\BackoffOptions.cs", "region": { @@ -171,7 +171,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka\Routing\Listeners.cs", "region": { @@ -184,7 +184,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka\Routing\RouterMsg.cs", "region": { diff --git a/analyzers/its/expected/akka.net/Akka.Benchmarks--netcoreapp3.1-S2094.json b/analyzers/its/expected/akka.net/Akka.Benchmarks--netcoreapp3.1-S2094.json index 025ab6cbded..77c88c4e851 100644 --- a/analyzers/its/expected/akka.net/Akka.Benchmarks--netcoreapp3.1-S2094.json +++ b/analyzers/its/expected/akka.net/Akka.Benchmarks--netcoreapp3.1-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\benchmark\Akka.Benchmarks\IO\TcpOperationsBenchmarks.cs", "region": { @@ -15,7 +15,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\benchmark\Akka.Benchmarks\IO\TcpOperationsBenchmarks.cs", "region": { diff --git a/analyzers/its/expected/akka.net/Akka.Cluster--netstandard2.0-S2094.json b/analyzers/its/expected/akka.net/Akka.Cluster--netstandard2.0-S2094.json index 7e3af656072..aac4b747412 100644 --- a/analyzers/its/expected/akka.net/Akka.Cluster--netstandard2.0-S2094.json +++ b/analyzers/its/expected/akka.net/Akka.Cluster--netstandard2.0-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Cluster\ClusterDaemon.cs", "region": { @@ -15,7 +15,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Cluster\ClusterHeartbeat.cs", "region": { diff --git a/analyzers/its/expected/akka.net/Akka.DI.TestKit--netstandard2.0-S2094.json b/analyzers/its/expected/akka.net/Akka.DI.TestKit--netstandard2.0-S2094.json index 8bbc0bb2e33..f0fd59ab4d8 100644 --- a/analyzers/its/expected/akka.net/Akka.DI.TestKit--netstandard2.0-S2094.json +++ b/analyzers/its/expected/akka.net/Akka.DI.TestKit--netstandard2.0-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\contrib\dependencyinjection\Akka.DI.TestKit\DiResolverSpec.cs", "region": { @@ -15,7 +15,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\contrib\dependencyinjection\Akka.DI.TestKit\DiResolverSpec.cs", "region": { @@ -28,7 +28,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\contrib\dependencyinjection\Akka.DI.TestKit\DiResolverSpec.cs", "region": { @@ -41,7 +41,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\contrib\dependencyinjection\Akka.DI.TestKit\DiResolverSpec.cs", "region": { diff --git a/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--net471-S2094.json b/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--net471-S2094.json index 3c7716e2695..cc98d74023d 100644 --- a/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--net471-S2094.json +++ b/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--net471-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner\Program.cs", "region": { @@ -15,7 +15,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner\Program.cs", "region": { diff --git a/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--net5.0-S2094.json b/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--net5.0-S2094.json index 3c7716e2695..cc98d74023d 100644 --- a/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--net5.0-S2094.json +++ b/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--net5.0-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner\Program.cs", "region": { @@ -15,7 +15,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner\Program.cs", "region": { diff --git a/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--netcoreapp3.1-S2094.json b/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--netcoreapp3.1-S2094.json index 3c7716e2695..cc98d74023d 100644 --- a/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--netcoreapp3.1-S2094.json +++ b/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--netcoreapp3.1-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner\Program.cs", "region": { @@ -15,7 +15,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner\Program.cs", "region": { diff --git a/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner.Shared--netstandard2.0-S2094.json b/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner.Shared--netstandard2.0-S2094.json index 42a6a955478..8ebac24a9d7 100644 --- a/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner.Shared--netstandard2.0-S2094.json +++ b/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner.Shared--netstandard2.0-S2094.json @@ -2,20 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\CompilerErrorCollection.cs", -"region": { -"startLine": 14, -"startColumn": 18, -"endLine": 14, -"endColumn": 41 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Reporting\TestRunCoordinator.cs", "region": { @@ -28,7 +15,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Sinks\Messages.cs", "region": { @@ -41,7 +28,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Sinks\MessageSinkActor.cs", "region": { @@ -54,7 +41,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Sinks\SinkCoordinator.cs", "region": { @@ -67,7 +54,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Sinks\SinkCoordinator.cs", "region": { @@ -80,7 +67,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Sinks\SinkCoordinator.cs", "region": { @@ -93,7 +80,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Sinks\TimelineLogCollectorActor.cs", "region": { @@ -106,7 +93,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Sinks\TimelineLogCollectorActor.cs", "region": { diff --git a/analyzers/its/expected/akka.net/Akka.Remote--netstandard2.0-S2094.json b/analyzers/its/expected/akka.net/Akka.Remote--netstandard2.0-S2094.json index 5ecaa3af7c5..627fcfd752e 100644 --- a/analyzers/its/expected/akka.net/Akka.Remote--netstandard2.0-S2094.json +++ b/analyzers/its/expected/akka.net/Akka.Remote--netstandard2.0-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Remote\Endpoint.cs", "region": { @@ -15,7 +15,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Remote\Endpoint.cs", "region": { @@ -28,7 +28,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Remote\EndpointManager.cs", "region": { @@ -41,7 +41,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Remote\EndpointManager.cs", "region": { @@ -54,7 +54,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Remote\Transport\AkkaPduCodec.cs", "region": { @@ -67,7 +67,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Remote\Transport\AkkaProtocolTransport.cs", "region": { @@ -80,7 +80,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Remote\Transport\AkkaProtocolTransport.cs", "region": { @@ -93,7 +93,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Remote\Transport\AkkaProtocolTransport.cs", "region": { @@ -106,7 +106,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Remote\Transport\AkkaProtocolTransport.cs", "region": { @@ -119,7 +119,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Remote\Transport\TestTransport.cs", "region": { @@ -132,7 +132,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Remote\Transport\ThrottleTransportAdapter.cs", "region": { diff --git a/analyzers/its/expected/akka.net/Akka.Remote.Tests.Performance--netcoreapp3.1-S2094.json b/analyzers/its/expected/akka.net/Akka.Remote.Tests.Performance--netcoreapp3.1-S2094.json index 76790ed6bfc..f4479e9850a 100644 --- a/analyzers/its/expected/akka.net/Akka.Remote.Tests.Performance--netcoreapp3.1-S2094.json +++ b/analyzers/its/expected/akka.net/Akka.Remote.Tests.Performance--netcoreapp3.1-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Remote.Tests.Performance\Transports\RemoteMessagingThroughputSpecBase.cs", "region": { diff --git a/analyzers/its/expected/akka.net/Akka.Streams--netstandard2.0-S2094.json b/analyzers/its/expected/akka.net/Akka.Streams--netstandard2.0-S2094.json index cea40b0ba52..1aed34bc8d5 100644 --- a/analyzers/its/expected/akka.net/Akka.Streams--netstandard2.0-S2094.json +++ b/analyzers/its/expected/akka.net/Akka.Streams--netstandard2.0-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Streams\Attributes.cs", "region": { @@ -15,7 +15,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Streams\Attributes.cs", "region": { @@ -28,7 +28,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Streams\Attributes.cs", "region": { @@ -41,7 +41,7 @@ }, { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\core\Akka.Streams\Stage\Context.cs", "region": { diff --git a/analyzers/its/expected/akka.net/RemotePingPong--net471-S2094.json b/analyzers/its/expected/akka.net/RemotePingPong--net471-S2094.json index 19fa046d379..c626699ce26 100644 --- a/analyzers/its/expected/akka.net/RemotePingPong--net471-S2094.json +++ b/analyzers/its/expected/akka.net/RemotePingPong--net471-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\benchmark\RemotePingPong\Program.cs", "region": { diff --git a/analyzers/its/expected/akka.net/RemotePingPong--net5.0-S2094.json b/analyzers/its/expected/akka.net/RemotePingPong--net5.0-S2094.json index 19fa046d379..c626699ce26 100644 --- a/analyzers/its/expected/akka.net/RemotePingPong--net5.0-S2094.json +++ b/analyzers/its/expected/akka.net/RemotePingPong--net5.0-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\benchmark\RemotePingPong\Program.cs", "region": { diff --git a/analyzers/its/expected/akka.net/RemotePingPong--netcoreapp3.1-S2094.json b/analyzers/its/expected/akka.net/RemotePingPong--netcoreapp3.1-S2094.json index 19fa046d379..c626699ce26 100644 --- a/analyzers/its/expected/akka.net/RemotePingPong--netcoreapp3.1-S2094.json +++ b/analyzers/its/expected/akka.net/RemotePingPong--netcoreapp3.1-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\benchmark\RemotePingPong\Program.cs", "region": { diff --git a/analyzers/its/expected/akka.net/TcpEchoService.Server--netcoreapp3.1-S2094.json b/analyzers/its/expected/akka.net/TcpEchoService.Server--netcoreapp3.1-S2094.json index 0c7c2328513..6b95eb1b717 100644 --- a/analyzers/its/expected/akka.net/TcpEchoService.Server--netcoreapp3.1-S2094.json +++ b/analyzers/its/expected/akka.net/TcpEchoService.Server--netcoreapp3.1-S2094.json @@ -2,7 +2,7 @@ "issues": [ { "id": "S2094", -"message": "Remove this empty class, or add members to it.", +"message": "Remove this empty class, write its code or make it an "interface".", "location": { "uri": "sources\akka.net\src\examples\TcpEchoService.Server\Actors.cs", "region": { From 11a7f5243b3f6bec9ca36706e01228621b715a5c Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Fri, 24 Feb 2023 08:01:54 +0100 Subject: [PATCH 3/5] Add more test cases refactor base class --- analyzers/rspec/cs/S2094_c#.html | 7 ++++--- analyzers/rspec/vbnet/S2094_vb.net.html | 7 ++++--- .../Rules/ClassShouldNotBeEmptyBase.cs | 12 +++++++----- .../TestCases/ClassShouldNotBeEmpty.cs | 6 ++++++ .../TestCases/ClassShouldNotBeEmpty.vb | 8 ++++++++ 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/analyzers/rspec/cs/S2094_c#.html b/analyzers/rspec/cs/S2094_c#.html index 369f2453587..61d730a79e8 100644 --- a/analyzers/rspec/cs/S2094_c#.html +++ b/analyzers/rspec/cs/S2094_c#.html @@ -14,9 +14,10 @@

Compliant Solution

}

Exceptions

-

Partial classes are ignored entirely, as they are often used with Source Generators. Subclasses of System.Exception are be ignored, as even an -empty Exception class can provide useful information by its type name alone. Subclasses of certain framework types - like the PageModel class used in -ASP.NET Core Razor Pages - are also be ignored.

+

Partial classes are ignored entirely, as they are often used with Source Generators. Subclasses of System.Exception are ignored, as even an empty +Exception class can provide useful information by its type name alone. Subclasses of System.Attribute are ignored, as well as classes which are +annotated with attributes. Subclasses of generic classes are ignored, as even when empty they can be used for type specialization. Subclasses of +certain framework types - like the PageModel class used in ASP.NET Core Razor Pages - are also be ignored.

 using Microsoft.AspNetCore.Mvc.RazorPages;
 
diff --git a/analyzers/rspec/vbnet/S2094_vb.net.html b/analyzers/rspec/vbnet/S2094_vb.net.html
index dd073431b49..7397e35a5fb 100644
--- a/analyzers/rspec/vbnet/S2094_vb.net.html
+++ b/analyzers/rspec/vbnet/S2094_vb.net.html
@@ -14,9 +14,10 @@ 

Compliant Solution

End Interface

Exceptions

-

Partial classes are ignored entirely, as they are often used with Source Generators. Subclasses of System.Exception will be ignored, as even an -empty Exception class can provide useful information by its type name alone. Subclasses of certain framework types - like the PageModel class used in -ASP.NET Core Razor Pages - will also be ignored.

+

Partial classes are ignored entirely, as they are often used with Source Generators. Subclasses of System.Exception are ignored, as even an empty +Exception class can provide useful information by its type name alone. Subclasses of System.Attribute are ignored, as well as classes which are +annotated with attributes. Subclasses of generic classes are ignored, as even when empty they can be used for type specialization. Subclasses of +certain framework types - like the PageModel class used in ASP.NET Core Razor Pages - are also be ignored.

 Imports Microsoft.AspNetCore.Mvc.RazorPages
 
diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
index 6ef20c7d917..9ec6961c4e8 100644
--- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
@@ -62,9 +62,11 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn
 
     private bool ShouldIgnoreBecauseOfBaseClassOrInterface(SyntaxNode node, SemanticModel model) =>
         IsClassWithDeclaredBaseClass(node)
-        && (HasGenericBaseClassOrInterface(node)
-            || (model.GetDeclaredSymbol(node) is INamedTypeSymbol classSymbol
-                && (classSymbol.BaseType is { IsAbstract: true }
-                || classSymbol.DerivesFromAny(BaseClassesToIgnore)
-                || classSymbol.ImplementsAny(InterfacesToIgnore))));
+        && (HasGenericBaseClassOrInterface(node) || ShouldIgnoreType(node, model));
+
+    private static bool ShouldIgnoreType(SyntaxNode node, SemanticModel model) =>
+        model.GetDeclaredSymbol(node) is INamedTypeSymbol classSymbol
+            && (classSymbol.BaseType is { IsAbstract: true }
+            || classSymbol.DerivesFromAny(BaseClassesToIgnore)
+            || classSymbol.ImplementsAny(InterfacesToIgnore));
 }
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
index 940ae272e6e..c842c307146 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
@@ -82,6 +82,12 @@ interface IIntegerSet: ISet { }
 [ComVisible(true)]
 class ClassWithAttribute { }                 // Compliant - types with attributes are ignored
 
+[ComVisible(true), Obsolete]
+class ClassWithMultipleAttributes { }
+
+[]                                           // Error
+class AttributeError { }
+
 static class StaticEmpty { }                 // Noncompliant
 
 abstract class AbstractEmpty { }             // Noncompliant
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
index 2bc1ff1627e..6a7845a4431 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
@@ -115,6 +115,14 @@ End Interface
 Class ClassWithAttribute                            ' Compliant - types with attributes are ignored
 End Class
 
+
+Class ClassWithMultipleAttributes
+End Class
+
+<>                                                  ' Error
+Class AttributeError                                ' Noncompliant
+End Class
+
 MustInherit Class AbstractEmpty                     ' Noncompliant
 End Class
 

From 77cf181661eb4d23f05894d9cdc273f228128499 Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Fri, 24 Feb 2023 09:53:16 +0100
Subject: [PATCH 4/5] Update RSPEC

---
 analyzers/rspec/cs/S2094_c#.html        | 2 +-
 analyzers/rspec/vbnet/S2094_vb.net.html | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/analyzers/rspec/cs/S2094_c#.html b/analyzers/rspec/cs/S2094_c#.html
index 61d730a79e8..dcee0efaa5c 100644
--- a/analyzers/rspec/cs/S2094_c#.html
+++ b/analyzers/rspec/cs/S2094_c#.html
@@ -17,7 +17,7 @@ 

Exceptions

Partial classes are ignored entirely, as they are often used with Source Generators. Subclasses of System.Exception are ignored, as even an empty Exception class can provide useful information by its type name alone. Subclasses of System.Attribute are ignored, as well as classes which are annotated with attributes. Subclasses of generic classes are ignored, as even when empty they can be used for type specialization. Subclasses of -certain framework types - like the PageModel class used in ASP.NET Core Razor Pages - are also be ignored.

+certain framework types - like the PageModel class used in ASP.NET Core Razor Pages - are also ignored.

 using Microsoft.AspNetCore.Mvc.RazorPages;
 
diff --git a/analyzers/rspec/vbnet/S2094_vb.net.html b/analyzers/rspec/vbnet/S2094_vb.net.html
index 7397e35a5fb..7f83fc3a456 100644
--- a/analyzers/rspec/vbnet/S2094_vb.net.html
+++ b/analyzers/rspec/vbnet/S2094_vb.net.html
@@ -17,7 +17,7 @@ 

Exceptions

Partial classes are ignored entirely, as they are often used with Source Generators. Subclasses of System.Exception are ignored, as even an empty Exception class can provide useful information by its type name alone. Subclasses of System.Attribute are ignored, as well as classes which are annotated with attributes. Subclasses of generic classes are ignored, as even when empty they can be used for type specialization. Subclasses of -certain framework types - like the PageModel class used in ASP.NET Core Razor Pages - are also be ignored.

+certain framework types - like the PageModel class used in ASP.NET Core Razor Pages - are also ignored.

 Imports Microsoft.AspNetCore.Mvc.RazorPages
 

From d5b1d894fb7540566d5f151658fcc45b447f06a4 Mon Sep 17 00:00:00 2001
From: Andrei Epure 
Date: Fri, 24 Feb 2023 11:28:11 +0100
Subject: [PATCH 5/5] refactor to reduce duplication

---
 .../Rules/ClassShouldNotBeEmpty.cs                  | 13 +++++++------
 .../Rules/ClassShouldNotBeEmptyBase.cs              | 13 +++++++------
 .../Rules/ClassShouldNotBeEmpty.cs                  | 13 +++++++------
 3 files changed, 21 insertions(+), 18 deletions(-)

diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs
index e6f687199a3..70fe3c4b6d6 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs
@@ -21,7 +21,7 @@
 namespace SonarAnalyzer.Rules.CSharp;
 
 [DiagnosticAnalyzer(LanguageNames.CSharp)]
-public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase
+public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase
 {
     protected override ILanguageFacade Language => CSharpFacade.Instance;
 
@@ -30,12 +30,13 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase x.IsKind(SyntaxKind.PartialKeyword))
         && (node is ClassDeclarationSyntax || IsParameterlessRecord(node));
 
-    protected override bool IsClassWithDeclaredBaseClass(SyntaxNode node) =>
-        node is ClassDeclarationSyntax { BaseList: not null };
-
-    protected override bool HasGenericBaseClassOrInterface(SyntaxNode node) =>
+    protected override BaseTypeDeclarationSyntax GetIfHasDeclaredBaseClass(SyntaxNode node) =>
         node is ClassDeclarationSyntax { BaseList: not null } declaration
-        && declaration.BaseList.Types.Any(x => x.Type is GenericNameSyntax);
+            ? declaration
+            : null;
+
+    protected override bool HasGenericBaseClassOrInterface(BaseTypeDeclarationSyntax declaration) =>
+        declaration.BaseList.Types.Any(x => x.Type is GenericNameSyntax);
 
     protected override bool HasAnyAttribute(SyntaxNode node) =>
         node is TypeDeclarationSyntax { AttributeLists.Count: > 0  };
diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
index 9ec6961c4e8..aba8dd35454 100644
--- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
@@ -20,8 +20,9 @@
 
 namespace SonarAnalyzer.Rules;
 
-public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAnalyzer
+public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAnalyzer
     where TSyntaxKind : struct
+    where TDeclarationSyntax: SyntaxNode
 {
     private const string DiagnosticId = "S2094";
 
@@ -34,8 +35,8 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn
         KnownType.Microsoft_AspNetCore_Mvc_IActionResult);
 
     protected abstract bool IsEmptyAndNotPartial(SyntaxNode node);
-    protected abstract bool IsClassWithDeclaredBaseClass(SyntaxNode node);
-    protected abstract bool HasGenericBaseClassOrInterface(SyntaxNode node);
+    protected abstract TDeclarationSyntax GetIfHasDeclaredBaseClass(SyntaxNode node);
+    protected abstract bool HasGenericBaseClassOrInterface(TDeclarationSyntax declaration);
     protected abstract bool HasAnyAttribute(SyntaxNode node);
     protected abstract string DeclarationTypeKeyword(SyntaxNode node);
     protected abstract bool HasConditionalCompilationDirectives(SyntaxNode node);
@@ -61,10 +62,10 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn
             Language.SyntaxKind.ClassAndRecordClassDeclarations);
 
     private bool ShouldIgnoreBecauseOfBaseClassOrInterface(SyntaxNode node, SemanticModel model) =>
-        IsClassWithDeclaredBaseClass(node)
-        && (HasGenericBaseClassOrInterface(node) || ShouldIgnoreType(node, model));
+        GetIfHasDeclaredBaseClass(node) is { } declaration
+        && (HasGenericBaseClassOrInterface(declaration) || ShouldIgnoreType(declaration, model));
 
-    private static bool ShouldIgnoreType(SyntaxNode node, SemanticModel model) =>
+    private static bool ShouldIgnoreType(TDeclarationSyntax node, SemanticModel model) =>
         model.GetDeclaredSymbol(node) is INamedTypeSymbol classSymbol
             && (classSymbol.BaseType is { IsAbstract: true }
             || classSymbol.DerivesFromAny(BaseClassesToIgnore)
diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs
index 761d7bcf595..1bff2c9f1bb 100644
--- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs
@@ -21,7 +21,7 @@
 namespace SonarAnalyzer.Rules.VisualBasic;
 
 [DiagnosticAnalyzer(LanguageNames.VisualBasic)]
-public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase
+public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase
 {
     protected override ILanguageFacade Language => VisualBasicFacade.Instance;
 
@@ -29,12 +29,13 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase x.IsKind(SyntaxKind.PartialKeyword));
 
-    protected override bool IsClassWithDeclaredBaseClass(SyntaxNode node) =>
-        node is ClassBlockSyntax { Inherits.Count: > 0 };
-
-    protected override bool HasGenericBaseClassOrInterface(SyntaxNode node) =>
+    protected override TypeBlockSyntax GetIfHasDeclaredBaseClass(SyntaxNode node) =>
         node is ClassBlockSyntax { Inherits.Count: > 0 } classBlock
-        && classBlock.Inherits.Any(x => x.Types.Any(t => t is GenericNameSyntax));
+            ? classBlock
+            : null;
+
+    protected override bool HasGenericBaseClassOrInterface(TypeBlockSyntax declaration) =>
+        declaration.Inherits.Any(x => x.Types.Any(t => t is GenericNameSyntax));
 
     protected override bool HasAnyAttribute(SyntaxNode node) =>
         node is ClassBlockSyntax { ClassStatement.AttributeLists.Count: > 0 };