From 0fea5f96bf3d9da7033e4c36a0d9c6b6780672e9 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Fri, 10 Feb 2023 08:55:08 +0100 Subject: [PATCH 01/37] Initial scaffolding --- analyzers/rspec/cs/S2094_c#.html | 16 ++++++++ analyzers/rspec/cs/S2094_c#.json | 17 ++++++++ analyzers/rspec/cs/Sonar_way_profile.json | 1 + analyzers/rspec/vbnet/S2094_vb.net.html | 16 ++++++++ analyzers/rspec/vbnet/S2094_vb.net.json | 17 ++++++++ analyzers/rspec/vbnet/Sonar_way_profile.json | 1 + .../Rules/ClassShouldNotBeEmpty.cs | 39 ++++++++++++++++++ .../Rules/ClassShouldNotBeEmptyBase.cs | 31 ++++++++++++++ .../Rules/ClassShouldNotBeEmpty.cs | 39 ++++++++++++++++++ .../PackagingTests/RuleTypeMappingCS.cs | 2 +- .../PackagingTests/RuleTypeMappingVB.cs | 2 +- .../Rules/ClassShouldNotBeEmptyTest.cs | 41 +++++++++++++++++++ .../TestCases/ClassShouldNotBeEmpty.cs | 5 +++ .../TestCases/ClassShouldNotBeEmpty.vb | 7 ++++ 14 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 analyzers/rspec/cs/S2094_c#.html create mode 100644 analyzers/rspec/cs/S2094_c#.json create mode 100644 analyzers/rspec/vbnet/S2094_vb.net.html create mode 100644 analyzers/rspec/vbnet/S2094_vb.net.json create mode 100644 analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs create mode 100644 analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs create mode 100644 analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb diff --git a/analyzers/rspec/cs/S2094_c#.html b/analyzers/rspec/cs/S2094_c#.html new file mode 100644 index 00000000000..e611be58832 --- /dev/null +++ b/analyzers/rspec/cs/S2094_c#.html @@ -0,0 +1,16 @@ +

There is no good excuse for an empty class. If it’s being used simply as a common extension point, it should be replaced with an +interface. If it was stubbed in as a placeholder for future development it should be fleshed-out. In any other case, it should be +eliminated.

+

Noncompliant Code Example

+
+public class Empty // Noncompliant
+{
+}
+
+

Compliant Solution

+
+public interface IEmpty
+{
+}
+
+ diff --git a/analyzers/rspec/cs/S2094_c#.json b/analyzers/rspec/cs/S2094_c#.json new file mode 100644 index 00000000000..78db5e8197d --- /dev/null +++ b/analyzers/rspec/cs/S2094_c#.json @@ -0,0 +1,17 @@ +{ + "title": "Classes should not be empty", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "clumsy" + ], + "defaultSeverity": "Minor", + "ruleSpecification": "RSPEC-2094", + "sqKey": "S2094", + "scope": "All", + "quickfix": "unknown" +} diff --git a/analyzers/rspec/cs/Sonar_way_profile.json b/analyzers/rspec/cs/Sonar_way_profile.json index 82393f4f8fe..d53f965a41d 100644 --- a/analyzers/rspec/cs/Sonar_way_profile.json +++ b/analyzers/rspec/cs/Sonar_way_profile.json @@ -59,6 +59,7 @@ "S2068", "S2077", "S2092", + "S2094", "S2114", "S2115", "S2123", diff --git a/analyzers/rspec/vbnet/S2094_vb.net.html b/analyzers/rspec/vbnet/S2094_vb.net.html new file mode 100644 index 00000000000..49ccfc9495f --- /dev/null +++ b/analyzers/rspec/vbnet/S2094_vb.net.html @@ -0,0 +1,16 @@ +

There is no good excuse for an empty class. If it’s being used simply as a common extension point, it should be replaced with an +interface. If it was stubbed in as a placeholder for future development it should be fleshed-out. In any other case, it should be +eliminated.

+

Noncompliant Code Example

+
+Public Class Empty ' Noncompliant
+
+End Class
+
+

Compliant Solution

+
+Public Interface IEmpty
+
+End Interface
+
+ diff --git a/analyzers/rspec/vbnet/S2094_vb.net.json b/analyzers/rspec/vbnet/S2094_vb.net.json new file mode 100644 index 00000000000..78db5e8197d --- /dev/null +++ b/analyzers/rspec/vbnet/S2094_vb.net.json @@ -0,0 +1,17 @@ +{ + "title": "Classes should not be empty", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "clumsy" + ], + "defaultSeverity": "Minor", + "ruleSpecification": "RSPEC-2094", + "sqKey": "S2094", + "scope": "All", + "quickfix": "unknown" +} diff --git a/analyzers/rspec/vbnet/Sonar_way_profile.json b/analyzers/rspec/vbnet/Sonar_way_profile.json index e6a352709de..2935043a4f2 100644 --- a/analyzers/rspec/vbnet/Sonar_way_profile.json +++ b/analyzers/rspec/vbnet/Sonar_way_profile.json @@ -38,6 +38,7 @@ "S1940", "S2068", "S2077", + "S2094", "S2166", "S2178", "S2222", diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs new file mode 100644 index 00000000000..295f2fd2acc --- /dev/null +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs @@ -0,0 +1,39 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace SonarAnalyzer.Rules.CSharp; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase +{ + + protected override ILanguageFacade Language => CSharpFacade.Instance; + + protected override void Initialize(SonarAnalysisContext context) => + context.RegisterNodeAction(c => + { + var node = c.Node; + if (true) + { + c.ReportIssue(Diagnostic.Create(Rule, node.GetLocation())); + } + }, + SyntaxKind.InvocationExpression); +} diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs new file mode 100644 index 00000000000..651303fee80 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs @@ -0,0 +1,31 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace SonarAnalyzer.Rules; + +public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAnalyzer + where TSyntaxKind : struct +{ + private const string DiagnosticId = "S2094"; + + protected override string MessageFormat => "FIXME"; + + protected ClassShouldNotBeEmptyBase() : base(DiagnosticId) { } +} diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs new file mode 100644 index 00000000000..2feb6e5ca19 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs @@ -0,0 +1,39 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace SonarAnalyzer.Rules.VisualBasic; + +[DiagnosticAnalyzer(LanguageNames.VisualBasic)] +public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase +{ + + protected override ILanguageFacade Language => VisualBasicFacade.Instance; + + protected override void Initialize(SonarAnalysisContext context) => + context.RegisterNodeAction(c => + { + var node = c.Node; + if (true) + { + c.ReportIssue(Diagnostic.Create(Rule, node.GetLocation())); + } + }, + SyntaxKind.InvocationExpression); +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs index b51630272dd..7cf659d06dc 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs @@ -2018,7 +2018,7 @@ internal static class RuleTypeMappingCS // ["S2091"], ["S2092"] = "SECURITY_HOTSPOT", // ["S2093"], - // ["S2094"], + ["S2094"] = "CODE_SMELL", // ["S2095"], // ["S2096"], // ["S2097"], diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs index 25791bb1cfd..43bb49da7ac 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs @@ -2018,7 +2018,7 @@ internal static class RuleTypeMappingVB // ["S2091"], // ["S2092"], // ["S2093"], - // ["S2094"], + ["S2094"] = "CODE_SMELL", // ["S2095"], // ["S2096"], // ["S2097"], diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs new file mode 100644 index 00000000000..2261f909ecd --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs @@ -0,0 +1,41 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +using CS = SonarAnalyzer.Rules.CSharp; +using VB = SonarAnalyzer.Rules.VisualBasic; + +namespace SonarAnalyzer.UnitTest.Rules; + +[TestClass] +public class ClassShouldNotBeEmptyTest +{ + private readonly VerifierBuilder builderCS = new VerifierBuilder(); + + [TestMethod] + public void ClassShouldNotBeEmpty_CS() => + builderCS.AddPaths("ClassShouldNotBeEmpty.cs").Verify(); + + private readonly VerifierBuilder builderVB = new VerifierBuilder(); // FIXME: Move this up + + [TestMethod] + public void ClassShouldNotBeEmpty_VB() => + builderVB.AddPaths("ClassShouldNotBeEmpty.vb").Verify(); +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs new file mode 100644 index 00000000000..a790d6d1f92 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs @@ -0,0 +1,5 @@ +using System; + +public class Program +{ +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb new file mode 100644 index 00000000000..f5b9e9276c6 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb @@ -0,0 +1,7 @@ +Public Class Program + + Public Sub Test() + + End Sub + +End Class From 30a73840cbc03d9d533d354985e4de65123ed33d Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Mon, 13 Feb 2023 11:31:58 +0100 Subject: [PATCH 02/37] Add more C# test cases --- .../Rules/ClassShouldNotBeEmptyTest.cs | 14 ++++- .../ClassShouldNotBeEmpty.CSharp10.cs | 19 +++++++ .../ClassShouldNotBeEmpty.CSharp9.cs | 10 ++++ .../TestCases/ClassShouldNotBeEmpty.cs | 54 ++++++++++++++++++- 4 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs index 2261f909ecd..de697e8d248 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ - using CS = SonarAnalyzer.Rules.CSharp; using VB = SonarAnalyzer.Rules.VisualBasic; @@ -28,12 +27,23 @@ namespace SonarAnalyzer.UnitTest.Rules; public class ClassShouldNotBeEmptyTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); + private readonly VerifierBuilder builderVB = new VerifierBuilder(); // FIXME: Move this up [TestMethod] public void ClassShouldNotBeEmpty_CS() => builderCS.AddPaths("ClassShouldNotBeEmpty.cs").Verify(); - private readonly VerifierBuilder builderVB = new VerifierBuilder(); // FIXME: Move this up +#if NET + + [TestMethod] + public void ClassShouldNotBeEmpty_CSharp9() => + builderCS.AddPaths("ClassShouldNotBeEmpty.CSharp9.cs").WithOptions(ParseOptionsHelper.FromCSharp9).Verify(); + + [TestMethod] + public void ClassShouldNotBeEmpty_CSharp10() => + builderCS.AddPaths("ClassShouldNotBeEmpty.CSharp10.cs").WithOptions(ParseOptionsHelper.FromCSharp10).Verify(); + +#endif [TestMethod] public void ClassShouldNotBeEmpty_VB() => diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs new file mode 100644 index 00000000000..4d169aca74d --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs @@ -0,0 +1,19 @@ + +record class EmptyRecordClass1(); // Noncompliant +// ^^^^^^^^^^^^^^^^^ +record class EmptyRecordClass2() { }; // Noncompliant + +record struct EmptyRecordStruct1(); // Compliant - this rule only deals with classes +record struct EmptyRecordStruct2() { }; + +record class NotEmptyRecordClass1(int RecordMember); +record class NotEmptyRecordClass2() +{ + int RecordMember => 0; +}; + +record struct NotEmptyRecordStruct1(int RecordMember); +record struct NotEmptyRecordStruct2() +{ + int RecordMember => 0; +}; diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs new file mode 100644 index 00000000000..6a0f4efb50f --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs @@ -0,0 +1,10 @@ + +record EmptyRecord1(); // Noncompliant +// ^^^^^^^^^^^^ +record EmptyRecord2() { }; // Noncompliant + +record NotEmptyRecord1(int RecordMember); +record NotEmptyRecord2() +{ + int RecordMember => 0; +}; diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs index a790d6d1f92..be10493efa9 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs @@ -1,5 +1,57 @@ using System; -public class Program +class Empty { } // Noncompliant {{Remove this empty class, write its code or make it an "interface".}} +// ^^^^^ + +public class PublicEmpty { } // Noncompliant +internal class InternalEmpty { } // Noncompliant + +class EmptyWithComments // Noncompliant +{ + // Some comment +} + +class NotEmpty { + public int SomeProperty => 0; } + +class OuterClass +{ + class InnerEmpty1 { } // Noncompliant + private class InnerEmpty2 { } // Noncompliant + protected class InnerEmpty3 { } // Noncompliant + internal class InnerEmpty4 { } // Noncompliant + protected internal class InnerEmpty5 { } // Noncompliant + public class InnerEmpty6 { } // Noncompliant + + public class InnerEmptyWithComments // Noncompliant + { + // Some comment + } + + class InnerNonEmpty + { + public int SomeProperty => 0; + } +} + +static class StaticEmpty { } // Noncompliant + +partial class PartialEmpty { } // Noncompliant +partial class PartialEmpty +{ + public int SomeProperty => 0; +} + +interface IMarker { } // Compliant - using marker interfaces vs. attributes is a topic of debate, but they are commonly used + +struct EmptyStruct { } // Compliant - this rule only deals with classes + +enum EmptyEnum { } // Compliant - this rule only deals with classes + +class { } // Error + + +// Razor Pages: PageModel + From 6f8ab44b0a2cc862ff65cfe6d89c963fb94ab772 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Mon, 13 Feb 2023 11:33:07 +0100 Subject: [PATCH 03/37] Rename ClassAndRecordDeclaration --- .../Facade/CSharpSyntaxKindFacade.cs | 2 +- .../Rules/ClassShouldNotBeEmpty.cs | 15 ++++----------- .../Facade/ISyntaxKindFacade.cs | 2 +- .../Rules/ClassNotInstantiatableBase.cs | 2 +- .../Rules/ClassShouldNotBeEmptyBase.cs | 17 ++++++++++++++++- .../Rules/ExceptionsShouldBePublicBase.cs | 2 +- .../Facade/VisualBasicSyntaxKindFacade.cs | 2 +- .../Rules/ClassShouldNotBeEmpty.cs | 12 +----------- 8 files changed, 26 insertions(+), 28 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs index 4262139bf3f..0c3bc40a7e5 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs @@ -24,7 +24,7 @@ internal sealed class CSharpSyntaxKindFacade : ISyntaxKindFacade { public SyntaxKind Attribute => SyntaxKind.Attribute; public SyntaxKind ClassDeclaration => SyntaxKind.ClassDeclaration; - public SyntaxKind[] ClassAndRecordDeclaration => new[] + public SyntaxKind[] ClassAndRecordClassDeclaration => new[] { SyntaxKind.ClassDeclaration, SyntaxKindEx.RecordClassDeclaration, diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs index 295f2fd2acc..63048b5672b 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs @@ -23,17 +23,10 @@ namespace SonarAnalyzer.Rules.CSharp; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase { - protected override ILanguageFacade Language => CSharpFacade.Instance; - protected override void Initialize(SonarAnalysisContext context) => - context.RegisterNodeAction(c => - { - var node = c.Node; - if (true) - { - c.ReportIssue(Diagnostic.Create(Rule, node.GetLocation())); - } - }, - SyntaxKind.InvocationExpression); + protected override bool IsRecordClassWithEmptyTypeList(SyntaxNode node) => + RecordDeclarationSyntaxWrapper.IsInstance(node) + && !((RecordDeclarationSyntaxWrapper)node).ParameterList.Parameters.Any() + && node.ChildNodes().All(x => x is ParameterListSyntax); } diff --git a/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs b/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs index 807708293b4..c54e0a9ed54 100644 --- a/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs +++ b/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs @@ -25,7 +25,7 @@ public interface ISyntaxKindFacade { abstract TSyntaxKind Attribute { get; } abstract TSyntaxKind ClassDeclaration { get; } - abstract TSyntaxKind[] ClassAndRecordDeclaration { get; } + abstract TSyntaxKind[] ClassAndRecordClassDeclaration { get; } abstract TSyntaxKind[] ClassAndModuleDeclarations { get; } abstract TSyntaxKind[] CommentTrivia { get; } abstract TSyntaxKind[] ComparisonKinds { get; } diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassNotInstantiatableBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassNotInstantiatableBase.cs index 9ec20458fb6..dc68273782b 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassNotInstantiatableBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassNotInstantiatableBase.cs @@ -36,7 +36,7 @@ public abstract class ClassNotInstantiatableBase : context.RegisterSymbolAction(CheckClassWithOnlyUnusedPrivateConstructors, SymbolKind.NamedType); private bool IsTypeDeclaration(SyntaxNode node) => - Language.Syntax.IsAnyKind(node, Language.SyntaxKind.ClassAndRecordDeclaration); + Language.Syntax.IsAnyKind(node, Language.SyntaxKind.ClassAndRecordClassDeclaration); private bool IsAnyConstructorCalled(INamedTypeSymbol namedType, IEnumerable typeDeclarations) => typeDeclarations diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs index 651303fee80..b93263e81ec 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs @@ -25,7 +25,22 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn { private const string DiagnosticId = "S2094"; - protected override string MessageFormat => "FIXME"; + protected override string MessageFormat => "Remove this empty class, write its code or make it an \"interface\"."; protected ClassShouldNotBeEmptyBase() : base(DiagnosticId) { } + + protected override void Initialize(SonarAnalysisContext context) => + context.RegisterNodeAction( + Language.GeneratedCodeRecognizer, + c => + { + if (Language.Syntax.NodeIdentifier(c.Node) is { IsMissing: false } identifier + && (!c.Node.ChildNodes().Any() || IsRecordClassWithEmptyTypeList(c.Node))) + { + c.ReportIssue(Diagnostic.Create(Rule, identifier.GetLocation())); + } + }, + Language.SyntaxKind.ClassAndRecordClassDeclaration); + + protected abstract bool IsRecordClassWithEmptyTypeList(SyntaxNode node); } diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ExceptionsShouldBePublicBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ExceptionsShouldBePublicBase.cs index 578533d7b3a..2d46f590c5e 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/ExceptionsShouldBePublicBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ExceptionsShouldBePublicBase.cs @@ -46,5 +46,5 @@ public abstract class ExceptionsShouldBePublicBase : SonarDiagnosti c.ReportIssue(Diagnostic.Create(Rule, Language.Syntax.NodeIdentifier(c.Node).Value.GetLocation())); } }, - Language.SyntaxKind.ClassAndRecordDeclaration); + Language.SyntaxKind.ClassAndRecordClassDeclaration); } diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs index 2a388144ee2..930e53b41c3 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs @@ -24,7 +24,7 @@ internal sealed class VisualBasicSyntaxKindFacade : ISyntaxKindFacade SyntaxKind.Attribute; public SyntaxKind ClassDeclaration => SyntaxKind.ClassBlock; - public SyntaxKind[] ClassAndRecordDeclaration => new[] { SyntaxKind.ClassBlock }; + public SyntaxKind[] ClassAndRecordClassDeclaration => new[] { SyntaxKind.ClassBlock }; public SyntaxKind[] ClassAndModuleDeclarations => new[] { SyntaxKind.ClassBlock, diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs index 2feb6e5ca19..3cab5459dc0 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs @@ -23,17 +23,7 @@ namespace SonarAnalyzer.Rules.VisualBasic; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase { - protected override ILanguageFacade Language => VisualBasicFacade.Instance; - protected override void Initialize(SonarAnalysisContext context) => - context.RegisterNodeAction(c => - { - var node = c.Node; - if (true) - { - c.ReportIssue(Diagnostic.Create(Rule, node.GetLocation())); - } - }, - SyntaxKind.InvocationExpression); + protected override bool IsRecordClassWithEmptyTypeList(SyntaxNode node) => false; } From fde4a5052cf29959b376e4f6679208332fe19afe Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Mon, 13 Feb 2023 11:51:23 +0100 Subject: [PATCH 04/37] Add VB test cases --- .../Rules/ClassShouldNotBeEmpty.cs | 4 +- .../Rules/ClassShouldNotBeEmptyBase.cs | 5 +- .../Rules/ClassShouldNotBeEmpty.cs | 6 +- .../ClassShouldNotBeEmpty.CSharp10.cs | 3 +- .../ClassShouldNotBeEmpty.CSharp9.cs | 3 +- .../TestCases/ClassShouldNotBeEmpty.vb | 74 ++++++++++++++++++- 6 files changed, 86 insertions(+), 9 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs index 63048b5672b..14161004de7 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs @@ -25,7 +25,9 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase Language => CSharpFacade.Instance; - protected override bool IsRecordClassWithEmptyTypeList(SyntaxNode node) => + protected override bool IsEmptyClass(SyntaxNode node) => !node.ChildNodes().Any(); + + protected override bool IsEmptyRecordClass(SyntaxNode node) => RecordDeclarationSyntaxWrapper.IsInstance(node) && !((RecordDeclarationSyntaxWrapper)node).ParameterList.Parameters.Any() && node.ChildNodes().All(x => x is ParameterListSyntax); diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs index b93263e81ec..c7dcb1954d9 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs @@ -35,12 +35,13 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn c => { if (Language.Syntax.NodeIdentifier(c.Node) is { IsMissing: false } identifier - && (!c.Node.ChildNodes().Any() || IsRecordClassWithEmptyTypeList(c.Node))) + && (IsEmptyClass(c.Node) || IsEmptyRecordClass(c.Node))) { c.ReportIssue(Diagnostic.Create(Rule, identifier.GetLocation())); } }, Language.SyntaxKind.ClassAndRecordClassDeclaration); - protected abstract bool IsRecordClassWithEmptyTypeList(SyntaxNode node); + protected abstract bool IsEmptyClass(SyntaxNode node); + protected abstract bool IsEmptyRecordClass(SyntaxNode node); } diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs index 3cab5459dc0..aaa8cc0107c 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs @@ -25,5 +25,9 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase Language => VisualBasicFacade.Instance; - protected override bool IsRecordClassWithEmptyTypeList(SyntaxNode node) => false; + protected override bool IsEmptyClass(SyntaxNode node) => + node is ClassBlockSyntax + && node.ChildNodes().All(x => x is ClassStatementSyntax or EndBlockStatementSyntax); + + protected override bool IsEmptyRecordClass(SyntaxNode node) => false; } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs index 4d169aca74d..88f7b9fc71a 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs @@ -1,4 +1,5 @@ - +using System; + record class EmptyRecordClass1(); // Noncompliant // ^^^^^^^^^^^^^^^^^ 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 6a0f4efb50f..771d077faaf 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs @@ -1,4 +1,5 @@ - +using System; + record EmptyRecord1(); // Noncompliant // ^^^^^^^^^^^^ record EmptyRecord2() { }; // Noncompliant diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb index f5b9e9276c6..adc11e92a8f 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb @@ -1,7 +1,75 @@ -Public Class Program + +Class Empty ' Noncompliant {{Remove this empty class, write its code or make it an "interface".}} + ' ^^^^^ +End Class + + +Public Class PublicEmpty ' Noncompliant +End Class + +Class InternalEmpty ' Noncompliant +End Class + +Class EmptyWithComments ' Noncompliant + ' Some comment +End Class + +Class NotEmpty + Public ReadOnly Property SomeProperty As Integer + Get + Return 0 + End Get + End Property +End Class + +Class OuterClass + Class InnerEmpty1 ' Noncompliant + End Class - Public Sub Test() + Private Class InnerEmpty2 ' Noncompliant + End Class - End Sub + Protected Class InnerEmpty3 ' Noncompliant + End Class + Class InnerEmpty4 ' Noncompliant + End Class + + Protected Class InnerEmpty5 ' Noncompliant + End Class + + Public Class InnerEmpty6 ' Noncompliant + End Class + + Public Class InnerEmptyWithComments ' Noncompliant + ' Some comment + End Class + + Class InnerNonEmpty + Public ReadOnly Property SomeProperty As Integer + Get + Return 0 + End Get + End Property + End Class +End Class + +Partial Class PartialEmpty ' Noncompliant End Class + +Partial Class PartialEmpty + Public ReadOnly Property SomeProperty As Integer + Get + Return 0 + End Get + End Property +End Class + +Interface IMarker ' Compliant - using marker interfaces vs. attributes is a topic of debate, but they are commonly used +End Interface + +Structure EmptyStruct ' Compliant - this rule only deals with classes +End Structure + +Class ' Error +End Class From e87c429e719f5e328510e34d5ee17dc36a08496e Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Mon, 13 Feb 2023 12:25:55 +0100 Subject: [PATCH 05/37] Fix member check --- .../Rules/ClassShouldNotBeEmpty.cs | 5 ++--- .../Rules/ClassShouldNotBeEmpty.cs | 4 +--- .../MetadataReferences/AspNetCoreMetadataReference.cs | 1 + .../Rules/ClassShouldNotBeEmptyTest.cs | 11 ++++++++++- .../TestCases/ClassShouldNotBeEmpty.Inheritance.cs | 6 ++++++ 5 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs index 14161004de7..40edb198216 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs @@ -25,10 +25,9 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase Language => CSharpFacade.Instance; - protected override bool IsEmptyClass(SyntaxNode node) => !node.ChildNodes().Any(); + protected override bool IsEmptyClass(SyntaxNode node) => node is ClassDeclarationSyntax { Members.Count : 0 }; protected override bool IsEmptyRecordClass(SyntaxNode node) => RecordDeclarationSyntaxWrapper.IsInstance(node) - && !((RecordDeclarationSyntaxWrapper)node).ParameterList.Parameters.Any() - && node.ChildNodes().All(x => x is ParameterListSyntax); + && (RecordDeclarationSyntaxWrapper)node is { Members.Count: 0, ParameterList.Parameters.Count: 0 }; } diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs index aaa8cc0107c..8c26d37a288 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs @@ -25,9 +25,7 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase Language => VisualBasicFacade.Instance; - protected override bool IsEmptyClass(SyntaxNode node) => - node is ClassBlockSyntax - && node.ChildNodes().All(x => x is ClassStatementSyntax or EndBlockStatementSyntax); + protected override bool IsEmptyClass(SyntaxNode node) => node is ClassBlockSyntax { Members.Count : 0 }; protected override bool IsEmptyRecordClass(SyntaxNode node) => false; } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/MetadataReferences/AspNetCoreMetadataReference.cs b/analyzers/tests/SonarAnalyzer.UnitTest/MetadataReferences/AspNetCoreMetadataReference.cs index fdb8994b483..3cd9c1d5b99 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/MetadataReferences/AspNetCoreMetadataReference.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/MetadataReferences/AspNetCoreMetadataReference.cs @@ -37,6 +37,7 @@ internal static class AspNetCoreMetadataReference internal static MetadataReference MicrosoftAspNetCoreMvcAbstractions { get; } = Create(typeof(Microsoft.AspNetCore.Mvc.IActionResult)); internal static MetadataReference MicrosoftAspNetCoreMvcCore { get; } = Create(typeof(Microsoft.AspNetCore.Mvc.ControllerBase)); internal static MetadataReference MicrosoftAspNetCoreMvcViewFeatures { get; } = Create(typeof(Microsoft.AspNetCore.Mvc.Controller)); + internal static MetadataReference MicrosoftAspNetCoreRazorPages { get; } = Create(typeof(Microsoft.AspNetCore.Mvc.RazorPages.PageModel)); internal static MetadataReference MicrosoftAspNetCoreWebHost { get; } = Create(typeof(Microsoft.AspNetCore.WebHost)); internal static MetadataReference MicrosoftExtensionsHostingAbstractions { get; } = Create(typeof(Microsoft.Extensions.Hosting.IHost)); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs index de697e8d248..6389e16d205 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs @@ -27,7 +27,7 @@ namespace SonarAnalyzer.UnitTest.Rules; public class ClassShouldNotBeEmptyTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); - private readonly VerifierBuilder builderVB = new VerifierBuilder(); // FIXME: Move this up + private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void ClassShouldNotBeEmpty_CS() => @@ -35,6 +35,11 @@ public class ClassShouldNotBeEmptyTest #if NET + private static IEnumerable AdditionalReferences => new[] + { + AspNetCoreMetadataReference.MicrosoftAspNetCoreRazorPages + }; + [TestMethod] public void ClassShouldNotBeEmpty_CSharp9() => builderCS.AddPaths("ClassShouldNotBeEmpty.CSharp9.cs").WithOptions(ParseOptionsHelper.FromCSharp9).Verify(); @@ -43,6 +48,10 @@ public class ClassShouldNotBeEmptyTest public void ClassShouldNotBeEmpty_CSharp10() => builderCS.AddPaths("ClassShouldNotBeEmpty.CSharp10.cs").WithOptions(ParseOptionsHelper.FromCSharp10).Verify(); + [TestMethod] + public void ClassShouldNotBeEmpty_Inheritance() => + builderCS.AddPaths("ClassShouldNotBeEmpty.Inheritance.cs").AddReferences(AdditionalReferences).Verify(); + #endif [TestMethod] diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs new file mode 100644 index 00000000000..36acb2755ea --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs @@ -0,0 +1,6 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +public class EmptyPageModel: PageModel // Compliant - an empty PageModel can be fully functional, the C# code can be in cshtml file +{ +} + From 3ea75535367bc5ebf06aa07b3c37e26a8ca84719 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Mon, 13 Feb 2023 12:30:54 +0100 Subject: [PATCH 06/37] Add VB test case for inheritance --- .../Rules/ClassShouldNotBeEmptyTest.cs | 14 +++++++++----- .../TestCases/ClassShouldNotBeEmpty.Inheritance.vb | 5 +++++ 2 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs index 6389e16d205..eb448c80d0a 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs @@ -33,6 +33,10 @@ public class ClassShouldNotBeEmptyTest public void ClassShouldNotBeEmpty_CS() => builderCS.AddPaths("ClassShouldNotBeEmpty.cs").Verify(); + [TestMethod] + public void ClassShouldNotBeEmpty_VB() => + builderVB.AddPaths("ClassShouldNotBeEmpty.vb").Verify(); + #if NET private static IEnumerable AdditionalReferences => new[] @@ -49,12 +53,12 @@ public class ClassShouldNotBeEmptyTest builderCS.AddPaths("ClassShouldNotBeEmpty.CSharp10.cs").WithOptions(ParseOptionsHelper.FromCSharp10).Verify(); [TestMethod] - public void ClassShouldNotBeEmpty_Inheritance() => + public void ClassShouldNotBeEmpty_Inheritance_CS() => builderCS.AddPaths("ClassShouldNotBeEmpty.Inheritance.cs").AddReferences(AdditionalReferences).Verify(); -#endif - [TestMethod] - public void ClassShouldNotBeEmpty_VB() => - builderVB.AddPaths("ClassShouldNotBeEmpty.vb").Verify(); + public void ClassShouldNotBeEmpty_Inheritance_VB() => + builderVB.AddPaths("ClassShouldNotBeEmpty.Inheritance.vb").AddReferences(AdditionalReferences).Verify(); + +#endif } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb new file mode 100644 index 00000000000..c632efeaba4 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb @@ -0,0 +1,5 @@ +Imports Microsoft.AspNetCore.Mvc.RazorPages + +Public Class EmptyPageModel ' Compliant - an empty PageModel can be fully functional, the VB code can be in vbhtml file + Inherits PageModel +End Class From cff217722a34cebe39250dbf6edc54c4552fb2b9 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Mon, 13 Feb 2023 17:00:02 +0100 Subject: [PATCH 07/37] Add exceptions for special base classes --- .../Facade/CSharpSyntaxFacade.cs | 2 ++ .../SonarAnalyzer.Common/Facade/SyntaxFacade.cs | 1 + .../src/SonarAnalyzer.Common/Helpers/KnownType.cs | 1 + .../Rules/ClassShouldNotBeEmptyBase.cs | 13 ++++++++++++- .../Facade/VisualBasicSyntaxFacade.cs | 2 ++ .../TestCases/ClassShouldNotBeEmpty.Inheritance.cs | 8 +++++++- .../TestCases/ClassShouldNotBeEmpty.Inheritance.vb | 14 +++++++++++++- .../TestCases/ClassShouldNotBeEmpty.cs | 3 --- 8 files changed, 38 insertions(+), 6 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs index c26432354f0..2bb9a36df45 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs @@ -48,6 +48,8 @@ internal sealed class CSharpSyntaxFacade : SyntaxFacade public override bool IsAnyKind(SyntaxTrivia trivia, params SyntaxKind[] syntaxKinds) => trivia.IsAnyKind(syntaxKinds); + public override bool HasDeclaredBaseClass(SyntaxNode node) => node is ClassDeclarationSyntax { BaseList: not null }; + public override bool IsNullLiteral(SyntaxNode node) => node.IsNullLiteral(); public override IEnumerable ArgumentExpressions(SyntaxNode node) => diff --git a/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs b/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs index 9144a08af14..9e289870b52 100644 --- a/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs @@ -32,6 +32,7 @@ public abstract class SyntaxFacade public abstract bool IsAnyKind(SyntaxNode node, ISet syntaxKinds); public abstract bool IsAnyKind(SyntaxNode node, params TSyntaxKind[] syntaxKinds); public abstract bool IsAnyKind(SyntaxTrivia trivia, params TSyntaxKind[] syntaxKinds); + public abstract bool HasDeclaredBaseClass(SyntaxNode node); public abstract IEnumerable ArgumentExpressions(SyntaxNode node); public abstract ImmutableArray AssignmentTargets(SyntaxNode assignment); public abstract SyntaxNode AssignmentLeft(SyntaxNode assignment); diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs index 0d305123925..f6fcc633afc 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs @@ -63,6 +63,7 @@ internal sealed partial class KnownType internal static readonly KnownType Microsoft_AspNetCore_Mvc_IgnoreAntiforgeryTokenAttribute = new("Microsoft.AspNetCore.Mvc.IgnoreAntiforgeryTokenAttribute"); internal static readonly KnownType Microsoft_AspNetCore_Mvc_NonActionAttribute = new("Microsoft.AspNetCore.Mvc.NonActionAttribute"); internal static readonly KnownType Microsoft_AspNetCore_Mvc_NonControllerAttribute = new("Microsoft.AspNetCore.Mvc.NonControllerAttribute"); + internal static readonly KnownType Microsoft_AspNetCore_Mvc_RazorPages_PageModel = new("Microsoft.AspNetCore.Mvc.RazorPages.PageModel"); internal static readonly KnownType Microsoft_AspNetCore_Mvc_RequestFormLimitsAttribute = new("Microsoft.AspNetCore.Mvc.RequestFormLimitsAttribute"); internal static readonly KnownType Microsoft_AspNetCore_Mvc_RequestSizeLimitAttribute = new("Microsoft.AspNetCore.Mvc.RequestSizeLimitAttribute"); internal static readonly KnownType Microsoft_AspNetCore_Razor_Hosting_RazorCompiledItemAttribute = new("Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute"); diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs index c7dcb1954d9..9ef1da57334 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs @@ -18,6 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using Microsoft.CodeAnalysis; + namespace SonarAnalyzer.Rules; public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAnalyzer @@ -25,6 +27,8 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn { private const string DiagnosticId = "S2094"; + private static readonly ImmutableArray SubClassesToIgnore = ImmutableArray.Create(KnownType.Microsoft_AspNetCore_Mvc_RazorPages_PageModel); + protected override string MessageFormat => "Remove this empty class, write its code or make it an \"interface\"."; protected ClassShouldNotBeEmptyBase() : base(DiagnosticId) { } @@ -35,10 +39,17 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn c => { if (Language.Syntax.NodeIdentifier(c.Node) is { IsMissing: false } identifier - && (IsEmptyClass(c.Node) || IsEmptyRecordClass(c.Node))) + && (IsEmptyClass(c.Node) || IsEmptyRecordClass(c.Node)) + && !ShouldIgnoreBecauseOfBaseClass(c.Node, c.SemanticModel)) { c.ReportIssue(Diagnostic.Create(Rule, identifier.GetLocation())); } + + bool ShouldIgnoreBecauseOfBaseClass(SyntaxNode node, SemanticModel model) => + Language.Syntax.HasDeclaredBaseClass(node) + && model.GetDeclaredSymbol(node) is INamedTypeSymbol classSymbol + && classSymbol.DerivesFromAny(SubClassesToIgnore); + }, Language.SyntaxKind.ClassAndRecordClassDeclaration); diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs index c03a1e1f4bd..6897c31286f 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs @@ -50,6 +50,8 @@ internal sealed class VisualBasicSyntaxFacade : SyntaxFacade public override bool IsAnyKind(SyntaxTrivia trivia, params SyntaxKind[] syntaxKinds) => trivia.IsAnyKind(syntaxKinds); + public override bool HasDeclaredBaseClass(SyntaxNode node) => node is ClassBlockSyntax { Inherits.Count: > 0 }; + public override IEnumerable ArgumentExpressions(SyntaxNode node) => node switch { diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs index 36acb2755ea..53c0557e8d3 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs @@ -1,6 +1,12 @@ using Microsoft.AspNetCore.Mvc.RazorPages; -public class EmptyPageModel: PageModel // Compliant - an empty PageModel can be fully functional, the C# code can be in cshtml file +public class BaseClass +{ + int Prop => 0; +} +public class SubClass: BaseClass { } // Noncompliant - not derived from any special base class + +public class EmptyPageModel: PageModel // Compliant - an empty PageModel can be fully functional, the C# code can be in the cshtml file { } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb index c632efeaba4..9c7c50654be 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb @@ -1,5 +1,17 @@ Imports Microsoft.AspNetCore.Mvc.RazorPages -Public Class EmptyPageModel ' Compliant - an empty PageModel can be fully functional, the VB code can be in vbhtml file +Public Class BaseClass + Private ReadOnly Property Prop As Integer + Get + Return 0 + End Get + End Property +End Class + +Public Class SubClass ' Noncompliant - not derived from any special base class + Inherits BaseClass +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 diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs index be10493efa9..160f0d233bd 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs @@ -52,6 +52,3 @@ enum EmptyEnum { } // Compliant - this rule only deals class { } // Error - -// Razor Pages: PageModel - From ddfe3c3bbf03a2c5900dbaf7f9b6c4ff37701fa2 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Tue, 14 Feb 2023 00:03:10 +0100 Subject: [PATCH 08/37] Refactor SyntaxFacade methods --- .../src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs | 2 -- .../src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs | 2 ++ analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs | 1 - .../SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs | 6 ++---- .../Facade/VisualBasicSyntaxFacade.cs | 2 -- .../Rules/ClassShouldNotBeEmpty.cs | 2 ++ 6 files changed, 6 insertions(+), 9 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs index 2bb9a36df45..c26432354f0 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs @@ -48,8 +48,6 @@ internal sealed class CSharpSyntaxFacade : SyntaxFacade public override bool IsAnyKind(SyntaxTrivia trivia, params SyntaxKind[] syntaxKinds) => trivia.IsAnyKind(syntaxKinds); - public override bool HasDeclaredBaseClass(SyntaxNode node) => node is ClassDeclarationSyntax { BaseList: not null }; - public override bool IsNullLiteral(SyntaxNode node) => node.IsNullLiteral(); public override IEnumerable ArgumentExpressions(SyntaxNode node) => diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs index 40edb198216..af726afebad 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs @@ -30,4 +30,6 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase RecordDeclarationSyntaxWrapper.IsInstance(node) && (RecordDeclarationSyntaxWrapper)node is { Members.Count: 0, ParameterList.Parameters.Count: 0 }; + + protected override bool IsClassWithDeclaredBaseClass(SyntaxNode node) => node is ClassDeclarationSyntax { BaseList: not null }; } diff --git a/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs b/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs index 9e289870b52..9144a08af14 100644 --- a/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs @@ -32,7 +32,6 @@ public abstract class SyntaxFacade public abstract bool IsAnyKind(SyntaxNode node, ISet syntaxKinds); public abstract bool IsAnyKind(SyntaxNode node, params TSyntaxKind[] syntaxKinds); public abstract bool IsAnyKind(SyntaxTrivia trivia, params TSyntaxKind[] syntaxKinds); - public abstract bool HasDeclaredBaseClass(SyntaxNode node); public abstract IEnumerable ArgumentExpressions(SyntaxNode node); public abstract ImmutableArray AssignmentTargets(SyntaxNode assignment); public abstract SyntaxNode AssignmentLeft(SyntaxNode assignment); diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs index 9ef1da57334..4d60043a833 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs @@ -18,8 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using Microsoft.CodeAnalysis; - namespace SonarAnalyzer.Rules; public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAnalyzer @@ -46,13 +44,13 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn } bool ShouldIgnoreBecauseOfBaseClass(SyntaxNode node, SemanticModel model) => - Language.Syntax.HasDeclaredBaseClass(node) + IsClassWithDeclaredBaseClass(node) && model.GetDeclaredSymbol(node) is INamedTypeSymbol classSymbol && classSymbol.DerivesFromAny(SubClassesToIgnore); - }, Language.SyntaxKind.ClassAndRecordClassDeclaration); protected abstract bool IsEmptyClass(SyntaxNode node); protected abstract bool IsEmptyRecordClass(SyntaxNode node); + protected abstract bool IsClassWithDeclaredBaseClass(SyntaxNode node); } diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs index 6897c31286f..c03a1e1f4bd 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs @@ -50,8 +50,6 @@ internal sealed class VisualBasicSyntaxFacade : SyntaxFacade public override bool IsAnyKind(SyntaxTrivia trivia, params SyntaxKind[] syntaxKinds) => trivia.IsAnyKind(syntaxKinds); - public override bool HasDeclaredBaseClass(SyntaxNode node) => node is ClassBlockSyntax { Inherits.Count: > 0 }; - public override IEnumerable ArgumentExpressions(SyntaxNode node) => node switch { diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs index 8c26d37a288..9dc4086fb06 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs @@ -28,4 +28,6 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase node is ClassBlockSyntax { Members.Count : 0 }; protected override bool IsEmptyRecordClass(SyntaxNode node) => false; + + protected override bool IsClassWithDeclaredBaseClass(SyntaxNode node) => node is ClassBlockSyntax { Inherits.Count: > 0 }; } From 7e732f2c4fd8432344a65b602bdd8d0677aaabf5 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Tue, 14 Feb 2023 00:52:13 +0100 Subject: [PATCH 09/37] Add type name to rule message --- .../Rules/ClassShouldNotBeEmpty.cs | 2 ++ .../Rules/ClassShouldNotBeEmptyBase.cs | 5 ++-- .../Rules/ClassShouldNotBeEmpty.cs | 2 ++ .../ClassShouldNotBeEmpty.CSharp10.cs | 2 +- .../ClassShouldNotBeEmpty.CSharp9.cs | 2 +- .../TestCases/ClassShouldNotBeEmpty.cs | 4 +-- .../TestCases/ClassShouldNotBeEmpty.vb | 28 +++++++++---------- 7 files changed, 25 insertions(+), 20 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs index af726afebad..823be7c8e95 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs @@ -32,4 +32,6 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase node is ClassDeclarationSyntax { BaseList: not null }; + + protected override string DeclaredTypeNameOf(SyntaxNode node) => ((TypeDeclarationSyntax)node).Keyword.ValueText; } diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs index 4d60043a833..f14821351f1 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs @@ -27,7 +27,7 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn private static readonly ImmutableArray SubClassesToIgnore = ImmutableArray.Create(KnownType.Microsoft_AspNetCore_Mvc_RazorPages_PageModel); - protected override string MessageFormat => "Remove this empty class, write its code or make it an \"interface\"."; + protected override string MessageFormat => "Remove this empty {0}, or add members to it."; protected ClassShouldNotBeEmptyBase() : base(DiagnosticId) { } @@ -40,7 +40,7 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn && (IsEmptyClass(c.Node) || IsEmptyRecordClass(c.Node)) && !ShouldIgnoreBecauseOfBaseClass(c.Node, c.SemanticModel)) { - c.ReportIssue(Diagnostic.Create(Rule, identifier.GetLocation())); + c.ReportIssue(Diagnostic.Create(Rule, identifier.GetLocation(), DeclaredTypeNameOf(c.Node))); } bool ShouldIgnoreBecauseOfBaseClass(SyntaxNode node, SemanticModel model) => @@ -53,4 +53,5 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn protected abstract bool IsEmptyClass(SyntaxNode node); protected abstract bool IsEmptyRecordClass(SyntaxNode node); protected abstract bool IsClassWithDeclaredBaseClass(SyntaxNode node); + protected abstract string DeclaredTypeNameOf(SyntaxNode node); } diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs index 9dc4086fb06..18b9738a32f 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs @@ -30,4 +30,6 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase false; protected override bool IsClassWithDeclaredBaseClass(SyntaxNode node) => node is ClassBlockSyntax { Inherits.Count: > 0 }; + + protected override string DeclaredTypeNameOf(SyntaxNode node) => ((TypeBlockSyntax)node).BlockStatement.DeclarationKeyword.ValueText.ToLower(); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs index 88f7b9fc71a..9e0649a9c14 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs @@ -1,6 +1,6 @@ using System; -record class EmptyRecordClass1(); // Noncompliant +record class EmptyRecordClass1(); // Noncompliant {{Remove this empty record, or add members to it.}} // ^^^^^^^^^^^^^^^^^ 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 771d077faaf..9014be91a30 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 +record EmptyRecord1(); // Noncompliant {{Remove this empty record, or add members to it.}} // ^^^^^^^^^^^^ record EmptyRecord2() { }; // Noncompliant diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs index 160f0d233bd..6cf4440d18f 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs @@ -1,6 +1,6 @@ using System; -class Empty { } // Noncompliant {{Remove this empty class, write its code or make it an "interface".}} +class Empty { } // Noncompliant {{Remove this empty class, or add members to it.}} // ^^^^^ public class PublicEmpty { } // Noncompliant @@ -44,7 +44,7 @@ partial class PartialEmpty public int SomeProperty => 0; } -interface IMarker { } // Compliant - using marker interfaces vs. attributes is a topic of debate, but they are commonly used +interface IMarker { } // Compliant - this rule only deals with classes struct EmptyStruct { } // Compliant - this rule only deals with classes diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb index adc11e92a8f..ba78f3ab8ed 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb @@ -1,16 +1,16 @@  -Class Empty ' Noncompliant {{Remove this empty class, write its code or make it an "interface".}} +Class Empty ' Noncompliant {{Remove this empty class, or add members to it.}} ' ^^^^^ End Class -Public Class PublicEmpty ' Noncompliant +Public Class PublicEmpty ' Noncompliant End Class -Class InternalEmpty ' Noncompliant +Class InternalEmpty ' Noncompliant End Class -Class EmptyWithComments ' Noncompliant +Class EmptyWithComments ' Noncompliant ' Some comment End Class @@ -23,22 +23,22 @@ Class NotEmpty End Class Class OuterClass - Class InnerEmpty1 ' Noncompliant + Class InnerEmpty1 ' Noncompliant End Class - Private Class InnerEmpty2 ' Noncompliant + Private Class InnerEmpty2 ' Noncompliant End Class - Protected Class InnerEmpty3 ' Noncompliant + Protected Class InnerEmpty3 ' Noncompliant End Class - Class InnerEmpty4 ' Noncompliant + Class InnerEmpty4 ' Noncompliant End Class - Protected Class InnerEmpty5 ' Noncompliant + Protected Class InnerEmpty5 ' Noncompliant End Class - Public Class InnerEmpty6 ' Noncompliant + Public Class InnerEmpty6 ' Noncompliant End Class Public Class InnerEmptyWithComments ' Noncompliant @@ -54,7 +54,7 @@ Class OuterClass End Class End Class -Partial Class PartialEmpty ' Noncompliant +Partial Class PartialEmpty ' Noncompliant End Class Partial Class PartialEmpty @@ -65,11 +65,11 @@ Partial Class PartialEmpty End Property End Class -Interface IMarker ' Compliant - using marker interfaces vs. attributes is a topic of debate, but they are commonly used +Interface IMarker ' Compliant - this rule only deals with classes End Interface -Structure EmptyStruct ' Compliant - this rule only deals with classes +Structure EmptyStruct ' Compliant - this rule only deals with classes End Structure -Class ' Error +Class ' Error End Class From d28148b90e39ac5cbf28209a6d88af2bcb683745 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Tue, 14 Feb 2023 09:11:41 +0100 Subject: [PATCH 10/37] Update Sarif files --- ...97-5AFD-4813-AEDC-AF33FACEADF0}-S2094.json | 56 +++ .../expected/Nancy/Nancy--net452-S2094.json | 30 ++ .../Nancy/Nancy--netstandard2.0-S2094.json | 30 ++ ...Nancy.ViewEngines.Razor--net452-S2094.json | 17 + .../its/expected/Net5/Net5--net5.0-S2094.json | 147 ++++++++ .../its/expected/Net7/Net7--net7.0-S2094.json | 30 ++ .../akka.net/Akka--netstandard2.0-S2094.json | 329 ++++++++++++++++++ .../Akka.Benchmarks--netcoreapp3.1-S2094.json | 30 ++ .../Akka.Cluster--netstandard2.0-S2094.json | 30 ++ ...Akka.DI.TestKit--netstandard2.0-S2094.json | 56 +++ ...kka.MultiNodeTestRunner--net471-S2094.json | 30 ++ ...kka.MultiNodeTestRunner--net5.0-S2094.json | 30 ++ ...tiNodeTestRunner--netcoreapp3.1-S2094.json | 30 ++ ...stRunner.Shared--netstandard2.0-S2094.json | 121 +++++++ .../Akka.Remote--netstandard2.0-S2094.json | 186 ++++++++++ ...ests.Performance--netcoreapp3.1-S2094.json | 17 + .../Akka.Streams--netstandard2.0-S2094.json | 69 ++++ ...ests.Performance--netcoreapp3.1-S2094.json | 17 + .../RemotePingPong--net471-S2094.json | 17 + .../RemotePingPong--net5.0-S2094.json | 17 + .../RemotePingPong--netcoreapp3.1-S2094.json | 17 + ...hoService.Server--netcoreapp3.1-S2094.json | 17 + 22 files changed, 1323 insertions(+) create mode 100644 analyzers/its/expected/Ember-MM/Ember.Plugins-{9496C697-5AFD-4813-AEDC-AF33FACEADF0}-S2094.json create mode 100644 analyzers/its/expected/Nancy/Nancy--net452-S2094.json create mode 100644 analyzers/its/expected/Nancy/Nancy--netstandard2.0-S2094.json create mode 100644 analyzers/its/expected/Nancy/Nancy.ViewEngines.Razor--net452-S2094.json create mode 100644 analyzers/its/expected/Net5/Net5--net5.0-S2094.json create mode 100644 analyzers/its/expected/Net7/Net7--net7.0-S2094.json create mode 100644 analyzers/its/expected/akka.net/Akka--netstandard2.0-S2094.json create mode 100644 analyzers/its/expected/akka.net/Akka.Benchmarks--netcoreapp3.1-S2094.json create mode 100644 analyzers/its/expected/akka.net/Akka.Cluster--netstandard2.0-S2094.json create mode 100644 analyzers/its/expected/akka.net/Akka.DI.TestKit--netstandard2.0-S2094.json create mode 100644 analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--net471-S2094.json create mode 100644 analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--net5.0-S2094.json create mode 100644 analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--netcoreapp3.1-S2094.json create mode 100644 analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner.Shared--netstandard2.0-S2094.json create mode 100644 analyzers/its/expected/akka.net/Akka.Remote--netstandard2.0-S2094.json create mode 100644 analyzers/its/expected/akka.net/Akka.Remote.Tests.Performance--netcoreapp3.1-S2094.json create mode 100644 analyzers/its/expected/akka.net/Akka.Streams--netstandard2.0-S2094.json create mode 100644 analyzers/its/expected/akka.net/Akka.Tests.Performance--netcoreapp3.1-S2094.json create mode 100644 analyzers/its/expected/akka.net/RemotePingPong--net471-S2094.json create mode 100644 analyzers/its/expected/akka.net/RemotePingPong--net5.0-S2094.json create mode 100644 analyzers/its/expected/akka.net/RemotePingPong--netcoreapp3.1-S2094.json create mode 100644 analyzers/its/expected/akka.net/TcpEchoService.Server--netcoreapp3.1-S2094.json 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 new file mode 100644 index 00000000000..744cfb72235 --- /dev/null +++ b/analyzers/its/expected/Ember-MM/Ember.Plugins-{9496C697-5AFD-4813-AEDC-AF33FACEADF0}-S2094.json @@ -0,0 +1,56 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Ember-MM\Ember.Plugins\PluginActionContext.cs", +"region": { +"startLine": 6, +"startColumn": 18, +"endLine": 6, +"endColumn": 37 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Ember-MM\Ember.Plugins\PluginSectionHandler.cs", +"region": { +"startLine": 113, +"startColumn": 23, +"endLine": 113, +"endColumn": 45 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Ember-MM\Ember.Plugins\PluginSectionHandler.cs", +"region": { +"startLine": 114, +"startColumn": 23, +"endLine": 114, +"endColumn": 44 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Ember-MM\Ember.Plugins\Scraper\Exceptions.cs", +"region": { +"startLine": 9, +"startColumn": 18, +"endLine": 9, +"endColumn": 39 +} +} +} +] +} diff --git a/analyzers/its/expected/Nancy/Nancy--net452-S2094.json b/analyzers/its/expected/Nancy/Nancy--net452-S2094.json new file mode 100644 index 00000000000..8295ed01fd2 --- /dev/null +++ b/analyzers/its/expected/Nancy/Nancy--net452-S2094.json @@ -0,0 +1,30 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Nancy\src\Nancy\IncludeInNancyAssemblyScanningAttribute.cs", +"region": { +"startLine": 14, +"startColumn": 25, +"endLine": 14, +"endColumn": 64 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Nancy\src\Nancy\Json\ScriptIgnoreAttribute.cs", +"region": { +"startLine": 38, +"startColumn": 25, +"endLine": 38, +"endColumn": 46 +} +} +} +] +} diff --git a/analyzers/its/expected/Nancy/Nancy--netstandard2.0-S2094.json b/analyzers/its/expected/Nancy/Nancy--netstandard2.0-S2094.json new file mode 100644 index 00000000000..8295ed01fd2 --- /dev/null +++ b/analyzers/its/expected/Nancy/Nancy--netstandard2.0-S2094.json @@ -0,0 +1,30 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Nancy\src\Nancy\IncludeInNancyAssemblyScanningAttribute.cs", +"region": { +"startLine": 14, +"startColumn": 25, +"endLine": 14, +"endColumn": 64 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Nancy\src\Nancy\Json\ScriptIgnoreAttribute.cs", +"region": { +"startLine": 38, +"startColumn": 25, +"endLine": 38, +"endColumn": 46 +} +} +} +] +} diff --git a/analyzers/its/expected/Nancy/Nancy.ViewEngines.Razor--net452-S2094.json b/analyzers/its/expected/Nancy/Nancy.ViewEngines.Razor--net452-S2094.json new file mode 100644 index 00000000000..95c17c2e5b8 --- /dev/null +++ b/analyzers/its/expected/Nancy/Nancy.ViewEngines.Razor--net452-S2094.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Nancy\src\Nancy.ViewEngines.Razor\NancyRazorViewBase.cs", +"region": { +"startLine": 14, +"startColumn": 27, +"endLine": 14, +"endColumn": 45 +} +} +} +] +} diff --git a/analyzers/its/expected/Net5/Net5--net5.0-S2094.json b/analyzers/its/expected/Net5/Net5--net5.0-S2094.json new file mode 100644 index 00000000000..064d7f29838 --- /dev/null +++ b/analyzers/its/expected/Net5/Net5--net5.0-S2094.json @@ -0,0 +1,147 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Net5\Net5\ModuleInitializers.cs", +"region": { +"startLine": 3, +"startColumn": 18, +"endLine": 3, +"endColumn": 36 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Net5\Net5\ParameterValidation.cs", +"region": { +"startLine": 3, +"startColumn": 18, +"endLine": 3, +"endColumn": 37 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Net5\Net5\RelaxRefPartialOrder.cs", +"region": { +"startLine": 3, +"startColumn": 26, +"endLine": 3, +"endColumn": 46 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Net5\Net5\S2330.cs", +"region": { +"startLine": 5, +"startColumn": 32, +"endLine": 5, +"endColumn": 37 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Net5\Net5\S2330.cs", +"region": { +"startLine": 6, +"startColumn": 23, +"endLine": 6, +"endColumn": 28 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Net5\Net5\S2330.cs", +"region": { +"startLine": 7, +"startColumn": 23, +"endLine": 7, +"endColumn": 29 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Net5\Net5\S3240.cs", +"region": { +"startLine": 5, +"startColumn": 24, +"endLine": 5, +"endColumn": 29 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Net5\Net5\S3240.cs", +"region": { +"startLine": 6, +"startColumn": 15, +"endLine": 6, +"endColumn": 20 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Net5\Net5\S3247.cs", +"region": { +"startLine": 8, +"startColumn": 15, +"endLine": 8, +"endColumn": 24 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Net5\Net5\S3453.cs", +"region": { +"startLine": 22, +"startColumn": 26, +"endLine": 22, +"endColumn": 34 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Net5\Net5\StaticLambdas.cs", +"region": { +"startLine": 3, +"startColumn": 18, +"endLine": 3, +"endColumn": 31 +} +} +} +] +} diff --git a/analyzers/its/expected/Net7/Net7--net7.0-S2094.json b/analyzers/its/expected/Net7/Net7--net7.0-S2094.json new file mode 100644 index 00000000000..9cb36f3fb59 --- /dev/null +++ b/analyzers/its/expected/Net7/Net7--net7.0-S2094.json @@ -0,0 +1,30 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Net7\Net7\features\GenericAttributes.cs", +"region": { +"startLine": 5, +"startColumn": 22, +"endLine": 5, +"endColumn": 38 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\Net7\Net7\features\WarningWave7.cs", +"region": { +"startLine": 5, +"startColumn": 22, +"endLine": 5, +"endColumn": 35 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/Akka--netstandard2.0-S2094.json b/analyzers/its/expected/akka.net/Akka--netstandard2.0-S2094.json new file mode 100644 index 00000000000..810f78af4d6 --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka--netstandard2.0-S2094.json @@ -0,0 +1,329 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\Actor\ActorSelection.cs", +"region": { +"startLine": 388, +"startColumn": 27, +"endLine": 388, +"endColumn": 47 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\Actor\ChildrenContainer\Internal\SuspendReason.cs", +"region": { +"startLine": 32, +"startColumn": 22, +"endLine": 32, +"endColumn": 30 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\Actor\FSM.cs", +"region": { +"startLine": 202, +"startColumn": 31, +"endLine": 202, +"endColumn": 37 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\Annotations\Attributes.cs", +"region": { +"startLine": 23, +"startColumn": 25, +"endLine": 23, +"endColumn": 45 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\Annotations\Attributes.cs", +"region": { +"startLine": 44, +"startColumn": 25, +"endLine": 44, +"endColumn": 46 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\Annotations\Attributes.cs", +"region": { +"startLine": 63, +"startColumn": 25, +"endLine": 63, +"endColumn": 46 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\Event\ActorEventBus.cs", +"region": { +"startLine": 17, +"startColumn": 27, +"endLine": 17, +"endColumn": 40 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\Event\LoggerInitialized.cs", +"region": { +"startLine": 15, +"startColumn": 18, +"endLine": 15, +"endColumn": 35 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\Event\Logging.cs", +"region": { +"startLine": 18, +"startColumn": 18, +"endLine": 18, +"endColumn": 44 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\IO\Dns.cs", +"region": { +"startLine": 62, +"startColumn": 31, +"endLine": 62, +"endColumn": 38 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\IO\Inet.cs", +"region": { +"startLine": 53, +"startColumn": 31, +"endLine": 53, +"endColumn": 51 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\IO\Inet.cs", +"region": { +"startLine": 71, +"startColumn": 31, +"endLine": 71, +"endColumn": 53 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\IO\Inet.cs", +"region": { +"startLine": 229, +"startColumn": 31, +"endLine": 229, +"endColumn": 43 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\IO\Tcp.cs", +"region": { +"startLine": 56, +"startColumn": 33, +"endLine": 56, +"endColumn": 48 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\IO\Tcp.cs", +"region": { +"startLine": 87, +"startColumn": 22, +"endLine": 87, +"endColumn": 29 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\IO\Tcp.cs", +"region": { +"startLine": 738, +"startColumn": 22, +"endLine": 738, +"endColumn": 27 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\IO\Udp.cs", +"region": { +"startLine": 37, +"startColumn": 33, +"endLine": 37, +"endColumn": 48 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\IO\Udp.cs", +"region": { +"startLine": 107, +"startColumn": 31, +"endLine": 107, +"endColumn": 38 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\IO\Udp.cs", +"region": { +"startLine": 348, +"startColumn": 31, +"endLine": 348, +"endColumn": 36 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\IO\UdpConnected.cs", +"region": { +"startLine": 95, +"startColumn": 31, +"endLine": 95, +"endColumn": 38 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\IO\UdpConnected.cs", +"region": { +"startLine": 295, +"startColumn": 31, +"endLine": 295, +"endColumn": 36 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\Pattern\BackoffOptions.cs", +"region": { +"startLine": 243, +"startColumn": 27, +"endLine": 243, +"endColumn": 38 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\Routing\Listeners.cs", +"region": { +"startLine": 37, +"startColumn": 27, +"endLine": 37, +"endColumn": 42 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\Routing\ResizablePoolActor.cs", +"region": { +"startLine": 70, +"startColumn": 18, +"endLine": 70, +"endColumn": 24 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka\Routing\RouterMsg.cs", +"region": { +"startLine": 31, +"startColumn": 27, +"endLine": 31, +"endColumn": 50 +} +} +} +] +} 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 new file mode 100644 index 00000000000..025ab6cbded --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.Benchmarks--netcoreapp3.1-S2094.json @@ -0,0 +1,30 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\benchmark\Akka.Benchmarks\IO\TcpOperationsBenchmarks.cs", +"region": { +"startLine": 76, +"startColumn": 22, +"endLine": 76, +"endColumn": 43 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\benchmark\Akka.Benchmarks\IO\TcpOperationsBenchmarks.cs", +"region": { +"startLine": 77, +"startColumn": 22, +"endLine": 77, +"endColumn": 48 +} +} +} +] +} 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 new file mode 100644 index 00000000000..7e3af656072 --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.Cluster--netstandard2.0-S2094.json @@ -0,0 +1,30 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Cluster\ClusterDaemon.cs", +"region": { +"startLine": 281, +"startColumn": 24, +"endLine": 281, +"endColumn": 36 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Cluster\ClusterHeartbeat.cs", +"region": { +"startLine": 368, +"startColumn": 23, +"endLine": 368, +"endColumn": 36 +} +} +} +] +} 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 new file mode 100644 index 00000000000..8bbc0bb2e33 --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.DI.TestKit--netstandard2.0-S2094.json @@ -0,0 +1,56 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\contrib\dependencyinjection\Akka.DI.TestKit\DiResolverSpec.cs", +"region": { +"startLine": 28, +"startColumn": 15, +"endLine": 28, +"endColumn": 27 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\contrib\dependencyinjection\Akka.DI.TestKit\DiResolverSpec.cs", +"region": { +"startLine": 60, +"startColumn": 26, +"endLine": 60, +"endColumn": 34 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\contrib\dependencyinjection\Akka.DI.TestKit\DiResolverSpec.cs", +"region": { +"startLine": 76, +"startColumn": 26, +"endLine": 76, +"endColumn": 33 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\contrib\dependencyinjection\Akka.DI.TestKit\DiResolverSpec.cs", +"region": { +"startLine": 78, +"startColumn": 26, +"endLine": 78, +"endColumn": 37 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--net471-S2094.json b/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--net471-S2094.json new file mode 100644 index 00000000000..3c7716e2695 --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--net471-S2094.json @@ -0,0 +1,30 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner\Program.cs", +"region": { +"startLine": 617, +"startColumn": 22, +"endLine": 617, +"endColumn": 34 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner\Program.cs", +"region": { +"startLine": 618, +"startColumn": 22, +"endLine": 618, +"endColumn": 37 +} +} +} +] +} 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 new file mode 100644 index 00000000000..3c7716e2695 --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--net5.0-S2094.json @@ -0,0 +1,30 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner\Program.cs", +"region": { +"startLine": 617, +"startColumn": 22, +"endLine": 617, +"endColumn": 34 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner\Program.cs", +"region": { +"startLine": 618, +"startColumn": 22, +"endLine": 618, +"endColumn": 37 +} +} +} +] +} 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 new file mode 100644 index 00000000000..3c7716e2695 --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner--netcoreapp3.1-S2094.json @@ -0,0 +1,30 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner\Program.cs", +"region": { +"startLine": 617, +"startColumn": 22, +"endLine": 617, +"endColumn": 34 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner\Program.cs", +"region": { +"startLine": 618, +"startColumn": 22, +"endLine": 618, +"endColumn": 37 +} +} +} +] +} 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 new file mode 100644 index 00000000000..42a6a955478 --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.MultiNodeTestRunner.Shared--netstandard2.0-S2094.json @@ -0,0 +1,121 @@ +{ +"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.", +"location": { +"uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Reporting\TestRunCoordinator.cs", +"region": { +"startLine": 27, +"startColumn": 22, +"endLine": 27, +"endColumn": 41 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Sinks\Messages.cs", +"region": { +"startLine": 156, +"startColumn": 18, +"endLine": 156, +"endColumn": 28 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Sinks\MessageSinkActor.cs", +"region": { +"startLine": 42, +"startColumn": 22, +"endLine": 42, +"endColumn": 41 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Sinks\SinkCoordinator.cs", +"region": { +"startLine": 43, +"startColumn": 22, +"endLine": 43, +"endColumn": 35 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Sinks\SinkCoordinator.cs", +"region": { +"startLine": 48, +"startColumn": 22, +"endLine": 48, +"endColumn": 32 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Sinks\SinkCoordinator.cs", +"region": { +"startLine": 66, +"startColumn": 22, +"endLine": 66, +"endColumn": 37 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Sinks\TimelineLogCollectorActor.cs", +"region": { +"startLine": 190, +"startColumn": 22, +"endLine": 190, +"endColumn": 31 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.MultiNodeTestRunner.Shared\Sinks\TimelineLogCollectorActor.cs", +"region": { +"startLine": 202, +"startColumn": 22, +"endLine": 202, +"endColumn": 32 +} +} +} +] +} 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 new file mode 100644 index 00000000000..f22783e9ead --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.Remote--netstandard2.0-S2094.json @@ -0,0 +1,186 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote\Endpoint.cs", +"region": { +"startLine": 794, +"startColumn": 22, +"endLine": 794, +"endColumn": 45 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote\Endpoint.cs", +"region": { +"startLine": 799, +"startColumn": 22, +"endLine": 799, +"endColumn": 28 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote\EndpointManager.cs", +"region": { +"startLine": 115, +"startColumn": 31, +"endLine": 115, +"endColumn": 46 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote\EndpointManager.cs", +"region": { +"startLine": 140, +"startColumn": 29, +"endLine": 140, +"endColumn": 44 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote\EndpointManager.cs", +"region": { +"startLine": 145, +"startColumn": 29, +"endLine": 145, +"endColumn": 45 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote\EndpointManager.cs", +"region": { +"startLine": 289, +"startColumn": 29, +"endLine": 289, +"endColumn": 34 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote\Transport\AkkaPduCodec.cs", +"region": { +"startLine": 95, +"startColumn": 27, +"endLine": 95, +"endColumn": 36 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote\Transport\AkkaProtocolTransport.cs", +"region": { +"startLine": 491, +"startColumn": 20, +"endLine": 491, +"endColumn": 34 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote\Transport\AkkaProtocolTransport.cs", +"region": { +"startLine": 493, +"startColumn": 20, +"endLine": 493, +"endColumn": 34 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote\Transport\AkkaProtocolTransport.cs", +"region": { +"startLine": 538, +"startColumn": 29, +"endLine": 538, +"endColumn": 46 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote\Transport\AkkaProtocolTransport.cs", +"region": { +"startLine": 542, +"startColumn": 29, +"endLine": 542, +"endColumn": 53 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote\Transport\AkkaProtocolTransport.cs", +"region": { +"startLine": 722, +"startColumn": 20, +"endLine": 722, +"endColumn": 38 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote\Transport\TestTransport.cs", +"region": { +"startLine": 297, +"startColumn": 27, +"endLine": 297, +"endColumn": 35 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote\Transport\ThrottleTransportAdapter.cs", +"region": { +"startLine": 947, +"startColumn": 22, +"endLine": 947, +"endColumn": 29 +} +} +} +] +} 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 new file mode 100644 index 00000000000..76790ed6bfc --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.Remote.Tests.Performance--netcoreapp3.1-S2094.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Remote.Tests.Performance\Transports\RemoteMessagingThroughputSpecBase.cs", +"region": { +"startLine": 43, +"startColumn": 26, +"endLine": 43, +"endColumn": 36 +} +} +} +] +} 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 new file mode 100644 index 00000000000..62e548be722 --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.Streams--netstandard2.0-S2094.json @@ -0,0 +1,69 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Streams\Attributes.cs", +"region": { +"startLine": 257, +"startColumn": 26, +"endLine": 257, +"endColumn": 39 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Streams\Attributes.cs", +"region": { +"startLine": 263, +"startColumn": 26, +"endLine": 263, +"endColumn": 35 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Streams\Attributes.cs", +"region": { +"startLine": 275, +"startColumn": 26, +"endLine": 275, +"endColumn": 42 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Streams\Stage\Context.cs", +"region": { +"startLine": 53, +"startColumn": 25, +"endLine": 53, +"endColumn": 38 +} +} +}, +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Streams\Stage\Stage.cs", +"region": { +"startLine": 92, +"startColumn": 27, +"endLine": 92, +"endColumn": 40 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/Akka.Tests.Performance--netcoreapp3.1-S2094.json b/analyzers/its/expected/akka.net/Akka.Tests.Performance--netcoreapp3.1-S2094.json new file mode 100644 index 00000000000..b8f785e6216 --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.Tests.Performance--netcoreapp3.1-S2094.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\core\Akka.Tests.Performance\Actor\ActorSystemShutdownSpec.cs", +"region": { +"startLine": 19, +"startColumn": 23, +"endLine": 19, +"endColumn": 38 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/RemotePingPong--net471-S2094.json b/analyzers/its/expected/akka.net/RemotePingPong--net471-S2094.json new file mode 100644 index 00000000000..19fa046d379 --- /dev/null +++ b/analyzers/its/expected/akka.net/RemotePingPong--net471-S2094.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\benchmark\RemotePingPong\Program.cs", +"region": { +"startLine": 226, +"startColumn": 26, +"endLine": 226, +"endColumn": 36 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/RemotePingPong--net5.0-S2094.json b/analyzers/its/expected/akka.net/RemotePingPong--net5.0-S2094.json new file mode 100644 index 00000000000..19fa046d379 --- /dev/null +++ b/analyzers/its/expected/akka.net/RemotePingPong--net5.0-S2094.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\benchmark\RemotePingPong\Program.cs", +"region": { +"startLine": 226, +"startColumn": 26, +"endLine": 226, +"endColumn": 36 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/RemotePingPong--netcoreapp3.1-S2094.json b/analyzers/its/expected/akka.net/RemotePingPong--netcoreapp3.1-S2094.json new file mode 100644 index 00000000000..19fa046d379 --- /dev/null +++ b/analyzers/its/expected/akka.net/RemotePingPong--netcoreapp3.1-S2094.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\benchmark\RemotePingPong\Program.cs", +"region": { +"startLine": 226, +"startColumn": 26, +"endLine": 226, +"endColumn": 36 +} +} +} +] +} 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 new file mode 100644 index 00000000000..0c7c2328513 --- /dev/null +++ b/analyzers/its/expected/akka.net/TcpEchoService.Server--netcoreapp3.1-S2094.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, or add members to it.", +"location": { +"uri": "sources\akka.net\src\examples\TcpEchoService.Server\Actors.cs", +"region": { +"startLine": 47, +"startColumn": 22, +"endLine": 47, +"endColumn": 32 +} +} +} +] +} From a13ef28c7797681ceadb24490c8c659ac90e4608 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Tue, 14 Feb 2023 19:02:13 +0100 Subject: [PATCH 11/37] Fix Java test cases --- its/projects/NoSonarTest/Class1.cs | 3 ++- its/projects/VbNoSonarTest/Class1.vb | 5 +++++ .../WebConfig.CSharp/WebConfig.CSharp/Dummy.cs | 3 ++- its/projects/WebConfig.VB/WebConfig.VB/Dummy.vb | 5 +++++ .../sonar/it/csharp/IncrementalAnalysisTest.java | 13 +++++++++++-- .../it/csharp/ProjectLevelDuplicationTest.java | 2 +- 6 files changed, 26 insertions(+), 5 deletions(-) diff --git a/its/projects/NoSonarTest/Class1.cs b/its/projects/NoSonarTest/Class1.cs index 478cb4f4c4c..3c8694a6917 100644 --- a/its/projects/NoSonarTest/Class1.cs +++ b/its/projects/NoSonarTest/Class1.cs @@ -2,8 +2,9 @@ namespace CSLib.foo { - class IFoo + public class IFoo { + public int Prop => 42; } class IBar // NOSONAR diff --git a/its/projects/VbNoSonarTest/Class1.vb b/its/projects/VbNoSonarTest/Class1.vb index f74b5360629..f4ceb44c546 100644 --- a/its/projects/VbNoSonarTest/Class1.vb +++ b/its/projects/VbNoSonarTest/Class1.vb @@ -17,4 +17,9 @@ Public Class IFoo ' NOSONAR End Class Public Class ABCDEFGHIJKLMNOPQRSTUVWXYZ + Public ReadOnly Property Prop As Integer + Get + Return 42 + End Get + End Property End Class diff --git a/its/projects/WebConfig.CSharp/WebConfig.CSharp/Dummy.cs b/its/projects/WebConfig.CSharp/WebConfig.CSharp/Dummy.cs index ee8e16dbee4..5b95f27d009 100644 --- a/its/projects/WebConfig.CSharp/WebConfig.CSharp/Dummy.cs +++ b/its/projects/WebConfig.CSharp/WebConfig.CSharp/Dummy.cs @@ -1,4 +1,5 @@ -public class Dummy +public class Dummy { // This is present just to have a file to analyze. + public int Prop => 42; } diff --git a/its/projects/WebConfig.VB/WebConfig.VB/Dummy.vb b/its/projects/WebConfig.VB/WebConfig.VB/Dummy.vb index 507f3246d11..7cc629b4999 100644 --- a/its/projects/WebConfig.VB/WebConfig.VB/Dummy.vb +++ b/its/projects/WebConfig.VB/WebConfig.VB/Dummy.vb @@ -1,3 +1,8 @@ Public Class Dummy ' This is present just to have a file to analyze. + Public ReadOnly Property Prop As Integer + Get + Return 42 + End Get + End Property End Class diff --git a/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java b/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java index b86be5883e0..a6e9282ef8f 100644 --- a/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java +++ b/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java @@ -29,6 +29,7 @@ import java.io.FileWriter; import java.io.IOException; import java.nio.file.Path; +import java.util.stream.Collectors; import java.util.List; import org.junit.Before; import org.junit.Rule; @@ -110,7 +111,11 @@ public void incrementalPrAnalysis_cacheAvailableChangesDone_issuesReportedForCha assertThat(endStepResults.getLogs()).doesNotContain("Adding normal issue S1134: " + unchanged2Path); assertThat(endStepResults.getLogs()).contains("Adding normal issue S1134: " + withChangesPath); assertThat(endStepResults.getLogs()).contains("Adding normal issue S1134: " + fileToBeAddedPath); - List allIssues = TestUtils.getIssues(ORCHESTRATOR, PROJECT, "42"); + List allIssues = TestUtils + .getIssues(ORCHESTRATOR, PROJECT, "42") + .stream() + .filter(x -> x.getRule().startsWith("csharpsquid:S1134")) + .collect(Collectors.toList()); assertThat(allIssues).hasSize(2); assertThat(allIssues.get(0).getRule()).isEqualTo("csharpsquid:S1134"); assertThat(allIssues.get(0).getComponent()).isEqualTo("IncrementalPRAnalysis:IncrementalPRAnalysis/AddedFile.cs"); @@ -132,7 +137,11 @@ public void incrementalPrAnalysis_cacheAvailableProjectBaseDirChanged_everything assertTrue(endStepResults.isSuccess()); assertCacheIsUsed(beginStepResults, PROJECT); assertAllFilesWereAnalysed(endStepResults, projectDir); - List allIssues = TestUtils.getIssues(ORCHESTRATOR, PROJECT, "42"); + List allIssues = TestUtils + .getIssues(ORCHESTRATOR, PROJECT, "42") + .stream() + .filter(x -> x.getRule().startsWith("csharpsquid:S1134")) + .collect(Collectors.toList()); assertThat(allIssues).hasSize(3); assertThat(allIssues.get(0).getRule()).isEqualTo("csharpsquid:S1134"); assertThat(allIssues.get(0).getComponent()).isEqualTo("IncrementalPRAnalysis:Unchanged1.cs"); diff --git a/its/src/test/java/com/sonar/it/csharp/ProjectLevelDuplicationTest.java b/its/src/test/java/com/sonar/it/csharp/ProjectLevelDuplicationTest.java index 569bec85251..446eebab298 100644 --- a/its/src/test/java/com/sonar/it/csharp/ProjectLevelDuplicationTest.java +++ b/its/src/test/java/com/sonar/it/csharp/ProjectLevelDuplicationTest.java @@ -52,7 +52,7 @@ public void containsOnlyOneProjectLevelIssue() throws Exception { List issues = getIssues("ProjectLevelDuplication") .stream() - .filter(x -> x.getRule().startsWith("csharpsquid:")) + .filter(x -> x.getRule().startsWith("csharpsquid:S3904")) .collect(Collectors.toList()); assertThat(issues).hasSize(1); } From 1905bc3be746775f73e8e113437b1ce22ce0f66b Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Tue, 14 Feb 2023 19:12:55 +0100 Subject: [PATCH 12/37] Update VB.NET rule description --- analyzers/rspec/vbnet/S2094_vb.net.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyzers/rspec/vbnet/S2094_vb.net.html b/analyzers/rspec/vbnet/S2094_vb.net.html index 49ccfc9495f..1aac2d59fd1 100644 --- a/analyzers/rspec/vbnet/S2094_vb.net.html +++ b/analyzers/rspec/vbnet/S2094_vb.net.html @@ -1,5 +1,5 @@

There is no good excuse for an empty class. If it’s being used simply as a common extension point, it should be replaced with an -interface. If it was stubbed in as a placeholder for future development it should be fleshed-out. In any other case, it should be +Interface. If it was stubbed in as a placeholder for future development it should be fleshed-out. In any other case, it should be eliminated.

Noncompliant Code Example


From c0e7b2959afe9cf2a271d9fdd70f1a9e9389ebc0 Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Wed, 15 Feb 2023 09:39:12 +0100
Subject: [PATCH 13/37] Refactor IsEmpty method and SyntaxFacade

---
 .../Facade/CSharpSyntaxKindFacade.cs              |  2 +-
 .../Rules/ClassShouldNotBeEmpty.cs                | 10 ++++------
 .../Facade/ISyntaxKindFacade.cs                   |  2 +-
 .../Rules/ClassNotInstantiatableBase.cs           |  6 +++---
 .../Rules/ClassShouldNotBeEmptyBase.cs            | 15 +++++++--------
 .../Rules/ExceptionsShouldBePublicBase.cs         |  2 +-
 .../Facade/VisualBasicSyntaxKindFacade.cs         |  2 +-
 .../Rules/ClassShouldNotBeEmpty.cs                |  6 ++----
 8 files changed, 20 insertions(+), 25 deletions(-)

diff --git a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs
index 0c3bc40a7e5..fb4acffd24a 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs
@@ -24,7 +24,7 @@ internal sealed class CSharpSyntaxKindFacade : ISyntaxKindFacade
 {
     public SyntaxKind Attribute => SyntaxKind.Attribute;
     public SyntaxKind ClassDeclaration => SyntaxKind.ClassDeclaration;
-    public SyntaxKind[] ClassAndRecordClassDeclaration => new[]
+    public SyntaxKind[] ClassAndRecordClassDeclarations => new[]
     {
         SyntaxKind.ClassDeclaration,
         SyntaxKindEx.RecordClassDeclaration,
diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs
index 823be7c8e95..122e898c7bc 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs
@@ -25,13 +25,11 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase Language => CSharpFacade.Instance;
 
-    protected override bool IsEmptyClass(SyntaxNode node) => node is ClassDeclarationSyntax { Members.Count : 0 };
-
-    protected override bool IsEmptyRecordClass(SyntaxNode node) =>
-        RecordDeclarationSyntaxWrapper.IsInstance(node)
-        && (RecordDeclarationSyntaxWrapper)node is { Members.Count: 0, ParameterList.Parameters.Count: 0 };
+    protected override bool IsEmpty(SyntaxNode node) =>
+        node is ClassDeclarationSyntax { Members.Count: 0 }
+        || (RecordDeclarationSyntaxWrapper.IsInstance(node) && (RecordDeclarationSyntaxWrapper)node is { Members.Count: 0, ParameterList.Parameters.Count: 0 });
 
     protected override bool IsClassWithDeclaredBaseClass(SyntaxNode node) => node is ClassDeclarationSyntax { BaseList: not null };
 
-    protected override string DeclaredTypeNameOf(SyntaxNode node) => ((TypeDeclarationSyntax)node).Keyword.ValueText;
+    protected override string DeclarationTypeKeyword(SyntaxNode node) => ((TypeDeclarationSyntax)node).Keyword.ValueText;
 }
diff --git a/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs b/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs
index c54e0a9ed54..74343e74ed0 100644
--- a/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs
@@ -25,7 +25,7 @@ public interface ISyntaxKindFacade
 {
     abstract TSyntaxKind Attribute { get; }
     abstract TSyntaxKind ClassDeclaration { get; }
-    abstract TSyntaxKind[] ClassAndRecordClassDeclaration { get; }
+    abstract TSyntaxKind[] ClassAndRecordClassDeclarations { get; }
     abstract TSyntaxKind[] ClassAndModuleDeclarations { get; }
     abstract TSyntaxKind[] CommentTrivia { get; }
     abstract TSyntaxKind[] ComparisonKinds { get; }
diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassNotInstantiatableBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassNotInstantiatableBase.cs
index dc68273782b..fc6d0a8a846 100644
--- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassNotInstantiatableBase.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassNotInstantiatableBase.cs
@@ -35,8 +35,8 @@ public abstract class ClassNotInstantiatableBase :
         protected override void Initialize(SonarAnalysisContext context) =>
             context.RegisterSymbolAction(CheckClassWithOnlyUnusedPrivateConstructors, SymbolKind.NamedType);
 
-        private bool IsTypeDeclaration(SyntaxNode node) =>
-            Language.Syntax.IsAnyKind(node, Language.SyntaxKind.ClassAndRecordClassDeclaration);
+        private bool IsClassTypeDeclaration(SyntaxNode node) =>
+            Language.Syntax.IsAnyKind(node, Language.SyntaxKind.ClassAndRecordClassDeclarations);
 
         private bool IsAnyConstructorCalled(INamedTypeSymbol namedType, IEnumerable typeDeclarations) =>
             typeDeclarations
@@ -79,7 +79,7 @@ private void CheckClassWithOnlyUnusedPrivateConstructors(SonarSymbolReportingCon
 
         private bool IsAnyNestedTypeExtendingCurrentType(IEnumerable descendantNodes, INamedTypeSymbol namedType, SemanticModel semanticModel) =>
             descendantNodes
-                .Where(IsTypeDeclaration)
+                .Where(IsClassTypeDeclaration)
                 .Select(x => (semanticModel.GetDeclaredSymbol(x) as ITypeSymbol)?.BaseType)
                 .WhereNotNull()
                 .Any(baseType => baseType.OriginalDefinition.DerivesFrom(namedType));
diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
index f14821351f1..af9ae822843 100644
--- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
@@ -29,6 +29,10 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn
 
     protected override string MessageFormat => "Remove this empty {0}, or add members to it.";
 
+    protected abstract bool IsEmpty(SyntaxNode node);
+    protected abstract bool IsClassWithDeclaredBaseClass(SyntaxNode node);
+    protected abstract string DeclarationTypeKeyword(SyntaxNode node);
+
     protected ClassShouldNotBeEmptyBase() : base(DiagnosticId) { }
 
     protected override void Initialize(SonarAnalysisContext context) =>
@@ -37,10 +41,10 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn
             c =>
             {
                 if (Language.Syntax.NodeIdentifier(c.Node) is { IsMissing: false } identifier
-                    && (IsEmptyClass(c.Node) || IsEmptyRecordClass(c.Node))
+                    && IsEmpty(c.Node)
                     && !ShouldIgnoreBecauseOfBaseClass(c.Node, c.SemanticModel))
                 {
-                    c.ReportIssue(Diagnostic.Create(Rule, identifier.GetLocation(), DeclaredTypeNameOf(c.Node)));
+                    c.ReportIssue(Diagnostic.Create(Rule, identifier.GetLocation(), DeclarationTypeKeyword(c.Node)));
                 }
 
                 bool ShouldIgnoreBecauseOfBaseClass(SyntaxNode node, SemanticModel model) =>
@@ -48,10 +52,5 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn
                     && model.GetDeclaredSymbol(node) is INamedTypeSymbol classSymbol
                     && classSymbol.DerivesFromAny(SubClassesToIgnore);
             },
-            Language.SyntaxKind.ClassAndRecordClassDeclaration);
-
-    protected abstract bool IsEmptyClass(SyntaxNode node);
-    protected abstract bool IsEmptyRecordClass(SyntaxNode node);
-    protected abstract bool IsClassWithDeclaredBaseClass(SyntaxNode node);
-    protected abstract string DeclaredTypeNameOf(SyntaxNode node);
+            Language.SyntaxKind.ClassAndRecordClassDeclarations);
 }
diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ExceptionsShouldBePublicBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ExceptionsShouldBePublicBase.cs
index 2d46f590c5e..5abe025433b 100644
--- a/analyzers/src/SonarAnalyzer.Common/Rules/ExceptionsShouldBePublicBase.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Rules/ExceptionsShouldBePublicBase.cs
@@ -46,5 +46,5 @@ public abstract class ExceptionsShouldBePublicBase : SonarDiagnosti
                     c.ReportIssue(Diagnostic.Create(Rule, Language.Syntax.NodeIdentifier(c.Node).Value.GetLocation()));
                 }
             },
-            Language.SyntaxKind.ClassAndRecordClassDeclaration);
+            Language.SyntaxKind.ClassAndRecordClassDeclarations);
 }
diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs
index 930e53b41c3..ba546b61865 100644
--- a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs
@@ -24,7 +24,7 @@ internal sealed class VisualBasicSyntaxKindFacade : ISyntaxKindFacade SyntaxKind.Attribute;
     public SyntaxKind ClassDeclaration => SyntaxKind.ClassBlock;
-    public SyntaxKind[] ClassAndRecordClassDeclaration => new[] { SyntaxKind.ClassBlock };
+    public SyntaxKind[] ClassAndRecordClassDeclarations => new[] { SyntaxKind.ClassBlock };
     public SyntaxKind[] ClassAndModuleDeclarations => new[]
     {
         SyntaxKind.ClassBlock,
diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs
index 18b9738a32f..aeca85c8c63 100644
--- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs
@@ -25,11 +25,9 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase Language => VisualBasicFacade.Instance;
 
-    protected override bool IsEmptyClass(SyntaxNode node) => node is ClassBlockSyntax { Members.Count : 0 };
-
-    protected override bool IsEmptyRecordClass(SyntaxNode node) => false;
+    protected override bool IsEmpty(SyntaxNode node) => node is ClassBlockSyntax { Members.Count: 0 };
 
     protected override bool IsClassWithDeclaredBaseClass(SyntaxNode node) => node is ClassBlockSyntax { Inherits.Count: > 0 };
 
-    protected override string DeclaredTypeNameOf(SyntaxNode node) => ((TypeBlockSyntax)node).BlockStatement.DeclarationKeyword.ValueText.ToLower();
+    protected override string DeclarationTypeKeyword(SyntaxNode node) => ((TypeBlockSyntax)node).BlockStatement.DeclarationKeyword.ValueText.ToLower();
 }

From 948cbc098e72ec6421b1312b57b2849395c64df8 Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Wed, 15 Feb 2023 10:34:18 +0100
Subject: [PATCH 14/37] Add System.Exception to list of ignored subclasses

---
 .../Rules/ClassShouldNotBeEmptyBase.cs                   | 4 +++-
 .../TestCases/ClassShouldNotBeEmpty.CSharp10.cs          | 3 +--
 .../TestCases/ClassShouldNotBeEmpty.CSharp9.cs           | 3 +--
 .../TestCases/ClassShouldNotBeEmpty.Inheritance.cs       | 9 +++++++--
 .../TestCases/ClassShouldNotBeEmpty.Inheritance.vb       | 5 +++++
 .../TestCases/ClassShouldNotBeEmpty.cs                   | 3 +--
 6 files changed, 18 insertions(+), 9 deletions(-)

diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
index af9ae822843..9d0a004d09e 100644
--- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
@@ -25,7 +25,9 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn
 {
     private const string DiagnosticId = "S2094";
 
-    private static readonly ImmutableArray SubClassesToIgnore = ImmutableArray.Create(KnownType.Microsoft_AspNetCore_Mvc_RazorPages_PageModel);
+    private static readonly ImmutableArray SubClassesToIgnore = ImmutableArray.Create(
+        KnownType.Microsoft_AspNetCore_Mvc_RazorPages_PageModel,
+        KnownType.System_Exception);
 
     protected override string MessageFormat => "Remove this empty {0}, or add members to it.";
 
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs
index 9e0649a9c14..8cae02d75cc 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs
@@ -1,5 +1,4 @@
-using System;
-
+
 record class EmptyRecordClass1();        // Noncompliant {{Remove this empty record, or add members to it.}}
 //           ^^^^^^^^^^^^^^^^^
 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 9014be91a30..ef03bd125d6 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs
@@ -1,5 +1,4 @@
-using System;
-
+
 record EmptyRecord1();                      // Noncompliant {{Remove this empty record, or add members to it.}}
 //     ^^^^^^^^^^^^
 record EmptyRecord2() { };                  // Noncompliant
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs
index 53c0557e8d3..0fe41e23355 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs
@@ -1,12 +1,17 @@
 using Microsoft.AspNetCore.Mvc.RazorPages;
+using System;
 
 public class BaseClass
 {
     int Prop => 0;
 }
-public class SubClass: BaseClass { }   // Noncompliant - not derived from any special base class
+public class SubClass: BaseClass { }    // Noncompliant - not derived from any special base class
 
-public class EmptyPageModel: PageModel // Compliant - an empty PageModel can be fully functional, the C# code can be in the cshtml file
+public class EmptyPageModel: PageModel  // Compliant - an empty PageModel can be fully functional, the C# code can be in the cshtml file
+{
+}
+
+public class CustomException: Exception // Compliant - empty exception classes are allowed, the name of the class already provides information
 {
 }
 
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb
index 9c7c50654be..5845fdbd185 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 System
 
 Public Class BaseClass
     Private ReadOnly Property Prop As Integer
@@ -15,3 +16,7 @@ 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 CustomException ' Compliant - empty exception classes are allowed, the name of the class already provides information
+    Inherits Exception
+End Class
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
index 6cf4440d18f..81a2a9ac8cf 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
@@ -1,5 +1,4 @@
-using System;
-
+
 class Empty { }                              // Noncompliant {{Remove this empty class, or add members to it.}}
 //    ^^^^^
 

From 8434811cbebaaa1b64f1a3a49313a6989667953f Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Wed, 15 Feb 2023 10:36:25 +0100
Subject: [PATCH 15/37] Replace property constants

---
 .../TestCases/ClassShouldNotBeEmpty.CSharp10.cs             | 4 ++--
 .../TestCases/ClassShouldNotBeEmpty.CSharp9.cs              | 2 +-
 .../TestCases/ClassShouldNotBeEmpty.Inheritance.cs          | 2 +-
 .../TestCases/ClassShouldNotBeEmpty.Inheritance.vb          | 2 +-
 .../TestCases/ClassShouldNotBeEmpty.cs                      | 6 +++---
 .../TestCases/ClassShouldNotBeEmpty.vb                      | 6 +++---
 6 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs
index 8cae02d75cc..b80fcaa1e26 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs
@@ -9,11 +9,11 @@ record struct EmptyRecordStruct2() { };
 record class NotEmptyRecordClass1(int RecordMember);
 record class NotEmptyRecordClass2()
 {
-    int RecordMember => 0;
+    int RecordMember => 42;
 };
 
 record struct NotEmptyRecordStruct1(int RecordMember);
 record struct NotEmptyRecordStruct2()
 {
-    int RecordMember => 0;
+    int RecordMember => 42;
 };
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs
index ef03bd125d6..ece089447cb 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs
@@ -6,5 +6,5 @@ record EmptyRecord2() { };                  // Noncompliant
 record NotEmptyRecord1(int RecordMember);
 record NotEmptyRecord2()
 {
-    int RecordMember => 0;
+    int RecordMember => 42;
 };
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs
index 0fe41e23355..a291e234a67 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs
@@ -3,7 +3,7 @@
 
 public class BaseClass
 {
-    int Prop => 0;
+    int Prop => 42;
 }
 public class SubClass: BaseClass { }    // Noncompliant - not derived from any special base class
 
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb
index 5845fdbd185..d0340925474 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb
@@ -4,7 +4,7 @@ Imports System
 Public Class BaseClass
     Private ReadOnly Property Prop As Integer
         Get
-            Return 0
+            Return 42
         End Get
     End Property
 End Class
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
index 81a2a9ac8cf..9183f8c2a95 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
@@ -12,7 +12,7 @@ class EmptyWithComments                      // Noncompliant
 
 class NotEmpty
 {
-    public int SomeProperty => 0;
+    public int SomeProperty => 42;
 }
 
 class OuterClass
@@ -31,7 +31,7 @@ public class InnerEmptyWithComments      // Noncompliant
 
     class InnerNonEmpty
     {
-        public int SomeProperty => 0;
+        public int SomeProperty => 42;
     }
 }
 
@@ -40,7 +40,7 @@ static class StaticEmpty { }                 // Noncompliant
 partial class PartialEmpty { }               // Noncompliant
 partial class PartialEmpty
 {
-    public int SomeProperty => 0;
+    public int SomeProperty => 42;
 }
 
 interface IMarker { }                        // Compliant - this rule only deals with classes
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
index ba78f3ab8ed..5b649830fde 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
@@ -17,7 +17,7 @@ End Class
 Class NotEmpty
     Public ReadOnly Property SomeProperty As Integer
         Get
-            Return 0
+            Return 42
         End Get
     End Property
 End Class
@@ -48,7 +48,7 @@ Class OuterClass
     Class InnerNonEmpty
         Public ReadOnly Property SomeProperty As Integer
             Get
-                Return 0
+                Return 42
             End Get
         End Property
     End Class
@@ -60,7 +60,7 @@ End Class
 Partial Class PartialEmpty
     Public ReadOnly Property SomeProperty As Integer
         Get
-            Return 0
+            Return 42
         End Get
     End Property
 End Class

From b68c4be7a758a6eb060a3e84922c9abcca415502 Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Wed, 15 Feb 2023 11:15:52 +0100
Subject: [PATCH 16/37] Add test cases for abstract classes

---
 .../Rules/ClassShouldNotBeEmptyBase.cs          |  2 +-
 .../ClassShouldNotBeEmpty.Inheritance.cs        | 16 +++++++++++-----
 .../ClassShouldNotBeEmpty.Inheritance.vb        | 17 +++++++++++++++++
 3 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
index 9d0a004d09e..02ecbb7cbef 100644
--- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
@@ -52,7 +52,7 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn
                 bool ShouldIgnoreBecauseOfBaseClass(SyntaxNode node, SemanticModel model) =>
                     IsClassWithDeclaredBaseClass(node)
                     && model.GetDeclaredSymbol(node) is INamedTypeSymbol classSymbol
-                    && classSymbol.DerivesFromAny(SubClassesToIgnore);
+                    && (classSymbol.BaseType is { IsAbstract: true } || classSymbol.DerivesFromAny(SubClassesToIgnore));
             },
             Language.SyntaxKind.ClassAndRecordClassDeclarations);
 }
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs
index a291e234a67..c1753d38de1 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.cs
@@ -1,17 +1,23 @@
 using Microsoft.AspNetCore.Mvc.RazorPages;
 using System;
 
-public class BaseClass
+class BaseClass
 {
     int Prop => 42;
 }
-public class SubClass: BaseClass { }    // Noncompliant - not derived from any special base class
+class SubClass: BaseClass { }                                        // Noncompliant - not derived from any special base class
 
-public class EmptyPageModel: PageModel  // Compliant - an empty PageModel can be fully functional, the C# code can be in the cshtml file
+abstract class AbstractBaseWithAbstractMethods
 {
+    public abstract void AbstractMethod();
 }
-
-public class CustomException: Exception // Compliant - empty exception classes are allowed, the name of the class already provides information
+abstract class AbstractBaseWithoutAbstractMethods
 {
+    public virtual void DefaultMethod() { }
 }
+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
 
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb
index d0340925474..60cf17b6482 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.Inheritance.vb
@@ -13,6 +13,23 @@ Public Class SubClass       ' Noncompliant - not derived from any special base c
     Inherits BaseClass
 End Class
 
+MustInherit Class AbstractBaseWithAbstractMethods
+    Public MustOverride Sub AbstractMethod()
+End Class
+
+MustInherit Class AbstractBaseWithoutAbstractMethods
+    Public Overridable Sub DefaultMethod()
+    End Sub
+End Class
+
+Class NoImplementation       ' Error - abstract methods should be implemented
+    Inherits AbstractBaseWithAbstractMethods
+End Class
+
+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
 End Class

From cfb5601a91d808fba4bc5211109d8a01833b9437 Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Wed, 15 Feb 2023 11:39:16 +0100
Subject: [PATCH 17/37] Add test cases for generic classes and records

---
 .../Rules/ClassShouldNotBeEmptyBase.cs        |  4 +-
 .../Rules/ClassShouldNotBeEmptyTest.cs        | 30 +++++++++---
 .../ClassShouldNotBeEmpty.CSharp9.cs          | 10 +++-
 .../TestCases/ClassShouldNotBeEmpty.cs        | 18 +++++++
 .../TestCases/ClassShouldNotBeEmpty.vb        | 48 +++++++++++++------
 5 files changed, 84 insertions(+), 26 deletions(-)

diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
index 02ecbb7cbef..f5c66fb34be 100644
--- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
@@ -25,7 +25,7 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn
 {
     private const string DiagnosticId = "S2094";
 
-    private static readonly ImmutableArray SubClassesToIgnore = ImmutableArray.Create(
+    private static readonly ImmutableArray BaseClassesToIgnore = ImmutableArray.Create(
         KnownType.Microsoft_AspNetCore_Mvc_RazorPages_PageModel,
         KnownType.System_Exception);
 
@@ -52,7 +52,7 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn
                 bool ShouldIgnoreBecauseOfBaseClass(SyntaxNode node, SemanticModel model) =>
                     IsClassWithDeclaredBaseClass(node)
                     && model.GetDeclaredSymbol(node) is INamedTypeSymbol classSymbol
-                    && (classSymbol.BaseType is { IsAbstract: true } || classSymbol.DerivesFromAny(SubClassesToIgnore));
+                    && (classSymbol.BaseType is { IsAbstract: true } || classSymbol.DerivesFromAny(BaseClassesToIgnore));
             },
             Language.SyntaxKind.ClassAndRecordClassDeclarations);
 }
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs
index eb448c80d0a..e5c2643502a 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ClassShouldNotBeEmptyTest.cs
@@ -31,34 +31,50 @@ public class ClassShouldNotBeEmptyTest
 
     [TestMethod]
     public void ClassShouldNotBeEmpty_CS() =>
-        builderCS.AddPaths("ClassShouldNotBeEmpty.cs").Verify();
+        builderCS
+            .AddPaths("ClassShouldNotBeEmpty.cs")
+            .Verify();
 
     [TestMethod]
     public void ClassShouldNotBeEmpty_VB() =>
-        builderVB.AddPaths("ClassShouldNotBeEmpty.vb").Verify();
+        builderVB
+            .AddPaths("ClassShouldNotBeEmpty.vb")
+            .Verify();
 
 #if NET
 
-    private static IEnumerable AdditionalReferences => new[]
+    private static readonly MetadataReference[] AdditionalReferences = new[]
     {
         AspNetCoreMetadataReference.MicrosoftAspNetCoreRazorPages
     };
 
     [TestMethod]
     public void ClassShouldNotBeEmpty_CSharp9() =>
-        builderCS.AddPaths("ClassShouldNotBeEmpty.CSharp9.cs").WithOptions(ParseOptionsHelper.FromCSharp9).Verify();
+        builderCS
+            .AddPaths("ClassShouldNotBeEmpty.CSharp9.cs")
+            .WithOptions(ParseOptionsHelper.FromCSharp9)
+            .Verify();
 
     [TestMethod]
     public void ClassShouldNotBeEmpty_CSharp10() =>
-        builderCS.AddPaths("ClassShouldNotBeEmpty.CSharp10.cs").WithOptions(ParseOptionsHelper.FromCSharp10).Verify();
+        builderCS
+            .AddPaths("ClassShouldNotBeEmpty.CSharp10.cs")
+            .WithOptions(ParseOptionsHelper.FromCSharp10)
+            .Verify();
 
     [TestMethod]
     public void ClassShouldNotBeEmpty_Inheritance_CS() =>
-        builderCS.AddPaths("ClassShouldNotBeEmpty.Inheritance.cs").AddReferences(AdditionalReferences).Verify();
+        builderCS
+            .AddPaths("ClassShouldNotBeEmpty.Inheritance.cs")
+            .AddReferences(AdditionalReferences)
+            .Verify();
 
     [TestMethod]
     public void ClassShouldNotBeEmpty_Inheritance_VB() =>
-        builderVB.AddPaths("ClassShouldNotBeEmpty.Inheritance.vb").AddReferences(AdditionalReferences).Verify();
+        builderVB
+            .AddPaths("ClassShouldNotBeEmpty.Inheritance.vb")
+            .AddReferences(AdditionalReferences)
+            .Verify();
 
 #endif
 }
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs
index ece089447cb..b338a7b145f 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs
@@ -1,10 +1,16 @@
 
-record EmptyRecord1();                      // Noncompliant {{Remove this empty record, or add members to it.}}
+record EmptyRecord1();                                              // Noncompliant {{Remove this empty record, or add members to it.}}
 //     ^^^^^^^^^^^^
-record EmptyRecord2() { };                  // Noncompliant
+record EmptyRecord2() { };                                          // Noncompliant
 
 record NotEmptyRecord1(int RecordMember);
 record NotEmptyRecord2()
 {
     int RecordMember => 42;
 };
+
+record EmptyGenericRecord();                                     // Noncompliant
+//     ^^^^^^^^^^^^^^^^^^
+public record EmptyGenericRecordWithContraint() where T : class; // Noncompliant
+record NotEmptyGenericRecord(T RecordMember);
+public record NotEmptyGenericRecordWithContraint(T RecordMember) where T : class;
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
index 9183f8c2a95..9b4afa10855 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
@@ -35,6 +35,24 @@ class InnerNonEmpty
     }
 }
 
+class GenericEmpty { }                    // Noncompliant
+//    ^^^^^^^^^^^^
+class GenericEmptyWithConstraints         // Noncompliant
+    where T : class
+{
+}
+
+class GenericNotEmpty
+{
+    void Method(T arg) { }
+}
+class GenericNotEmptyWithConstraints
+    where T : class
+{
+    void Method(T arg) { }
+}
+
+
 static class StaticEmpty { }                 // Noncompliant
 
 partial class PartialEmpty { }               // Noncompliant
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
index 5b649830fde..379faf7c262 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
@@ -1,16 +1,16 @@
 
-Class Empty                            ' Noncompliant {{Remove this empty class, or add members to it.}}
+Class Empty                                         ' Noncompliant {{Remove this empty class, or add members to it.}}
     ' ^^^^^
 End Class
 
 
-Public Class PublicEmpty               ' Noncompliant
+Public Class PublicEmpty                            ' Noncompliant
 End Class
 
-Class InternalEmpty                    ' Noncompliant
+Class InternalEmpty                                 ' Noncompliant
 End Class
 
-Class EmptyWithComments                ' Noncompliant
+Class EmptyWithComments                             ' Noncompliant
     ' Some comment
 End Class
 
@@ -23,25 +23,25 @@ Class NotEmpty
 End Class
 
 Class OuterClass
-    Class InnerEmpty1                   ' Noncompliant
+    Class InnerEmpty1                               ' Noncompliant
     End Class
 
-    Private Class InnerEmpty2           ' Noncompliant
+    Private Class InnerEmpty2                       ' Noncompliant
     End Class
 
-    Protected Class InnerEmpty3         ' Noncompliant
+    Protected Class InnerEmpty3                     ' Noncompliant
     End Class
 
-    Class InnerEmpty4                   ' Noncompliant
+    Class InnerEmpty4                               ' Noncompliant
     End Class
 
-    Protected Class InnerEmpty5         ' Noncompliant
+    Protected Class InnerEmpty5                     ' Noncompliant
     End Class
 
-    Public Class InnerEmpty6            ' Noncompliant
+    Public Class InnerEmpty6                        ' Noncompliant
     End Class
 
-    Public Class InnerEmptyWithComments ' Noncompliant
+    Public Class InnerEmptyWithComments             ' Noncompliant
         ' Some comment
     End Class
 
@@ -54,7 +54,25 @@ Class OuterClass
     End Class
 End Class
 
-Partial Class PartialEmpty               ' Noncompliant
+Class GenericEmpty(Of T)
+    ' ^^^^^^^^^^^^
+End Class
+
+Class GenericEmptyWithConstraints(Of T As Class)    ' Noncompliant
+End Class
+
+Class GenericNotEmpty(Of T)
+    Private Sub Method(arg As T)
+    End Sub
+End Class
+
+Class GenericNotEmptyWithConstraints(Of T As Class)
+    Private Sub Method(arg As T)
+    End Sub
+End Class
+
+
+Partial Class PartialEmpty                          ' Noncompliant
 End Class
 
 Partial Class PartialEmpty
@@ -65,11 +83,11 @@ Partial Class PartialEmpty
     End Property
 End Class
 
-Interface IMarker                        ' Compliant - this rule only deals with classes
+Interface IMarker                                   ' Compliant - this rule only deals with classes
 End Interface
 
-Structure EmptyStruct                    ' Compliant - this rule only deals with classes
+Structure EmptyStruct                               ' Compliant - this rule only deals with classes
 End Structure
 
-Class                                    ' Error
+Class                                               ' Error
 End Class                                    

From 8998fc389d6fee841f0050e4ffafa6b1b8737a53 Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Wed, 15 Feb 2023 11:49:47 +0100
Subject: [PATCH 18/37] Add more compliant test cases

---
 .../TestCases/ClassShouldNotBeEmpty.cs        | 27 ++++++++++++++--
 .../TestCases/ClassShouldNotBeEmpty.vb        | 32 +++++++++++++++++--
 2 files changed, 53 insertions(+), 6 deletions(-)

diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
index 9b4afa10855..cd7b474710a 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
@@ -1,4 +1,5 @@
-
+using System;
+
 class Empty { }                              // Noncompliant {{Remove this empty class, or add members to it.}}
 //    ^^^^^
 
@@ -10,9 +11,29 @@ class EmptyWithComments                      // Noncompliant
     // Some comment
 }
 
-class NotEmpty
+class ClassWithProperty
 {
-    public int SomeProperty => 42;
+    int SomeProperty => 42;
+}
+class ClassWithField
+{
+    int SomeField = 42;
+}
+class ClassWithMethod
+{
+    void Method() { }
+}
+class ClassWithIndexer
+{
+    int this[int index] => 42;
+}
+class ClassWithDelegate
+{
+    delegate void MethodDelegate();
+}
+class ClassWithEvent
+{
+    event EventHandler CustomEvent;
 }
 
 class OuterClass
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
index 379faf7c262..a4077b4623a 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
@@ -1,4 +1,5 @@
-
+Imports System
+
 Class Empty                                         ' Noncompliant {{Remove this empty class, or add members to it.}}
     ' ^^^^^
 End Class
@@ -14,14 +15,39 @@ Class EmptyWithComments                             ' Noncompliant
     ' Some comment
 End Class
 
-Class NotEmpty
-    Public ReadOnly Property SomeProperty As Integer
+Class ClassWithProperty
+    Private ReadOnly Property SomeProperty As Integer
         Get
             Return 42
         End Get
     End Property
 End Class
 
+Class ClassWithField
+    Private SomeField As Integer = 42
+End Class
+
+Class ClassWithMethod
+    Private Sub Method()
+    End Sub
+End Class
+
+Class ClassWithIndexer
+    Private ReadOnly Property Item(index As Integer) As Integer
+        Get
+            Return 42
+        End Get
+    End Property
+End Class
+
+Class ClassWithDelegate
+    Delegate Sub MethodDelegate()
+End Class
+
+Class ClassWithEvent
+    Private Event CustomEvent As EventHandler
+End Class
+
 Class OuterClass
     Class InnerEmpty1                               ' Noncompliant
     End Class

From ed3d55eab893579ba27982861d40ea70eb4d23c1 Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Wed, 15 Feb 2023 15:03:25 +0100
Subject: [PATCH 19/37] Update RSPEC

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

diff --git a/analyzers/rspec/cs/S2094_c#.html b/analyzers/rspec/cs/S2094_c#.html
index e611be58832..beb6494732e 100644
--- a/analyzers/rspec/cs/S2094_c#.html
+++ b/analyzers/rspec/cs/S2094_c#.html
@@ -13,4 +13,14 @@ 

Compliant Solution

{ }
+

Exceptions

+

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.

+
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+public class EmptyPageModel: PageModel // Compliant - an empty PageModel can be fully functional, the C# code can be in the cshtml file
+{
+}
+
diff --git a/analyzers/rspec/vbnet/S2094_vb.net.html b/analyzers/rspec/vbnet/S2094_vb.net.html index 1aac2d59fd1..8e38afd283e 100644 --- a/analyzers/rspec/vbnet/S2094_vb.net.html +++ b/analyzers/rspec/vbnet/S2094_vb.net.html @@ -13,4 +13,14 @@

Compliant Solution

End Interface +

Exceptions

+

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.

+
+Imports Microsoft.AspNetCore.Mvc.RazorPages
+
+Public Class EmptyPageModel ' Compliant - an empty PageModel can be fully functional, the VB code can be in the vbhtml file
+    Inherits PageModel
+End Class
+
From d7498d4b659acc1228b4032f8f6736abcbb20bba Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Wed, 15 Feb 2023 15:13:15 +0100 Subject: [PATCH 20/37] More test cases for records --- .../ClassShouldNotBeEmpty.CSharp9.cs | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs index b338a7b145f..c78b9af3687 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs @@ -1,16 +1,38 @@ - +using System; + record EmptyRecord1(); // Noncompliant {{Remove this empty record, or add members to it.}} // ^^^^^^^^^^^^ record EmptyRecord2() { }; // Noncompliant -record NotEmptyRecord1(int RecordMember); -record NotEmptyRecord2() +record RecordWithParameters(int RecordMember); + +record RecordWithProperty +{ + int SomeProperty => 42; +} +record RecordWithField +{ + int SomeField = 42; +} +record RecordWithMethod +{ + void Method() { } +} +record RecordWithIndexer +{ + int this[int index] => 42; +} +record RecordWithDelegate +{ + delegate void MethodDelegate(); +} +record RecordWithEvent { - int RecordMember => 42; -}; + event EventHandler CustomEvent; +} record EmptyGenericRecord(); // Noncompliant // ^^^^^^^^^^^^^^^^^^ -public record EmptyGenericRecordWithContraint() where T : class; // Noncompliant +record EmptyGenericRecordWithContraint() where T : class; // Noncompliant record NotEmptyGenericRecord(T RecordMember); -public record NotEmptyGenericRecordWithContraint(T RecordMember) where T : class; +record NotEmptyGenericRecordWithContraint(T RecordMember) where T : class; From 4cf60e25e2e36166a29ea85e3e7bc287351f182f Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Wed, 15 Feb 2023 15:15:29 +0100 Subject: [PATCH 21/37] Test case for method override --- .../TestCases/ClassShouldNotBeEmpty.CSharp9.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs index c78b9af3687..d34db48d172 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs @@ -18,6 +18,10 @@ record RecordWithMethod { void Method() { } } +record RecordWithMethodOverride +{ + public override string ToString() => ""; +} record RecordWithIndexer { int this[int index] => 42; From 5734036a722fb117bb65c15cb5934206309fd1e3 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Wed, 15 Feb 2023 15:37:47 +0100 Subject: [PATCH 22/37] Update Sarif files --- ...97-5AFD-4813-AEDC-AF33FACEADF0}-S2094.json | 39 ------ .../expected/Nancy/Nancy--net452-S2094.json | 30 ---- .../Nancy/Nancy--netstandard2.0-S2094.json | 30 ---- ...Nancy.ViewEngines.Razor--net452-S2094.json | 17 --- .../its/expected/Net5/Net5--net5.0-S2094.json | 39 ------ .../its/expected/Net7/Net7--net7.0-S2094.json | 13 -- .../akka.net/Akka--netstandard2.0-S2094.json | 130 ------------------ .../Akka.Remote--netstandard2.0-S2094.json | 39 ------ .../Akka.Streams--netstandard2.0-S2094.json | 13 -- ...ests.Performance--netcoreapp3.1-S2094.json | 17 --- 10 files changed, 367 deletions(-) delete mode 100644 analyzers/its/expected/Nancy/Nancy--net452-S2094.json delete mode 100644 analyzers/its/expected/Nancy/Nancy--netstandard2.0-S2094.json delete mode 100644 analyzers/its/expected/Nancy/Nancy.ViewEngines.Razor--net452-S2094.json delete mode 100644 analyzers/its/expected/akka.net/Akka.Tests.Performance--netcoreapp3.1-S2094.json 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 744cfb72235..0ac2683eee3 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 @@ -12,45 +12,6 @@ "endColumn": 37 } } -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\Ember-MM\Ember.Plugins\PluginSectionHandler.cs", -"region": { -"startLine": 113, -"startColumn": 23, -"endLine": 113, -"endColumn": 45 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\Ember-MM\Ember.Plugins\PluginSectionHandler.cs", -"region": { -"startLine": 114, -"startColumn": 23, -"endLine": 114, -"endColumn": 44 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\Ember-MM\Ember.Plugins\Scraper\Exceptions.cs", -"region": { -"startLine": 9, -"startColumn": 18, -"endLine": 9, -"endColumn": 39 -} -} } ] } diff --git a/analyzers/its/expected/Nancy/Nancy--net452-S2094.json b/analyzers/its/expected/Nancy/Nancy--net452-S2094.json deleted file mode 100644 index 8295ed01fd2..00000000000 --- a/analyzers/its/expected/Nancy/Nancy--net452-S2094.json +++ /dev/null @@ -1,30 +0,0 @@ -{ -"issues": [ -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\Nancy\src\Nancy\IncludeInNancyAssemblyScanningAttribute.cs", -"region": { -"startLine": 14, -"startColumn": 25, -"endLine": 14, -"endColumn": 64 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\Nancy\src\Nancy\Json\ScriptIgnoreAttribute.cs", -"region": { -"startLine": 38, -"startColumn": 25, -"endLine": 38, -"endColumn": 46 -} -} -} -] -} diff --git a/analyzers/its/expected/Nancy/Nancy--netstandard2.0-S2094.json b/analyzers/its/expected/Nancy/Nancy--netstandard2.0-S2094.json deleted file mode 100644 index 8295ed01fd2..00000000000 --- a/analyzers/its/expected/Nancy/Nancy--netstandard2.0-S2094.json +++ /dev/null @@ -1,30 +0,0 @@ -{ -"issues": [ -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\Nancy\src\Nancy\IncludeInNancyAssemblyScanningAttribute.cs", -"region": { -"startLine": 14, -"startColumn": 25, -"endLine": 14, -"endColumn": 64 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\Nancy\src\Nancy\Json\ScriptIgnoreAttribute.cs", -"region": { -"startLine": 38, -"startColumn": 25, -"endLine": 38, -"endColumn": 46 -} -} -} -] -} diff --git a/analyzers/its/expected/Nancy/Nancy.ViewEngines.Razor--net452-S2094.json b/analyzers/its/expected/Nancy/Nancy.ViewEngines.Razor--net452-S2094.json deleted file mode 100644 index 95c17c2e5b8..00000000000 --- a/analyzers/its/expected/Nancy/Nancy.ViewEngines.Razor--net452-S2094.json +++ /dev/null @@ -1,17 +0,0 @@ -{ -"issues": [ -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\Nancy\src\Nancy.ViewEngines.Razor\NancyRazorViewBase.cs", -"region": { -"startLine": 14, -"startColumn": 27, -"endLine": 14, -"endColumn": 45 -} -} -} -] -} diff --git a/analyzers/its/expected/Net5/Net5--net5.0-S2094.json b/analyzers/its/expected/Net5/Net5--net5.0-S2094.json index 064d7f29838..90cdb625440 100644 --- a/analyzers/its/expected/Net5/Net5--net5.0-S2094.json +++ b/analyzers/its/expected/Net5/Net5--net5.0-S2094.json @@ -56,32 +56,6 @@ "id": "S2094", "message": "Remove this empty class, or add members to it.", "location": { -"uri": "sources\Net5\Net5\S2330.cs", -"region": { -"startLine": 6, -"startColumn": 23, -"endLine": 6, -"endColumn": 28 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\Net5\Net5\S2330.cs", -"region": { -"startLine": 7, -"startColumn": 23, -"endLine": 7, -"endColumn": 29 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { "uri": "sources\Net5\Net5\S3240.cs", "region": { "startLine": 5, @@ -95,19 +69,6 @@ "id": "S2094", "message": "Remove this empty class, or add members to it.", "location": { -"uri": "sources\Net5\Net5\S3240.cs", -"region": { -"startLine": 6, -"startColumn": 15, -"endLine": 6, -"endColumn": 20 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { "uri": "sources\Net5\Net5\S3247.cs", "region": { "startLine": 8, diff --git a/analyzers/its/expected/Net7/Net7--net7.0-S2094.json b/analyzers/its/expected/Net7/Net7--net7.0-S2094.json index 9cb36f3fb59..289b4af880f 100644 --- a/analyzers/its/expected/Net7/Net7--net7.0-S2094.json +++ b/analyzers/its/expected/Net7/Net7--net7.0-S2094.json @@ -4,19 +4,6 @@ "id": "S2094", "message": "Remove this empty class, or add members to it.", "location": { -"uri": "sources\Net7\Net7\features\GenericAttributes.cs", -"region": { -"startLine": 5, -"startColumn": 22, -"endLine": 5, -"endColumn": 38 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { "uri": "sources\Net7\Net7\features\WarningWave7.cs", "region": { "startLine": 5, 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 810f78af4d6..6370b12ad44 100644 --- a/analyzers/its/expected/akka.net/Akka--netstandard2.0-S2094.json +++ b/analyzers/its/expected/akka.net/Akka--netstandard2.0-S2094.json @@ -17,19 +17,6 @@ "id": "S2094", "message": "Remove this empty class, or add members to it.", "location": { -"uri": "sources\akka.net\src\core\Akka\Actor\ChildrenContainer\Internal\SuspendReason.cs", -"region": { -"startLine": 32, -"startColumn": 22, -"endLine": 32, -"endColumn": 30 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { "uri": "sources\akka.net\src\core\Akka\Actor\FSM.cs", "region": { "startLine": 202, @@ -43,58 +30,6 @@ "id": "S2094", "message": "Remove this empty class, or add members to it.", "location": { -"uri": "sources\akka.net\src\core\Akka\Annotations\Attributes.cs", -"region": { -"startLine": 23, -"startColumn": 25, -"endLine": 23, -"endColumn": 45 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\akka.net\src\core\Akka\Annotations\Attributes.cs", -"region": { -"startLine": 44, -"startColumn": 25, -"endLine": 44, -"endColumn": 46 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\akka.net\src\core\Akka\Annotations\Attributes.cs", -"region": { -"startLine": 63, -"startColumn": 25, -"endLine": 63, -"endColumn": 46 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\akka.net\src\core\Akka\Event\ActorEventBus.cs", -"region": { -"startLine": 17, -"startColumn": 27, -"endLine": 17, -"endColumn": 40 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { "uri": "sources\akka.net\src\core\Akka\Event\LoggerInitialized.cs", "region": { "startLine": 15, @@ -136,32 +71,6 @@ "location": { "uri": "sources\akka.net\src\core\Akka\IO\Inet.cs", "region": { -"startLine": 53, -"startColumn": 31, -"endLine": 53, -"endColumn": 51 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\akka.net\src\core\Akka\IO\Inet.cs", -"region": { -"startLine": 71, -"startColumn": 31, -"endLine": 71, -"endColumn": 53 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\akka.net\src\core\Akka\IO\Inet.cs", -"region": { "startLine": 229, "startColumn": 31, "endLine": 229, @@ -238,19 +147,6 @@ "id": "S2094", "message": "Remove this empty class, or add members to it.", "location": { -"uri": "sources\akka.net\src\core\Akka\IO\Udp.cs", -"region": { -"startLine": 348, -"startColumn": 31, -"endLine": 348, -"endColumn": 36 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { "uri": "sources\akka.net\src\core\Akka\IO\UdpConnected.cs", "region": { "startLine": 95, @@ -264,19 +160,6 @@ "id": "S2094", "message": "Remove this empty class, or add members to it.", "location": { -"uri": "sources\akka.net\src\core\Akka\IO\UdpConnected.cs", -"region": { -"startLine": 295, -"startColumn": 31, -"endLine": 295, -"endColumn": 36 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { "uri": "sources\akka.net\src\core\Akka\Pattern\BackoffOptions.cs", "region": { "startLine": 243, @@ -303,19 +186,6 @@ "id": "S2094", "message": "Remove this empty class, or add members to it.", "location": { -"uri": "sources\akka.net\src\core\Akka\Routing\ResizablePoolActor.cs", -"region": { -"startLine": 70, -"startColumn": 18, -"endLine": 70, -"endColumn": 24 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { "uri": "sources\akka.net\src\core\Akka\Routing\RouterMsg.cs", "region": { "startLine": 31, 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 f22783e9ead..5ecaa3af7c5 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 @@ -45,32 +45,6 @@ "location": { "uri": "sources\akka.net\src\core\Akka.Remote\EndpointManager.cs", "region": { -"startLine": 140, -"startColumn": 29, -"endLine": 140, -"endColumn": 44 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\akka.net\src\core\Akka.Remote\EndpointManager.cs", -"region": { -"startLine": 145, -"startColumn": 29, -"endLine": 145, -"endColumn": 45 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\akka.net\src\core\Akka.Remote\EndpointManager.cs", -"region": { "startLine": 289, "startColumn": 29, "endLine": 289, @@ -136,19 +110,6 @@ "location": { "uri": "sources\akka.net\src\core\Akka.Remote\Transport\AkkaProtocolTransport.cs", "region": { -"startLine": 542, -"startColumn": 29, -"endLine": 542, -"endColumn": 53 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\akka.net\src\core\Akka.Remote\Transport\AkkaProtocolTransport.cs", -"region": { "startLine": 722, "startColumn": 20, "endLine": 722, 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 62e548be722..cea40b0ba52 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 @@ -51,19 +51,6 @@ "endColumn": 38 } } -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\akka.net\src\core\Akka.Streams\Stage\Stage.cs", -"region": { -"startLine": 92, -"startColumn": 27, -"endLine": 92, -"endColumn": 40 -} -} } ] } diff --git a/analyzers/its/expected/akka.net/Akka.Tests.Performance--netcoreapp3.1-S2094.json b/analyzers/its/expected/akka.net/Akka.Tests.Performance--netcoreapp3.1-S2094.json deleted file mode 100644 index b8f785e6216..00000000000 --- a/analyzers/its/expected/akka.net/Akka.Tests.Performance--netcoreapp3.1-S2094.json +++ /dev/null @@ -1,17 +0,0 @@ -{ -"issues": [ -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { -"uri": "sources\akka.net\src\core\Akka.Tests.Performance\Actor\ActorSystemShutdownSpec.cs", -"region": { -"startLine": 19, -"startColumn": 23, -"endLine": 19, -"endColumn": 38 -} -} -} -] -} From d7d96d6fd3d8e847572c0628564e775a2f7a7cd9 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Wed, 15 Feb 2023 16:44:49 +0100 Subject: [PATCH 23/37] Refactoring --- .../SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs index f5c66fb34be..e6e2d99347c 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs @@ -29,12 +29,12 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn KnownType.Microsoft_AspNetCore_Mvc_RazorPages_PageModel, KnownType.System_Exception); - protected override string MessageFormat => "Remove this empty {0}, or add members to it."; - protected abstract bool IsEmpty(SyntaxNode node); protected abstract bool IsClassWithDeclaredBaseClass(SyntaxNode node); protected abstract string DeclarationTypeKeyword(SyntaxNode node); + protected override string MessageFormat => "Remove this empty {0}, or add members to it."; + protected ClassShouldNotBeEmptyBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => From 6e7b536c2984cac7a2e0b67d24400c8a00d9dc4d Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Wed, 15 Feb 2023 17:35:21 +0100 Subject: [PATCH 24/37] Add exceptions to partial classes and records --- .../SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs | 7 ++++--- .../Rules/ClassShouldNotBeEmptyBase.cs | 4 ++-- .../Rules/ClassShouldNotBeEmpty.cs | 4 +++- .../TestCases/ClassShouldNotBeEmpty.CSharp9.cs | 2 ++ .../TestCases/ClassShouldNotBeEmpty.cs | 7 ++++--- .../TestCases/ClassShouldNotBeEmpty.vb | 6 +++--- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs index 122e898c7bc..e5b8cb16a0a 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs @@ -25,9 +25,10 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase Language => CSharpFacade.Instance; - protected override bool IsEmpty(SyntaxNode node) => - node is ClassDeclarationSyntax { Members.Count: 0 } - || (RecordDeclarationSyntaxWrapper.IsInstance(node) && (RecordDeclarationSyntaxWrapper)node is { Members.Count: 0, ParameterList.Parameters.Count: 0 }); + protected override bool IsEmptyAndNotPartial(SyntaxNode node) => + node is TypeDeclarationSyntax { Members.Count: 0 } typeDeclaration && !typeDeclaration.Modifiers.Any(x => x.IsKind(SyntaxKind.PartialKeyword)) + && (node is ClassDeclarationSyntax + || (RecordDeclarationSyntaxWrapper.IsInstance(node) && (RecordDeclarationSyntaxWrapper)node is { ParameterList.Parameters.Count: 0 })); protected override bool IsClassWithDeclaredBaseClass(SyntaxNode node) => node is ClassDeclarationSyntax { BaseList: not null }; diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs index e6e2d99347c..0fa818cbe87 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs @@ -29,7 +29,7 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn KnownType.Microsoft_AspNetCore_Mvc_RazorPages_PageModel, KnownType.System_Exception); - protected abstract bool IsEmpty(SyntaxNode node); + protected abstract bool IsEmptyAndNotPartial(SyntaxNode node); protected abstract bool IsClassWithDeclaredBaseClass(SyntaxNode node); protected abstract string DeclarationTypeKeyword(SyntaxNode node); @@ -43,7 +43,7 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn c => { if (Language.Syntax.NodeIdentifier(c.Node) is { IsMissing: false } identifier - && IsEmpty(c.Node) + && IsEmptyAndNotPartial(c.Node) && !ShouldIgnoreBecauseOfBaseClass(c.Node, c.SemanticModel)) { c.ReportIssue(Diagnostic.Create(Rule, identifier.GetLocation(), DeclarationTypeKeyword(c.Node))); diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs index aeca85c8c63..931ae271056 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs @@ -25,7 +25,9 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase Language => VisualBasicFacade.Instance; - protected override bool IsEmpty(SyntaxNode node) => node is ClassBlockSyntax { Members.Count: 0 }; + protected override bool IsEmptyAndNotPartial(SyntaxNode node) => + node is ClassBlockSyntax { Members.Count: 0 } classSyntax + && !classSyntax.ClassStatement.Modifiers.Any(x => x.IsKind(SyntaxKind.PartialKeyword)); protected override bool IsClassWithDeclaredBaseClass(SyntaxNode node) => node is ClassBlockSyntax { Inherits.Count: > 0 }; diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs index d34db48d172..00dc4148138 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs @@ -35,6 +35,8 @@ record RecordWithEvent event EventHandler CustomEvent; } +partial record EmptyPartialRecord(); // Compliant - partial classes are ignored, so partial record classes are ignored as well + record EmptyGenericRecord(); // Noncompliant // ^^^^^^^^^^^^^^^^^^ record EmptyGenericRecordWithContraint() where T : class; // Noncompliant diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs index cd7b474710a..6e28333fc46 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs @@ -76,10 +76,11 @@ class GenericNotEmptyWithConstraints static class StaticEmpty { } // Noncompliant -partial class PartialEmpty { } // Noncompliant -partial class PartialEmpty +partial class PartialEmpty { } // Compliant - Source Generators and some frameworks use empty partial classes as placeholders + +partial class PartialNotEmpty { - public int SomeProperty => 42; + int Prop => 42; } interface IMarker { } // Compliant - this rule only deals with classes diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb index a4077b4623a..42c01f82416 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb @@ -98,11 +98,11 @@ Class GenericNotEmptyWithConstraints(Of T As Class) End Class -Partial Class PartialEmpty ' Noncompliant +Partial Class PartialEmpty ' Compliant - Source Generators and some frameworks use empty partial classes as placeholders End Class -Partial Class PartialEmpty - Public ReadOnly Property SomeProperty As Integer +Partial Class PartialNotEmpty + Public ReadOnly Property Prop As Integer Get Return 42 End Get From e2aac495f05498d2d0a5930cae5021ad2de427fc Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Wed, 15 Feb 2023 17:53:19 +0100 Subject: [PATCH 25/37] Update Sarif files --- analyzers/its/expected/Net5/Net5--net5.0-S2094.json | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/analyzers/its/expected/Net5/Net5--net5.0-S2094.json b/analyzers/its/expected/Net5/Net5--net5.0-S2094.json index 90cdb625440..808fb732c6d 100644 --- a/analyzers/its/expected/Net5/Net5--net5.0-S2094.json +++ b/analyzers/its/expected/Net5/Net5--net5.0-S2094.json @@ -30,19 +30,6 @@ "id": "S2094", "message": "Remove this empty class, or add members to it.", "location": { -"uri": "sources\Net5\Net5\RelaxRefPartialOrder.cs", -"region": { -"startLine": 3, -"startColumn": 26, -"endLine": 3, -"endColumn": 46 -} -} -}, -{ -"id": "S2094", -"message": "Remove this empty class, or add members to it.", -"location": { "uri": "sources\Net5\Net5\S2330.cs", "region": { "startLine": 5, From 361928a7d335bf4ead0dcb5dc0f19409ec578e5e Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay Date: Wed, 15 Feb 2023 17:59:03 +0100 Subject: [PATCH 26/37] Update RSPEC --- analyzers/rspec/cs/S2094_c#.html | 5 +++-- analyzers/rspec/vbnet/S2094_vb.net.html | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/analyzers/rspec/cs/S2094_c#.html b/analyzers/rspec/cs/S2094_c#.html index beb6494732e..369f2453587 100644 --- a/analyzers/rspec/cs/S2094_c#.html +++ b/analyzers/rspec/cs/S2094_c#.html @@ -14,8 +14,9 @@

Compliant Solution

}

Exceptions

-

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 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.

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

Compliant Solution

End Interface

Exceptions

-

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 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.

 Imports Microsoft.AspNetCore.Mvc.RazorPages
 

From f411c97e2dcc5b46ceef6f0c31e964794cf07957 Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Wed, 15 Feb 2023 18:48:42 +0100
Subject: [PATCH 27/37] startsWith -> equals

---
 .../java/com/sonar/it/csharp/IncrementalAnalysisTest.java     | 4 ++--
 .../java/com/sonar/it/csharp/ProjectLevelDuplicationTest.java | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java b/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java
index a6e9282ef8f..cbb82b342f4 100644
--- a/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java
+++ b/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java
@@ -114,7 +114,7 @@ public void incrementalPrAnalysis_cacheAvailableChangesDone_issuesReportedForCha
     List allIssues = TestUtils
         .getIssues(ORCHESTRATOR, PROJECT, "42")
         .stream()
-        .filter(x -> x.getRule().startsWith("csharpsquid:S1134"))
+        .filter(x -> x.getRule().equals("csharpsquid:S1134"))
         .collect(Collectors.toList());
     assertThat(allIssues).hasSize(2);
     assertThat(allIssues.get(0).getRule()).isEqualTo("csharpsquid:S1134");
@@ -140,7 +140,7 @@ public void incrementalPrAnalysis_cacheAvailableProjectBaseDirChanged_everything
     List allIssues = TestUtils
       .getIssues(ORCHESTRATOR, PROJECT, "42")
       .stream()
-      .filter(x -> x.getRule().startsWith("csharpsquid:S1134"))
+      .filter(x -> x.getRule().equals("csharpsquid:S1134"))
       .collect(Collectors.toList());
     assertThat(allIssues).hasSize(3);
     assertThat(allIssues.get(0).getRule()).isEqualTo("csharpsquid:S1134");
diff --git a/its/src/test/java/com/sonar/it/csharp/ProjectLevelDuplicationTest.java b/its/src/test/java/com/sonar/it/csharp/ProjectLevelDuplicationTest.java
index 446eebab298..20430a93fa9 100644
--- a/its/src/test/java/com/sonar/it/csharp/ProjectLevelDuplicationTest.java
+++ b/its/src/test/java/com/sonar/it/csharp/ProjectLevelDuplicationTest.java
@@ -52,7 +52,7 @@ public void containsOnlyOneProjectLevelIssue() throws Exception {
 
     List issues = getIssues("ProjectLevelDuplication")
       .stream()
-      .filter(x -> x.getRule().startsWith("csharpsquid:S3904"))
+      .filter(x -> x.getRule().equals("csharpsquid:S3904"))
       .collect(Collectors.toList());
     assertThat(issues).hasSize(1);
   }

From e6d4c76219597226afe55eee771247d0f77beb7c Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Wed, 15 Feb 2023 19:23:56 +0100
Subject: [PATCH 28/37] Test commit

---
 .../SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb    | 1 +
 1 file changed, 1 insertion(+)

diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
index 42c01f82416..718aac7e66c 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
@@ -115,5 +115,6 @@ End Interface
 Structure EmptyStruct                               ' Compliant - this rule only deals with classes
 End Structure
 
+
 Class                                               ' Error
 End Class                                    

From a2e654116a8d64e654b08bd7800b6713936a011e Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Wed, 15 Feb 2023 19:29:04 +0100
Subject: [PATCH 29/37] Revert previous

---
 .../SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb    | 1 -
 1 file changed, 1 deletion(-)

diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
index 718aac7e66c..42c01f82416 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
@@ -115,6 +115,5 @@ End Interface
 Structure EmptyStruct                               ' Compliant - this rule only deals with classes
 End Structure
 
-
 Class                                               ' Error
 End Class                                    

From 273cb7096dc73c1799efbde61397d6579123a613 Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Thu, 16 Feb 2023 16:31:55 +0100
Subject: [PATCH 30/37] Refactor Java test cases

---
 .../it/csharp/IncrementalAnalysisTest.java    | 44 ++++++++++---------
 .../csharp/ProjectLevelDuplicationTest.java   |  4 +-
 2 files changed, 26 insertions(+), 22 deletions(-)

diff --git a/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java b/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java
index cbb82b342f4..82e4d600820 100644
--- a/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java
+++ b/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java
@@ -44,6 +44,7 @@
 
 public class IncrementalAnalysisTest {
   private static final String PROJECT = "IncrementalPRAnalysis";
+  private static final String PULL_REQUEST_KEY = "42";
 
   @Rule
   public TemporaryFolder temp = TestUtils.createTempFolder();
@@ -69,7 +70,7 @@ public void incrementalPrAnalysis_NoCache_FullAnalysisDone() throws IOException
     assertThat(beginStepResults.getLogs()).contains("Processing pull request with base branch 'base-branch'.");
     assertThat(beginStepResults.getLogs()).contains("Cache data is not available. Incremental PR analysis is disabled.");
     assertAllFilesWereAnalysed(endStepResults, projectDir);
-    List allIssues = TestUtils.getIssues(ORCHESTRATOR, PROJECT, "42");
+    List allIssues = TestUtils.getIssues(ORCHESTRATOR, PROJECT, PULL_REQUEST_KEY);
     assertThat(allIssues).hasSize(1);
     assertThat(allIssues.get(0).getRule()).isEqualTo("csharpsquid:S1134");
   }
@@ -86,7 +87,7 @@ public void incrementalPrAnalysis_cacheAvailableNoChanges_nothingReported() thro
     assertTrue(endStepResults.isSuccess());
     assertCacheIsUsed(beginStepResults, PROJECT);
     assertThat(endStepResults.getLogs()).doesNotContain("Adding normal issue S1134");
-    List allIssues = TestUtils.getIssues(ORCHESTRATOR, PROJECT, "42");
+    List allIssues = TestUtils.getIssues(ORCHESTRATOR, PROJECT, PULL_REQUEST_KEY);
     assertThat(allIssues).isEmpty();
   }
 
@@ -111,16 +112,16 @@ public void incrementalPrAnalysis_cacheAvailableChangesDone_issuesReportedForCha
     assertThat(endStepResults.getLogs()).doesNotContain("Adding normal issue S1134: " + unchanged2Path);
     assertThat(endStepResults.getLogs()).contains("Adding normal issue S1134: " + withChangesPath);
     assertThat(endStepResults.getLogs()).contains("Adding normal issue S1134: " + fileToBeAddedPath);
-    List allIssues = TestUtils
-        .getIssues(ORCHESTRATOR, PROJECT, "42")
+    List fixMeTagTrackingIssues = TestUtils
+        .getIssues(ORCHESTRATOR, PROJECT, PULL_REQUEST_KEY)
         .stream()
         .filter(x -> x.getRule().equals("csharpsquid:S1134"))
         .collect(Collectors.toList());
-    assertThat(allIssues).hasSize(2);
-    assertThat(allIssues.get(0).getRule()).isEqualTo("csharpsquid:S1134");
-    assertThat(allIssues.get(0).getComponent()).isEqualTo("IncrementalPRAnalysis:IncrementalPRAnalysis/AddedFile.cs");
-    assertThat(allIssues.get(1).getRule()).isEqualTo("csharpsquid:S1134");
-    assertThat(allIssues.get(1).getComponent()).isEqualTo("IncrementalPRAnalysis:IncrementalPRAnalysis/WithChanges.cs");
+    assertThat(fixMeTagTrackingIssues).hasSize(2);
+    assertThat(fixMeTagTrackingIssues.get(0).getRule()).isEqualTo("csharpsquid:S1134");
+    assertThat(fixMeTagTrackingIssues.get(0).getComponent()).isEqualTo("IncrementalPRAnalysis:IncrementalPRAnalysis/AddedFile.cs");
+    assertThat(fixMeTagTrackingIssues.get(1).getRule()).isEqualTo("csharpsquid:S1134");
+    assertThat(fixMeTagTrackingIssues.get(1).getComponent()).isEqualTo("IncrementalPRAnalysis:IncrementalPRAnalysis/WithChanges.cs");
   }
 
   @Test
@@ -137,18 +138,18 @@ public void incrementalPrAnalysis_cacheAvailableProjectBaseDirChanged_everything
     assertTrue(endStepResults.isSuccess());
     assertCacheIsUsed(beginStepResults, PROJECT);
     assertAllFilesWereAnalysed(endStepResults, projectDir);
-    List allIssues = TestUtils
-      .getIssues(ORCHESTRATOR, PROJECT, "42")
+    List fixMeTagTrackingIssues = TestUtils
+      .getIssues(ORCHESTRATOR, PROJECT, PULL_REQUEST_KEY)
       .stream()
       .filter(x -> x.getRule().equals("csharpsquid:S1134"))
       .collect(Collectors.toList());
-    assertThat(allIssues).hasSize(3);
-    assertThat(allIssues.get(0).getRule()).isEqualTo("csharpsquid:S1134");
-    assertThat(allIssues.get(0).getComponent()).isEqualTo("IncrementalPRAnalysis:Unchanged1.cs");
-    assertThat(allIssues.get(1).getRule()).isEqualTo("csharpsquid:S1134");
-    assertThat(allIssues.get(1).getComponent()).isEqualTo("IncrementalPRAnalysis:Unchanged2.cs");
-    assertThat(allIssues.get(2).getRule()).isEqualTo("csharpsquid:S1134");
-    assertThat(allIssues.get(2).getComponent()).isEqualTo("IncrementalPRAnalysis:WithChanges.cs");
+    assertThat(fixMeTagTrackingIssues).hasSize(3);
+    assertThat(fixMeTagTrackingIssues.get(0).getRule()).isEqualTo("csharpsquid:S1134");
+    assertThat(fixMeTagTrackingIssues.get(0).getComponent()).isEqualTo("IncrementalPRAnalysis:Unchanged1.cs");
+    assertThat(fixMeTagTrackingIssues.get(1).getRule()).isEqualTo("csharpsquid:S1134");
+    assertThat(fixMeTagTrackingIssues.get(1).getComponent()).isEqualTo("IncrementalPRAnalysis:Unchanged2.cs");
+    assertThat(fixMeTagTrackingIssues.get(2).getRule()).isEqualTo("csharpsquid:S1134");
+    assertThat(fixMeTagTrackingIssues.get(2).getComponent()).isEqualTo("IncrementalPRAnalysis:WithChanges.cs");
   }
 
   @Test
@@ -166,7 +167,10 @@ public void incrementalPrAnalysis_cacheAvailableDuplicationIntroduced_duplicatio
 
     assertTrue(endStepResults.isSuccess());
     assertCacheIsUsed(beginStepResults, projectName);
-    List duplications = TestUtils.getDuplication(ORCHESTRATOR, "IncrementalPRAnalysisDuplication:IncrementalPRAnalysisDuplication/CopyClass.cs", "42")
+    List duplications = TestUtils.getDuplication(
+        ORCHESTRATOR,
+        "IncrementalPRAnalysisDuplication:IncrementalPRAnalysisDuplication/CopyClass.cs",
+        PULL_REQUEST_KEY)
       .getDuplicationsList();
     assertThat(duplications).isNotEmpty();
   }
@@ -230,7 +234,7 @@ private BeginAndEndStepResults executeAnalysisForPRBranch(String project, Path p
 
     BuildResult beginStepResults = ORCHESTRATOR.executeBuild(beginStep
       .setProperty("sonar.pullrequest.base", "base-branch")
-      .setProperty("sonar.pullrequest.key", "42")
+      .setProperty("sonar.pullrequest.key", PULL_REQUEST_KEY)
       .setProperty("sonar.pullrequest.branch", "pull-request")
       .setProperty("sonar.verbose", "true"));
     TestUtils.runMSBuild(ORCHESTRATOR, projectDir, "/t:Restore,Rebuild");
diff --git a/its/src/test/java/com/sonar/it/csharp/ProjectLevelDuplicationTest.java b/its/src/test/java/com/sonar/it/csharp/ProjectLevelDuplicationTest.java
index 20430a93fa9..2fa97ca1925 100644
--- a/its/src/test/java/com/sonar/it/csharp/ProjectLevelDuplicationTest.java
+++ b/its/src/test/java/com/sonar/it/csharp/ProjectLevelDuplicationTest.java
@@ -50,10 +50,10 @@ public void containsOnlyOneProjectLevelIssue() throws Exception {
 
     assertThat(getComponent("ProjectLevelDuplication")).isNotNull();
 
-    List issues = getIssues("ProjectLevelDuplication")
+    List projectLevelIssues = getIssues("ProjectLevelDuplication")
       .stream()
       .filter(x -> x.getRule().equals("csharpsquid:S3904"))
       .collect(Collectors.toList());
-    assertThat(issues).hasSize(1);
+    assertThat(projectLevelIssues).hasSize(1);
   }
 }

From 55a3b3208625121a2beffb04d843abe067913c97 Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Fri, 17 Feb 2023 10:41:00 +0100
Subject: [PATCH 31/37] fixMeTagTrackingIssues -> fixMeIssues

---
 .../it/csharp/IncrementalAnalysisTest.java    | 28 +++++++++----------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java b/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java
index 82e4d600820..a8e183d5fcc 100644
--- a/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java
+++ b/its/src/test/java/com/sonar/it/csharp/IncrementalAnalysisTest.java
@@ -112,16 +112,16 @@ public void incrementalPrAnalysis_cacheAvailableChangesDone_issuesReportedForCha
     assertThat(endStepResults.getLogs()).doesNotContain("Adding normal issue S1134: " + unchanged2Path);
     assertThat(endStepResults.getLogs()).contains("Adding normal issue S1134: " + withChangesPath);
     assertThat(endStepResults.getLogs()).contains("Adding normal issue S1134: " + fileToBeAddedPath);
-    List fixMeTagTrackingIssues = TestUtils
+    List fixMeIssues = TestUtils
         .getIssues(ORCHESTRATOR, PROJECT, PULL_REQUEST_KEY)
         .stream()
         .filter(x -> x.getRule().equals("csharpsquid:S1134"))
         .collect(Collectors.toList());
-    assertThat(fixMeTagTrackingIssues).hasSize(2);
-    assertThat(fixMeTagTrackingIssues.get(0).getRule()).isEqualTo("csharpsquid:S1134");
-    assertThat(fixMeTagTrackingIssues.get(0).getComponent()).isEqualTo("IncrementalPRAnalysis:IncrementalPRAnalysis/AddedFile.cs");
-    assertThat(fixMeTagTrackingIssues.get(1).getRule()).isEqualTo("csharpsquid:S1134");
-    assertThat(fixMeTagTrackingIssues.get(1).getComponent()).isEqualTo("IncrementalPRAnalysis:IncrementalPRAnalysis/WithChanges.cs");
+    assertThat(fixMeIssues).hasSize(2);
+    assertThat(fixMeIssues.get(0).getRule()).isEqualTo("csharpsquid:S1134");
+    assertThat(fixMeIssues.get(0).getComponent()).isEqualTo("IncrementalPRAnalysis:IncrementalPRAnalysis/AddedFile.cs");
+    assertThat(fixMeIssues.get(1).getRule()).isEqualTo("csharpsquid:S1134");
+    assertThat(fixMeIssues.get(1).getComponent()).isEqualTo("IncrementalPRAnalysis:IncrementalPRAnalysis/WithChanges.cs");
   }
 
   @Test
@@ -138,18 +138,18 @@ public void incrementalPrAnalysis_cacheAvailableProjectBaseDirChanged_everything
     assertTrue(endStepResults.isSuccess());
     assertCacheIsUsed(beginStepResults, PROJECT);
     assertAllFilesWereAnalysed(endStepResults, projectDir);
-    List fixMeTagTrackingIssues = TestUtils
+    List fixMeIssues = TestUtils
       .getIssues(ORCHESTRATOR, PROJECT, PULL_REQUEST_KEY)
       .stream()
       .filter(x -> x.getRule().equals("csharpsquid:S1134"))
       .collect(Collectors.toList());
-    assertThat(fixMeTagTrackingIssues).hasSize(3);
-    assertThat(fixMeTagTrackingIssues.get(0).getRule()).isEqualTo("csharpsquid:S1134");
-    assertThat(fixMeTagTrackingIssues.get(0).getComponent()).isEqualTo("IncrementalPRAnalysis:Unchanged1.cs");
-    assertThat(fixMeTagTrackingIssues.get(1).getRule()).isEqualTo("csharpsquid:S1134");
-    assertThat(fixMeTagTrackingIssues.get(1).getComponent()).isEqualTo("IncrementalPRAnalysis:Unchanged2.cs");
-    assertThat(fixMeTagTrackingIssues.get(2).getRule()).isEqualTo("csharpsquid:S1134");
-    assertThat(fixMeTagTrackingIssues.get(2).getComponent()).isEqualTo("IncrementalPRAnalysis:WithChanges.cs");
+    assertThat(fixMeIssues).hasSize(3);
+    assertThat(fixMeIssues.get(0).getRule()).isEqualTo("csharpsquid:S1134");
+    assertThat(fixMeIssues.get(0).getComponent()).isEqualTo("IncrementalPRAnalysis:Unchanged1.cs");
+    assertThat(fixMeIssues.get(1).getRule()).isEqualTo("csharpsquid:S1134");
+    assertThat(fixMeIssues.get(1).getComponent()).isEqualTo("IncrementalPRAnalysis:Unchanged2.cs");
+    assertThat(fixMeIssues.get(2).getRule()).isEqualTo("csharpsquid:S1134");
+    assertThat(fixMeIssues.get(2).getComponent()).isEqualTo("IncrementalPRAnalysis:WithChanges.cs");
   }
 
   @Test

From fd745ab2663749ec616aad02e750dd8f37ccf752 Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Fri, 17 Feb 2023 10:46:37 +0100
Subject: [PATCH 32/37] Add test case for empty abstract classes

---
 .../SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs  | 3 ++-
 .../SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb  | 2 ++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
index 6e28333fc46..493dbc28ec3 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.cs
@@ -73,9 +73,10 @@ class GenericNotEmptyWithConstraints
     void Method(T arg) { }
 }
 
-
 static class StaticEmpty { }                 // Noncompliant
 
+abstract class AbstractEmpty { }             // Noncompliant
+
 partial class PartialEmpty { }               // Compliant - Source Generators and some frameworks use empty partial classes as placeholders
 
 partial class PartialNotEmpty
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
index 42c01f82416..07b2d3ab7cd 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
@@ -97,6 +97,8 @@ Class GenericNotEmptyWithConstraints(Of T As Class)
     End Sub
 End Class
 
+MustInherit Class AbstractEmpty                     ' Noncompliant
+End Class
 
 Partial Class PartialEmpty                          ' Compliant - Source Generators and some frameworks use empty partial classes as placeholders
 End Class

From dd7d9fe49b1cd67435c770d99d690bcdf1dc4991 Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Fri, 17 Feb 2023 12:13:28 +0100
Subject: [PATCH 33/37] Update test cases

---
 .../TestCases/ClassShouldNotBeEmpty.CSharp10.cs        |  6 ------
 .../TestCases/ClassShouldNotBeEmpty.CSharp9.cs         | 10 +++++-----
 .../TestCases/ClassShouldNotBeEmpty.vb                 |  8 ++++----
 3 files changed, 9 insertions(+), 15 deletions(-)

diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs
index b80fcaa1e26..1f2342b1612 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp10.cs
@@ -11,9 +11,3 @@ record class NotEmptyRecordClass2()
 {
     int RecordMember => 42;
 };
-
-record struct NotEmptyRecordStruct1(int RecordMember);
-record struct NotEmptyRecordStruct2()
-{
-    int RecordMember => 42;
-};
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs
index 00dc4148138..dbdd5228c73 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.CSharp9.cs
@@ -1,8 +1,8 @@
 using System;
 
-record EmptyRecord1();                                              // Noncompliant {{Remove this empty record, or add members to it.}}
+record EmptyRecord1();                                      // Noncompliant {{Remove this empty record, or add members to it.}}
 //     ^^^^^^^^^^^^
-record EmptyRecord2() { };                                          // Noncompliant
+record EmptyRecord2() { };                                  // Noncompliant
 
 record RecordWithParameters(int RecordMember);
 
@@ -35,10 +35,10 @@ record RecordWithEvent
     event EventHandler CustomEvent;
 }
 
-partial record EmptyPartialRecord();                                // Compliant - partial classes are ignored, so partial record classes are ignored as well
+partial record EmptyPartialRecord();                            // Compliant - partial classes are ignored, so partial record classes are ignored as well
 
-record EmptyGenericRecord();                                     // Noncompliant
+record EmptyGenericRecord();                                 // Noncompliant
 //     ^^^^^^^^^^^^^^^^^^
-record EmptyGenericRecordWithContraint() where T : class;        // Noncompliant
+record EmptyGenericRecordWithContraint() where T : class;    // Noncompliant
 record NotEmptyGenericRecord(T RecordMember);
 record NotEmptyGenericRecordWithContraint(T RecordMember) where T : class;
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
index 07b2d3ab7cd..d6221105cf5 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ClassShouldNotBeEmpty.vb
@@ -8,7 +8,7 @@ End Class
 Public Class PublicEmpty                            ' Noncompliant
 End Class
 
-Class InternalEmpty                                 ' Noncompliant
+Friend Class InternalEmpty                          ' Noncompliant
 End Class
 
 Class EmptyWithComments                             ' Noncompliant
@@ -58,10 +58,10 @@ Class OuterClass
     Protected Class InnerEmpty3                     ' Noncompliant
     End Class
 
-    Class InnerEmpty4                               ' Noncompliant
+    Friend Class InnerEmpty4                        ' Noncompliant
     End Class
 
-    Protected Class InnerEmpty5                     ' Noncompliant
+    Protected Friend Class InnerEmpty5              ' Noncompliant
     End Class
 
     Public Class InnerEmpty6                        ' Noncompliant
@@ -80,7 +80,7 @@ Class OuterClass
     End Class
 End Class
 
-Class GenericEmpty(Of T)
+Class GenericEmpty(Of T)                            ' Noncompliant
     ' ^^^^^^^^^^^^
 End Class
 

From 1a6a36d24e46d9f6a40efd2dbe7f83ccce1f3c30 Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Fri, 17 Feb 2023 14:58:55 +0100
Subject: [PATCH 34/37] Refactor rule class

---
 .../Rules/ClassShouldNotBeEmpty.cs                     |  5 ++++-
 .../Rules/ClassShouldNotBeEmptyBase.cs                 | 10 +++++-----
 .../Rules/ClassShouldNotBeEmpty.cs                     |  5 ++++-
 3 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs
index e5b8cb16a0a..81a4942d518 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs
@@ -32,5 +32,8 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase node is ClassDeclarationSyntax { BaseList: not null };
 
-    protected override string DeclarationTypeKeyword(SyntaxNode node) => ((TypeDeclarationSyntax)node).Keyword.ValueText;
+    protected override string DeclarationTypeKeyword(SyntaxNode node) =>
+        node is TypeDeclarationSyntax typeDeclaration
+            ? typeDeclaration.Keyword.ValueText
+            : "type";
 }
diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
index 0fa818cbe87..95de276a89d 100644
--- a/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
+++ b/analyzers/src/SonarAnalyzer.Common/Rules/ClassShouldNotBeEmptyBase.cs
@@ -48,11 +48,11 @@ public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAn
                 {
                     c.ReportIssue(Diagnostic.Create(Rule, identifier.GetLocation(), DeclarationTypeKeyword(c.Node)));
                 }
-
-                bool ShouldIgnoreBecauseOfBaseClass(SyntaxNode node, SemanticModel model) =>
-                    IsClassWithDeclaredBaseClass(node)
-                    && model.GetDeclaredSymbol(node) is INamedTypeSymbol classSymbol
-                    && (classSymbol.BaseType is { IsAbstract: true } || classSymbol.DerivesFromAny(BaseClassesToIgnore));
             },
             Language.SyntaxKind.ClassAndRecordClassDeclarations);
+
+    private bool ShouldIgnoreBecauseOfBaseClass(SyntaxNode node, SemanticModel model) =>
+        IsClassWithDeclaredBaseClass(node)
+        && 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 931ae271056..7d8c79c615e 100644
--- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs
@@ -31,5 +31,8 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase node is ClassBlockSyntax { Inherits.Count: > 0 };
 
-    protected override string DeclarationTypeKeyword(SyntaxNode node) => ((TypeBlockSyntax)node).BlockStatement.DeclarationKeyword.ValueText.ToLower();
+    protected override string DeclarationTypeKeyword(SyntaxNode node) =>
+        node is TypeBlockSyntax typeBlock
+            ? typeBlock.BlockStatement.DeclarationKeyword.ValueText
+            : "type";
 }

From 7c58e15d17a0fc8de99e93e0ab7ce4712b76c07c Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Sat, 18 Feb 2023 10:10:18 +0100
Subject: [PATCH 35/37] Refactor C# Rule

---
 .../Rules/ClassShouldNotBeEmpty.cs                     | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs
index 81a4942d518..91c5fa41350 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs
@@ -26,9 +26,9 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase Language => CSharpFacade.Instance;
 
     protected override bool IsEmptyAndNotPartial(SyntaxNode node) =>
-        node is TypeDeclarationSyntax { Members.Count: 0 } typeDeclaration && !typeDeclaration.Modifiers.Any(x => x.IsKind(SyntaxKind.PartialKeyword))
-        && (node is ClassDeclarationSyntax
-           || (RecordDeclarationSyntaxWrapper.IsInstance(node) && (RecordDeclarationSyntaxWrapper)node is { ParameterList.Parameters.Count: 0 }));
+        node is TypeDeclarationSyntax { Members.Count: 0 } typeDeclaration
+        && !typeDeclaration.Modifiers.Any(x => x.IsKind(SyntaxKind.PartialKeyword))
+        && (node is ClassDeclarationSyntax || IsParameterlessRecord(node));
 
     protected override bool IsClassWithDeclaredBaseClass(SyntaxNode node) => node is ClassDeclarationSyntax { BaseList: not null };
 
@@ -36,4 +36,8 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase
+        RecordDeclarationSyntaxWrapper.IsInstance(node)
+        && (RecordDeclarationSyntaxWrapper)node is { ParameterList.Parameters.Count: 0 };
 }

From 04df5d731b925c9f0acbeff71570c7cc54fd3a58 Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Tue, 21 Feb 2023 13:05:32 +0100
Subject: [PATCH 36/37] Fix UT

---
 .../SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs
index 7d8c79c615e..e9aff58a3b4 100644
--- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs
@@ -33,6 +33,6 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase
         node is TypeBlockSyntax typeBlock
-            ? typeBlock.BlockStatement.DeclarationKeyword.ValueText
+            ? typeBlock.BlockStatement.DeclarationKeyword.ValueText.ToLower()
             : "type";
 }

From 089b333ae0ba3a0d3702ada8105f15d52f0fa18e Mon Sep 17 00:00:00 2001
From: Zsolt Kolbay 
Date: Tue, 21 Feb 2023 16:40:25 +0100
Subject: [PATCH 37/37] Remove type check

---
 .../src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs  | 5 +----
 .../SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs | 5 +----
 2 files changed, 2 insertions(+), 8 deletions(-)

diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs
index 91c5fa41350..1dae0adc1d4 100644
--- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs
+++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs
@@ -32,10 +32,7 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase node is ClassDeclarationSyntax { BaseList: not null };
 
-    protected override string DeclarationTypeKeyword(SyntaxNode node) =>
-        node is TypeDeclarationSyntax typeDeclaration
-            ? typeDeclaration.Keyword.ValueText
-            : "type";
+    protected override string DeclarationTypeKeyword(SyntaxNode node) => ((TypeDeclarationSyntax)node).Keyword.ValueText;
 
     private bool IsParameterlessRecord(SyntaxNode node) =>
         RecordDeclarationSyntaxWrapper.IsInstance(node)
diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs
index e9aff58a3b4..931ae271056 100644
--- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs
@@ -31,8 +31,5 @@ public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase node is ClassBlockSyntax { Inherits.Count: > 0 };
 
-    protected override string DeclarationTypeKeyword(SyntaxNode node) =>
-        node is TypeBlockSyntax typeBlock
-            ? typeBlock.BlockStatement.DeclarationKeyword.ValueText.ToLower()
-            : "type";
+    protected override string DeclarationTypeKeyword(SyntaxNode node) => ((TypeBlockSyntax)node).BlockStatement.DeclarationKeyword.ValueText.ToLower();
 }