diff options
author | Andy Gocke <andy@commentout.net> | 2021-03-31 09:43:23 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-31 09:43:23 +0300 |
commit | dd7d70118b7146125781c830bbff47a8cb953f39 (patch) | |
tree | 44497e9560353b92d24512d110d596370b6554bb /test/ILLink.RoslynAnalyzer.Tests | |
parent | 28d5013b48f1684cc305baa017b2b716230a1377 (diff) |
Add codefix for RequiresUnreferencedCode (#1865)
Simple codefix that just adds RequiresUnreferencedCode to the containing
method if a RequiresUnreferencedCode analyzer warning fires.
Diffstat (limited to 'test/ILLink.RoslynAnalyzer.Tests')
3 files changed, 384 insertions, 3 deletions
diff --git a/test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj b/test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj index 03d9d5f59..f1aafa616 100644 --- a/test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj +++ b/test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj @@ -6,7 +6,9 @@ <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="$(MicrosoftCodeAnalysisCSharpAnalyzerTestingXunitVersion)" /> + <PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="$(MicrosoftCodeAnalysisCSharpAnalyzerTestingXunitVersion)" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisCSharpWorkspacesVersion)" /> + <ProjectReference Include="..\..\src\ILLink.CodeFix\ILLink.CodeFixProvider.csproj" /> <ProjectReference Include="..\..\src\ILLink.RoslynAnalyzer\ILLink.RoslynAnalyzer.csproj" /> <ProjectReference Include="../Mono.Linker.Tests.Cases\Mono.Linker.Tests.Cases.csproj" /> </ItemGroup> diff --git a/test/ILLink.RoslynAnalyzer.Tests/RequiresUnreferencedCodeAnalyzerTests.cs b/test/ILLink.RoslynAnalyzer.Tests/RequiresUnreferencedCodeAnalyzerTests.cs index 34faf97fc..4819defdd 100644 --- a/test/ILLink.RoslynAnalyzer.Tests/RequiresUnreferencedCodeAnalyzerTests.cs +++ b/test/ILLink.RoslynAnalyzer.Tests/RequiresUnreferencedCodeAnalyzerTests.cs @@ -2,11 +2,14 @@ // 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; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; using Xunit; -using VerifyCS = ILLink.RoslynAnalyzer.Tests.CSharpAnalyzerVerifier< - ILLink.RoslynAnalyzer.RequiresUnreferencedCodeAnalyzer>; +using VerifyCS = ILLink.RoslynAnalyzer.Tests.CSharpCodeFixVerifier< + ILLink.RoslynAnalyzer.RequiresUnreferencedCodeAnalyzer, + ILLink.CodeFix.RequiresUnreferencedCodeCodeFixProvider>; namespace ILLink.RoslynAnalyzer.Tests { @@ -19,6 +22,38 @@ namespace ILLink.RoslynAnalyzer.Tests expected); } + static Task VerifyRequiresUnreferencedCodeCodeFix ( + string source, + string fixedSource, + DiagnosticResult[] baselineExpected, + DiagnosticResult[] fixedExpected) + { + const string rucDef = @" +#nullable enable +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method, Inherited = false)] + public sealed class RequiresUnreferencedCodeAttribute : Attribute + { + public RequiresUnreferencedCodeAttribute(string message) { Message = message; } + public string Message { get; } + public string? Url { get; set; } + } +} +"; + var test = new VerifyCS.Test { + TestCode = source + rucDef, + FixedCode = fixedSource + rucDef, + }; + test.ExpectedDiagnostics.AddRange (baselineExpected); + test.TestState.AnalyzerConfigFiles.Add ( + ("/.editorconfig", SourceText.From (@$" +is_global = true +build_property.{MSBuildPropertyOptionNames.EnableTrimAnalyzer} = true"))); + test.FixedState.ExpectedDiagnostics.AddRange (fixedExpected); + return test.RunAsync (); + } + [Fact] public Task SimpleDiagnostic () { @@ -37,6 +72,251 @@ class C } [Fact] + public async Task SimpleDiagnosticFix () + { + var test = @" +using System.Diagnostics.CodeAnalysis; + +public class C +{ + [RequiresUnreferencedCodeAttribute(""message"")] + public int M1() => 0; + + int M2() => M1(); +} +class D +{ + public int M3(C c) => c.M1(); + + public class E + { + public int M4(C c) => c.M1(); + } +} +public class E +{ + public class F + { + public int M5(C c) => c.M1(); + } +} +"; + + var fixtest = @" +using System.Diagnostics.CodeAnalysis; + +public class C +{ + [RequiresUnreferencedCodeAttribute(""message"")] + public int M1() => 0; + + [RequiresUnreferencedCode(""Calls M1"")] + int M2() => M1(); +} +class D +{ + [RequiresUnreferencedCode(""Calls M1"")] + public int M3(C c) => c.M1(); + + public class E + { + [RequiresUnreferencedCode(""Calls M1"")] + public int M4(C c) => c.M1(); + } +} +public class E +{ + public class F + { + [RequiresUnreferencedCode()] + public int M5(C c) => c.M1(); + } +} +"; + + await VerifyRequiresUnreferencedCodeCodeFix (test, fixtest, new[] { + // /0/Test0.cs(9,17): warning IL2026: Using method 'C.M1()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. message. + VerifyCS.Diagnostic ().WithSpan (9, 17, 9, 21).WithArguments ("C.M1()", "message", ""), + // /0/Test0.cs(13,27): warning IL2026: Using method 'C.M1()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. message. + VerifyCS.Diagnostic().WithSpan(13, 27, 13, 33).WithArguments("C.M1()", "message", ""), + // /0/Test0.cs(17,31): warning IL2026: Using method 'C.M1()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. message. + VerifyCS.Diagnostic ().WithSpan (17, 31, 17, 37).WithArguments ("C.M1()", "message", ""), + // /0/Test0.cs(24,31): warning IL2026: Using method 'C.M1()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. message. + VerifyCS.Diagnostic ().WithSpan (24, 31, 24, 37).WithArguments ("C.M1()", "message", "") + }, new[] { + // /0/Test0.cs(27,10): error CS7036: There is no argument given that corresponds to the required formal parameter 'message' of 'RequiresUnreferencedCodeAttribute.RequiresUnreferencedCodeAttribute(string)' + DiagnosticResult.CompilerError("CS7036").WithSpan(27, 10, 27, 36).WithArguments("message", "System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute.RequiresUnreferencedCodeAttribute(string)"), + } + ); + } + + [Fact] + public Task FixInLambda () + { + var src = @" +using System; +using System.Diagnostics.CodeAnalysis; + +public class C +{ + [RequiresUnreferencedCodeAttribute(""message"")] + public int M1() => 0; + + Action M2() + { + return () => M1(); + } +}"; + var fix = @" +using System; +using System.Diagnostics.CodeAnalysis; + +public class C +{ + [RequiresUnreferencedCodeAttribute(""message"")] + public int M1() => 0; + + Action M2() + { + return () => M1(); + } +}"; + // No fix available inside a lambda, requries manual code change since attribute cannot + // be applied + return VerifyRequiresUnreferencedCodeCodeFix ( + src, + fix, + baselineExpected: new[] { + // /0/Test0.cs(12,22): warning IL2026: Using method 'C.M1()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. message. + VerifyCS.Diagnostic().WithSpan(12, 22, 12, 26).WithArguments("C.M1()", "message", "") + }, + fixedExpected: Array.Empty<DiagnosticResult> ()); + } + + [Fact] + public Task FixInLocalFunc () + { + var src = @" +using System; +using System.Diagnostics.CodeAnalysis; + +public class C +{ + [RequiresUnreferencedCodeAttribute(""message"")] + public int M1() => 0; + + Action M2() + { + void Wrapper () => M1(); + return Wrapper; + } +}"; + var fix = @" +using System; +using System.Diagnostics.CodeAnalysis; + +public class C +{ + [RequiresUnreferencedCodeAttribute(""message"")] + public int M1() => 0; + + Action M2() + { + [global::System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute(""Calls M1"")] void Wrapper () => M1(); + return Wrapper; + } +}"; + // Roslyn currently doesn't simplify the attribute name properly, see https://github.com/dotnet/roslyn/issues/52039 + return VerifyRequiresUnreferencedCodeCodeFix ( + src, + fix, + baselineExpected: new[] { + // /0/Test0.cs(12,28): warning IL2026: Using method 'C.M1()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. message. + VerifyCS.Diagnostic().WithSpan(12, 28, 12, 32).WithArguments("C.M1()", "message", "") + }, + fixedExpected: Array.Empty<DiagnosticResult> ()); + } + + [Fact] + public Task FixInCtor () + { + var src = @" +using System; +using System.Diagnostics.CodeAnalysis; + +public class C +{ + [RequiresUnreferencedCodeAttribute(""message"")] + public static int M1() => 0; + + public C() => M1(); +}"; + var fix = @" +using System; +using System.Diagnostics.CodeAnalysis; + +public class C +{ + [RequiresUnreferencedCodeAttribute(""message"")] + public static int M1() => 0; + + [RequiresUnreferencedCode()] + public C() => M1(); +}"; + // Roslyn currently doesn't simplify the attribute name properly, see https://github.com/dotnet/roslyn/issues/52039 + return VerifyRequiresUnreferencedCodeCodeFix ( + src, + fix, + baselineExpected: new[] { + // /0/Test0.cs(10,19): warning IL2026: Using method 'C.M1()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. message. + VerifyCS.Diagnostic().WithSpan(10, 19, 10, 23).WithArguments("C.M1()", "message", "") + }, + fixedExpected: new[] { + // /0/Test0.cs(10,6): error CS7036: There is no argument given that corresponds to the required formal parameter 'message' of 'RequiresUnreferencedCodeAttribute.RequiresUnreferencedCodeAttribute(string)' + DiagnosticResult.CompilerError("CS7036").WithSpan(10, 6, 10, 32).WithArguments("message", "System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute.RequiresUnreferencedCodeAttribute(string)"), + }); + } + + [Fact] + public Task FixInPropertyDecl () + { + var src = @" +using System; +using System.Diagnostics.CodeAnalysis; + +public class C +{ + [RequiresUnreferencedCodeAttribute(""message"")] + public int M1() => 0; + + int M2 => M1(); +}"; + var fix = @" +using System; +using System.Diagnostics.CodeAnalysis; + +public class C +{ + [RequiresUnreferencedCodeAttribute(""message"")] + public int M1() => 0; + + int M2 => M1(); +}"; + // Can't apply RUC on properties at the moment + return VerifyRequiresUnreferencedCodeCodeFix ( + src, + fix, + baselineExpected: new[] { + // /0/Test0.cs(10,15): warning IL2026: Using method 'C.M1()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. message. + VerifyCS.Diagnostic().WithSpan(10, 15, 10, 19).WithArguments("C.M1()", "message", "") + }, + fixedExpected: new[] { + // /0/Test0.cs(10,15): warning IL2026: Using method 'C.M1()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. message. + VerifyCS.Diagnostic().WithSpan(10, 15, 10, 19).WithArguments("C.M1()", "message", "") + }); + } + + [Fact] public Task TestRequiresWithMessageAndUrlOnMethod () { var MessageAndUrlOnMethod = @" @@ -78,7 +358,7 @@ class C } }"; return VerifyRequiresUnreferencedCodeAnalyzer (PropertyRequires, - // (8,7): warning IL2026: Using method 'C.PropertyRequires.get' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. Message for --getter PropertyRequires--. + // (8,7): warning IL2026: Using method 'C.PropertyRequires.get' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. Message for --getter PropertyRequires--. VerifyCS.Diagnostic ().WithSpan (8, 7, 8, 23).WithArguments ("C.PropertyRequires.get", "Message for --getter PropertyRequires--", "") ); } diff --git a/test/ILLink.RoslynAnalyzer.Tests/Verifiers/CSharpCodeFixVerifier`2.cs b/test/ILLink.RoslynAnalyzer.Tests/Verifiers/CSharpCodeFixVerifier`2.cs new file mode 100644 index 000000000..6ba811280 --- /dev/null +++ b/test/ILLink.RoslynAnalyzer.Tests/Verifiers/CSharpCodeFixVerifier`2.cs @@ -0,0 +1,99 @@ +// 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; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace ILLink.RoslynAnalyzer.Tests +{ + /// <summary> + /// A default verifier for diagnostic analyzers with code fixes. + /// </summary> + /// <typeparam name="TAnalyzer">The <see cref="DiagnosticAnalyzer"/> to test.</typeparam> + /// <typeparam name="TCodeFix">The <see cref="CodeFixProvider"/> to test.</typeparam> + /// <typeparam name="TTest">The test implementation to use.</typeparam> + public partial class CSharpCodeFixVerifier<TAnalyzer, TCodeFix> + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + public class Test : CSharpCodeFixTest<TAnalyzer, TCodeFix, XUnitVerifier> + { + public Test () + { + SolutionTransforms.Add ((solution, projectId) => { + var compilationOptions = solution.GetProject (projectId)!.CompilationOptions; + compilationOptions = compilationOptions!.WithSpecificDiagnosticOptions ( + compilationOptions.SpecificDiagnosticOptions.SetItems (CSharpVerifierHelper.NullableWarnings)); + solution = solution.WithProjectCompilationOptions (projectId, compilationOptions); + + return solution; + }); + } + } + + /// <inheritdoc cref="AnalyzerVerifier{TAnalyzer}.Diagnostic()"/> + public static DiagnosticResult Diagnostic () + => CSharpAnalyzerVerifier<TAnalyzer>.Diagnostic (); + + /// <inheritdoc cref="AnalyzerVerifier{TAnalyzer, TTest}.Diagnostic(string)"/> + public static DiagnosticResult Diagnostic (string diagnosticId) + => CSharpAnalyzerVerifier<TAnalyzer>.Diagnostic (diagnosticId); + + /// <inheritdoc cref="AnalyzerVerifier{TAnalyzer, TTest}.Diagnostic(DiagnosticDescriptor)"/> + public static DiagnosticResult Diagnostic (DiagnosticDescriptor descriptor) + => CSharpAnalyzerVerifier<TAnalyzer>.Diagnostic (descriptor); + + /// <inheritdoc cref="AnalyzerVerifier{TAnalyzer, TTest}.VerifyAnalyzerAsync(string, DiagnosticResult[])"/> + public static Task VerifyAnalyzerAsync (string source, (string, string)[]? analyzerOptions = null, params DiagnosticResult[] expected) + => CSharpAnalyzerVerifier<TAnalyzer>.VerifyAnalyzerAsync (source, analyzerOptions, expected); + + /// <summary> + /// Verifies the analyzer provides diagnostics which, in combination with the code fix, produce the expected + /// fixed code. + /// </summary> + /// <param name="source">The source text to test. Any diagnostics are defined in markup.</param> + /// <param name="fixedSource">The expected fixed source text. Any remaining diagnostics are defined in markup.</param> + /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> + public static Task VerifyCodeFixAsync (string source, string fixedSource) + => VerifyCodeFixAsync (source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + + /// <summary> + /// Verifies the analyzer provides diagnostics which, in combination with the code fix, produce the expected + /// fixed code. + /// </summary> + /// <param name="source">The source text to test, which may include markup syntax.</param> + /// <param name="expected">The expected diagnostic. This diagnostic is in addition to any diagnostics defined in + /// markup.</param> + /// <param name="fixedSource">The expected fixed source text. Any remaining diagnostics are defined in markup.</param> + /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> + public static Task VerifyCodeFixAsync (string source, DiagnosticResult expected, string fixedSource) + => VerifyCodeFixAsync (source, new[] { expected }, fixedSource); + + /// <summary> + /// Verifies the analyzer provides diagnostics which, in combination with the code fix, produce the expected + /// fixed code. + /// </summary> + /// <param name="source">The source text to test, which may include markup syntax.</param> + /// <param name="expected">The expected diagnostics. These diagnostics are in addition to any diagnostics + /// defined in markup.</param> + /// <param name="fixedSource">The expected fixed source text. Any remaining diagnostics are defined in markup.</param> + /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> + public static Task VerifyCodeFixAsync (string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange (expected); + return test.RunAsync (CancellationToken.None); + } + } +} |