-
Notifications
You must be signed in to change notification settings - Fork 231
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New Rule: ExcludeFromCodeCoverage attributes should include a justifi…
- Loading branch information
Showing
16 changed files
with
675 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<p>The <a | ||
href="https://learn.microsoft.com/dotnet/api/system.diagnostics.codeanalysis.excludefromcodecoverageattribute">ExcludeFromCodeCoverageAttribute</a> is | ||
used to exclude portions of code from <a href="https://learn.microsoft.com/dotnet/core/testing/unit-testing-code-coverage">code coverage | ||
reporting</a>. It is a bad practice to retain code that is not covered by unit tests. In .Net 5, the <code>Justification</code> property was added to | ||
the <code>ExcludeFromCodeCoverageAttribute</code> as an opportunity to document the rationale for the exclusion. This rule raises an issue when no | ||
such justification is given.</p> | ||
<h2>Noncompliant Code Example</h2> | ||
<pre> | ||
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; | ||
} | ||
} | ||
</pre> | ||
<h2>Compliant Solution</h2> | ||
<pre> | ||
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; | ||
} | ||
} | ||
</pre> | ||
<h2>See</h2> | ||
<ul> | ||
<li> <a href="https://learn.microsoft.com/dotnet/api/system.diagnostics.codeanalysis.excludefromcodecoverageattribute">API browser</a> - | ||
ExcludeFromCodeCoverageAttribute </li> | ||
<li> <a href="https://learn.microsoft.com/dotnet/core/testing/unit-testing-code-coverage">DevOps and testing</a> - Code coverage reporting </li> | ||
</ul> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<p>The <a | ||
href="https://learn.microsoft.com/dotnet/api/system.diagnostics.codeanalysis.excludefromcodecoverageattribute">ExcludeFromCodeCoverageAttribute</a> is | ||
used to exclude portions of code from <a href="https://learn.microsoft.com/dotnet/core/testing/unit-testing-code-coverage">code coverage | ||
reporting</a>. It is a bad practice to retain code that is not covered by unit tests. In .Net 5, the <code>Justification</code> property was added to | ||
the <code>ExcludeFromCodeCoverageAttribute</code> as an opportunity to document the rationale for the exclusion. This rule raises an issue when no | ||
such justification is given.</p> | ||
<h2>Noncompliant Code Example</h2> | ||
<pre> | ||
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 | ||
</pre> | ||
<h2>Compliant Solution</h2> | ||
<pre> | ||
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 | ||
</pre> | ||
<h2>See</h2> | ||
<ul> | ||
<li> <a href="https://learn.microsoft.com/dotnet/api/system.diagnostics.codeanalysis.excludefromcodecoverageattribute">API browser</a> - | ||
ExcludeFromCodeCoverageAttribute </li> | ||
<li> <a href="https://learn.microsoft.com/dotnet/core/testing/unit-testing-code-coverage">DevOps and testing</a> - Code coverage reporting </li> | ||
</ul> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} |
33 changes: 33 additions & 0 deletions
33
...zers/src/SonarAnalyzer.CSharp/Rules/ExcludeFromCodeCoverageAttributesNeedJustification.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<SyntaxKind> | ||
{ | ||
protected override ILanguageFacade<SyntaxKind> 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; | ||
} |
58 changes: 58 additions & 0 deletions
58
.../src/SonarAnalyzer.Common/Rules/ExcludeFromCodeCoverageAttributesNeedJustificationBase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<TSyntaxKind> : SonarDiagnosticAnalyzer<TSyntaxKind> | ||
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); | ||
|
||
/// <summary>"Justification" was added in .Net 5, while ExcludeFromCodeCoverage in netstandard2.0.</summary> | ||
private static bool HasJustificationProperty(INamedTypeSymbol symbol) => | ||
symbol.MemberNames.Contains(JustificationPropertyName); | ||
} |
34 changes: 34 additions & 0 deletions
34
...src/SonarAnalyzer.VisualBasic/Rules/ExcludeFromCodeCoverageAttributesNeedJustification.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<SyntaxKind> | ||
{ | ||
protected override ILanguageFacade<SyntaxKind> 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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
...ts/SonarAnalyzer.UnitTest/Rules/ExcludeFromCodeCoverageAttributesNeedJustificationTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<CS.ExcludeFromCodeCoverageAttributesNeedJustification>(); | ||
private readonly VerifierBuilder builderVB = new VerifierBuilder<VB.ExcludeFromCodeCoverageAttributesNeedJustification>(); | ||
|
||
#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("<Assembly:System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage> ' 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 | ||
|
||
} |
Oops, something went wrong.