diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ObsoleteAttributes.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ObsoleteAttributes.cs index 4b89a6e447e..4147ec53ec6 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ObsoleteAttributes.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ObsoleteAttributes.cs @@ -24,4 +24,10 @@ namespace SonarAnalyzer.Rules.CSharp; public sealed class ObsoleteAttributes : ObsoleteAttributesBase { protected override ILanguageFacade Language => CSharpFacade.Instance; + + protected override SyntaxNode GetExplanationExpression(SyntaxNode node) => + node is AttributeSyntax { ArgumentList.Arguments: { Count: >= 1 } arguments } + && arguments[0] is { Expression: { } expression } + ? expression + : null; } diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ObsoleteAttributesBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ObsoleteAttributesBase.cs index 5d07b213c31..81da4f7345c 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/ObsoleteAttributesBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ObsoleteAttributesBase.cs @@ -31,6 +31,8 @@ public abstract class ObsoleteAttributesBase : SonarDiagnosticAnaly protected abstract ILanguageFacade Language { get; } + protected abstract SyntaxNode GetExplanationExpression(SyntaxNode node); + public override ImmutableArray SupportedDiagnostics { get; } internal DiagnosticDescriptor ExplanationNeededRule { get; } @@ -54,11 +56,15 @@ protected sealed override void Initialize(SonarAnalysisContext context) => var location = c.Node.GetLocation(); c.ReportIssue(Diagnostic.Create(RemoveRule, location)); - if (!attribute.GetParameters().Any()) + if (NoExplanation(c.Node, c.SemanticModel)) { c.ReportIssue(Diagnostic.Create(ExplanationNeededRule, location)); } } }, Language.SyntaxKind.Attribute); + + private bool NoExplanation(SyntaxNode node, SemanticModel model) => + GetExplanationExpression(node) is not { } justification + || string.IsNullOrWhiteSpace(Language.FindConstantValue(model, justification) as string); } diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ObsoleteAttributes.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ObsoleteAttributes.cs index 281e3f02633..1beb9d950fc 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ObsoleteAttributes.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ObsoleteAttributes.cs @@ -24,4 +24,10 @@ namespace SonarAnalyzer.Rules.VisualBasic; public sealed class ObsoleteAttributes : ObsoleteAttributesBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; + + protected override SyntaxNode GetExplanationExpression(SyntaxNode node) => + node is AttributeSyntax { ArgumentList.Arguments: { Count: >= 1 } arguments } + && arguments[0] is SimpleArgumentSyntax { Expression: { } } argument + ? argument.Expression + : null; } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ObsoleteAttributesNeedExplanation.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ObsoleteAttributesNeedExplanation.cs index caf6a3aca94..58f9aa455d8 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ObsoleteAttributesNeedExplanation.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ObsoleteAttributesNeedExplanation.cs @@ -12,6 +12,15 @@ void FullyDeclaredNamespace() { } [global::System.Obsolete] // Noncompliant void GloballyDeclaredNamespace() { } + [Obsolete(null)] // Noncompliant + void WithNull() { } + + [Obsolete("")] // Noncompliant + void WithEmptyString() { } + + [Obsolete(" ")] // Noncompliant + void WithWhiteSpace() { } + [Obsolete] // Noncompliant [CLSCompliant(false)] uint Multiple() { return 0; } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ObsoleteAttributesNeedExplanation.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ObsoleteAttributesNeedExplanation.vb index 256f3bd708a..a123ba43d75 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ObsoleteAttributesNeedExplanation.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ObsoleteAttributesNeedExplanation.vb @@ -14,6 +14,18 @@ Class Noncompliant Private Sub GloballyDeclaredNamespace() End Sub + ' Noncompliant + Sub WithNothing() { } + End Sub + + ' Noncompliant + Sub WithEmptyString() { } + End Sub + + ' Noncompliant + Sub WithWhiteSpace() { } + End Sub + ' Noncompliant Private Function Multiple() As UInteger