File: src\Analyzers\CSharp\Tests\RemoveUnnecessarySuppressions\RemoveUnnecessaryAttributeSuppressionsTests.cs
Web Access
Project: src\src\Features\CSharpTest\Microsoft.CodeAnalysis.CSharp.Features.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Features.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.RemoveUnnecessarySuppressions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
using VerifyCS = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.CSharpCodeFixVerifier<
    Microsoft.CodeAnalysis.CSharp.RemoveUnnecessarySuppressions.CSharpRemoveUnnecessaryAttributeSuppressionsDiagnosticAnalyzer,
    Microsoft.CodeAnalysis.RemoveUnnecessarySuppressions.RemoveUnnecessaryAttributeSuppressionsCodeFixProvider>;
 
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.RemoveUnnecessarySuppressions;
 
[Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessarySuppressions)]
[WorkItem("https://github.com/dotnet/roslyn/issues/44176")]
public class RemoveUnnecessaryAttributeSuppressionsTests
{
    [Theory, CombinatorialData]
    public void TestStandardProperty(AnalyzerProperty property)
        => VerifyCS.VerifyStandardProperty(property);
 
    [Theory]
    // Field
    [InlineData(@"Scope = ""member""", @"Target = ""~F:N.C.F""", "assembly")]
    // Property
    [InlineData(@"Scope = ""member""", @"Target = ""~P:N.C.P""", "assembly")]
    // Method
    [InlineData(@"Scope = ""member""", @"Target = ""~M:N.C.M()""", "assembly")]
    // Type
    [InlineData(@"Scope = ""member""", @"Target = ""~T:N.C""", "assembly")]
    // Namespace
    [InlineData(@"Scope = ""namespace""", @"Target = ""~N:N""", "assembly")]
    // NamespaceAndDescendants
    [InlineData(@"Scope = ""namespaceanddescendants""", @"Target = ""~N:N""", "assembly")]
    // Module - no scope, no target
    [InlineData(null, null, "assembly")]
    // Module - no target
    [InlineData(@"Scope = ""module""", null, "assembly")]
    // Module - null target
    [InlineData(@"Scope = ""module""", @"Target = null", "assembly")]
    // Resource - not handled
    [InlineData(@"Scope = ""resource""", @"Target = """"", "assembly")]
    // 'module' attribute target
    [InlineData(@"Scope = ""member""", @"Target = ""~M:N.C.M()""", "module")]
    // Member with non-matching scope (seems to be respected by suppression decoder)
    [InlineData(@"Scope = ""type""", @"Target = ""~M:N.C.M()""", "assembly")]
    [InlineData(@"Scope = ""namespace""", @"Target = ""~F:N.C.F""", "assembly")]
    // Case insensitive scope
    [InlineData(@"Scope = ""Member""", @"Target = ""~F:N.C.F""", "assembly")]
    [InlineData(@"Scope = ""MEMBER""", @"Target = ""~F:N.C.F""", "assembly")]
    public async Task ValidSuppressions(string? scope, string? target, string attributeTarget)
    {
        var scopeString = scope != null ? $@", {scope}" : string.Empty;
        var targetString = target != null ? $@", {target}" : string.Empty;
 
        var input = $@"
[{attributeTarget}: System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Justification = ""Pending""{scopeString}{targetString})]
 
namespace N
{{
    class C
    {{
        public int F;
        public int P {{ get; }}
        public void M() {{ }}
    }}
}}";
        await VerifyCS.VerifyCodeFixAsync(input, input);
    }
 
    [Theory]
    // Field - no matching symbol
    [InlineData(@"Scope = ""member""", @"Target = ""~F:N.C.F2""", "assembly")]
    // Field - no matching symbol (case insensitive)
    [InlineData(@"Scope = ""Member""", @"Target = ""~F:N.C.F2""", "assembly")]
    [InlineData(@"Scope = ""MEMBER""", @"Target = ""~F:N.C.F2""", "assembly")]
    // Property - invalid scope
    [InlineData(@"Scope = ""invalid""", @"Target = ""~P:N.C.P""", "assembly")]
    // Method - wrong signature
    [InlineData(@"Scope = ""member""", @"Target = ""~M:N.C.M(System.Int32)""", "assembly")]
    // Method - module scope
    [InlineData(@"Scope = ""module""", @"Target = ""~M:N.C.M()""", "assembly")]
    // Method - null scope
    [InlineData(@"Scope = null", @"Target = ""~M:N.C.M()""", "assembly")]
    // Method - no scope
    [InlineData(null, @"Target = ""~M:N.C.M()""", "assembly")]
    // Member scope - null target
    [InlineData(@"Scope = ""member""", @"Target = null", "assembly")]
    // Member scope - no target
    [InlineData(@"Scope = ""member""", null, "assembly")]
    // Type - no matching namespace
    [InlineData(@"Scope = ""type""", @"Target = ""~T:N2.C""", "assembly")]
    // Namespace - extra namespace qualification
    [InlineData(@"Scope = ""namespace""", @"Target = ""~N:N.N2""", "assembly")]
    // NamespaceAndDescendants - empty target
    [InlineData(@"Scope = ""namespaceanddescendants""", @"Target = """"", "assembly")]
    // Module - no scope, empty target
    [InlineData(null, @"Target = """"", "assembly")]
    // Module - no scope, non-empty target
    [InlineData(null, @"Target = ""~T:N.C""", "assembly")]
    // Module scope, empty target
    [InlineData(@"Scope = ""module""", @"Target = """"", "assembly")]
    // Module no scope, non-empty target
    [InlineData(@"Scope = ""module""", @"Target = ""~T:N.C""", "assembly")]
    public async Task InvalidSuppressions(string? scope, string? target, string attributeTarget)
    {
        var scopeString = scope != null ? $@", {scope}" : string.Empty;
        var targetString = target != null ? $@", {target}" : string.Empty;
 
        var input = $@"
[{attributeTarget}: [|System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Justification = ""Pending""{scopeString}{targetString})|]]
 
namespace N
{{
    class C
    {{
        public int F;
        public int P {{ get; }}
        public void M() {{ }}
    }}
}}";
 
        var fixedCode = $@"
 
namespace N
{{
    class C
    {{
        public int F;
        public int P {{ get; }}
        public void M() {{ }}
    }}
}}";
        await VerifyCS.VerifyCodeFixAsync(input, fixedCode);
    }
 
    [Fact]
    public async Task ValidAndInvalidSuppressions()
    {
        var attributePrefix = @"System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Justification = ""Pending""";
        var validSuppression = $@"{attributePrefix}, Scope = ""member"", Target = ""~T:C"")";
        var invalidSuppression = $@"[|{attributePrefix}, Scope = ""member"", Target = """")|]";
 
        var input = $@"
[assembly: {validSuppression}]
[assembly: {invalidSuppression}]
[assembly: {validSuppression}, {validSuppression}]
[assembly: {invalidSuppression}, {invalidSuppression}]
[assembly: {validSuppression}, {invalidSuppression}]
[assembly: {invalidSuppression}, {validSuppression}]
[assembly: {invalidSuppression}, {validSuppression}, {invalidSuppression}, {validSuppression}]
 
class C {{ }}
";
 
        var fixedCode = $@"
[assembly: {validSuppression}]
[assembly: {validSuppression}, {validSuppression}]
[assembly: {validSuppression}]
[assembly: {validSuppression}]
[assembly: {validSuppression}, {validSuppression}]
 
class C {{ }}
";
        await VerifyCS.VerifyCodeFixAsync(input, fixedCode);
    }
 
    [Theory]
    [InlineData("")]
    [InlineData(@", Scope = ""member"", Target = ""~M:C.M()""")]
    [InlineData(@", Scope = ""invalid"", Target = ""invalid""")]
    public async Task LocalSuppressions(string scopeAndTarget)
    {
        var input = $@"
[System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Justification = ""Pending""{scopeAndTarget})]
class C
{{
    public void M() {{ }}
}}";
        await VerifyCS.VerifyCodeFixAsync(input, input);
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/45465")]
    public async Task LegacyModeGlobalSuppressionWithNamespaceAndDescendantsScope()
    {
        var target = "N:N.InvalidChild";
        var input = $@"
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Scope = ""namespaceanddescendants"", Target = {{|#0:""{target}""|}})]
 
namespace N
{{
    class C {{ }}
}}";
        var expectedDiagnostic = VerifyCS.Diagnostic(AbstractRemoveUnnecessaryAttributeSuppressionsDiagnosticAnalyzer.LegacyFormatTargetDescriptor)
                                    .WithLocation(0)
                                    .WithArguments(target);
 
        await VerifyCS.VerifyCodeFixAsync(input, expectedDiagnostic, input);
    }
}