diff --git a/NuGet.config b/NuGet.config index 35f5827..26b8cc5 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,6 +1,7 @@ + diff --git a/PublicApiAnalyzer/PublicApiAnalyzer.Test/ApiDesign/DeclarePublicAPIAnalyzerTests.cs b/PublicApiAnalyzer/PublicApiAnalyzer.Test/ApiDesign/DeclarePublicAPIAnalyzerTests.cs index d5d95f0..eaf9a12 100644 --- a/PublicApiAnalyzer/PublicApiAnalyzer.Test/ApiDesign/DeclarePublicAPIAnalyzerTests.cs +++ b/PublicApiAnalyzer/PublicApiAnalyzer.Test/ApiDesign/DeclarePublicAPIAnalyzerTests.cs @@ -4,26 +4,20 @@ namespace PublicApiAnalyzer.Test.ApiDesign { using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; using System.Threading.Tasks; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CodeActions; - using Microsoft.CodeAnalysis.CodeFixes; - using Microsoft.CodeAnalysis.Diagnostics; - using Microsoft.CodeAnalysis.Text; + using Microsoft.CodeAnalysis.CSharp.Testing; + using Microsoft.CodeAnalysis.Testing; + using Microsoft.CodeAnalysis.Testing.Verifiers; using PublicApiAnalyzer.ApiDesign; - using TestHelper; using Xunit; using Path = System.IO.Path; - public class DeclarePublicAPIAnalyzerTests : CodeFixVerifier + public class DeclarePublicAPIAnalyzerTests { private string shippedText; - private string shippedFilePath; + private string shippedFilePath = DeclarePublicAPIAnalyzer.ShippedFileName; private string unshippedText; - private string unshippedFilePath; + private string unshippedFilePath = DeclarePublicAPIAnalyzer.UnshippedFileName; [Fact] public async Task SimpleMissingTypeAsync() @@ -37,14 +31,10 @@ private C() { } this.shippedText = string.Empty; this.unshippedText = string.Empty; + var fixedApi = "C" + Environment.NewLine; - var expected = this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("C").WithLocation(2, 14); - await this.VerifyCSharpDiagnosticAsync(source, expected, CancellationToken.None).ConfigureAwait(false); - - string fixedApi = "C" + Environment.NewLine; - var updatedApi = await this.GetUpdatedApiAsync(source, 0, CancellationToken.None).ConfigureAwait(false); - - Assert.Equal(fixedApi, updatedApi.ToString()); + var expected = new DiagnosticResult(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("C").WithLocation(2, 14); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedApi, expected).ConfigureAwait(false); } [Fact] @@ -62,56 +52,40 @@ public void Method() { } this.shippedText = string.Empty; this.unshippedText = string.Empty; + var fixedApi = @"C +C.ArrowExpressionProperty.get -> int +C.C() -> void +C.Field -> int +C.Method() -> void +C.Property.get -> int +C.Property.set -> void +"; DiagnosticResult[] expected = { // Test0.cs(2,14): error RS0016: Symbol 'C' is not part of the declared API. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("C").WithLocation(2, 14), + new DiagnosticResult(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("C").WithLocation(2, 14), // Test0.cs(2,14): warning RS0016: Symbol 'implicit constructor for C' is not part of the declared API. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("implicit constructor for C").WithLocation(2, 14), + new DiagnosticResult(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("implicit constructor for C").WithLocation(2, 14), // Test0.cs(4,16): error RS0016: Symbol 'Field' is not part of the declared API. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("Field").WithLocation(4, 16), + new DiagnosticResult(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("Field").WithLocation(4, 16), // Test0.cs(5,27): error RS0016: Symbol 'Property.get' is not part of the declared API. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("Property.get").WithLocation(5, 27), + new DiagnosticResult(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("Property.get").WithLocation(5, 27), // Test0.cs(5,32): error RS0016: Symbol 'Property.set' is not part of the declared API. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("Property.set").WithLocation(5, 32), + new DiagnosticResult(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("Property.set").WithLocation(5, 32), // Test0.cs(6,17): error RS0016: Symbol 'Method' is not part of the declared API. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("Method").WithLocation(6, 17), + new DiagnosticResult(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("Method").WithLocation(6, 17), // Test0.cs(7,43): error RS0016: Symbol 'ArrowExpressionProperty.get' is not part of the declared API. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("ArrowExpressionProperty.get").WithLocation(7, 43), + new DiagnosticResult(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("ArrowExpressionProperty.get").WithLocation(7, 43), }; - await this.VerifyCSharpDiagnosticAsync(source, expected, CancellationToken.None).ConfigureAwait(false); - - string fixedApi = "C" + Environment.NewLine; - var updatedApi = await this.GetUpdatedApiAsync(source, 0, CancellationToken.None).ConfigureAwait(false); - Assert.Equal(fixedApi, updatedApi.ToString()); - - fixedApi = "C.C() -> void" + Environment.NewLine; - updatedApi = await this.GetUpdatedApiAsync(source, 1, CancellationToken.None).ConfigureAwait(false); - Assert.Equal(fixedApi, updatedApi.ToString()); - - fixedApi = "C.Field -> int" + Environment.NewLine; - updatedApi = await this.GetUpdatedApiAsync(source, 2, CancellationToken.None).ConfigureAwait(false); - Assert.Equal(fixedApi, updatedApi.ToString()); - - fixedApi = "C.Property.get -> int" + Environment.NewLine; - updatedApi = await this.GetUpdatedApiAsync(source, 3, CancellationToken.None).ConfigureAwait(false); - Assert.Equal(fixedApi, updatedApi.ToString()); - - fixedApi = "C.Property.set -> void" + Environment.NewLine; - updatedApi = await this.GetUpdatedApiAsync(source, 4, CancellationToken.None).ConfigureAwait(false); - Assert.Equal(fixedApi, updatedApi.ToString()); - - fixedApi = "C.Method() -> void" + Environment.NewLine; - updatedApi = await this.GetUpdatedApiAsync(source, 5, CancellationToken.None).ConfigureAwait(false); - Assert.Equal(fixedApi, updatedApi.ToString()); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedApi, expected).ConfigureAwait(false); } [Fact] @@ -130,9 +104,9 @@ private C() { } this.unshippedText = string.Empty; // PublicAPI.Shipped.txt(3,1): warning RS0017: Symbol 'C -> void()' is part of the declared API, but is either not public or could not be found - var expected = this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.RemoveDeletedApiRule).WithArguments("C -> void()").WithLocation(DeclarePublicAPIAnalyzer.ShippedFileName, 3, 1); + var expected = new DiagnosticResult(DeclarePublicAPIAnalyzer.RemoveDeletedApiRule).WithArguments("C -> void()").WithLocation(DeclarePublicAPIAnalyzer.ShippedFileName, 3, 1); - await this.VerifyCSharpDiagnosticAsync(source, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source, expected).ConfigureAwait(false); } [Fact] @@ -149,7 +123,7 @@ public class C C.C() -> void"; this.unshippedText = string.Empty; - await this.VerifyCSharpDiagnosticAsync(source, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source).ConfigureAwait(false); } [Fact] @@ -166,7 +140,7 @@ public class C this.unshippedText = @" C.C() -> void"; - await this.VerifyCSharpDiagnosticAsync(source, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source).ConfigureAwait(false); } [Fact] @@ -183,10 +157,10 @@ public class C this.unshippedText = string.Empty; // Test0.cs(2,14): warning RS0016: Symbol 'implicit constructor for C' is not part of the declared API. - string arg = string.Format(RoslynDiagnosticsResources.PublicImplicitConstructorErrorMessageName, "C"); - var expected = this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments(arg).WithLocation(2, 14); + var arg = string.Format(RoslynDiagnosticsResources.PublicImplicitConstructorErrorMessageName, "C"); + var expected = new DiagnosticResult(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments(arg).WithLocation(2, 14); - await this.VerifyCSharpDiagnosticAsync(source, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source, expected).ConfigureAwait(false); } [Fact] @@ -203,10 +177,10 @@ public struct C this.unshippedText = string.Empty; // Test0.cs(2,15): warning RS0016: Symbol 'implicit constructor for C' is not part of the declared API. - string arg = string.Format(RoslynDiagnosticsResources.PublicImplicitConstructorErrorMessageName, "C"); - var expected = this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments(arg).WithLocation(2, 15); + var arg = string.Format(RoslynDiagnosticsResources.PublicImplicitConstructorErrorMessageName, "C"); + var expected = new DiagnosticResult(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments(arg).WithLocation(2, 15); - await this.VerifyCSharpDiagnosticAsync(source, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source, expected).ConfigureAwait(false); } [Fact] @@ -227,10 +201,10 @@ public C(int value) this.unshippedText = string.Empty; // Test0.cs(2,15): warning RS0016: Symbol 'implicit constructor for C' is not part of the declared API. - string arg = string.Format(RoslynDiagnosticsResources.PublicImplicitConstructorErrorMessageName, "C"); - var expected = this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments(arg).WithLocation(2, 15); + var arg = string.Format(RoslynDiagnosticsResources.PublicImplicitConstructorErrorMessageName, "C"); + var expected = new DiagnosticResult(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments(arg).WithLocation(2, 15); - await this.VerifyCSharpDiagnosticAsync(source, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source, expected).ConfigureAwait(false); } [Fact] @@ -249,9 +223,9 @@ private C() { } this.unshippedText = string.Empty; // PublicAPI.Shipped.txt(3,1): warning RS0017: Symbol 'C.C() -> void' is part of the declared API, but is either not public or could not be found - var expected = this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.RemoveDeletedApiRule).WithArguments("C.C() -> void").WithLocation(DeclarePublicAPIAnalyzer.ShippedFileName, 3, 1); + var expected = new DiagnosticResult(DeclarePublicAPIAnalyzer.RemoveDeletedApiRule).WithArguments("C.C() -> void").WithLocation(DeclarePublicAPIAnalyzer.ShippedFileName, 3, 1); - await this.VerifyCSharpDiagnosticAsync(source, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source, expected).ConfigureAwait(false); } [Fact] @@ -276,7 +250,7 @@ public void Method() { } "; this.unshippedText = string.Empty; - await this.VerifyCSharpDiagnosticAsync(source, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source).ConfigureAwait(false); } [Fact] @@ -302,7 +276,7 @@ public void Method() { } C.Method() -> void "; - await this.VerifyCSharpDiagnosticAsync(source, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source).ConfigureAwait(false); } [Fact] @@ -327,7 +301,7 @@ public enum E E.V3 = 3 -> E "; - await this.VerifyCSharpDiagnosticAsync(source, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source).ConfigureAwait(false); } [Fact] @@ -354,7 +328,7 @@ public class C {DeclarePublicAPIAnalyzer.RemovedApiPrefix}C.Method() -> void "; - await this.VerifyCSharpDiagnosticAsync(source, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source).ConfigureAwait(false); } [Fact] @@ -378,10 +352,10 @@ public class C this.unshippedText = string.Empty; - // error RS0024: The contents of the public API files are invalid: The shipped API file can't have removed members - var expected = this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.PublicApiFilesInvalid).WithArguments(DeclarePublicAPIAnalyzer.InvalidReasonShippedCantHaveRemoved); + // error RS0024: The contents of the public API files are invalid: The shipped API file can't have removed members + var expected = new DiagnosticResult(DeclarePublicAPIAnalyzer.PublicApiFilesInvalid).WithArguments(DeclarePublicAPIAnalyzer.InvalidReasonShippedCantHaveRemoved); - await this.VerifyCSharpDiagnosticAsync(source, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source, expected).ConfigureAwait(false); } [Fact] @@ -406,12 +380,12 @@ public class C this.unshippedText = string.Empty; // Warning RS0025: The symbol 'C.Property.get -> int' appears more than once in the public API files. - var expected = this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DuplicateSymbolInApiFiles) + var expected = new DiagnosticResult(DeclarePublicAPIAnalyzer.DuplicateSymbolInApiFiles) .WithArguments("C.Property.get -> int") .WithLocation(DeclarePublicAPIAnalyzer.ShippedFileName, 6, 1) .WithLocation(DeclarePublicAPIAnalyzer.ShippedFileName, 4, 1); - await this.VerifyCSharpDiagnosticAsync(source, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source, expected).ConfigureAwait(false); } [Fact] @@ -436,12 +410,12 @@ public class C C.Property.get -> int"; // Warning RS0025: The symbol 'C.Property.get -> int' appears more than once in the public API files. - var expected = this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DuplicateSymbolInApiFiles) + var expected = new DiagnosticResult(DeclarePublicAPIAnalyzer.DuplicateSymbolInApiFiles) .WithArguments("C.Property.get -> int") .WithLocation(DeclarePublicAPIAnalyzer.UnshippedFileName, 2, 1) .WithLocation(DeclarePublicAPIAnalyzer.ShippedFileName, 4, 1); - await this.VerifyCSharpDiagnosticAsync(source, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source, expected).ConfigureAwait(false); } [Fact] @@ -468,11 +442,11 @@ private void Method() { } this.unshippedText = string.Empty; // PublicAPI.Shipped.txt(7,1): warning RS0017: Symbol 'C.Method() -> void' is part of the declared API, but is either not public or could not be found - var expected = this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.RemoveDeletedApiRule) + var expected = new DiagnosticResult(DeclarePublicAPIAnalyzer.RemoveDeletedApiRule) .WithArguments("C.Method() -> void") .WithLocation(DeclarePublicAPIAnalyzer.ShippedFileName, 7, 1); - await this.VerifyCSharpDiagnosticAsync(source, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source, expected).ConfigureAwait(false); } [Fact] @@ -503,9 +477,9 @@ private void Method() { } this.unshippedFilePath = Path.Combine(tempPath, DeclarePublicAPIAnalyzer.UnshippedFileName); // <%TEMP_PATH%>\PublicAPI.Shipped.txt(7,1): warning RS0017: Symbol 'C.Method() -> void' is part of the declared API, but is either not public or could not be found - var expected = this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.RemoveDeletedApiRule).WithArguments("C.Method() -> void").WithLocation(this.shippedFilePath, 7, 1); + var expected = new DiagnosticResult(DeclarePublicAPIAnalyzer.RemoveDeletedApiRule).WithArguments("C.Method() -> void").WithLocation(this.shippedFilePath, 7, 1); - await this.VerifyCSharpDiagnosticAsync(source, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source, expected).ConfigureAwait(false); } [Fact] @@ -526,7 +500,7 @@ public async Task TypeForwardsAreProcessed1Async() "; this.unshippedText = string.Empty; - await this.VerifyCSharpDiagnosticAsync(source, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source).ConfigureAwait(false); } [Fact] @@ -554,7 +528,7 @@ abstract System.StringComparer.GetHashCode(string obj) -> int (forwarded, contai "; this.unshippedText = $@""; - await this.VerifyCSharpDiagnosticAsync(source, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source).ConfigureAwait(false); } [Fact] @@ -616,28 +590,28 @@ public void Method5(string p1 = null) { } DiagnosticResult[] expected = { // Test0.cs(5,17): warning RS0016: Symbol 'Method1' is not part of the declared API. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("Method1").WithLocation(5, 17), + new DiagnosticResult(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("Method1").WithLocation(5, 17), // Test0.cs(8,17): warning RS0016: Symbol 'Method1' is not part of the declared API. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("Method1").WithLocation(8, 17), + new DiagnosticResult(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("Method1").WithLocation(8, 17), // Test0.cs(20,17): warning RS0026: Symbol 'Method4' violates the backcompat requirement: 'Do not add multiple overloads with optional parameters'. See 'https://github.com/DotNetAnalyzers/PublicApiAnalyzer/blob/master/documentation/RS0026.md' for details. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters).WithArguments("Method4", DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters.HelpLinkUri).WithLocation(20, 17), + new DiagnosticResult(DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters).WithArguments("Method4", DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters.HelpLinkUri).WithLocation(20, 17), // Test0.cs(25,17): warning RS0016: Symbol 'Method5' is not part of the declared API. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("Method5").WithLocation(25, 17), + new DiagnosticResult(DeclarePublicAPIAnalyzer.DeclareNewApiRule).WithArguments("Method5").WithLocation(25, 17), // Test0.cs(25,17): warning RS0026: Symbol 'Method5' violates the backcompat requirement: 'Do not add multiple overloads with optional parameters'. See 'https://github.com/DotNetAnalyzers/PublicApiAnalyzer/blob/master/documentation/RS0026.md' for details. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters).WithArguments("Method5", DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters.HelpLinkUri).WithLocation(25, 17), + new DiagnosticResult(DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters).WithArguments("Method5", DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters.HelpLinkUri).WithLocation(25, 17), // Test0.cs(26,17): warning RS0026: Symbol 'Method5' violates the backcompat requirement: 'Do not add multiple overloads with optional parameters'. See 'https://github.com/DotNetAnalyzers/PublicApiAnalyzer/blob/master/documentation/RS0026.md' for details. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters).WithArguments("Method5", DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters.HelpLinkUri).WithLocation(26, 17), + new DiagnosticResult(DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters).WithArguments("Method5", DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters.HelpLinkUri).WithLocation(26, 17), // Test0.cs(27,17): warning RS0026: Symbol 'Method5' violates the backcompat requirement: 'Do not add multiple overloads with optional parameters'. See 'https://github.com/DotNetAnalyzers/PublicApiAnalyzer/blob/master/documentation/RS0026.md' for details. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters).WithArguments("Method5", DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters.HelpLinkUri).WithLocation(27, 17), + new DiagnosticResult(DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters).WithArguments("Method5", DeclarePublicAPIAnalyzer.AvoidMultipleOverloadsWithOptionalParameters.HelpLinkUri).WithLocation(27, 17), }; - await this.VerifyCSharpDiagnosticAsync(source, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source, expected).ConfigureAwait(false); } [Fact] @@ -706,19 +680,19 @@ public void Method6(string p1) { } // unshipped DiagnosticResult[] expected = { // Test0.cs(21,17): warning RS0027: Symbol 'Method4' violates the backcompat requirement: 'Public API with optional parameter(s) should have the most parameters amongst its public overloads'. See 'https://github.com/DotNetAnalyzers/PublicApiAnalyzer/blob/master/documentation/RS0026.md' for details. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters).WithArguments("Method4", DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters.HelpLinkUri).WithLocation(21, 17), + new DiagnosticResult(DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters).WithArguments("Method4", DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters.HelpLinkUri).WithLocation(21, 17), // Test0.cs(26,17): warning RS0027: Symbol 'Method5' violates the backcompat requirement: 'Public API with optional parameter(s) should have the most parameters amongst its public overloads'. See 'https://github.com/DotNetAnalyzers/PublicApiAnalyzer/blob/master/documentation/RS0026.md' for details. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters).WithArguments("Method5", DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters.HelpLinkUri).WithLocation(26, 17), + new DiagnosticResult(DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters).WithArguments("Method5", DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters.HelpLinkUri).WithLocation(26, 17), // Test0.cs(31,17): warning RS0027: Symbol 'Method6' violates the backcompat requirement: 'Public API with optional parameter(s) should have the most parameters amongst its public overloads'. See 'https://github.com/DotNetAnalyzers/PublicApiAnalyzer/blob/master/documentation/RS0026.md' for details. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters).WithArguments("Method6", DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters.HelpLinkUri).WithLocation(31, 17), + new DiagnosticResult(DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters).WithArguments("Method6", DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters.HelpLinkUri).WithLocation(31, 17), // Test0.cs(32,17): warning RS0027: Symbol 'Method6' violates the backcompat requirement: 'Public API with optional parameter(s) should have the most parameters amongst its public overloads'. See 'https://github.com/DotNetAnalyzers/PublicApiAnalyzer/blob/master/documentation/RS0026.md' for details. - this.CSharpDiagnostic(DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters).WithArguments("Method6", DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters.HelpLinkUri).WithLocation(32, 17), + new DiagnosticResult(DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters).WithArguments("Method6", DeclarePublicAPIAnalyzer.OverloadWithOptionalParametersShouldHaveMostParameters.HelpLinkUri).WithLocation(32, 17), }; - await this.VerifyCSharpDiagnosticAsync(source, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyAnalyzerAsync(source, expected).ConfigureAwait(false); } [Fact] @@ -731,7 +705,7 @@ public class C public int Property { get; set; } public void Method() { } public int ArrowExpressionProperty => 0; - public int NewField; // Newly added field, not in current public API. + public int {|RS0016:NewField|}; // Newly added field, not in current public API. } "; @@ -752,7 +726,7 @@ public void Method() { } C.Property.get -> int C.Property.set -> void"; - await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText, cancellationToken: CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText).ConfigureAwait(false); } [Fact] @@ -766,7 +740,7 @@ public class C public int Property { get; set; } public void Method() { } public int ArrowExpressionProperty => 0; - public int NewField; + public int {|RS0016:NewField|}; } "; this.shippedText = string.Empty; @@ -775,7 +749,7 @@ public void Method() { } C.C() -> void C.Field -> int C.Method() -> void -C.ObsoleteField -> int +{|RS0017:C.ObsoleteField -> int|} C.Property.get -> int C.Property.set -> void"; var fixedUnshippedText = @"C @@ -787,14 +761,14 @@ public void Method() { } C.Property.get -> int C.Property.set -> void"; - await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText, cancellationToken: CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText).ConfigureAwait(false); } [Fact] public async Task TestSimpleMissingTypeFixAsync() { var source = @" -public class C +public class {|RS0016:C|} { private C() { } } @@ -805,19 +779,19 @@ private C() { } var fixedUnshippedText = @"C "; - await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText, cancellationToken: CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText).ConfigureAwait(false); } [Fact] public async Task TestMultipleMissingTypeAndMemberFixAsync() { var source = @" -public class C +public class {|RS0016:C|} { private C() { } - public int Field; + public int {|RS0016:Field|}; } -public class C2 { } +public class {|RS0016:{|RS0016:C2|}|} { } "; this.shippedText = string.Empty; @@ -828,7 +802,7 @@ public class C2 { } C2.C2() -> void "; - await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText, cancellationToken: CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText).ConfigureAwait(false); } [Fact] @@ -838,17 +812,17 @@ public async Task TestChangingMethodSignatureForAnUnshippedMethodFixAsync() public class C { private C() { } - public void Method(int p1){ } + public void {|RS0016:Method|}(int p1){ } } "; this.shippedText = @"C"; // previously method had no params, so the fix should remove the previous overload. - this.unshippedText = @"C.Method() -> void"; + this.unshippedText = @"{|RS0017:C.Method() -> void|}"; var fixedUnshippedText = @"C.Method(int p1) -> void"; - await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText, cancellationToken: CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText).ConfigureAwait(false); } [Fact] @@ -860,7 +834,7 @@ public class C private C() { } public void Method(int p1){ } public void Method(int p1, int p2){ } - public void Method(char p1){ } + public void {|RS0016:Method|}(char p1){ } } "; @@ -869,10 +843,10 @@ public void Method(char p1){ } C.Method(int p1, int p2) -> void"; // previously method had no params, so the fix should remove the previous overload. - this.unshippedText = @"C.Method() -> void"; + this.unshippedText = @"{|RS0017:C.Method() -> void|}"; var fixedUnshippedText = @"C.Method(char p1) -> void"; - await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText, cancellationToken: CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText).ConfigureAwait(false); } [Fact] @@ -882,7 +856,7 @@ public async Task TestAddingNewPublicOverloadFixAsync() public class C { private C() { } - public void Method(){ } + public void {|RS0016:Method|}(){ } internal void Method(int p1){ } internal void Method(int p1, int p2){ } public void Method(char p1){ } @@ -896,23 +870,23 @@ public void Method(char p1){ } C.Method() -> void C.Method(char p1) -> void"; - await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText, cancellationToken: CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText).ConfigureAwait(false); } [Fact] public async Task TestMissingTypeAndMemberAndNestedMembersFixAsync() { var source = @" -public class C +public class {|RS0016:C|} { private C() { } - public int Field; + public int {|RS0016:Field|}; public class CC { - public int Field; + public int {|RS0016:Field|}; } } -public class C2 { } +public class {|RS0016:{|RS0016:C2|}|} { } "; this.shippedText = @"C.CC @@ -925,23 +899,23 @@ public class C2 { } C2.C2() -> void "; - await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText, cancellationToken: CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText).ConfigureAwait(false); } [Fact] public async Task TestMissingNestedGenericMembersAndStaleMembersFixAsync() { var source = @" -public class C +public class {|RS0016:C|} { private C() { } - public CC Field; + public CC {|RS0016:Field|}; private C3.C4 Field2; private C3.C4 Method(C3.C4 p1) { throw new System.NotImplementedException(); } public class CC { - public int Field; - public CC Field2; + public int {|RS0016:Field|}; + public CC {|RS0016:Field2|}; } public class C3 @@ -949,7 +923,7 @@ public class C3 public class C4 { } } } -public class C2 { } +public class {|RS0016:{|RS0016:C2|}|} { } "; this.shippedText = string.Empty; @@ -959,8 +933,8 @@ public class C2 { } C.C3.C4.C4() -> void C.CC C.CC.CC() -> void -C.Field2 -> C.C3.C4 -C.Method(C.C3.C4 p1) -> C.C3.C4 +{|RS0017:C.Field2 -> C.C3.C4|} +{|RS0017:C.Method(C.C3.C4 p1) -> C.C3.C4|} "; var fixedUnshippedText = @"C C.C3 @@ -976,23 +950,23 @@ public class C2 { } C2.C2() -> void "; - await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText, cancellationToken: CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText).ConfigureAwait(false); } [Fact] public async Task TestWithExistingUnshippedNestedMembersFixAsync() { var source = @" -public class C +public class {|RS0016:C|} { private C() { } - public int Field; + public int {|RS0016:Field|}; public class CC { public int Field; } } -public class C2 { } +public class {|RS0016:{|RS0016:C2|}|} { } "; this.shippedText = string.Empty; @@ -1007,7 +981,7 @@ public class C2 { } C2 C2.C2() -> void"; - await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText, cancellationToken: CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText).ConfigureAwait(false); } [Fact] @@ -1017,7 +991,7 @@ public async Task TestWithExistingUnshippedNestedGenericMembersFixAsync() public class C { private C() { } - public class CC + public class {|RS0016:CC|} { public int Field; } @@ -1042,23 +1016,23 @@ private CC() { } C.CC C.CC.Field -> int"; - await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText, cancellationToken: CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText).ConfigureAwait(false); } [Fact] public async Task TestWithExistingShippedNestedMembersFixAsync() { var source = @" -public class C +public class {|RS0016:C|} { private C() { } - public int Field; + public int {|RS0016:Field|}; public class CC { public int Field; } } -public class C2 { } +public class {|RS0016:{|RS0016:C2|}|} { } "; this.shippedText = @"C.CC @@ -1071,46 +1045,46 @@ public class C2 { } C2.C2() -> void "; - await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText, cancellationToken: CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText).ConfigureAwait(false); } [Fact] public async Task TestOnlyRemoveStaleSiblingEntriesFixAsync() { var source = @" -public class C +public class {|RS0016:C|} { private C() { } - public int Field; + public int {|RS0016:Field|}; public class CC { private int Field; // This has a stale public API entry, but this shouldn't be removed unless we attempt to add a public API entry for a sibling. } } -public class C2 { } +public class {|RS0016:{|RS0016:C2|}|} { } "; this.shippedText = string.Empty; this.unshippedText = @" C.CC C.CC.CC() -> void -C.CC.Field -> int"; +{|RS0017:C.CC.Field -> int|}"; var fixedUnshippedText = @"C C.CC C.CC.CC() -> void -C.CC.Field -> int +{|RS0017:C.CC.Field -> int|} C.Field -> int C2 C2.C2() -> void"; - await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText, numberOfFixAllIterations: 2, cancellationToken: CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText).ConfigureAwait(false); } [Fact] public async Task TestAddTrailingNewlineByDefaultAsync() { var source = @" -public class C +public class {|RS0016:{|RS0016:C|}|} { } "; @@ -1121,7 +1095,7 @@ public class C C.C() -> void "; - await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText, cancellationToken: CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText).ConfigureAwait(false); } [Theory] @@ -1134,7 +1108,7 @@ public async Task TestPreserveTrailingNewlineAsync(string originalEndOfFile, str public class C { public int Property { get; } - public int NewField; // Newly added field, not in current public API. + public int {|RS0016:NewField|}; // Newly added field, not in current public API. } "; @@ -1147,70 +1121,59 @@ public class C C.NewField -> int C.Property.get -> int{expectedEndOfFile}"; - await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText, cancellationToken: CancellationToken.None).ConfigureAwait(false); - } - - protected override IEnumerable GetCSharpDiagnosticAnalyzers() - { - yield return new DeclarePublicAPIAnalyzer(); + await this.VerifyCSharpUnshippedFileFixAsync(source, fixedUnshippedText).ConfigureAwait(false); } - protected override CodeFixProvider GetCSharpCodeFixProvider() + private async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) { - return new DeclarePublicAPIFix(); - } - - protected override string GetShippedPublicApi() - { - return this.shippedText; - } + var test = new CSharpCodeFixTest + { + TestCode = source, + }; - protected override string GetShippedPublicApiFilePath() - { - return this.shippedFilePath; - } + if (this.unshippedText != null) + { + test.TestState.AdditionalFiles.Add((this.unshippedFilePath, this.unshippedText)); + } - protected override string GetUnshippedPublicApi() - { - return this.unshippedText; - } + if (this.shippedText != null) + { + test.TestState.AdditionalFiles.Add((this.shippedFilePath, this.shippedText)); + } - protected override string GetUnshippedPublicApiFilePath() - { - return this.unshippedFilePath; + test.Exclusions &= ~AnalysisExclusions.GeneratedCode; + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync().ConfigureAwait(false); } - private async Task VerifyCSharpUnshippedFileFixAsync(string source, string fixedUnshippedText, int numberOfFixAllIterations = 1, CancellationToken cancellationToken = default) + private async Task VerifyCSharpUnshippedFileFixAsync(string source, string fixedUnshippedText, params DiagnosticResult[] expected) { - VerifyCodeFixAsync verifyAsync = - async (Project project, bool fixAll, CancellationToken ct) => + var test = new CSharpCodeFixTest + { + TestState = { - var unshippedFile = project.AdditionalDocuments.Single(document => document.Name == DeclarePublicAPIAnalyzer.UnshippedFileName); - Assert.Equal(fixedUnshippedText, (await unshippedFile.GetTextAsync(ct).ConfigureAwait(false)).ToString()); - }; - - await this.VerifyCSharpFixAsync(source, verifyAsync, numberOfFixAllIterations: numberOfFixAllIterations, cancellationToken: CancellationToken.None).ConfigureAwait(false); - } + Sources = { source }, + AdditionalFiles = + { + (this.shippedFilePath, this.shippedText ?? string.Empty), + (this.unshippedFilePath, this.unshippedText ?? string.Empty), + }, + }, + FixedState = + { + Sources = { source }, + AdditionalFiles = + { + (this.shippedFilePath, this.shippedText ?? string.Empty), + (this.unshippedFilePath, fixedUnshippedText), + }, + InheritanceMode = StateInheritanceMode.Explicit, + }, + }; - private async Task GetUpdatedApiAsync(string source, int diagnosticIndex, CancellationToken cancellationToken) - { - var fixes = await this.GetOfferedCSharpFixesAsync(source, diagnosticIndex, cancellationToken).ConfigureAwait(false); - Assert.Equal(1, fixes.Item2.Length); - - var operations = await fixes.Item2[0].GetOperationsAsync(CancellationToken.None).ConfigureAwait(false); - Assert.Equal(1, operations.Length); - ApplyChangesOperation operation = operations[0] as ApplyChangesOperation; - Assert.NotNull(operation); - - var oldSolution = fixes.Item1; - var newSolution = operation.ChangedSolution; - var solutionChanges = newSolution.GetChanges(oldSolution); - var projectChanges = solutionChanges.GetProjectChanges().Single(); - var changedDocumentId = projectChanges.GetChangedAdditionalDocuments().Single(); - var newDocument = projectChanges.NewProject.GetAdditionalDocument(changedDocumentId); - var newText = await newDocument.GetTextAsync(CancellationToken.None).ConfigureAwait(false); - - return newText; + test.Exclusions &= ~AnalysisExclusions.GeneratedCode; + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync().ConfigureAwait(false); } } } diff --git a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/CodeFixVerifier.Helper.cs b/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/CodeFixVerifier.Helper.cs deleted file mode 100644 index 7fa66d3..0000000 --- a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/CodeFixVerifier.Helper.cs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -namespace TestHelper -{ - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CodeActions; - using Microsoft.CodeAnalysis.Formatting; - using Microsoft.CodeAnalysis.Simplification; - using Microsoft.CodeAnalysis.Text; - - /// - /// Diagnostic Producer class with extra methods dealing with applying code fixes. - /// All methods are static - /// - public abstract partial class CodeFixVerifier : DiagnosticVerifier - { - /// - /// Apply the inputted to the inputted document. - /// Meant to be used to apply code fixes. - /// - /// The to apply the fix on - /// A that will be applied to the - /// . - /// The that the task will observe. - /// A with the changes from the . - private static async Task ApplyFixAsync(Project project, CodeAction codeAction, CancellationToken cancellationToken) - { - var operations = await codeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false); - var solution = operations.OfType().Single().ChangedSolution; - return solution.GetProject(project.Id); - } - - /// - /// Compare two collections of s, and return a list of any new diagnostics that appear - /// only in the second collection. - /// - /// Considers to be the same if they have the same s. - /// In the case of multiple diagnostics with the same in a row, this method may not - /// necessarily return the new one. - /// - /// - /// The s that existed in the code before the code fix was - /// applied. - /// The s that exist in the code after the code fix was - /// applied. - /// A list of s that only surfaced in the code after the code fix was - /// applied. - private static IEnumerable GetNewDiagnostics(IEnumerable diagnostics, IEnumerable newDiagnostics) - { - var oldArray = diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); - var newArray = newDiagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); - - int oldIndex = 0; - int newIndex = 0; - - while (newIndex < newArray.Length) - { - if (oldIndex < oldArray.Length && oldArray[oldIndex].Id == newArray[newIndex].Id) - { - ++oldIndex; - ++newIndex; - } - else - { - yield return newArray[newIndex++]; - } - } - } - - /// - /// Get the existing compiler diagnostics on the input document. - /// - /// The to run the compiler diagnostic analyzers on. - /// The that the task will observe. - /// The compiler diagnostics that were found in the code. - private static async Task> GetCompilerDiagnosticsAsync(Project project, CancellationToken cancellationToken) - { - var allDiagnostics = ImmutableArray.Create(); - - foreach (var document in project.Documents) - { - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - allDiagnostics = allDiagnostics.AddRange(semanticModel.GetDiagnostics(cancellationToken: cancellationToken)); - } - - return allDiagnostics; - } - - /// - /// Given a document, turn it into a string based on the syntax root. - /// - /// The to be converted to a string. - /// The that the task will observe. - /// A string containing the syntax of the after formatting. - private static async Task GetStringFromDocumentAsync(Document document, CancellationToken cancellationToken) - { - var simplifiedDoc = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); - var formatted = await Formatter.FormatAsync(simplifiedDoc, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); - var sourceText = await formatted.GetTextAsync(cancellationToken).ConfigureAwait(false); - return sourceText.ToString(); - } - - /// - /// Implements a workaround for issue #936, force re-parsing to get the same sort of syntax tree as the original document. - /// - /// The project to update. - /// The . - /// The updated . - private static async Task RecreateProjectDocumentsAsync(Project project, CancellationToken cancellationToken) - { - foreach (var documentId in project.DocumentIds) - { - var document = project.GetDocument(documentId); - document = await RecreateDocumentAsync(document, cancellationToken).ConfigureAwait(false); - project = document.Project; - } - - return project; - } - - private static async Task RecreateDocumentAsync(Document document, CancellationToken cancellationToken) - { - var newText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - newText = newText.WithChanges(new TextChange(new TextSpan(0, 0), " ")); - newText = newText.WithChanges(new TextChange(new TextSpan(0, 1), string.Empty)); - return document.WithText(newText); - } - - /// - /// Formats the whitespace in all documents of the specified . - /// - /// The project to update. - /// The . - /// The updated . - private static async Task ReformatProjectDocumentsAsync(Project project, CancellationToken cancellationToken) - { - foreach (var documentId in project.DocumentIds) - { - var document = project.GetDocument(documentId); - document = await Formatter.FormatAsync(document, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); - project = document.Project; - } - - return project; - } - } -} diff --git a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/DiagnosticResult.cs b/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/DiagnosticResult.cs deleted file mode 100644 index d0fa59c..0000000 --- a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/DiagnosticResult.cs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -namespace TestHelper -{ - using System; - using Microsoft.CodeAnalysis; - - /// - /// Structure that stores information about a appearing in a source. - /// - public struct DiagnosticResult - { - private static readonly object[] EmptyArguments = new object[0]; - - private DiagnosticResultLocation[] locations; - private string message; - - public DiagnosticResult(DiagnosticDescriptor descriptor) - : this() - { - this.Id = descriptor.Id; - this.Severity = descriptor.DefaultSeverity; - this.MessageFormat = descriptor.MessageFormat; - } - - public DiagnosticResultLocation[] Locations - { - get - { - if (this.locations == null) - { - this.locations = new DiagnosticResultLocation[] { }; - } - - return this.locations; - } - - set - { - this.locations = value; - } - } - - public DiagnosticSeverity Severity - { - get; set; - } - - public string Id - { - get; set; - } - - public string Message - { - get - { - if (this.message != null) - { - return this.message; - } - - if (this.MessageFormat != null) - { - return string.Format(this.MessageFormat.ToString(), this.MessageArguments ?? EmptyArguments); - } - - return null; - } - - set - { - this.message = value; - } - } - - public LocalizableString MessageFormat - { - get; - set; - } - - public object[] MessageArguments - { - get; - set; - } - - public string Path - { - get - { - return this.Locations.Length > 0 ? this.Locations[0].Path : string.Empty; - } - } - - public int Line - { - get - { - return this.Locations.Length > 0 ? this.Locations[0].Line : -1; - } - } - - public int Column - { - get - { - return this.Locations.Length > 0 ? this.Locations[0].Column : -1; - } - } - - public DiagnosticResult WithArguments(params object[] arguments) - { - DiagnosticResult result = this; - result.MessageArguments = arguments; - return result; - } - - public DiagnosticResult WithMessageFormat(LocalizableString messageFormat) - { - DiagnosticResult result = this; - result.MessageFormat = messageFormat; - return result; - } - - public DiagnosticResult WithLocation(int line, int column) - { - return this.WithLocation("Test0.cs", line, column); - } - - public DiagnosticResult WithLocation(string path, int line, int column) - { - DiagnosticResult result = this; - Array.Resize(ref result.locations, (result.locations?.Length ?? 0) + 1); - result.locations[result.locations.Length - 1] = new DiagnosticResultLocation(path, line, column); - return result; - } - - public DiagnosticResult WithLineOffset(int offset) - { - DiagnosticResult result = this; - Array.Resize(ref result.locations, result.locations?.Length ?? 0); - for (int i = 0; i < result.locations.Length; i++) - { - result.locations[i].Line += offset; - } - - return result; - } - } -} diff --git a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/DiagnosticResultLocation.cs b/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/DiagnosticResultLocation.cs deleted file mode 100644 index a3cce50..0000000 --- a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/DiagnosticResultLocation.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -namespace TestHelper -{ - using System; - - /// - /// Location where the diagnostic appears, as determined by path, line number, and column number. - /// - public struct DiagnosticResultLocation - { - public string Path; - public int Line; - public int Column; - - public DiagnosticResultLocation(string path, int line, int column) - { - if (line < 0 && column < 0) - { - throw new ArgumentOutOfRangeException("At least one of line and column must be > 0"); - } - - if (line < -1 || column < -1) - { - throw new ArgumentOutOfRangeException("Both line and column must be >= -1"); - } - - this.Path = path; - this.Line = line; - this.Column = column; - } - } -} diff --git a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/DiagnosticVerifier.Helper.cs b/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/DiagnosticVerifier.Helper.cs deleted file mode 100644 index 1889506..0000000 --- a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/DiagnosticVerifier.Helper.cs +++ /dev/null @@ -1,346 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -namespace TestHelper -{ - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CSharp; - using Microsoft.CodeAnalysis.Diagnostics; - using Microsoft.CodeAnalysis.Text; - using PublicApiAnalyzer; - using PublicApiAnalyzer.ApiDesign; - using PublicApiAnalyzer.Test.Helpers; - - /// - /// Class for turning strings into documents and getting the diagnostics on them. - /// All methods are static. - /// - public abstract partial class DiagnosticVerifier - { - private static readonly string DefaultFilePathPrefix = "Test"; - private static readonly string CSharpDefaultFileExt = "cs"; - private static readonly string VisualBasicDefaultExt = "vb"; - private static readonly string CSharpDefaultFilePath = DefaultFilePathPrefix + 0 + "." + CSharpDefaultFileExt; - private static readonly string VisualBasicDefaultFilePath = DefaultFilePathPrefix + 0 + "." + VisualBasicDefaultExt; - private static readonly string TestProjectName = "TestProject"; - - /// - /// Given an analyzer and a collection of documents to apply it to, run the analyzer and gather an array of - /// diagnostics found. The returned diagnostics are then ordered by location in the source documents. - /// - /// The analyzer to run on the documents. - /// The s that the analyzer will be run on. - /// The that the task will observe. - /// A collection of s that surfaced in the source code, sorted by - /// . - protected static async Task> GetSortedDiagnosticsFromDocumentsAsync(ImmutableArray analyzers, Document[] documents, CancellationToken cancellationToken) - { - var projects = new HashSet(); - foreach (var document in documents) - { - projects.Add(document.Project); - } - - var diagnostics = ImmutableArray.CreateBuilder(); - foreach (var project in projects) - { - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - var compilationWithAnalyzers = compilation.WithAnalyzers(analyzers, project.AnalyzerOptions, cancellationToken); - var compilerDiagnostics = compilation.GetDiagnostics(cancellationToken); - var compilerErrors = compilerDiagnostics.Where(i => i.Severity == DiagnosticSeverity.Error); - var diags = await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().ConfigureAwait(false); - var allDiagnostics = await compilationWithAnalyzers.GetAllDiagnosticsAsync().ConfigureAwait(false); - var failureDiagnostics = allDiagnostics.Where(diagnostic => diagnostic.Id == "AD0001"); - foreach (var diag in diags.Concat(compilerErrors).Concat(failureDiagnostics)) - { - if (diag.Location == Location.None || !diag.Location.IsInSource) - { - diagnostics.Add(diag); - } - else - { - for (int i = 0; i < documents.Length; i++) - { - var document = documents[i]; - var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - if (tree == diag.Location.SourceTree) - { - diagnostics.Add(diag); - break; - } - } - } - } - } - - var results = SortDistinctDiagnostics(diagnostics); - return results.ToImmutableArray(); - } - - /// - /// Create a from a string through creating a project that contains it. - /// - /// Classes in the form of a string. - /// The language the source classes are in. Values may be taken from the - /// class. - /// The file name for the document, or to generate a default - /// filename according to the specified . - /// A created from the source string. - protected Document CreateDocument(string source, string language = LanguageNames.CSharp, string fileName = null) - { - string[] filenames = null; - if (fileName != null) - { - filenames = new[] { fileName }; - } - - return this.CreateProject(new[] { source }, language, filenames).Documents.Single(); - } - - /// - /// Creates a solution that will be used as parent for the sources that need to be checked. - /// - /// The project identifier to use. - /// The language for which the solution is being created. - /// The created solution. - protected virtual Solution CreateSolution(ProjectId projectId, string language) - { - var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true); - - Solution solution = new AdhocWorkspace() - .CurrentSolution - .AddProject(projectId, TestProjectName, TestProjectName, language) - .WithProjectCompilationOptions(projectId, compilationOptions) - .AddMetadataReference(projectId, MetadataReferences.CorlibReference) - .AddMetadataReference(projectId, MetadataReferences.SystemReference) - .AddMetadataReference(projectId, MetadataReferences.SystemCoreReference) - .AddMetadataReference(projectId, MetadataReferences.CSharpSymbolsReference) - .AddMetadataReference(projectId, MetadataReferences.CodeAnalysisReference); - - var publicApi = this.GetUnshippedPublicApi(); - if (publicApi != null) - { - var documentId = DocumentId.CreateNewId(projectId); - solution = solution.AddAdditionalDocument(documentId, DeclarePublicAPIAnalyzer.UnshippedFileName, publicApi, filePath: this.GetUnshippedPublicApiFilePath()); - } - - publicApi = this.GetShippedPublicApi(); - if (publicApi != null) - { - var documentId = DocumentId.CreateNewId(projectId); - solution = solution.AddAdditionalDocument(documentId, DeclarePublicAPIAnalyzer.ShippedFileName, publicApi, filePath: this.GetShippedPublicApiFilePath()); - } - - ParseOptions parseOptions = solution.GetProject(projectId).ParseOptions; - return solution.WithProjectParseOptions(projectId, parseOptions.WithDocumentationMode(DocumentationMode.Diagnose)); - } - - /// - /// Gets the diagnostics that will be suppressed. - /// - /// A collection of diagnostic identifiers. - protected virtual IEnumerable GetDisabledDiagnostics() - { - return Enumerable.Empty(); - } - - /// - /// Gets the content of the settings file to use. - /// - /// The contents of the settings file to use. - protected virtual string GetUnshippedPublicApi() - { - return null; - } - - protected virtual string GetUnshippedPublicApiFilePath() - { - return null; - } - - /// - /// Gets the content of the settings file to use. - /// - /// The contents of the settings file to use. - protected virtual string GetShippedPublicApi() - { - return null; - } - - protected virtual string GetShippedPublicApiFilePath() - { - return null; - } - - protected DiagnosticResult CSharpDiagnostic(string diagnosticId = null) - { - var analyzers = this.GetCSharpDiagnosticAnalyzers(); - var supportedDiagnostics = analyzers.SelectMany(analyzer => analyzer.SupportedDiagnostics); - if (diagnosticId == null) - { - return this.CSharpDiagnostic(supportedDiagnostics.Single()); - } - else - { - return this.CSharpDiagnostic(supportedDiagnostics.Single(i => i.Id == diagnosticId)); - } - } - - protected DiagnosticResult CSharpDiagnostic(DiagnosticDescriptor descriptor) - { - return new DiagnosticResult(descriptor); - } - - /// - /// Create a project using the input strings as sources. - /// - /// - /// This method first creates a by calling , and then - /// applies compilation options to the project by calling . - /// - /// Classes in the form of strings. - /// The language the source classes are in. Values may be taken from the - /// class. - /// The filenames or null if the default filename should be used - /// A created out of the s created from the source - /// strings. - protected Project CreateProject(string[] sources, string language = LanguageNames.CSharp, string[] filenames = null) - { - Project project = this.CreateProjectImpl(sources, language, filenames); - return this.ApplyCompilationOptions(project); - } - - /// - /// Create a project using the input strings as sources. - /// - /// Classes in the form of strings. - /// The language the source classes are in. Values may be taken from the - /// class. - /// The filenames or null if the default filename should be used - /// A created out of the s created from the source - /// strings. - protected virtual Project CreateProjectImpl(string[] sources, string language, string[] filenames) - { - string fileNamePrefix = DefaultFilePathPrefix; - string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt; - - var projectId = ProjectId.CreateNewId(debugName: TestProjectName); - var solution = this.CreateSolution(projectId, language); - - int count = 0; - for (int i = 0; i < sources.Length; i++) - { - string source = sources[i]; - var newFileName = filenames?[i] ?? fileNamePrefix + count + "." + fileExt; - var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); - solution = solution.AddDocument(documentId, newFileName, SourceText.From(source)); - count++; - } - - return solution.GetProject(projectId); - } - - /// - /// Applies compilation options to a project. - /// - /// - /// The default implementation configures the project by enabling all supported diagnostics of analyzers - /// included in as well as AD0001. After configuring these - /// diagnostics, any diagnostic IDs indicated in are explictly supressed - /// using . - /// - /// The project. - /// The modified project. - protected virtual Project ApplyCompilationOptions(Project project) - { - var analyzers = this.GetCSharpDiagnosticAnalyzers(); - - var supportedDiagnosticsSpecificOptions = new Dictionary(); - foreach (var analyzer in analyzers) - { - foreach (var diagnostic in analyzer.SupportedDiagnostics) - { - // make sure the analyzers we are testing are enabled - supportedDiagnosticsSpecificOptions[diagnostic.Id] = ReportDiagnostic.Default; - } - } - - // Report exceptions during the analysis process as errors - supportedDiagnosticsSpecificOptions.Add("AD0001", ReportDiagnostic.Error); - - foreach (var id in this.GetDisabledDiagnostics()) - { - supportedDiagnosticsSpecificOptions[id] = ReportDiagnostic.Suppress; - } - - // update the project compilation options - var modifiedSpecificDiagnosticOptions = supportedDiagnosticsSpecificOptions.ToImmutableDictionary().SetItems(project.CompilationOptions.SpecificDiagnosticOptions); - var modifiedCompilationOptions = project.CompilationOptions.WithSpecificDiagnosticOptions(modifiedSpecificDiagnosticOptions); - - Solution solution = project.Solution.WithProjectCompilationOptions(project.Id, modifiedCompilationOptions); - return solution.GetProject(project.Id); - } - - /// - /// Sort s by location in source document. - /// - /// A collection of s to be sorted. - /// A collection containing the input , sorted by - /// and . - private static Diagnostic[] SortDistinctDiagnostics(IEnumerable diagnostics) - { - return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ThenBy(d => d.Id).ToArray(); - } - - /// - /// Given classes in the form of strings, their language, and an to apply to - /// it, return the s found in the string after converting it to a - /// . - /// - /// Classes in the form of strings. - /// The language the source classes are in. Values may be taken from the - /// class. - /// The analyzers to be run on the sources. - /// The that the task will observe. - /// The filenames or null if the default filename should be used - /// A collection of s that surfaced in the source code, sorted by - /// . - private Task> GetSortedDiagnosticsAsync(string[] sources, string language, ImmutableArray analyzers, CancellationToken cancellationToken, string[] filenames) - { - return GetSortedDiagnosticsFromDocumentsAsync(analyzers, this.GetDocuments(sources, language, filenames), cancellationToken); - } - - /// - /// Given an array of strings as sources and a language, turn them into a and return the - /// documents and spans of it. - /// - /// Classes in the form of strings. - /// The language the source classes are in. Values may be taken from the - /// class. - /// The filenames or null if the default filename should be used - /// A collection of s representing the sources. - private Document[] GetDocuments(string[] sources, string language, string[] filenames) - { - if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic) - { - throw new ArgumentException("Unsupported Language"); - } - - var project = this.CreateProject(sources, language, filenames); - var documents = project.Documents.ToArray(); - - if (sources.Length != documents.Length) - { - throw new SystemException("Amount of sources did not match amount of Documents created"); - } - - return documents; - } - } -} diff --git a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/MetadataReferences.cs b/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/MetadataReferences.cs deleted file mode 100644 index 10ea839..0000000 --- a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/MetadataReferences.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -namespace PublicApiAnalyzer.Test.Helpers -{ - using System.Collections.Immutable; - using System.Linq; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CSharp; - - /// - /// Metadata references used to create test projects. - /// - internal static class MetadataReferences - { - internal static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location).WithAliases(ImmutableArray.Create("global", "corlib")); - internal static readonly MetadataReference SystemReference = MetadataReference.CreateFromFile(typeof(System.Diagnostics.Debug).Assembly.Location).WithAliases(ImmutableArray.Create("global", "system")); - internal static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location); - internal static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location); - internal static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location); - } -} diff --git a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/TestDiagnosticProvider.cs b/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/TestDiagnosticProvider.cs deleted file mode 100644 index c7adb14..0000000 --- a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/TestDiagnosticProvider.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -namespace PublicApiAnalyzer.Test.Helpers -{ - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CodeFixes; - - internal sealed class TestDiagnosticProvider : FixAllContext.DiagnosticProvider - { - private ImmutableArray diagnostics; - - private TestDiagnosticProvider(ImmutableArray diagnostics) - { - this.diagnostics = diagnostics; - } - - public override Task> GetAllDiagnosticsAsync(Project project, CancellationToken cancellationToken) - { - return Task.FromResult>(this.diagnostics); - } - - public override Task> GetDocumentDiagnosticsAsync(Document document, CancellationToken cancellationToken) - { - return Task.FromResult(this.diagnostics.Where(i => i.Location.GetLineSpan().Path == document.Name)); - } - - public override Task> GetProjectDiagnosticsAsync(Project project, CancellationToken cancellationToken) - { - return Task.FromResult(this.diagnostics.Where(i => !i.Location.IsInSource)); - } - - internal static TestDiagnosticProvider Create(ImmutableArray diagnostics) - { - return new TestDiagnosticProvider(diagnostics); - } - } -} diff --git a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/TestXmlReferenceResolver.cs b/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/TestXmlReferenceResolver.cs deleted file mode 100644 index db0a3d7..0000000 --- a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Helpers/TestXmlReferenceResolver.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -namespace PublicApiAnalyzer.Test.Helpers -{ - using System.Collections.Generic; - using System.IO; - using System.Runtime.CompilerServices; - using System.Text; - using Microsoft.CodeAnalysis; - - internal class TestXmlReferenceResolver : XmlReferenceResolver - { - public Dictionary XmlReferences { get; } = - new Dictionary(); - - public override bool Equals(object other) - { - return ReferenceEquals(this, other); - } - - public override int GetHashCode() - { - return RuntimeHelpers.GetHashCode(this); - } - - public override Stream OpenRead(string resolvedPath) - { - string content; - if (!this.XmlReferences.TryGetValue(resolvedPath, out content)) - { - return null; - } - - return new MemoryStream(Encoding.UTF8.GetBytes(content)); - } - - public override string ResolveReference(string path, string baseFilePath) - { - return path; - } - } -} diff --git a/PublicApiAnalyzer/PublicApiAnalyzer.Test/PublicApiAnalyzer.Test.csproj b/PublicApiAnalyzer/PublicApiAnalyzer.Test/PublicApiAnalyzer.Test.csproj index 1967ac6..14095f5 100644 --- a/PublicApiAnalyzer/PublicApiAnalyzer.Test/PublicApiAnalyzer.Test.csproj +++ b/PublicApiAnalyzer/PublicApiAnalyzer.Test/PublicApiAnalyzer.Test.csproj @@ -3,6 +3,8 @@ net452 + true + true @@ -16,6 +18,7 @@ + diff --git a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Verifiers/CodeFixVerifier.cs b/PublicApiAnalyzer/PublicApiAnalyzer.Test/Verifiers/CodeFixVerifier.cs deleted file mode 100644 index f89ebe4..0000000 --- a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Verifiers/CodeFixVerifier.cs +++ /dev/null @@ -1,626 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -namespace TestHelper -{ - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Diagnostics; - using System.Linq; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CodeActions; - using Microsoft.CodeAnalysis.CodeFixes; - using Microsoft.CodeAnalysis.Diagnostics; - using Microsoft.CodeAnalysis.Formatting; - using PublicApiAnalyzer.Test.Helpers; - using Xunit; - - /// - /// Superclass of all unit tests made for diagnostics with code fixes. - /// Contains methods used to verify correctness of code fixes. - /// - public abstract partial class CodeFixVerifier : DiagnosticVerifier - { - private const int DefaultNumberOfIncrementalIterations = -1000; - private const int DefaultIndentationSize = 4; - private const bool DefaultUseTabs = false; - - public CodeFixVerifier() - { - this.IndentationSize = DefaultIndentationSize; - this.UseTabs = DefaultUseTabs; - } - - protected delegate Task VerifyCodeFixAsync(Project project, bool fixAll, CancellationToken cancellationToken); - - /// - /// Gets or sets the value of the to apply to the test - /// workspace. - /// - /// - /// The value of the to apply to the test workspace. - /// - public int IndentationSize - { - get; - protected set; - } - - /// - /// Gets or sets a value indicating whether the option is applied to the - /// test workspace. - /// - /// - /// The value of the to apply to the test workspace. - /// - public bool UseTabs - { - get; - protected set; - } - - /// - /// Returns the code fix being tested (C#) - to be implemented in non-abstract class. - /// - /// The to be used for C# code. - protected abstract CodeFixProvider GetCSharpCodeFixProvider(); - - /// - /// Called to test a C# code fix when applied on the input source as a string. - /// - /// A class in the form of a string before the code fix was applied to it. - /// A class in the form of a string after the code fix was applied to it. - /// A class in the form of a string after the batch fixer was applied to it. - /// The name of the file in the project before the code fix was applied. - /// The name of the file in the project after the code fix was applied. - /// Index determining which code fix to apply if there are multiple. - /// A value indicating whether or not the test will fail if the code fix introduces other warnings after being applied. - /// The number of iterations the incremental fixer will be called. - /// If this value is less than 0, the negated value is treated as an upper limit as opposed to an exact - /// value. - /// The number of iterations the Fix All fixer will be called. If this - /// value is less than 0, the negated value is treated as an upper limit as opposed to an exact value. - /// The that the task will observe. - /// A representing the asynchronous operation. - protected Task VerifyCSharpFixAsync(string oldSource, string newSource, string batchNewSource = null, string oldFileName = null, string newFileName = null, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false, int numberOfIncrementalIterations = DefaultNumberOfIncrementalIterations, int numberOfFixAllIterations = 1, CancellationToken cancellationToken = default(CancellationToken)) - { - var batchNewSources = batchNewSource == null ? null : new[] { batchNewSource }; - var oldFileNames = oldFileName == null ? null : new[] { oldFileName }; - var newFileNames = newFileName == null ? null : new[] { newFileName }; - return this.VerifyCSharpFixAsync(new[] { oldSource }, new[] { newSource }, batchNewSources, oldFileNames, newFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfIncrementalIterations, numberOfFixAllIterations, cancellationToken); - } - - /// - /// Called to test a C# code fix when applied on the input source as a string. - /// - /// A class in the form of a string before the code fix was applied to it. - /// A validation function to verify the results of the code fix. - /// The name of the file in the project before the code fix was applied. - /// Index determining which code fix to apply if there are multiple. - /// A value indicating whether or not the test will fail if the code fix introduces other warnings after being applied. - /// The number of iterations the incremental fixer will be called. - /// If this value is less than 0, the negated value is treated as an upper limit as opposed to an exact - /// value. - /// The number of iterations the Fix All fixer will be called. If this - /// value is less than 0, the negated value is treated as an upper limit as opposed to an exact value. - /// The that the task will observe. - /// A representing the asynchronous operation. - protected Task VerifyCSharpFixAsync(string oldSource, VerifyCodeFixAsync verifyFixedProjectAsync, string oldFileName = null, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false, int numberOfIncrementalIterations = DefaultNumberOfIncrementalIterations, int numberOfFixAllIterations = 1, CancellationToken cancellationToken = default(CancellationToken)) - { - var oldFileNames = oldFileName == null ? null : new[] { oldFileName }; - return this.VerifyCSharpFixAsync(new[] { oldSource }, verifyFixedProjectAsync, oldFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfIncrementalIterations, numberOfFixAllIterations, cancellationToken); - } - - /// - /// Called to test a C# code fix when applied on the input source as a string. - /// - /// An array of sources in the form of strings before the code fix was applied to them. - /// An array of sources in the form of strings after the code fix was applied to them. - /// An array of sources in the form of a strings after the batch fixer was applied to them. - /// An array of file names in the project before the code fix was applied. - /// An array of file names in the project after the code fix was applied. - /// Index determining which code fix to apply if there are multiple. - /// A value indicating whether or not the test will fail if the code fix introduces other warnings after being applied. - /// The number of iterations the incremental fixer will be called. - /// If this value is less than 0, the negated value is treated as an upper limit as opposed to an exact - /// value. - /// The number of iterations the Fix All fixer will be called. If this - /// value is less than 0, the negated value is treated as an upper limit as opposed to an exact value. - /// The that the task will observe. - /// A representing the asynchronous operation. - protected async Task VerifyCSharpFixAsync(string[] oldSources, string[] newSources, string[] batchNewSources = null, string[] oldFileNames = null, string[] newFileNames = null, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false, int numberOfIncrementalIterations = DefaultNumberOfIncrementalIterations, int numberOfFixAllIterations = 1, CancellationToken cancellationToken = default(CancellationToken)) - { - var t1 = this.VerifyFixInternalAsync(LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), oldSources, newSources, oldFileNames, newFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfIncrementalIterations, FixEachAnalyzerDiagnosticAsync, cancellationToken).ConfigureAwait(false); - - var fixAllProvider = this.GetCSharpCodeFixProvider().GetFixAllProvider(); - Assert.NotEqual(WellKnownFixAllProviders.BatchFixer, fixAllProvider); - - if (fixAllProvider == null) - { - await t1; - } - else - { - if (Debugger.IsAttached) - { - await t1; - } - - var t2 = this.VerifyFixInternalAsync(LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), oldSources, batchNewSources ?? newSources, oldFileNames, newFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfFixAllIterations, FixAllAnalyzerDiagnosticsInDocumentAsync, cancellationToken).ConfigureAwait(false); - if (Debugger.IsAttached) - { - await t2; - } - - var t3 = this.VerifyFixInternalAsync(LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), oldSources, batchNewSources ?? newSources, oldFileNames, newFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfFixAllIterations, FixAllAnalyzerDiagnosticsInProjectAsync, cancellationToken).ConfigureAwait(false); - if (Debugger.IsAttached) - { - await t3; - } - - var t4 = this.VerifyFixInternalAsync(LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), oldSources, batchNewSources ?? newSources, oldFileNames, newFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfFixAllIterations, FixAllAnalyzerDiagnosticsInSolutionAsync, cancellationToken).ConfigureAwait(false); - if (Debugger.IsAttached) - { - await t4; - } - - if (!Debugger.IsAttached) - { - // Allow the operations to run in parallel - await t1; - await t2; - await t3; - await t4; - } - } - } - - /// - /// Called to test a C# code fix when applied on the input source as a string. - /// - /// An array of sources in the form of strings before the code fix was applied to them. - /// A validation function to verify the results of the code fix. - /// An array of file names in the project before the code fix was applied. - /// Index determining which code fix to apply if there are multiple. - /// A value indicating whether or not the test will fail if the code fix introduces other warnings after being applied. - /// The number of iterations the incremental fixer will be called. - /// If this value is less than 0, the negated value is treated as an upper limit as opposed to an exact - /// value. - /// The number of iterations the Fix All fixer will be called. If this - /// value is less than 0, the negated value is treated as an upper limit as opposed to an exact value. - /// The that the task will observe. - /// A representing the asynchronous operation. - protected async Task VerifyCSharpFixAsync(string[] oldSources, VerifyCodeFixAsync verifyFixedProjectAsync, string[] oldFileNames = null, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false, int numberOfIncrementalIterations = DefaultNumberOfIncrementalIterations, int numberOfFixAllIterations = 1, CancellationToken cancellationToken = default(CancellationToken)) - { -#pragma warning disable SA1101 // Prefix local calls with this - Func verifyAsync = (project, ct) => verifyFixedProjectAsync(project, false, ct); -#pragma warning restore SA1101 // Prefix local calls with this - - var t1 = this.VerifyFixInternalAsync(LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), oldSources, oldFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfIncrementalIterations, FixEachAnalyzerDiagnosticAsync, verifyAsync, cancellationToken).ConfigureAwait(false); - - var fixAllProvider = this.GetCSharpCodeFixProvider().GetFixAllProvider(); - Assert.NotEqual(WellKnownFixAllProviders.BatchFixer, fixAllProvider); - - if (fixAllProvider == null) - { - await t1; - } - else - { - if (Debugger.IsAttached) - { - await t1; - } - -#pragma warning disable SA1101 // Prefix local calls with this - Func verifyFixAllAsync = (project, ct) => verifyFixedProjectAsync(project, true, ct); -#pragma warning restore SA1101 // Prefix local calls with this - - var t2 = this.VerifyFixInternalAsync(LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), oldSources, oldFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfFixAllIterations, FixAllAnalyzerDiagnosticsInDocumentAsync, verifyFixAllAsync, cancellationToken).ConfigureAwait(false); - if (Debugger.IsAttached) - { - await t2; - } - - var t3 = this.VerifyFixInternalAsync(LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), oldSources, oldFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfFixAllIterations, FixAllAnalyzerDiagnosticsInProjectAsync, verifyFixAllAsync, cancellationToken).ConfigureAwait(false); - if (Debugger.IsAttached) - { - await t3; - } - - var t4 = this.VerifyFixInternalAsync(LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), oldSources, oldFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfFixAllIterations, FixAllAnalyzerDiagnosticsInSolutionAsync, verifyFixAllAsync, cancellationToken).ConfigureAwait(false); - if (Debugger.IsAttached) - { - await t4; - } - - if (!Debugger.IsAttached) - { - // Allow the operations to run in parallel - await t1; - await t2; - await t3; - await t4; - } - } - } - - /// - /// Called to test a C# fix all provider when applied on the input source as a string. - /// - /// A class in the form of a string before the code fix was applied to it. - /// A class in the form of a string after the code fix was applied to it. - /// Index determining which code fix to apply if there are multiple. - /// A value indicating whether or not the test will fail if the code fix introduces other warnings after being applied. - /// The number of iterations the fixer will be called. If this value is less - /// than 0, the negated value is treated as an upper limit as opposed to an exact value. - /// The that the task will observe. - /// A representing the asynchronous operation. - protected async Task VerifyCSharpFixAllFixAsync(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false, int numberOfIterations = 1, CancellationToken cancellationToken = default(CancellationToken)) - { - await this.VerifyFixInternalAsync(LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), new[] { oldSource }, new[] { newSource }, null, null, codeFixIndex, allowNewCompilerDiagnostics, numberOfIterations, FixAllAnalyzerDiagnosticsInDocumentAsync, cancellationToken).ConfigureAwait(false); - } - - /// - /// Gets all offered code fixes for the specified diagnostic within the given source. - /// - /// A valid C# source file in the form of a string. - /// Index determining which diagnostic to use for determining the offered code fixes. Uses the first diagnostic if null. - /// The that the task will observe. - /// The collection of offered code actions. This collection may be empty. - protected async Task>> GetOfferedCSharpFixesAsync(string source, int? diagnosticIndex = null, CancellationToken cancellationToken = default(CancellationToken)) - { - return await this.GetOfferedFixesInternalAsync(LanguageNames.CSharp, source, diagnosticIndex, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), this.GetCSharpCodeFixProvider(), cancellationToken).ConfigureAwait(false); - } - - /// - protected override Solution CreateSolution(ProjectId projectId, string language) - { - Solution solution = base.CreateSolution(projectId, language); - solution.Workspace.Options = - solution.Workspace.Options - .WithChangedOption(FormattingOptions.IndentationSize, language, this.IndentationSize) - .WithChangedOption(FormattingOptions.UseTabs, language, this.UseTabs); - return solution; - } - - private static async Task FixEachAnalyzerDiagnosticAsync(ImmutableArray analyzers, CodeFixProvider codeFixProvider, int? codeFixIndex, Project project, int numberOfIterations, CancellationToken cancellationToken) - { - int expectedNumberOfIterations = numberOfIterations; - if (numberOfIterations < 0) - { - numberOfIterations = -numberOfIterations; - } - - var previousDiagnostics = ImmutableArray.Create(); - - bool done; - do - { - var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzers, project.Documents.ToArray(), cancellationToken).ConfigureAwait(false); - if (analyzerDiagnostics.Length == 0) - { - break; - } - - if (!AreDiagnosticsDifferent(analyzerDiagnostics, previousDiagnostics)) - { - break; - } - - if (--numberOfIterations < 0) - { - Assert.True(false, "The upper limit for the number of code fix iterations was exceeded"); - } - - previousDiagnostics = analyzerDiagnostics; - - done = true; - foreach (var diagnostic in analyzerDiagnostics) - { - if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id)) - { - // do not pass unsupported diagnostics to a code fix provider - continue; - } - - var actions = new List(); - var context = new CodeFixContext(project.GetDocument(diagnostic.Location.SourceTree), diagnostic, (a, d) => actions.Add(a), cancellationToken); - await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); - - if (actions.Count > 0) - { - var fixedProject = await ApplyFixAsync(project, actions.ElementAt(codeFixIndex.GetValueOrDefault(0)), cancellationToken).ConfigureAwait(false); - if (fixedProject != project) - { - done = false; - - project = await RecreateProjectDocumentsAsync(fixedProject, cancellationToken).ConfigureAwait(false); - break; - } - } - } - } - while (!done); - - if (expectedNumberOfIterations >= 0) - { - Assert.Equal($"{expectedNumberOfIterations} iterations", $"{expectedNumberOfIterations - numberOfIterations} iterations"); - } - - return project; - } - - private static Task FixAllAnalyzerDiagnosticsInDocumentAsync(ImmutableArray analyzers, CodeFixProvider codeFixProvider, int? codeFixIndex, Project project, int numberOfIterations, CancellationToken cancellationToken) - { - return FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope.Document, analyzers, codeFixProvider, codeFixIndex, project, numberOfIterations, cancellationToken); - } - - private static Task FixAllAnalyzerDiagnosticsInProjectAsync(ImmutableArray analyzers, CodeFixProvider codeFixProvider, int? codeFixIndex, Project project, int numberOfIterations, CancellationToken cancellationToken) - { - return FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope.Project, analyzers, codeFixProvider, codeFixIndex, project, numberOfIterations, cancellationToken); - } - - private static Task FixAllAnalyzerDiagnosticsInSolutionAsync(ImmutableArray analyzers, CodeFixProvider codeFixProvider, int? codeFixIndex, Project project, int numberOfIterations, CancellationToken cancellationToken) - { - return FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope.Solution, analyzers, codeFixProvider, codeFixIndex, project, numberOfIterations, cancellationToken); - } - - private static async Task FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope scope, ImmutableArray analyzers, CodeFixProvider codeFixProvider, int? codeFixIndex, Project project, int numberOfIterations, CancellationToken cancellationToken) - { - int expectedNumberOfIterations = numberOfIterations; - if (numberOfIterations < 0) - { - numberOfIterations = -numberOfIterations; - } - - var previousDiagnostics = ImmutableArray.Create(); - - var fixAllProvider = codeFixProvider.GetFixAllProvider(); - - if (fixAllProvider == null) - { - return null; - } - - bool done; - do - { - var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzers, project.Documents.ToArray(), cancellationToken).ConfigureAwait(false); - if (analyzerDiagnostics.Length == 0) - { - break; - } - - if (!AreDiagnosticsDifferent(analyzerDiagnostics, previousDiagnostics)) - { - break; - } - - if (--numberOfIterations < 0) - { - Assert.True(false, "The upper limit for the number of fix all iterations was exceeded"); - } - - Diagnostic firstDiagnostic = null; - string equivalenceKey = null; - foreach (var diagnostic in analyzerDiagnostics) - { - if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id)) - { - // do not pass unsupported diagnostics to a code fix provider - continue; - } - - var actions = new List(); - var context = new CodeFixContext(project.GetDocument(diagnostic.Location.SourceTree), diagnostic, (a, d) => actions.Add(a), cancellationToken); - await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); - if (actions.Count > (codeFixIndex ?? 0)) - { - firstDiagnostic = diagnostic; - equivalenceKey = actions[codeFixIndex ?? 0].EquivalenceKey; - break; - } - } - - if (firstDiagnostic == null) - { - return project; - } - - previousDiagnostics = analyzerDiagnostics; - - done = true; - - FixAllContext.DiagnosticProvider fixAllDiagnosticProvider = TestDiagnosticProvider.Create(analyzerDiagnostics); - - IEnumerable analyzerDiagnosticIds = analyzers.SelectMany(x => x.SupportedDiagnostics).Select(x => x.Id); - IEnumerable compilerDiagnosticIds = codeFixProvider.FixableDiagnosticIds.Where(x => x.StartsWith("CS", StringComparison.Ordinal)); - IEnumerable disabledDiagnosticIds = project.CompilationOptions.SpecificDiagnosticOptions.Where(x => x.Value == ReportDiagnostic.Suppress).Select(x => x.Key); - IEnumerable relevantIds = analyzerDiagnosticIds.Concat(compilerDiagnosticIds).Except(disabledDiagnosticIds).Distinct(); - FixAllContext fixAllContext = new FixAllContext(project.GetDocument(firstDiagnostic.Location.SourceTree), codeFixProvider, scope, equivalenceKey, relevantIds, fixAllDiagnosticProvider, cancellationToken); - - CodeAction action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false); - if (action == null) - { - return project; - } - - var fixedProject = await ApplyFixAsync(project, action, cancellationToken).ConfigureAwait(false); - if (fixedProject != project) - { - done = false; - - project = await RecreateProjectDocumentsAsync(fixedProject, cancellationToken).ConfigureAwait(false); - } - } - while (!done); - - if (expectedNumberOfIterations >= 0) - { - Assert.Equal($"{expectedNumberOfIterations} iterations", $"{expectedNumberOfIterations - numberOfIterations} iterations"); - } - - return project; - } - - private static bool AreDiagnosticsDifferent(ImmutableArray analyzerDiagnostics, ImmutableArray previousDiagnostics) - { - if (analyzerDiagnostics.Length != previousDiagnostics.Length) - { - return true; - } - - for (var i = 0; i < analyzerDiagnostics.Length; i++) - { - if ((analyzerDiagnostics[i].Id != previousDiagnostics[i].Id) - || (analyzerDiagnostics[i].Location.SourceSpan != previousDiagnostics[i].Location.SourceSpan)) - { - return true; - } - } - - return false; - } - - private async Task ApplyFixInternalAsync( - string language, - ImmutableArray analyzers, - CodeFixProvider codeFixProvider, - string[] oldSources, - string[] oldFileNames, - int? codeFixIndex, - bool allowNewCompilerDiagnostics, - int numberOfIterations, - Func, CodeFixProvider, int?, Project, int, CancellationToken, Task> getFixedProject, - CancellationToken cancellationToken) - { - if (oldFileNames != null) - { - // Make sure the test case is consistent regarding the number of sources and file names before the code fix - Assert.Equal($"{oldSources.Length} old file names", $"{oldFileNames.Length} old file names"); - } - - var project = this.CreateProject(oldSources, language, oldFileNames); - var compilerDiagnostics = await GetCompilerDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); - - project = await getFixedProject(analyzers, codeFixProvider, codeFixIndex, project, numberOfIterations, cancellationToken).ConfigureAwait(false); - - var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false)); - - // Check if applying the code fix introduced any new compiler diagnostics - if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) - { - // Format and get the compiler diagnostics again so that the locations make sense in the output - project = await ReformatProjectDocumentsAsync(project, cancellationToken).ConfigureAwait(false); - newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false)); - - var message = new StringBuilder(); - message.Append("Fix introduced new compiler diagnostics:\r\n"); - newCompilerDiagnostics.Aggregate(message, (sb, d) => sb.Append(d.ToString()).Append("\r\n")); - foreach (var document in project.Documents) - { - message.Append("\r\n").Append(document.Name).Append(":\r\n"); - message.Append((await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false)).ToFullString()); - message.Append("\r\n"); - } - - Assert.True(false, message.ToString()); - } - - return project; - } - - private async Task VerifyFixInternalAsync( - string language, - ImmutableArray analyzers, - CodeFixProvider codeFixProvider, - string[] oldSources, - string[] newSources, - string[] oldFileNames, - string[] newFileNames, - int? codeFixIndex, - bool allowNewCompilerDiagnostics, - int numberOfIterations, - Func, CodeFixProvider, int?, Project, int, CancellationToken, Task> getFixedProject, - CancellationToken cancellationToken) - { - if (oldFileNames != null) - { - // Make sure the test case is consistent regarding the number of sources and file names before the code fix - Assert.Equal($"{oldSources.Length} old file names", $"{oldFileNames.Length} old file names"); - } - - if (newFileNames != null) - { - // Make sure the test case is consistent regarding the number of sources and file names after the code fix - Assert.Equal($"{newSources.Length} new file names", $"{newFileNames.Length} new file names"); - } - - // After applying all of the code fixes, compare the resulting string to the inputed one - Func verifyFixedProjectAsync = - async (project, ct) => - { - var updatedDocuments = project.Documents.ToArray(); - - Assert.Equal($"{newSources.Length} documents", $"{updatedDocuments.Length} documents"); - - for (int i = 0; i < updatedDocuments.Length; i++) - { - var actual = await GetStringFromDocumentAsync(updatedDocuments[i], cancellationToken).ConfigureAwait(false); - Assert.Equal(newSources[i], actual); - - if (newFileNames != null) - { - Assert.Equal(newFileNames[i], updatedDocuments[i].Name); - } - } - }; - - await this.VerifyFixInternalAsync(language, analyzers, codeFixProvider, oldSources, oldFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfIterations, getFixedProject, verifyFixedProjectAsync, cancellationToken).ConfigureAwait(false); - } - - private async Task VerifyFixInternalAsync( - string language, - ImmutableArray analyzers, - CodeFixProvider codeFixProvider, - string[] oldSources, - string[] oldFileNames, - int? codeFixIndex, - bool allowNewCompilerDiagnostics, - int numberOfIterations, - Func, CodeFixProvider, int?, Project, int, CancellationToken, Task> getFixedProject, - Func verifyFixedProjectAsync, - CancellationToken cancellationToken) - { - var project = await this.ApplyFixInternalAsync(language, analyzers, codeFixProvider, oldSources, oldFileNames, codeFixIndex, allowNewCompilerDiagnostics, numberOfIterations, getFixedProject, cancellationToken).ConfigureAwait(false); - - await verifyFixedProjectAsync(project, cancellationToken).ConfigureAwait(false); - } - - private async Task>> GetOfferedFixesInternalAsync(string language, string source, int? diagnosticIndex, ImmutableArray analyzers, CodeFixProvider codeFixProvider, CancellationToken cancellationToken) - { - var document = this.CreateDocument(source, language); - var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzers, new[] { document }, cancellationToken).ConfigureAwait(false); - - var index = diagnosticIndex.HasValue ? diagnosticIndex.Value : 0; - - Assert.True(index < analyzerDiagnostics.Count()); - - var actions = new List(); - - // do not pass unsupported diagnostics to a code fix provider - if (codeFixProvider.FixableDiagnosticIds.Contains(analyzerDiagnostics[index].Id)) - { - var context = new CodeFixContext(document, analyzerDiagnostics[index], (a, d) => actions.Add(a), cancellationToken); - await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); - } - - return Tuple.Create(document.Project.Solution, actions.ToImmutableArray()); - } - } -} diff --git a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Verifiers/DiagnosticVerifier.cs b/PublicApiAnalyzer/PublicApiAnalyzer.Test/Verifiers/DiagnosticVerifier.cs deleted file mode 100644 index eadfaf0..0000000 --- a/PublicApiAnalyzer/PublicApiAnalyzer.Test/Verifiers/DiagnosticVerifier.cs +++ /dev/null @@ -1,346 +0,0 @@ -// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -namespace TestHelper -{ - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Linq; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.Diagnostics; - using Xunit; - - /// - /// Superclass of all unit tests for s. - /// - public abstract partial class DiagnosticVerifier - { - protected static DiagnosticResult[] EmptyDiagnosticResults { get; } = { }; - - /// - /// Verifies that the analyzer will properly handle an empty source. - /// - /// A representing the asynchronous unit test. - [Fact] - public async Task TestEmptySourceAsync() - { - var testCode = string.Empty; - await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); - } - - /// - /// Verifies that each diagnostics contains a in the expected - /// format. - /// - [Fact] - public void TestHelpLink() - { - foreach (var diagnosticAnalyzer in this.GetCSharpDiagnosticAnalyzers()) - { - foreach (var diagnostic in diagnosticAnalyzer.SupportedDiagnostics) - { - if (diagnostic.DefaultSeverity == DiagnosticSeverity.Hidden && diagnostic.CustomTags.Contains(WellKnownDiagnosticTags.NotConfigurable)) - { - // This diagnostic will never appear in the UI. - continue; - } - - string expected = $"https://github.com/DotNetAnalyzers/PublicApiAnalyzer/blob/master/docs/{diagnostic.Id}.md"; - Assert.Equal(expected, diagnostic.HelpLinkUri); - } - } - } - - /// - /// Gets the C# analyzers being tested - /// - /// - /// New instances of all the C# analyzers being tested. - /// - protected abstract IEnumerable GetCSharpDiagnosticAnalyzers(); - - /// - /// Called to test a C# when applied on the single input source as a string. - /// - /// Input a for the expected . - /// - /// - /// A class in the form of a string to run the analyzer on. - /// A s describing the that should - /// be reported by the analyzer for the specified source. - /// The that the task will observe. - /// The filename or null if the default filename should be used - /// A representing the asynchronous operation. - protected Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult expected, CancellationToken cancellationToken, string filename = null) - { - return this.VerifyCSharpDiagnosticAsync(source, new[] { expected }, cancellationToken, filename); - } - - /// - /// Called to test a C# when applied on the single input source as a string. - /// - /// Input a for each expected. - /// - /// - /// A class in the form of a string to run the analyzer on. - /// A collection of s describing the - /// s that should be reported by the analyzer for the specified source. - /// The that the task will observe. - /// The filename or null if the default filename should be used - /// A representing the asynchronous operation. - protected Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[] expected, CancellationToken cancellationToken, string filename = null) - { - return this.VerifyDiagnosticsAsync(new[] { source }, LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), expected, cancellationToken, filename != null ? new[] { filename } : null); - } - - /// - /// Called to test a C# when applied on the input strings as sources. - /// - /// Input a for each expected. - /// - /// - /// A collection of strings to create source documents from to run the analyzers - /// on. - /// A collection of s describing the - /// s that should be reported by the analyzer for the specified sources. - /// The that the task will observe. - /// The filenames or null if the default filename should be used - /// A representing the asynchronous operation. - protected Task VerifyCSharpDiagnosticAsync(string[] sources, DiagnosticResult[] expected, CancellationToken cancellationToken, string[] filenames = null) - { - return this.VerifyDiagnosticsAsync(sources, LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzers().ToImmutableArray(), expected, cancellationToken, filenames); - } - - /// - /// Checks each of the actual s found and compares them with the corresponding - /// in the array of expected results. s are considered - /// equal only if the , , - /// , and of the - /// match the actual . - /// - /// The s found by the compiler after running the analyzer - /// on the source code. - /// The analyzers that have been run on the sources. - /// A collection of s describing the expected - /// diagnostics for the sources. - private static void VerifyDiagnosticResults(IEnumerable actualResults, ImmutableArray analyzers, DiagnosticResult[] expectedResults) - { - int expectedCount = expectedResults.Length; - int actualCount = actualResults.Count(); - - if (expectedCount != actualCount) - { - string diagnosticsOutput = actualResults.Any() ? FormatDiagnostics(analyzers, actualResults.ToArray()) : " NONE."; - - Assert.True( - false, - string.Format("Mismatch between number of diagnostics returned, expected \"{0}\" actual \"{1}\"\r\n\r\nDiagnostics:\r\n{2}\r\n", expectedCount, actualCount, diagnosticsOutput)); - } - - for (int i = 0; i < expectedResults.Length; i++) - { - var actual = actualResults.ElementAt(i); - var expected = expectedResults[i]; - - if (expected.Line == -1 && expected.Column == -1) - { - if (actual.Location != Location.None) - { - string message = - string.Format( - "Expected:\nA project diagnostic with No location\nActual:\n{0}", - FormatDiagnostics(analyzers, actual)); - Assert.True(false, message); - } - } - else - { - VerifyDiagnosticLocation(analyzers, actual, actual.Location, expected.Locations.First()); - var additionalLocations = actual.AdditionalLocations.ToArray(); - - if (additionalLocations.Length != expected.Locations.Length - 1) - { - string message = - string.Format( - "Expected {0} additional locations but got {1} for Diagnostic:\r\n {2}\r\n", - expected.Locations.Length - 1, - additionalLocations.Length, - FormatDiagnostics(analyzers, actual)); - Assert.True(false, message); - } - - for (int j = 0; j < additionalLocations.Length; ++j) - { - VerifyDiagnosticLocation(analyzers, actual, additionalLocations[j], expected.Locations[j + 1]); - } - } - - if (actual.Id != expected.Id) - { - string message = - string.Format( - "Expected diagnostic id to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Id, - actual.Id, - FormatDiagnostics(analyzers, actual)); - Assert.True(false, message); - } - - if (actual.Severity != expected.Severity) - { - string message = - string.Format( - "Expected diagnostic severity to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Severity, - actual.Severity, - FormatDiagnostics(analyzers, actual)); - Assert.True(false, message); - } - - if (actual.GetMessage() != expected.Message) - { - string message = - string.Format( - "Expected diagnostic message to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Message, - actual.GetMessage(), - FormatDiagnostics(analyzers, actual)); - Assert.True(false, message); - } - } - } - - /// - /// Helper method to that checks the location of a - /// and compares it with the location described by a - /// . - /// - /// The analyzer that have been run on the sources. - /// The diagnostic that was found in the code. - /// The location of the diagnostic found in the code. - /// The describing the expected location of the - /// diagnostic. - private static void VerifyDiagnosticLocation(ImmutableArray analyzers, Diagnostic diagnostic, Location actual, DiagnosticResultLocation expected) - { - var actualSpan = actual.GetLineSpan(); - - string message = - string.Format( - "Expected diagnostic to be in file \"{0}\" was actually in file \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Path, - actualSpan.Path, - FormatDiagnostics(analyzers, diagnostic)); - Assert.True( - actualSpan.Path == expected.Path || (actualSpan.Path != null && actualSpan.Path.Contains("Test0.") && expected.Path.Contains("Test.")), - message); - - var actualLinePosition = actualSpan.StartLinePosition; - - // Only check line position if it matters - if (expected.Line > 0) - { - if (actualLinePosition.Line + 1 != expected.Line) - { - string message2 = - string.Format( - "Expected diagnostic to be on line \"{0}\" was actually on line \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Line, - actualLinePosition.Line + 1, - FormatDiagnostics(analyzers, diagnostic)); - Assert.True(false, message2); - } - } - - // Only check column position if it matters - if (expected.Column > 0) - { - if (actualLinePosition.Character + 1 != expected.Column) - { - string message2 = - string.Format( - "Expected diagnostic to start at column \"{0}\" was actually at column \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Column, - actualLinePosition.Character + 1, - FormatDiagnostics(analyzers, diagnostic)); - Assert.True(false, message2); - } - } - } - - /// - /// Helper method to format a into an easily readable string. - /// - /// The analyzers that this verifier tests. - /// A collection of s to be formatted. - /// The formatted as a string. - private static string FormatDiagnostics(ImmutableArray analyzers, params Diagnostic[] diagnostics) - { - var builder = new StringBuilder(); - for (int i = 0; i < diagnostics.Length; ++i) - { - var diagnosticsId = diagnostics[i].Id; - - builder.Append("// ").AppendLine(diagnostics[i].ToString()); - - var applicableAnalyzer = analyzers.FirstOrDefault(a => a.SupportedDiagnostics.Any(dd => dd.Id == diagnosticsId)); - if (applicableAnalyzer != null) - { - var analyzerType = applicableAnalyzer.GetType(); - - var location = diagnostics[i].Location; - if (location == Location.None) - { - builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, diagnosticsId); - } - else if (!location.IsInSource) - { - builder.AppendFormat("GetMetadataResult({0})", diagnostics[i]); - } - else - { - string resultMethodName = diagnostics[i].Location.SourceTree.FilePath.EndsWith(".cs") ? "GetCSharpResultAt" : "GetBasicResultAt"; - var linePosition = diagnostics[i].Location.GetLineSpan().StartLinePosition; - - builder.AppendFormat( - "{0}({1}, {2}, {3}.{4})", - resultMethodName, - linePosition.Line + 1, - linePosition.Character + 1, - analyzerType.Name, - diagnosticsId); - } - - if (i != diagnostics.Length - 1) - { - builder.Append(','); - } - - builder.AppendLine(); - } - } - - return builder.ToString(); - } - - /// - /// General method that gets a collection of actual s found in the source after the - /// analyzer is run, then verifies each of them. - /// - /// An array of strings to create source documents from to run the analyzers on. - /// The language of the classes represented by the source strings. - /// The analyzers to be run on the source code. - /// A collection of s that should appear after the analyzer - /// is run on the sources. - /// The that the task will observe. - /// The filenames or null if the default filename should be used - /// A representing the asynchronous operation. - private async Task VerifyDiagnosticsAsync(string[] sources, string language, ImmutableArray analyzers, DiagnosticResult[] expected, CancellationToken cancellationToken, string[] filenames) - { - VerifyDiagnosticResults(await this.GetSortedDiagnosticsAsync(sources, language, analyzers, cancellationToken, filenames).ConfigureAwait(false), analyzers, expected); - } - } -}