diff --git a/README.md b/README.md index 31ba9bacb4d..ebb662793cd 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ languages in [SonarQube](http://www.sonarqube.org/), [SonarCloud](https://sonarc ## Features -* [380+ C# rules](https://rules.sonarsource.com/csharp) and [170+ VB.​NET rules](https://rules.sonarsource.com/vbnet) +* [390+ C# rules](https://rules.sonarsource.com/csharp) and [170+ VB.​NET rules](https://rules.sonarsource.com/vbnet) * Metrics (cognitive complexity, duplications, number of lines etc.) * Import of [test coverage reports](https://community.sonarsource.com/t/9871) from Visual Studio Code Coverage, dotCover, OpenCover, Coverlet, Altcover. * Import of third party Roslyn Analyzers results diff --git a/analyzers/rspec/cs/S6513_c#.html b/analyzers/rspec/cs/S6513_c#.html new file mode 100644 index 00000000000..3d61921fdf4 --- /dev/null +++ b/analyzers/rspec/cs/S6513_c#.html @@ -0,0 +1,53 @@ +

The ExcludeFromCodeCoverageAttribute is +used to exclude portions of code from code coverage +reporting. It is a bad practice to retain code that is not covered by unit tests. In .Net 5, the Justification property was added to +the ExcludeFromCodeCoverageAttribute as an opportunity to document the rationale for the exclusion. This rule raises an issue when no +such justification is given.

+

Noncompliant Code Example

+
+public struct Coordinates
+{
+    public int X { get; }
+    public int Y { get; }
+
+    [ExcludeFromCodeCoverage] // Noncompliant
+    public override bool Equals(object obj) => obj is Coordinates coordinates && X == coordinates.X && Y == coordinates.Y;
+
+    [ExcludeFromCodeCoverage] // Noncompliant
+    public override int GetHashCode()
+    {
+        var hashCode = 1861411795;
+        hashCode = hashCode * -1521134295 + X.GetHashCode();
+        hashCode = hashCode * -1521134295 + Y.GetHashCode();
+        return hashCode;
+    }
+}
+
+

Compliant Solution

+
+public struct Coordinates
+{
+    public int X { get; }
+    public int Y { get; }
+
+    [ExcludeFromCodeCoverage(Justification = "Code generated by Visual Studio refactoring")] // Compliant
+    public override bool Equals(object obj) => obj is Coordinates coordinates && X == coordinates.X && Y == coordinates.Y;
+
+    [ExcludeFromCodeCoverage(Justification = "Code generated by Visual Studio refactoring")] // Compliant
+    public override int GetHashCode()
+    {
+        var hashCode = 1861411795;
+        hashCode = hashCode * -1521134295 + X.GetHashCode();
+        hashCode = hashCode * -1521134295 + Y.GetHashCode();
+        return hashCode;
+    }
+}
+
+

See

+ + diff --git a/analyzers/rspec/cs/S6513_c#.json b/analyzers/rspec/cs/S6513_c#.json new file mode 100644 index 00000000000..ed7960cee55 --- /dev/null +++ b/analyzers/rspec/cs/S6513_c#.json @@ -0,0 +1,17 @@ +{ + "title": "\"ExcludeFromCodeCoverage\" attributes should include a justification", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "bad-practice" + ], + "defaultSeverity": "Minor", + "ruleSpecification": "RSPEC-6513", + "sqKey": "S6513", + "scope": "Main", + "quickfix": "unknown" +} diff --git a/analyzers/rspec/vbnet/S6513_vb.net.html b/analyzers/rspec/vbnet/S6513_vb.net.html new file mode 100644 index 00000000000..b17edc5ad00 --- /dev/null +++ b/analyzers/rspec/vbnet/S6513_vb.net.html @@ -0,0 +1,67 @@ +

The ExcludeFromCodeCoverageAttribute is +used to exclude portions of code from code coverage +reporting. It is a bad practice to retain code that is not covered by unit tests. In .Net 5, the Justification property was added to +the ExcludeFromCodeCoverageAttribute as an opportunity to document the rationale for the exclusion. This rule raises an issue when no +such justification is given.

+

Noncompliant Code Example

+
+Public Structure Coordinates
+
+    Public ReadOnly Property X As Integer
+    Public ReadOnly Property Y As Integer
+
+    <ExcludeFromCodeCoverage> ' Noncompliant
+    Public Overrides Function Equals(obj As Object) As Boolean
+        If Not (TypeOf obj Is Coordinates) Then
+            Return False
+        End If
+
+        Dim coordinates = DirectCast(obj, Coordinates)
+        Return X = coordinates.X AndAlso
+               Y = coordinates.Y
+    End Function
+
+    <ExcludeFromCodeCoverage> ' Noncompliant
+    Public Overrides Function GetHashCode() As Integer
+        Dim hashCode As Long = 1861411795
+        hashCode = (hashCode * -1521134295 + X.GetHashCode()).GetHashCode()
+        hashCode = (hashCode * -1521134295 + Y.GetHashCode()).GetHashCode()
+        Return hashCode
+    End Function
+End Structure
+
+

Compliant Solution

+
+Public Structure Coordinates
+
+    Public ReadOnly Property X As Integer
+    Public ReadOnly Property Y As Integer
+
+    <ExcludeFromCodeCoverage(Justification:="Code generated by Visual Studio refactoring")> ' Compliant
+    Public Overrides Function Equals(obj As Object) As Boolean
+        If Not (TypeOf obj Is Coordinates) Then
+            Return False
+        End If
+
+        Dim coordinates = DirectCast(obj, Coordinates)
+        Return X = coordinates.X AndAlso
+               Y = coordinates.Y
+    End Function
+
+    <ExcludeFromCodeCoverage(Justification:="Code generated by Visual Studio refactoring")> ' Compliant
+    Public Overrides Function GetHashCode() As Integer
+        Dim hashCode As Long = 1861411795
+        hashCode = (hashCode * -1521134295 + X.GetHashCode()).GetHashCode()
+        hashCode = (hashCode * -1521134295 + Y.GetHashCode()).GetHashCode()
+        Return hashCode
+    End Function
+End Structure
+
+

See

+ + diff --git a/analyzers/rspec/vbnet/S6513_vb.net.json b/analyzers/rspec/vbnet/S6513_vb.net.json new file mode 100644 index 00000000000..ed7960cee55 --- /dev/null +++ b/analyzers/rspec/vbnet/S6513_vb.net.json @@ -0,0 +1,17 @@ +{ + "title": "\"ExcludeFromCodeCoverage\" attributes should include a justification", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "bad-practice" + ], + "defaultSeverity": "Minor", + "ruleSpecification": "RSPEC-6513", + "sqKey": "S6513", + "scope": "Main", + "quickfix": "unknown" +} diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ExcludeFromCodeCoverageAttributesNeedJustification.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ExcludeFromCodeCoverageAttributesNeedJustification.cs new file mode 100644 index 00000000000..5db33bed676 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ExcludeFromCodeCoverageAttributesNeedJustification.cs @@ -0,0 +1,33 @@ +/* + * 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 ExcludeFromCodeCoverageAttributesNeedJustification : ExcludeFromCodeCoverageAttributesNeedJustificationBase +{ + protected override ILanguageFacade Language => CSharpFacade.Instance; + + protected override SyntaxNode GetJustificationExpression(SyntaxNode node) => + node is AttributeSyntax { ArgumentList.Arguments: { Count: 1 } arguments } + && arguments[0] is { NameEquals.Name.Identifier.ValueText: JustificationPropertyName, Expression: { } expression } + ? expression + : null; +} diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ExcludeFromCodeCoverageAttributesNeedJustificationBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ExcludeFromCodeCoverageAttributesNeedJustificationBase.cs new file mode 100644 index 00000000000..16c42322b93 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ExcludeFromCodeCoverageAttributesNeedJustificationBase.cs @@ -0,0 +1,58 @@ +/* + * 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 ExcludeFromCodeCoverageAttributesNeedJustificationBase : SonarDiagnosticAnalyzer + where TSyntaxKind : struct +{ + private const string DiagnosticId = "S6513"; + + protected const string JustificationPropertyName = "Justification"; + + protected override string MessageFormat => "Add a justification."; + + protected abstract SyntaxNode GetJustificationExpression(SyntaxNode node); + + protected ExcludeFromCodeCoverageAttributesNeedJustificationBase() : base(DiagnosticId) { } + + protected sealed override void Initialize(SonarAnalysisContext context) => + context.RegisterNodeAction( + Language.GeneratedCodeRecognizer, + c => + { + if (NoJustification(c.Node, c.SemanticModel) + && c.SemanticModel.GetSymbolInfo(c.Node).Symbol is IMethodSymbol attribute + && attribute.IsInType(KnownType.System_Diagnostics_CodeAnalysis_ExcludeFromCodeCoverageAttribute) + && HasJustificationProperty(attribute.ContainingType)) + { + c.ReportIssue(Diagnostic.Create(Rule, c.Node.GetLocation())); + } + }, + Language.SyntaxKind.Attribute); + + private bool NoJustification(SyntaxNode node, SemanticModel model) => + GetJustificationExpression(node) is not { } justification + || string.IsNullOrWhiteSpace(Language.FindConstantValue(model, justification) as string); + + /// "Justification" was added in .Net 5, while ExcludeFromCodeCoverage in netstandard2.0. + private static bool HasJustificationProperty(INamedTypeSymbol symbol) => + symbol.MemberNames.Contains(JustificationPropertyName); +} diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ExcludeFromCodeCoverageAttributesNeedJustification.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ExcludeFromCodeCoverageAttributesNeedJustification.cs new file mode 100644 index 00000000000..093af77ebba --- /dev/null +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ExcludeFromCodeCoverageAttributesNeedJustification.cs @@ -0,0 +1,34 @@ +/* + * 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 ExcludeFromCodeCoverageAttributesNeedJustification : ExcludeFromCodeCoverageAttributesNeedJustificationBase +{ + protected override ILanguageFacade Language => VisualBasicFacade.Instance; + + protected override SyntaxNode GetJustificationExpression(SyntaxNode node) => + node is AttributeSyntax { ArgumentList.Arguments: { Count: 1 } arguments } + && arguments[0] is SimpleArgumentSyntax { Expression: { } } argument + && Language.NameComparer.Equals(argument.GetName(), JustificationPropertyName) + ? argument.Expression + : null; +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs index a79296bd910..cec58c9beb0 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs @@ -6437,7 +6437,7 @@ internal static class RuleTypeMappingCS // ["S6510"], // ["S6511"], // ["S6512"], - // ["S6513"], + ["S6513"] = "CODE_SMELL", // ["S6514"], // ["S6515"], // ["S6516"], diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs index b6d6396c5bc..47d0f2d65af 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs @@ -6437,7 +6437,7 @@ internal static class RuleTypeMappingVB // ["S6510"], // ["S6511"], // ["S6512"], - // ["S6513"], + ["S6513"] = "CODE_SMELL", // ["S6514"], // ["S6515"], // ["S6516"], diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ExcludeFromCodeCoverageAttributesNeedJustificationTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ExcludeFromCodeCoverageAttributesNeedJustificationTest.cs new file mode 100644 index 00000000000..8d36703ea21 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ExcludeFromCodeCoverageAttributesNeedJustificationTest.cs @@ -0,0 +1,68 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2022 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 ExcludeFromCodeCoverageAttributesNeedJustificationTest +{ + private readonly VerifierBuilder builderCS = new VerifierBuilder(); + private readonly VerifierBuilder builderVB = new VerifierBuilder(); + +#if NET + + [TestMethod] + public void ExcludeFromCodeCoverageAttributesNeedJustification_OnAssembly_CS() => + builderCS.AddSnippet("[assembly:System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] // Noncompliant").Verify(); + + [TestMethod] + public void ExcludeFromCodeCoverageAttributesNeedJustification_OnAssembly_VB() => + builderVB.AddSnippet(" ' Noncompliant").Verify(); + + [TestMethod] + public void ExcludeFromCodeCoverageAttributesNeedJustification_CS() => + builderCS.AddPaths("ExcludeFromCodeCoverageAttributesNeedJustification.cs").Verify(); + + [TestMethod] + public void ExcludeFromCodeCoverageAttributesNeedJustification_CSharp9() => + builderCS.AddPaths("ExcludeFromCodeCoverageAttributesNeedJustification.CSharp9.cs").WithTopLevelStatements().Verify(); + + [TestMethod] + public void ExcludeFromCodeCoverageAttributesNeedJustification_CSharp10() => + builderCS.AddPaths("ExcludeFromCodeCoverageAttributesNeedJustification.CSharp10.cs").WithOptions(ParseOptionsHelper.FromCSharp10).Verify(); + + [TestMethod] + public void ExcludeFromCodeCoverageAttributesNeedJustification_VB() => + builderVB.AddPaths("ExcludeFromCodeCoverageAttributesNeedJustification.vb").Verify(); + +#endif + +#if netframework + + [TestMethod] + public void ExcludeFromCodeCoverageAttributesNeedJustification_IgnoredForNet48() => + builderCS.AddPaths("ExcludeFromCodeCoverageAttributesNeedJustification.net48.cs").Verify(); + +#endif + +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ExcludeFromCodeCoverageAttributesNeedJustification.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ExcludeFromCodeCoverageAttributesNeedJustification.CSharp10.cs new file mode 100644 index 00000000000..acac372d875 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ExcludeFromCodeCoverageAttributesNeedJustification.CSharp10.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +class NonCompliant +{ + public void Method(IEnumerable collection) + { + [ExcludeFromCodeCoverage] int Get() => 1; // Noncompliant + + _ = collection.Select([ExcludeFromCodeCoverage] (x) => x + 1); // Noncompliant + + Action a = [ExcludeFromCodeCoverage] () => { }; // Noncompliant + + Action x = true + ? ([ExcludeFromCodeCoverage] () => { }) // Noncompliant + : [ExcludeFromCodeCoverage] () => { }; // Noncompliant + + Call([ExcludeFromCodeCoverage(Justification = "justification")] (x) => { }); + } + + private void Call(Action action) => action(1); +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ExcludeFromCodeCoverageAttributesNeedJustification.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ExcludeFromCodeCoverageAttributesNeedJustification.CSharp9.cs new file mode 100644 index 00000000000..5c4f6589d2c --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ExcludeFromCodeCoverageAttributesNeedJustification.CSharp9.cs @@ -0,0 +1,18 @@ +using System.Diagnostics.CodeAnalysis; +using System; + +[ExcludeFromCodeCoverage] // Noncompliant +void LocalMethod() { } + +[ExcludeFromCodeCoverage] // Noncompliant +static void StaticLocalMethod() { } + +[ExcludeFromCodeCoverage] // Noncompliant {{Add a justification.}} +public record Record +{ + public void Method() + { + [ExcludeFromCodeCoverage] // Noncompliant + static void LocalMethod() { } + } +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ExcludeFromCodeCoverageAttributesNeedJustification.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ExcludeFromCodeCoverageAttributesNeedJustification.cs new file mode 100644 index 00000000000..0b64c7a14fd --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ExcludeFromCodeCoverageAttributesNeedJustification.cs @@ -0,0 +1,113 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Alias = System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute; + +[ExcludeFromCodeCoverage] // Noncompliant ^2#23 {{Add a justification.}} +class Noncompliant +{ + [ExcludeFromCodeCoverage()] // Noncompliant + void WithBrackets() { } + + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] // Noncompliant + void FullyDeclaredNamespace() { } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] // Noncompliant + void GloballyDeclaredNamespace() { } + + [ExcludeFromCodeCoverage(Justification = null)] // Noncompliant + void WithNull() { } + + [ExcludeFromCodeCoverage(Justification = "")] // Noncompliant + void WithEmptyString() { } + + [ExcludeFromCodeCoverage(Justification = " ")] // Noncompliant + void WithWhiteSpace() { } + + [Alias(Justification = "")] // Noncompliant + void WithAlias() { } + + [ExcludeFromCodeCoverage] // Noncompliant + [CLSCompliant(false)] + uint Multiple() { return 0; } + + [ExcludeFromCodeCoverage, CLSCompliant(false)] +// ^^^^^^^^^^^^^^^^^^^^^^^ + uint Combined() { return 0; } + + [ExcludeFromCodeCoverage] // Noncompliant + Noncompliant() { } + + [ExcludeFromCodeCoverage] // Noncompliant + void Method() { } + + [ExcludeFromCodeCoverage] // Noncompliant + event EventHandler Event; +} + +interface IInterface +{ + [ExcludeFromCodeCoverage] // Noncompliant + void Method(); +} + +[ExcludeFromCodeCoverage] // Noncompliant +struct ProgramStruct +{ + [ExcludeFromCodeCoverage] // Noncompliant + void Method() { } +} + +[ExcludeFromCodeCoverage(Justification = "justification")] +class Compliant +{ + [ExcludeFromCodeCoverage(Justification = "justification")] + Compliant() { } + + [ExcludeFromCodeCoverage(Justification = "justification")] + void Method() { } + + [ExcludeFromCodeCoverage(Justification = "justification")] + string Property { get; set; } + + [ExcludeFromCodeCoverage(Justification = "justification")] + event EventHandler Event; +} + +interface IComplaintInterface +{ + [ExcludeFromCodeCoverage(Justification = "justification")] + void Method(); +} + +[ExcludeFromCodeCoverage(Justification = "justification")] +struct ComplaintStruct +{ + [ExcludeFromCodeCoverage(Justification = "justification")] + void Method() { } +} + +class NotApplicable +{ + [CLSCompliant(false)] + enum Enum { foo, bar } + + NotApplicable() { } + + void Method() { } + + int Property { get; set; } + + int Field; + + event EventHandler Event; + + delegate void Delegate(); + + [OtherNamespace.ExcludeFromCodeCoverage] + void SameName() { } +} + +namespace OtherNamespace +{ + public class ExcludeFromCodeCoverageAttribute : Attribute { } +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ExcludeFromCodeCoverageAttributesNeedJustification.net48.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ExcludeFromCodeCoverageAttributesNeedJustification.net48.cs new file mode 100644 index 00000000000..e4c7bed3892 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ExcludeFromCodeCoverageAttributesNeedJustification.net48.cs @@ -0,0 +1,47 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +[ExcludeFromCodeCoverage] +class Ignores +{ + [ExcludeFromCodeCoverage(Justification = "not existing")] // Error[CS0246] + void JustifcationDoesNotExist() { } + + [ExcludeFromCodeCoverage()] // Compliant: "Justification" property was added in .Net 5 + void WithBrackets() { } + + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + void FullyDeclaredNamespace() { } + + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + void GloballyDeclaredNamespace() { } + + [ExcludeFromCodeCoverage] + [CLSCompliant(false)] + uint Multiple() { return 0; } + + [ExcludeFromCodeCoverage, CLSCompliant(false)] + uint Combined() { return 0; } + + [ExcludeFromCodeCoverage] + Ignores() { } + + [ExcludeFromCodeCoverage] + void Method() { } + + [ExcludeFromCodeCoverage] + event EventHandler Event; +} + +interface IInterface +{ + [ExcludeFromCodeCoverage] + void Method(); +} + +[ExcludeFromCodeCoverage] +struct ProgramStruct +{ + [ExcludeFromCodeCoverage] + void Method() { } +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ExcludeFromCodeCoverageAttributesNeedJustification.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ExcludeFromCodeCoverageAttributesNeedJustification.vb new file mode 100644 index 00000000000..6446f8eb3b1 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ExcludeFromCodeCoverageAttributesNeedJustification.vb @@ -0,0 +1,123 @@ +' Commented line for concurrent namespace +Imports System.Diagnostics.CodeAnalysis +Imports [Alias] = System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute + + ' Noncompliant^2#23 {{Add a justification.}} +Class Noncompliant + Sub WithBrackets() ' Noncompliant^6#25 + End Sub + + ' Noncompliant + Sub FullyDeclaredNamespace() + End Sub + + ' Noncompliant + Sub GloballyDeclaredNamespace() + End Sub + + <[Alias](Justification:="")> ' Noncompliant + Sub WithAlias() + End Sub + + ' Noncompliant + Sub WithNothing() { } + End Sub + + ' Noncompliant + Sub WithEmptyString() { } + End Sub + + ' Noncompliant + Sub WithWhiteSpace() { } + End Sub + + ' Noncompliant + + Function Multiple() As UInteger + Return 0 + End Function + + Function Combined() As UInteger ' Noncompliant + Return 0 + End Function + + ' Noncompliant + Sub New() + End Sub + + ' Noncompliant + Sub Method() + End Sub + + ' Noncompliant + Property [Property] As Integer + + ' Noncompliant + Event [Event] As EventHandler + +End Class + +Interface IInterface + ' Noncompliant + Sub Method() +End Interface + + ' Noncompliant +Structure ProgramStruct + ' Noncompliant + Sub Method() + End Sub +End Structure + + +Class Compliant + + + Sub New() + End Sub + + + Sub Method() + End Sub + + + Property [Property] As String + + + Event [Event] As EventHandler +End Class + +Interface IComplaintInterface + + Sub Method() +End Interface + + +Structure ComplaintStruct + + Sub Method() + End Sub +End Structure + +Class NotApplicable + + Sub New() + End Sub + + Sub Method() + End Sub + + Property [Property] As Integer + + Event [Event] As EventHandler + + + Sub SameName() + End Sub +End Class + +Namespace NotSystem + Public Class ExcludeFromCodeCoverageAttribute + Inherits Attribute + End Class +End Namespace