|
// 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);
}
}
|