diff --git a/src/NServiceBus.Core.Analyzer.Tests/.editorconfig b/src/NServiceBus.Core.Analyzer.Tests/.editorconfig index 0f61b0e110..aee80b6e7c 100644 --- a/src/NServiceBus.Core.Analyzer.Tests/.editorconfig +++ b/src/NServiceBus.Core.Analyzer.Tests/.editorconfig @@ -5,3 +5,6 @@ dotnet_diagnostic.CA2007.severity = none # Justification: Tests don't support cancellation and don't need to forward IMessageHandlerContext.CancellationToken dotnet_diagnostic.NSB0002.severity = suggestion + +# bug in analyzer when multi-targetting +dotnet_diagnostic.IDE0063.severity = none # Use simple 'using' statement diff --git a/src/NServiceBus.Core.Analyzer.Tests/AwaitOrCaptureTasksAnalyzerTests.cs b/src/NServiceBus.Core.Analyzer.Tests/AwaitOrCaptureTasksAnalyzerTests.cs index a23708e55e..e705aac66c 100644 --- a/src/NServiceBus.Core.Analyzer.Tests/AwaitOrCaptureTasksAnalyzerTests.cs +++ b/src/NServiceBus.Core.Analyzer.Tests/AwaitOrCaptureTasksAnalyzerTests.cs @@ -2,71 +2,69 @@ namespace NServiceBus.Core.Analyzer.Tests { using System.Threading.Tasks; using Helpers; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.Diagnostics; using NUnit.Framework; [TestFixture] - public class AwaitOrCaptureTasksAnalyzerTests : DiagnosticVerifier + public class AwaitOrCaptureTasksAnalyzerTests : AnalyzerTestFixture { // IPipelineContext - [TestCase("IPipelineContext", "obj.Send(new object(), new SendOptions());")] - [TestCase("IPipelineContext", "obj.Send(_ => { }, new SendOptions());")] - [TestCase("IPipelineContext", "obj.Publish(new object(), new PublishOptions());")] - [TestCase("IPipelineContext", "obj.Publish(_ => { }, new PublishOptions());")] + [TestCase("IPipelineContext", "obj.Send(new object(), new SendOptions())")] + [TestCase("IPipelineContext", "obj.Send(_ => { }, new SendOptions())")] + [TestCase("IPipelineContext", "obj.Publish(new object(), new PublishOptions())")] + [TestCase("IPipelineContext", "obj.Publish(_ => { }, new PublishOptions())")] // PipelineContextExtensions - [TestCase("IPipelineContext", "obj.Send(new object());")] - [TestCase("IPipelineContext", "obj.Send(_ => { });")] - [TestCase("IPipelineContext", "obj.Send(\"destination\", new object());")] - [TestCase("IPipelineContext", "obj.Send(\"destination\", _ => { });")] - [TestCase("IPipelineContext", "obj.SendLocal(new object());")] - [TestCase("IPipelineContext", "obj.SendLocal(_ => { });")] - [TestCase("IPipelineContext", "obj.Publish(new object());")] - [TestCase("IPipelineContext", "obj.Publish();")] - [TestCase("IPipelineContext", "obj.Publish(_ => { });")] + [TestCase("IPipelineContext", "obj.Send(new object())")] + [TestCase("IPipelineContext", "obj.Send(_ => { })")] + [TestCase("IPipelineContext", "obj.Send(\"destination\", new object())")] + [TestCase("IPipelineContext", "obj.Send(\"destination\", _ => { })")] + [TestCase("IPipelineContext", "obj.SendLocal(new object())")] + [TestCase("IPipelineContext", "obj.SendLocal(_ => { })")] + [TestCase("IPipelineContext", "obj.Publish(new object())")] + [TestCase("IPipelineContext", "obj.Publish()")] + [TestCase("IPipelineContext", "obj.Publish(_ => { })")] // IMessageProcessingContext - [TestCase("IMessageProcessingContext", "obj.Reply(new object(), new ReplyOptions());")] - [TestCase("IMessageProcessingContext", "obj.Reply(_ => { }, new ReplyOptions());")] - [TestCase("IMessageProcessingContext", "obj.ForwardCurrentMessageTo(\"destination\");")] + [TestCase("IMessageProcessingContext", "obj.Reply(new object(), new ReplyOptions())")] + [TestCase("IMessageProcessingContext", "obj.Reply(_ => { }, new ReplyOptions())")] + [TestCase("IMessageProcessingContext", "obj.ForwardCurrentMessageTo(\"destination\")")] // MessageProcessingContextExtensions - [TestCase("IMessageProcessingContext", "obj.Reply(new object());")] - [TestCase("IMessageProcessingContext", "obj.Reply(_ => { });")] + [TestCase("IMessageProcessingContext", "obj.Reply(new object())")] + [TestCase("IMessageProcessingContext", "obj.Reply(_ => { })")] // IMessageSession - [TestCase("IMessageSession", "obj.Send(new object(), new SendOptions());")] - [TestCase("IMessageSession", "obj.Send(_ => { }, new SendOptions());")] - [TestCase("IMessageSession", "obj.Publish(new object(), new PublishOptions());")] - [TestCase("IMessageSession", "obj.Publish(_ => { }, new PublishOptions());")] - [TestCase("IMessageSession", "obj.Subscribe(typeof(object), new SubscribeOptions());")] - [TestCase("IMessageSession", "obj.Unsubscribe(typeof(object), new UnsubscribeOptions());")] + [TestCase("IMessageSession", "obj.Send(new object(), new SendOptions())")] + [TestCase("IMessageSession", "obj.Send(_ => { }, new SendOptions())")] + [TestCase("IMessageSession", "obj.Publish(new object(), new PublishOptions())")] + [TestCase("IMessageSession", "obj.Publish(_ => { }, new PublishOptions())")] + [TestCase("IMessageSession", "obj.Subscribe(typeof(object), new SubscribeOptions())")] + [TestCase("IMessageSession", "obj.Unsubscribe(typeof(object), new UnsubscribeOptions())")] // MessageSessionExtensions - [TestCase("IMessageSession", "obj.Send(new object());")] - [TestCase("IMessageSession", "obj.Send(_ => { });")] - [TestCase("IMessageSession", "obj.Send(\"destination\", new object());")] - [TestCase("IMessageSession", "obj.Send(\"destination\", _ => { });")] - [TestCase("IMessageSession", "obj.SendLocal(new object());")] - [TestCase("IMessageSession", "obj.SendLocal(_ => { });")] - [TestCase("IMessageSession", "obj.Publish(new object());")] - [TestCase("IMessageSession", "obj.Publish();")] - [TestCase("IMessageSession", "obj.Publish(_ => { });")] - [TestCase("IMessageSession", "obj.Subscribe(typeof(object));")] - [TestCase("IMessageSession", "obj.Subscribe();")] - [TestCase("IMessageSession", "obj.Unsubscribe(typeof(object));")] - [TestCase("IMessageSession", "obj.Unsubscribe();")] + [TestCase("IMessageSession", "obj.Send(new object())")] + [TestCase("IMessageSession", "obj.Send(_ => { })")] + [TestCase("IMessageSession", "obj.Send(\"destination\", new object())")] + [TestCase("IMessageSession", "obj.Send(\"destination\", _ => { })")] + [TestCase("IMessageSession", "obj.SendLocal(new object())")] + [TestCase("IMessageSession", "obj.SendLocal(_ => { })")] + [TestCase("IMessageSession", "obj.Publish(new object())")] + [TestCase("IMessageSession", "obj.Publish()")] + [TestCase("IMessageSession", "obj.Publish(_ => { })")] + [TestCase("IMessageSession", "obj.Subscribe(typeof(object))")] + [TestCase("IMessageSession", "obj.Subscribe()")] + [TestCase("IMessageSession", "obj.Unsubscribe(typeof(object))")] + [TestCase("IMessageSession", "obj.Unsubscribe()")] // Endpoint - [TestCase("EndpointConfiguration", "Endpoint.Create(obj);")] - [TestCase("EndpointConfiguration", "Endpoint.Start(obj);")] + [TestCase("EndpointConfiguration", "Endpoint.Create(obj)")] + [TestCase("EndpointConfiguration", "Endpoint.Start(obj)")] // IStartableEndpoint - [TestCase("IStartableEndpoint", "obj.Start();")] + [TestCase("IStartableEndpoint", "obj.Start()")] // IEndpointInstance - [TestCase("IEndpointInstance", "obj.Stop();")] + [TestCase("IEndpointInstance", "obj.Stop()")] public Task DiagnosticIsReportedForCorePublicMethods(string type, string call) { var source = @@ -77,33 +75,26 @@ class Foo {{ void Bar({type} obj) {{ - {call} + [|{call}|]; }} }}"; - var expected = new DiagnosticResult - { - Id = "NSB0001", - Severity = DiagnosticSeverity.Error, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 9) }, - }; - - return Verify(source, expected); + return Assert(AwaitOrCaptureTasksAnalyzer.DiagnosticId, source); } - [TestCase("session.Send(new object());")] - [TestCase("session.Send(new object(), new SendOptions());")] - [TestCase("session.Send(_ => { }, new SendOptions());")] - [TestCase("session.Send(_ => { });")] - [TestCase("session.Send(\"destination\", new object());")] - [TestCase("session.Send(\"destination\", _ => { });")] - [TestCase("session.SendLocal(new object());")] - [TestCase("session.SendLocal(_ => { });")] - [TestCase("session.Publish(new object());")] - [TestCase("session.Publish(new object(), new PublishOptions());")] - [TestCase("session.Publish();")] - [TestCase("session.Publish(_ => { });")] - [TestCase("session.Publish(_ => { }, new PublishOptions());")] + [TestCase("session.Send(new object())")] + [TestCase("session.Send(new object(), new SendOptions())")] + [TestCase("session.Send(_ => { }, new SendOptions())")] + [TestCase("session.Send(_ => { })")] + [TestCase("session.Send(\"destination\", new object())")] + [TestCase("session.Send(\"destination\", _ => { })")] + [TestCase("session.SendLocal(new object())")] + [TestCase("session.SendLocal(_ => { })")] + [TestCase("session.Publish(new object())")] + [TestCase("session.Publish(new object(), new PublishOptions())")] + [TestCase("session.Publish()")] + [TestCase("session.Publish(_ => { })")] + [TestCase("session.Publish(_ => { }, new PublishOptions())")] public Task DiagnosticIsReportedForUniformSession(string call) { var source = @@ -113,25 +104,18 @@ class Foo {{ void Bar(IUniformSession session) {{ - {call} + [|{call}|]; }} }}"; - var expected = new DiagnosticResult - { - Id = "NSB0001", - Severity = DiagnosticSeverity.Error, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 9) }, - }; - - return Verify(source, expected); + return Assert(AwaitOrCaptureTasksAnalyzer.DiagnosticId, source); } - [TestCase("RequestTimeout(context, DateTime.Now);")] - [TestCase("RequestTimeout(context, DateTime.Now, new object());")] - [TestCase("RequestTimeout(context, TimeSpan.Zero);")] - [TestCase("RequestTimeout(context, TimeSpan.Zero, new object());")] - [TestCase("ReplyToOriginator(context, new object());")] + [TestCase("RequestTimeout(context, DateTime.Now)")] + [TestCase("RequestTimeout(context, DateTime.Now, new object())")] + [TestCase("RequestTimeout(context, TimeSpan.Zero)")] + [TestCase("RequestTimeout(context, TimeSpan.Zero, new object())")] + [TestCase("ReplyToOriginator(context, new object())")] public Task DiagnosticIsReportedForSagaProtectedMethods(string call) { var source = @@ -141,20 +125,15 @@ class TestSaga : Saga {{ void Bar(IMessageHandlerContext context) {{ - {call} + [|{call}|]; }} protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) {{ }} }} -class Data : ContainSagaData {{}} -"; - var expected = new DiagnosticResult - { - Id = "NSB0001", - Severity = DiagnosticSeverity.Error, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 9) }, - }; - return Verify(source, expected); + +class Data : ContainSagaData {{}}"; + + return Assert(AwaitOrCaptureTasksAnalyzer.DiagnosticId, source); } [Test] @@ -167,18 +146,11 @@ class Foo { async Task Bar(IPipelineContext ctx) { - ctx.Send(new object(), new SendOptions()); + [|ctx.Send(new object(), new SendOptions())|]; } }"; - var expected = new DiagnosticResult - { - Id = "NSB0001", - Severity = DiagnosticSeverity.Error, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", 7, 9) }, - }; - - return Verify(source, expected); + return Assert(AwaitOrCaptureTasksAnalyzer.DiagnosticId, source); } [TestCase( @@ -267,8 +239,6 @@ void Bar(IMessageSession session) } }", Description = "because the send operation task is accessed.")] - public Task NoDiagnosticIsReported(string source) => NoDiagnostic(source); - - protected override DiagnosticAnalyzer GetAnalyzer() => new AwaitOrCaptureTasksAnalyzer(); + public Task NoDiagnosticIsReported(string source) => Assert(source); } } diff --git a/src/NServiceBus.Core.Analyzer.Tests/ForwardCancellationToken/ForwardCancellationTokenFixerTests.cs b/src/NServiceBus.Core.Analyzer.Tests/ForwardCancellationToken/ForwardCancellationTokenFixerTests.cs index 02e4e83524..6fd1eb9dae 100644 --- a/src/NServiceBus.Core.Analyzer.Tests/ForwardCancellationToken/ForwardCancellationTokenFixerTests.cs +++ b/src/NServiceBus.Core.Analyzer.Tests/ForwardCancellationToken/ForwardCancellationTokenFixerTests.cs @@ -2,18 +2,16 @@ { using System.Threading.Tasks; using Helpers; - using Microsoft.CodeAnalysis.CodeFixes; - using Microsoft.CodeAnalysis.Diagnostics; using NUnit.Framework; [TestFixture] - public class ForwardCancellationTokenFixerTests : CodeFixVerifier + public class ForwardCancellationTokenFixerTests : CodeFixTestFixture { [Test] public Task Simple() { - var test = @" -using NServiceBus; + var original = +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -25,11 +23,10 @@ public Task Handle(TestMessage message, IMessageHandlerContext context) static Task TestMethod(CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; +public class TestMessage : ICommand {}"; - var fixedTest = @" -using NServiceBus; + var expected = +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -41,17 +38,16 @@ public Task Handle(TestMessage message, IMessageHandlerContext context) static Task TestMethod(CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; +public class TestMessage : ICommand {}"; - return VerifyFix(test, fixedTest); + return Assert(original, expected); } [Test] - public Task NonStandardContextVaraibleName() + public Task NonStandardContextVariableName() { - var test = @" -using NServiceBus; + var original = +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -63,11 +59,10 @@ public Task Handle(TestMessage message, IMessageHandlerContext iRenamedItCuzICan static Task TestMethod(CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; +public class TestMessage : ICommand {}"; - var fixedTest = @" -using NServiceBus; + var expected = +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -79,17 +74,16 @@ public Task Handle(TestMessage message, IMessageHandlerContext iRenamedItCuzICan static Task TestMethod(CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; +public class TestMessage : ICommand {}"; - return VerifyFix(test, fixedTest); + return Assert(original, expected); } [Test] public Task OverloadsAndThis() { - var test = @" -using NServiceBus; + var original = +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -103,11 +97,10 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) Task TestMethod() { return Task.CompletedTask; } Task TestMethod(CancellationToken token) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; +public class TestMessage : ICommand {}"; - var fixedTest = @" -using NServiceBus; + var expected = +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -121,17 +114,16 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) Task TestMethod() { return Task.CompletedTask; } Task TestMethod(CancellationToken token) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; +public class TestMessage : ICommand {}"; - return VerifyFix(test, fixedTest); + return Assert(original, expected); } [Test] public Task DontMessUpGenericTypeParams() { - var test = @" -using NServiceBus; + var original = +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -146,11 +138,10 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) Task TestMethod(T value, CancellationToken token = default(CancellationToken)) { return Task.FromResult(value); } } -public class TestMessage : ICommand {} -"; +public class TestMessage : ICommand {}"; - var fixedTest = @" -using NServiceBus; + var expected = +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -165,17 +156,16 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) Task TestMethod(T value, CancellationToken token = default(CancellationToken)) { return Task.FromResult(value); } } -public class TestMessage : ICommand {} -"; +public class TestMessage : ICommand {}"; - return VerifyFix(test, fixedTest); + return Assert(original, expected); } [Test] public Task DontMessUpTrivia() { - var test = @" -using NServiceBus; + var original = +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -190,11 +180,10 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) Task TestMethod(int a, int b, int c, int d, int e, CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; +public class TestMessage : ICommand {}"; - var fixedTest = @" -using NServiceBus; + var expected = +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -209,17 +198,16 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) Task TestMethod(int a, int b, int c, int d, int e, CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; +public class TestMessage : ICommand {}"; - return VerifyFix(test, fixedTest); + return Assert(original, expected); } [Test] public Task MultipleOptionalParameters() { - var test = @" -using NServiceBus; + var original = +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -231,11 +219,10 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) Task TestMethod(int a, int b = 0, int c = 1, int d = 2, CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; +public class TestMessage : ICommand {}"; - var fixedTest = @" -using NServiceBus; + var expected = +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -247,14 +234,9 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) Task TestMethod(int a, int b = 0, int c = 1, int d = 2, CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; +public class TestMessage : ICommand {}"; - return VerifyFix(test, fixedTest); + return Assert(original, expected); } - - protected override DiagnosticAnalyzer GetAnalyzer() => new ForwardCancellationTokenAnalyzer(); - - protected override CodeFixProvider GetCodeFixProvider() => new ForwardCancellationTokenFixer(); } -} \ No newline at end of file +} diff --git a/src/NServiceBus.Core.Analyzer.Tests/ForwardCancellationToken/ForwardCancellationTokenTests.cs b/src/NServiceBus.Core.Analyzer.Tests/ForwardCancellationToken/ForwardCancellationTokenTests.cs index 0ffc15c333..beb33a4dd3 100644 --- a/src/NServiceBus.Core.Analyzer.Tests/ForwardCancellationToken/ForwardCancellationTokenTests.cs +++ b/src/NServiceBus.Core.Analyzer.Tests/ForwardCancellationToken/ForwardCancellationTokenTests.cs @@ -1,19 +1,15 @@ -#pragma warning disable IDE0022 // Use expression body for methods -namespace NServiceBus.Core.Analyzer.Tests +namespace NServiceBus.Core.Analyzer.Tests { using System.Threading.Tasks; using Helpers; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.Diagnostics; using NUnit.Framework; [TestFixture] - public class ForwardCancellationTokenTests : DiagnosticVerifier + public class ForwardCancellationTokenTests : AnalyzerTestFixture { [Test] - public Task Simple() - { - var source = + public Task Simple() => Assert( + ForwardCancellationTokenAnalyzer.DiagnosticId, @"using NServiceBus; using System.Threading; using System.Threading.Tasks; @@ -21,23 +17,16 @@ public class Foo : IHandleMessages { public Task Handle(TestMessage message, IMessageHandlerContext context) { - return TestMethod(); + return [|TestMethod()|]; } static Task TestMethod(CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; - - var expected = NotForwardedAt(8, 16); - - return Verify(source, expected); - } +public class TestMessage : ICommand {}"); [Test] - public Task MethodAcceptingTokenIsOnDifferentClass() - { - var source = + public Task MethodAcceptingTokenIsOnDifferentClass() => Assert( + ForwardCancellationTokenAnalyzer.DiagnosticId, @"using NServiceBus; using System.Threading; using System.Threading.Tasks; @@ -46,25 +35,18 @@ public class Foo : IHandleMessages public Task Handle(TestMessage message, IMessageHandlerContext context) { var thing = new Thing(); - return thing.TestMethod(); + return [|thing.TestMethod()|]; } } public class Thing { public Task TestMethod(CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; - - var expected = NotForwardedAt(9, 16); - - return Verify(source, expected); - } +public class TestMessage : ICommand {}"); [Test] - public Task MethodAcceptingTokenIsOnABaseClass() - { - var source = + public Task MethodAcceptingTokenIsOnABaseClass() => Assert( + ForwardCancellationTokenAnalyzer.DiagnosticId, @"using NServiceBus; using System.Threading; using System.Threading.Tasks; @@ -73,7 +55,7 @@ public class Foo : IHandleMessages public Task Handle(TestMessage message, IMessageHandlerContext context) { var thing = new Thing(); - return thing.TestMethod(); + return [|thing.TestMethod()|]; } } public class Thing : BaseThing @@ -87,18 +69,11 @@ public class BaseThing { public Task TestMethod(CancellationToken token) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; - - var expected = NotForwardedAt(9, 16); - - return Verify(source, expected); - } +public class TestMessage : ICommand {}"); [Test] - public Task MethodAcceptingTokenIsOnADerivedClass() - { - var source = + public Task MethodAcceptingTokenIsOnADerivedClass() => Assert( + ForwardCancellationTokenAnalyzer.DiagnosticId, @"using NServiceBus; using System.Threading; using System.Threading.Tasks; @@ -107,7 +82,7 @@ public class Foo : IHandleMessages public Task Handle(TestMessage message, IMessageHandlerContext context) { var thing = new Thing(); - return thing.TestMethod(); + return [|thing.TestMethod()|]; } } public class Thing : BaseThing @@ -118,18 +93,11 @@ public class BaseThing { public Task TestMethod() { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; - - var expected = NotForwardedAt(9, 16); - - return Verify(source, expected); - } +public class TestMessage : ICommand {}"); [Test] - public Task LotsOfParameters() - { - var source = + public Task LotsOfParameters() => Assert( + ForwardCancellationTokenAnalyzer.DiagnosticId, @"using NServiceBus; using System.Threading; using System.Threading.Tasks; @@ -137,23 +105,16 @@ public class Foo : IHandleMessages { public Task Handle(TestMessage message, IMessageHandlerContext context) { - return TestMethod(true, false, true, false, true); + return [|TestMethod(true, false, true, false, true)|]; } static Task TestMethod(bool p1, bool p2, bool p3, bool p4, bool p5, CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; - - var expected = NotForwardedAt(8, 16); - - return Verify(source, expected); - } +public class TestMessage : ICommand {}"); [Test] - public Task LotsOfOverloadsThatAllTakeToken() - { - var source = + public Task LotsOfOverloadsThatAllTakeToken() => Assert( + ForwardCancellationTokenAnalyzer.DiagnosticId, @"using NServiceBus; using System.Threading; using System.Threading.Tasks; @@ -161,11 +122,11 @@ public class Foo : IHandleMessages { public async Task Handle(TestMessage message, IMessageHandlerContext context) { - await TestMethod(true); - await TestMethod(true, false); - await TestMethod(true, false, true); - await TestMethod(true, false, true, false); - await TestMethod(true, false, true, false, true); + await [|TestMethod(true)|]; + await [|TestMethod(true, false)|]; + await [|TestMethod(true, false, true)|]; + await [|TestMethod(true, false, true, false)|]; + await [|TestMethod(true, false, true, false, true)|]; } static Task TestMethod(bool p1, CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } @@ -174,24 +135,11 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) static Task TestMethod(bool p1, bool p2, bool p3, bool p4, CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } static Task TestMethod(bool p1, bool p2, bool p3, bool p4, bool p5, CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; - var expecteds = new[] - { - NotForwardedAt(8, 15), - NotForwardedAt(9, 15), - NotForwardedAt(10, 15), - NotForwardedAt(11, 15), - NotForwardedAt(12, 15), - }; - - return Verify(source, expecteds); - } +public class TestMessage : ICommand {}"); [Test] - public Task LotsOfOverloadsButOnlyOneTakesToken() - { - var source = + public Task LotsOfOverloadsButOnlyOneTakesToken() => Assert( + ForwardCancellationTokenAnalyzer.DiagnosticId, @"using NServiceBus; using System.Threading; using System.Threading.Tasks; @@ -203,7 +151,7 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) await TestMethod(true, false); await TestMethod(true, false, true); await TestMethod(true, false, true, false); - await TestMethod(true, false, true, false, true); + await [|TestMethod(true, false, true, false, true)|]; } static Task TestMethod(bool p1) { return Task.CompletedTask; } @@ -212,20 +160,11 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) static Task TestMethod(bool p1, bool p2, bool p3, bool p4) { return Task.CompletedTask; } static Task TestMethod(bool p1, bool p2, bool p3, bool p4, bool p5, CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"; - var expecteds = new[] - { - NotForwardedAt(12, 15), - }; - - return Verify(source, expecteds); - } +public class TestMessage : ICommand {}"); [Test] - public Task CancellationTokenOnExtensionMethod() - { - var source = + public Task CancellationTokenOnExtensionMethod() => Assert( + ForwardCancellationTokenAnalyzer.DiagnosticId, @"using NServiceBus; using System.Threading; using System.Threading.Tasks; @@ -234,7 +173,7 @@ public class Foo : IHandleMessages public async Task Handle(TestMessage message, IMessageHandlerContext context) { var bar = new Bar(); - await bar.DoSomething(true); + await [|bar.DoSomething(true)|]; } } public class TestMessage : ICommand {} @@ -245,33 +184,12 @@ public static Task DoSomething(this Bar bar, bool value, CancellationToken token { return Task.CompletedTask; } -} -"; - var expecteds = new[] - { - NotForwardedAt(9, 15), - }; - - return Verify(source, expecteds); - } - - - static DiagnosticResult NotForwardedAt(int line, int character) - { - return new DiagnosticResult - { - Id = "NSB0002", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", line, character) } - }; - } +}"); [Test] - public Task NoBaseType() - { - var source = -@" -using NServiceBus; + public Task NoBaseType() => Assert( + ForwardCancellationTokenAnalyzer.DiagnosticId, +@"using NServiceBus; using System; using System.Threading; using System.Threading.Tasks; @@ -279,10 +197,10 @@ public class Foo { public async Task Handle(TestMessage message, IMessageHandlerContext context) { - await TestMethod(); - await Foo.TestMethod(); - await TestMethod(); - await Foo.TestMethod(); + await [|TestMethod()|]; + await [|Foo.TestMethod()|]; + await [|TestMethod()|]; + await [|Foo.TestMethod()|]; } public async Task Invoke(IMadeUpContext context, Func next) @@ -296,25 +214,11 @@ public async Task Invoke(IMadeUpContext context, Func next static Task TestMethod(CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } public class TestMessage : ICommand {} -public interface IMadeUpContext {} -"; - - var expecteds = new[] - { - NotForwardedAt(10, 15), - NotForwardedAt(11, 15), - NotForwardedAt(12, 15), - NotForwardedAt(13, 15), - }; - - return Verify(source, expecteds); - } +public interface IMadeUpContext {}"); [Test] - public Task NoDiagnosticWhenNotNServiceBus() - { - return NoDiagnostic(@" -using System; + public Task NoDiagnosticWhenNotNServiceBus() => Assert( +@"using System; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -352,15 +256,11 @@ public interface IHandleMessages public abstract class Behavior { public abstract Task Invoke(TContext context, Func next); -} -"); - } +}"); [Test] - public Task NoDiagnosticWhenTokenPassedToStaticMethod() - { - return NoDiagnostic(@" -using NServiceBus; + public Task NoDiagnosticWhenTokenPassedToStaticMethod() => Assert( +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -375,15 +275,11 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) static Task TestMethod(CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"); - } +public class TestMessage : ICommand {}"); [Test] - public Task NoDiagnosticWhenStaticMethodDoesNotSupportToken() - { - return NoDiagnostic(@" -using NServiceBus; + public Task NoDiagnosticWhenStaticMethodDoesNotSupportToken() => Assert( +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -396,15 +292,11 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) static Task TestMethod(bool value, string optionalParam = null) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"); - } +public class TestMessage : ICommand {}"); [Test] - public Task NoDiagnosticWhenTokenPassedToExternalStaticMethod() - { - return NoDiagnostic(@" -using NServiceBus; + public Task NoDiagnosticWhenTokenPassedToExternalStaticMethod() => Assert( +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -419,15 +311,11 @@ public static class OtherClass { public static Task TestMethod(CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"); - } +public class TestMessage : ICommand {}"); [Test] - public Task NoDiagnosticWhenExternalStaticMethodDoesNotSupportToken() - { - return NoDiagnostic(@" -using NServiceBus; + public Task NoDiagnosticWhenExternalStaticMethodDoesNotSupportToken() => Assert( +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -441,15 +329,11 @@ public static class OtherClass { public static Task TestMethod(bool value) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"); - } +public class TestMessage : ICommand {}"); [Test] - public Task NoDiagnosticWhenTokenPassedToExternalStaticMethodWithStaticUsing() - { - return NoDiagnostic(@" -using NServiceBus; + public Task NoDiagnosticWhenTokenPassedToExternalStaticMethodWithStaticUsing() => Assert( +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; using static OtherClass; @@ -465,15 +349,11 @@ public static class OtherClass { public static Task TestMethod(CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"); - } +public class TestMessage : ICommand {}"); [Test] - public Task NoDiagnosticWhenExternalStaticMethodDoesNotSupportTokenWithStaticUsing() - { - return NoDiagnostic(@" -using NServiceBus; + public Task NoDiagnosticWhenExternalStaticMethodDoesNotSupportTokenWithStaticUsing() => Assert( +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; using static OtherClass; @@ -488,15 +368,11 @@ public static class OtherClass { public static Task TestMethod(bool value) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"); - } +public class TestMessage : ICommand {}"); [Test] - public Task NoDiagnosticWhenTokenPassedToMemberMethod() - { - return NoDiagnostic(@" -using NServiceBus; + public Task NoDiagnosticWhenTokenPassedToMemberMethod() => Assert( +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -511,15 +387,11 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) Task TestMethod(CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"); - } +public class TestMessage : ICommand {}"); [Test] - public Task NoDiagnosticWhenMemberMethodDoesNotSupportToken() - { - return NoDiagnostic(@" -using NServiceBus; + public Task NoDiagnosticWhenMemberMethodDoesNotSupportToken() => Assert( +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -532,15 +404,11 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) Task TestMethod(bool value, string optionalParam = null) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"); - } +public class TestMessage : ICommand {}"); [Test] - public Task NoDiagnosticWhenMethodCandidateTakes2Tokens() - { - return NoDiagnostic(@" -using NServiceBus; + public Task NoDiagnosticWhenMethodCandidateTakes2Tokens() => Assert( +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -554,15 +422,11 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) Task TestMethod() { return Task.CompletedTask; } Task TestMethod(CancellationToken token1 = default(CancellationToken), CancellationToken token2 = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"); - } +public class TestMessage : ICommand {}"); [Test] - public Task NoDiagnosticIfCancellationTokenIsNotLastParameter() - { - return NoDiagnostic(@" -using NServiceBus; + public Task NoDiagnosticIfCancellationTokenIsNotLastParameter() => Assert( +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -576,15 +440,11 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) Task TestMethod(bool value) { return Task.CompletedTask; } Task TestMethod(CancellationToken token, bool value) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"); - } +public class TestMessage : ICommand {}"); [Test] - public Task NoDiagnosticIfMethodCandidateDoesntMatchExistingParameters() - { - return NoDiagnostic(@" -using NServiceBus; + public Task NoDiagnosticIfMethodCandidateDoesntMatchExistingParameters() => Assert( +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -598,15 +458,11 @@ public async Task Handle(TestMessage message, IMessageHandlerContext context) Task TestMethod(bool value1, bool value2) { return Task.CompletedTask; } Task TestMethod(string value1, string value2, CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"); - } +public class TestMessage : ICommand {}"); [Test] - public Task NoDiagnosticOnNamedRandomOrderParameters() - { - return NoDiagnostic(@" -using NServiceBus; + public Task NoDiagnosticOnNamedRandomOrderParameters() => Assert( +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -621,15 +477,11 @@ private Task DoSomething(int myInt = 0, bool myBool = false, string myString = n return Task.CompletedTask; } } -public class TestMessage : ICommand {} -"); - } +public class TestMessage : ICommand {}"); [Test] - public Task NoDiagnosticOnNamedRandomOrderParametersInExtensionMethod() - { - return NoDiagnostic(@" -using NServiceBus; + public Task NoDiagnosticOnNamedRandomOrderParametersInExtensionMethod() => Assert( +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -648,15 +500,11 @@ public static Task DoSomething(this Bar bar, int myInt = 0, bool myBool = false, { return Task.CompletedTask; } -} -"); - } +}"); [Test] - public Task NoDiagnosticOnCrazyWaysToCreateAToken() - { - return NoDiagnostic(@" -using NServiceBus; + public Task NoDiagnosticOnCrazyWaysToCreateAToken() => Assert( +@"using NServiceBus; using System.Threading; using System.Threading.Tasks; public class Foo : IHandleMessages @@ -692,11 +540,6 @@ private CancellationToken MakeToken() private CancellationToken privateToken = CancellationToken.None; } public class TestMessage : ICommand {} -public class Bar {} -"); - } - - protected override DiagnosticAnalyzer GetAnalyzer() => new ForwardCancellationTokenAnalyzer(); +public class Bar {}"); } } -#pragma warning restore IDE0022 // Use expression body for methods diff --git a/src/NServiceBus.Core.Analyzer.Tests/ForwardCancellationToken/ForwardFromPipelineTests.cs b/src/NServiceBus.Core.Analyzer.Tests/ForwardCancellationToken/ForwardFromPipelineTests.cs index 5d4c00fc89..82c55ff924 100644 --- a/src/NServiceBus.Core.Analyzer.Tests/ForwardCancellationToken/ForwardFromPipelineTests.cs +++ b/src/NServiceBus.Core.Analyzer.Tests/ForwardCancellationToken/ForwardFromPipelineTests.cs @@ -1,32 +1,30 @@ -#pragma warning disable IDE0022 // Use expression body for methods -namespace NServiceBus.Core.Analyzer.Tests +namespace NServiceBus.Core.Analyzer.Tests { using System; - using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using Helpers; using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.Diagnostics; using NServiceBus.MessageMutator; using NServiceBus.Pipeline; using NServiceBus.Sagas; using NUnit.Framework; [TestFixture] - public class ForwardFromPipelineTests : DiagnosticVerifier + public class ForwardFromPipelineTests : AnalyzerTestFixture { [Test] - [SuppressMessage("Style", "IDE0001:Simplify Names", Justification = "Clarity of different type names")] public void EachTypeHasABasicTest() { // These abstract classes are only extended internally var ignoredTypes = new[] { +#pragma warning disable IDE0001 // Simplify Names typeof(NServiceBus.Pipeline.ForkConnector<,>), typeof(NServiceBus.Pipeline.PipelineTerminator<>), typeof(NServiceBus.Pipeline.StageConnector<,>), typeof(NServiceBus.Pipeline.StageForkConnector<,,>), +#pragma warning restore IDE0001 // Simplify Names }; var pipelineTypes = typeof(EndpointConfiguration).Assembly.GetTypes() @@ -60,7 +58,7 @@ public void EachTypeHasABasicTest() TestContext.WriteLine(t.FullName); } - Assert.AreEqual(0, missingTestCases.Length, $"One or more pipeline type(s) are not covered by the {nameof(RunTestOnType)} test in this class."); + NUnit.Framework.Assert.AreEqual(0, missingTestCases.Length, $"One or more pipeline type(s) are not covered by the {nameof(RunTestOnType)} test in this class."); } [TestCase(typeof(IHandleMessages<>), "TestMessage", "Handle", "TestMessage message, IMessageHandlerContext context")] @@ -75,9 +73,8 @@ public void EachTypeHasABasicTest() [TestCase(typeof(IMutateOutgoingMessages), null, "MutateOutgoing", "MutateOutgoingMessageContext context")] public Task RunTestOnType(Type type, string genericTypeArgs, string methodName, string methodArguments) { - const string sourceFormat = - @" -using NServiceBus; + const string codeFormat = +@"using NServiceBus; using NServiceBus.MessageMutator; using NServiceBus.Pipeline; using NServiceBus.Sagas; @@ -88,19 +85,18 @@ public class Foo : BASE_TYPE_WITH_ARGUMENTS { public Task METHOD_NAME(METHOD_ARGUMENTS) { - return TestMethod(); + return [|TestMethod()|]; } static Task TestMethod(CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; } } public class TestMessage : ICommand {} -public class TestTimeout {} -"; +public class TestTimeout {}"; var baseTypeWithArgs = Regex.Replace(type.Name, "`.*", "") + (genericTypeArgs != null ? $"<{genericTypeArgs}>" : ""); // Too confusing to do normal string interpolation or formatting with all the curly braces - var code = sourceFormat + var code = codeFormat .Replace("BASE_TYPE_WITH_ARGUMENTS", baseTypeWithArgs) .Replace("METHOD_NAME", methodName) .Replace("METHOD_ARGUMENTS", methodArguments); @@ -110,12 +106,10 @@ public class TestTimeout {} code = code.Replace("public Task", "public override Task"); } - var expected = NotForwardedAt(13, 16); - TestContext.WriteLine($"Source Code for test case: {type.FullName}:"); TestContext.WriteLine(code); - return Verify(code, expected); + return Assert(ForwardCancellationTokenAnalyzer.DiagnosticId, code); } static bool HasMethodWithContextParameter(Type type) @@ -127,9 +121,9 @@ static bool HasMethodWithContextParameter(Type type) { foreach (var method in typeOrInterface.GetMethods()) { - foreach (var p in method.GetParameters()) + foreach (var param in method.GetParameters()) { - if (typeof(ICancellableContext).IsAssignableFrom(p.ParameterType)) + if (typeof(ICancellableContext).IsAssignableFrom(param.ParameterType)) { return true; } @@ -139,18 +133,5 @@ static bool HasMethodWithContextParameter(Type type) return false; } - - static DiagnosticResult NotForwardedAt(int line, int character) - { - return new DiagnosticResult - { - Id = "NSB0002", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test0.cs", line, character) } - }; - } - - protected override DiagnosticAnalyzer GetAnalyzer() => new ForwardCancellationTokenAnalyzer(); } } -#pragma warning restore IDE0022 // Use expression body for methods \ No newline at end of file diff --git a/src/NServiceBus.Core.Analyzer.Tests/Helpers/AnalyzerTestFixture.cs b/src/NServiceBus.Core.Analyzer.Tests/Helpers/AnalyzerTestFixture.cs new file mode 100644 index 0000000000..9aaeb9e912 --- /dev/null +++ b/src/NServiceBus.Core.Analyzer.Tests/Helpers/AnalyzerTestFixture.cs @@ -0,0 +1,143 @@ +namespace NServiceBus.Core.Analyzer.Tests.Helpers +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Linq; + using System.Reflection; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.Diagnostics; + using Microsoft.CodeAnalysis.Text; + using NServiceBus.UniformSession; + + public class AnalyzerTestFixture where TAnalyzer : DiagnosticAnalyzer, new() + { + protected Task Assert(string markupCode, CancellationToken cancellationToken = default) => + Assert(Array.Empty(), markupCode, cancellationToken); + + protected Task Assert(string expectedDiagnosticId, string markupCode, CancellationToken cancellationToken = default) => + Assert(new[] { expectedDiagnosticId }, markupCode, cancellationToken); + + protected async Task Assert(string[] expectedDiagnosticIds, string markupCode, CancellationToken cancellationToken = default) + { + var (code, markupSpans) = Parse(markupCode); + WriteCode(code); + + var document = CreateDocument(code); + + var compilerDiagnostics = await document.GetCompilerDiagnostics(cancellationToken); + WriteCompilerDiagnostics(compilerDiagnostics); + + var compilation = await document.Project.GetCompilationAsync(cancellationToken); + compilation.Compile(); + + var analyzerDiagnostics = (await compilation.GetAnalyzerDiagnostics(new TAnalyzer(), cancellationToken)).ToList(); + WriteAnalyzerDiagnostics(analyzerDiagnostics); + + var expectedSpansAndIds = expectedDiagnosticIds + .SelectMany(id => markupSpans.Select(span => (span, id))) + .OrderBy(item => item.span) + .ThenBy(item => item.id) + .ToList(); + + var actualSpansAndIds = analyzerDiagnostics + .Select(diagnostic => (diagnostic.Location.SourceSpan, diagnostic.Id)) + .ToList(); + + NUnit.Framework.CollectionAssert.AreEqual(expectedSpansAndIds, actualSpansAndIds); + } + + protected static void WriteCode(string code) + { + foreach (var (line, index) in code.Replace("\r\n", "\n").Split('\n') + .Select((line, index) => (line, index))) + { + Console.WriteLine($" {index + 1,3}: {line}"); + } + } + + protected static Document CreateDocument(string code) + { + var references = ImmutableList.Create( + MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location), + MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location), +#if NETCOREAPP + MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location), +#endif + MetadataReference.CreateFromFile(typeof(EndpointConfiguration).GetTypeInfo().Assembly.Location), + MetadataReference.CreateFromFile(typeof(IUniformSession).GetTypeInfo().Assembly.Location)); + + return new AdhocWorkspace() + .AddProject("TestProject", LanguageNames.CSharp) + .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) + .AddMetadataReferences(references) + .AddDocument("TestDocument", code); + } + + protected static void WriteCompilerDiagnostics(IEnumerable diagnostics) + { + Console.WriteLine("Compiler diagnostics:"); + + foreach (var diagnostic in diagnostics) + { + Console.WriteLine($" {diagnostic}"); + } + } + + protected static void WriteAnalyzerDiagnostics(IEnumerable diagnostics) + { + Console.WriteLine("Analyzer diagnostics:"); + + foreach (var diagnostic in diagnostics) + { + Console.WriteLine($" {diagnostic}"); + } + } + + static (string, List) Parse(string markupCode) + { + if (markupCode == null) + { + return (null, new List()); + } + + var code = new StringBuilder(); + var markupSpans = new List(); + + var remainingCode = markupCode; + var remainingCodeStart = 0; + + while (remainingCode.Length > 0) + { + var beforeAndAfterOpening = remainingCode.Split(new[] { "[|" }, 2, StringSplitOptions.None); + + if (beforeAndAfterOpening.Length == 1) + { + _ = code.Append(beforeAndAfterOpening[0]); + break; + } + + var midAndAfterClosing = beforeAndAfterOpening[1].Split(new[] { "|]" }, 2, StringSplitOptions.None); + + if (midAndAfterClosing.Length == 1) + { + throw new Exception("The markup code does not contain a closing '|]'"); + } + + var markupSpan = new TextSpan(remainingCodeStart + beforeAndAfterOpening[0].Length, midAndAfterClosing[0].Length); + + _ = code.Append(beforeAndAfterOpening[0]).Append(midAndAfterClosing[0]); + markupSpans.Add(markupSpan); + + remainingCode = midAndAfterClosing[1]; + remainingCodeStart += beforeAndAfterOpening[0].Length + markupSpan.Length; + } + + return (code.ToString(), markupSpans); + } + } +} diff --git a/src/NServiceBus.Core.Analyzer.Tests/Helpers/CodeFixTestFixture.cs b/src/NServiceBus.Core.Analyzer.Tests/Helpers/CodeFixTestFixture.cs new file mode 100644 index 0000000000..1b4b7ddfbb --- /dev/null +++ b/src/NServiceBus.Core.Analyzer.Tests/Helpers/CodeFixTestFixture.cs @@ -0,0 +1,74 @@ +namespace NServiceBus.Core.Analyzer.Tests.Helpers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CodeFixes; + using Microsoft.CodeAnalysis.Diagnostics; + + public abstract class CodeFixTestFixture : AnalyzerTestFixture + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + protected new virtual async Task Assert(string original, string expected, CancellationToken cancellationToken = default) + { + var actual = await Fix(original, cancellationToken); + + // normalize line endings, just in case + actual = actual.Replace("\r\n", "\n"); + expected = expected.Replace("\r\n", "\n"); + + NUnit.Framework.Assert.AreEqual(expected, actual); + } + + static async Task Fix(string code, CancellationToken cancellationToken, IEnumerable originalCompilerDiagnostics = null) + { + WriteCode(code); + + var document = CreateDocument(code); + + var compilerDiagnostics = await document.GetCompilerDiagnostics(cancellationToken); + WriteCompilerDiagnostics(compilerDiagnostics); + + if (originalCompilerDiagnostics == null) + { + originalCompilerDiagnostics = compilerDiagnostics; + } + else + { + NUnit.Framework.CollectionAssert.AreEqual(originalCompilerDiagnostics, compilerDiagnostics, "Fix introduced new compiler diagnostics."); + } + + var compilation = await document.Project.GetCompilationAsync(cancellationToken); + compilation.Compile(); + + var analyzerDiagnostics = (await compilation.GetAnalyzerDiagnostics(new TAnalyzer(), cancellationToken)).ToList(); + WriteAnalyzerDiagnostics(analyzerDiagnostics); + + if (!analyzerDiagnostics.Any()) + { + return code; + } + + var actions = await document.GetCodeActions(new TCodeFix(), analyzerDiagnostics.First(), cancellationToken); + + if (!actions.Any()) + { + return code; + } + + Console.WriteLine("Applying code fix actions..."); + foreach (var action in actions) + { + document = await document.ApplyChanges(action, cancellationToken); + } + + code = await document.GetCode(cancellationToken); + + return await Fix(code, cancellationToken, originalCompilerDiagnostics); + } + } +} diff --git a/src/NServiceBus.Core.Analyzer.Tests/Helpers/CodeFixVerifier.cs b/src/NServiceBus.Core.Analyzer.Tests/Helpers/CodeFixVerifier.cs deleted file mode 100644 index 5f46ebda70..0000000000 --- a/src/NServiceBus.Core.Analyzer.Tests/Helpers/CodeFixVerifier.cs +++ /dev/null @@ -1,124 +0,0 @@ -namespace NServiceBus.Core.Analyzer.Tests.Helpers -{ - 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.Formatting; - using Microsoft.CodeAnalysis.Simplification; - using NUnit.Framework; - - public abstract class CodeFixVerifier : DiagnosticVerifier - { - protected abstract CodeFixProvider GetCodeFixProvider(); - - protected virtual async Task VerifyFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false, CancellationToken cancellationToken = default) - { - var analyzer = GetAnalyzer(); - var codeFixProvider = GetCodeFixProvider(); - - var adhocProject = CreateProject(oldSource); - - var document = adhocProject.Documents.First(); - var analyzerDiagnostics = await GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }, cancellationToken); - var compilerDiagnostics = await GetCompilerDiagnostics(document, cancellationToken); - var attempts = analyzerDiagnostics.Length; - - for (int i = 0; i < attempts; ++i) - { - var actions = new List(); - var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None); - await codeFixProvider.RegisterCodeFixesAsync(context); - - if (!actions.Any()) - { - break; - } - - if (codeFixIndex != null) - { - document = await ApplyFix(document, actions.ElementAt((int)codeFixIndex), cancellationToken); - break; - } - - document = await ApplyFix(document, actions.ElementAt(0), cancellationToken); - analyzerDiagnostics = await GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }, cancellationToken); - - var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnostics(document, cancellationToken)); - - //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 - document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync(cancellationToken).Result, Formatter.Annotation, document.Project.Solution.Workspace, cancellationToken: cancellationToken)); - newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, await GetCompilerDiagnostics(document, cancellationToken)); - - Assert.True(false, - string.Format("Fix introduced new compiler diagnostics:\r\n{0}\r\n\r\nNew document:\r\n{1}\r\n", - string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString())), - document.GetSyntaxRootAsync(cancellationToken).Result.ToFullString())); - } - - //check if there are analyzer diagnostics left after the code fix - if (!analyzerDiagnostics.Any()) - { - break; - } - } - - //after applying all of the code fixes, compare the resulting string to the inputted one - var actual = await GetStringFromDocument(document, cancellationToken); - - // Normalize line endings, just in case - actual = actual.Replace("\r\n", "\n"); - newSource = newSource.Replace("\r\n", "\n"); - Assert.AreEqual(newSource, actual); - } - - 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++]; - } - } - } - - static async Task> GetCompilerDiagnostics(Document document, CancellationToken cancellationToken) - { - var model = await document.GetSemanticModelAsync(cancellationToken); - return model.GetDiagnostics(cancellationToken: cancellationToken); - } - - static async Task GetStringFromDocument(Document document, CancellationToken cancellationToken) - { - var simplifiedDoc = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken); - var root = await simplifiedDoc.GetSyntaxRootAsync(cancellationToken); - root = Formatter.Format(root, Formatter.Annotation, simplifiedDoc.Project.Solution.Workspace, cancellationToken: cancellationToken); - return root.GetText().ToString(); - } - - static async Task ApplyFix(Document document, CodeAction codeAction, CancellationToken cancellationToken) - { - var operations = await codeAction.GetOperationsAsync(CancellationToken.None); - var solution = operations.OfType().Single().ChangedSolution; - return solution.GetDocument(document.Id); - } - } -} diff --git a/src/NServiceBus.Core.Analyzer.Tests/Helpers/CompilationExtensions.cs b/src/NServiceBus.Core.Analyzer.Tests/Helpers/CompilationExtensions.cs new file mode 100644 index 0000000000..9a526e4000 --- /dev/null +++ b/src/NServiceBus.Core.Analyzer.Tests/Helpers/CompilationExtensions.cs @@ -0,0 +1,51 @@ +namespace NServiceBus.Core.Analyzer.Tests.Helpers +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.Diagnostics; + + static class CompilationExtensions + { + public static void Compile(this Compilation compilation) + { + using (var peStream = new MemoryStream()) + { + var emitResult = compilation.Emit(peStream); + + if (!emitResult.Success) + { + throw new Exception("Compilation failed."); + } + } + } + + public static async Task> GetAnalyzerDiagnostics(this Compilation compilation, DiagnosticAnalyzer analyzer, CancellationToken cancellationToken = default) + { + var exceptions = new List(); + + var analysisOptions = new CompilationWithAnalyzersOptions( + new AnalyzerOptions(ImmutableArray.Empty), + (exception, _, __) => exceptions.Add(exception), + concurrentAnalysis: false, + logAnalyzerExecutionTime: false); + + + if (exceptions.Any()) + { + throw new AggregateException(exceptions); + } + + return (await compilation + .WithAnalyzers(ImmutableArray.Create(analyzer), analysisOptions) + .GetAnalyzerDiagnosticsAsync(cancellationToken)) + .OrderBy(diagnostic => diagnostic.Location.SourceSpan) + .ThenBy(diagnostic => diagnostic.Id); + } + } +} diff --git a/src/NServiceBus.Core.Analyzer.Tests/Helpers/DiagnosticResult.cs b/src/NServiceBus.Core.Analyzer.Tests/Helpers/DiagnosticResult.cs deleted file mode 100644 index df1159f241..0000000000 --- a/src/NServiceBus.Core.Analyzer.Tests/Helpers/DiagnosticResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace NServiceBus.Core.Analyzer.Tests.Helpers -{ - using Microsoft.CodeAnalysis; - - public class DiagnosticResult - { - public DiagnosticResultLocation[] Locations { get; set; } - - public DiagnosticSeverity Severity { get; set; } - - public string Id { get; set; } - - public string Message { get; set; } - } -} diff --git a/src/NServiceBus.Core.Analyzer.Tests/Helpers/DiagnosticResultLocation.cs b/src/NServiceBus.Core.Analyzer.Tests/Helpers/DiagnosticResultLocation.cs deleted file mode 100644 index 5da6662a36..0000000000 --- a/src/NServiceBus.Core.Analyzer.Tests/Helpers/DiagnosticResultLocation.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace NServiceBus.Core.Analyzer.Tests.Helpers -{ - using System; - - public readonly struct DiagnosticResultLocation - { - public DiagnosticResultLocation(string path, int line, int character) - { - if (line < -1) - { - throw new ArgumentOutOfRangeException(nameof(line), "line must be >= -1"); - } - - if (character < -1) - { - throw new ArgumentOutOfRangeException(nameof(character), "character must be >= -1"); - } - - Path = path; - Line = line; - Character = character; - } - - public string Path { get; } - - public int Line { get; } - - public int Character { get; } - } -} diff --git a/src/NServiceBus.Core.Analyzer.Tests/Helpers/DiagnosticVerifier.cs b/src/NServiceBus.Core.Analyzer.Tests/Helpers/DiagnosticVerifier.cs deleted file mode 100644 index d24001d23b..0000000000 --- a/src/NServiceBus.Core.Analyzer.Tests/Helpers/DiagnosticVerifier.cs +++ /dev/null @@ -1,259 +0,0 @@ -namespace NServiceBus.Core.Analyzer.Tests.Helpers -{ - 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.CSharp; - using Microsoft.CodeAnalysis.Diagnostics; - using Microsoft.CodeAnalysis.Text; - using NUnit.Framework; - using UniformSession; - - public abstract class DiagnosticVerifier - { - static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); - static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location); - static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location); - static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location); - static readonly MetadataReference NServiceBusReference = MetadataReference.CreateFromFile(typeof(EndpointConfiguration).Assembly.Location); - static readonly MetadataReference TestLib = MetadataReference.CreateFromFile(typeof(IUniformSession).Assembly.Location); - - protected abstract DiagnosticAnalyzer GetAnalyzer(); - - protected Task NoDiagnostic(string source, CancellationToken cancellationToken = default) => Verify(new[] { source }, Array.Empty(), cancellationToken); - - protected Task Verify(string source, DiagnosticResult expectedResult, CancellationToken cancellationToken = default) => Verify(new[] { source }, new[] { expectedResult }, cancellationToken); - - protected Task Verify(string source, DiagnosticResult[] expectedResults, CancellationToken cancellationToken = default) => Verify(new[] { source }, expectedResults, cancellationToken); - - async Task Verify(string[] sources, DiagnosticResult[] expectedResults, CancellationToken cancellationToken) - { - var analyzer = GetAnalyzer(); - - var actualResults = await GetSortedDiagnostics(sources, analyzer, cancellationToken); - - var expectedCount = expectedResults.Length; - var actualCount = actualResults.Length; - - if (expectedCount != actualCount) - { - var diagnosticsOutput = actualResults.Any() ? FormatDiagnostics(analyzer, actualResults.ToArray()) : " NONE."; - - Assert.Fail($"Mismatch between number of diagnostics returned, expected \"{expectedCount}\" actual \"{actualCount}\"\r\n\r\nDiagnostics:\r\n{diagnosticsOutput}\r\n"); - } - - for (var expectedResultIndex = 0; expectedResultIndex < expectedResults.Length; expectedResultIndex++) - { - var actual = actualResults.ElementAt(expectedResultIndex); - var expected = expectedResults[expectedResultIndex]; - - if (!expected.Locations.Any() && actual.Location != Location.None) - { - Assert.Fail($"Expected:\nA project diagnostic with no location\nActual:\n{FormatDiagnostics(analyzer, actual)}"); - } - else - { - VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First()); - - var additionalLocations = actual.AdditionalLocations.ToArray(); - - if (additionalLocations.Length != expected.Locations.Length - 1) - { - Assert.Fail($"Expected {expected.Locations.Length - 1} additional locations but got {additionalLocations.Length} for Diagnostic:\r\n {FormatDiagnostics(analyzer, actual)}\r\n"); - } - - for (var additionalLocationIndex = 0; additionalLocationIndex < additionalLocations.Length; ++additionalLocationIndex) - { - VerifyDiagnosticLocation(analyzer, actual, additionalLocations[additionalLocationIndex], expected.Locations[additionalLocationIndex + 1]); - } - } - - if (actual.Id != expected.Id) - { - Assert.Fail($"Expected diagnostic id to be \"{expected.Id}\" was \"{actual.Id}\"\r\n\r\nDiagnostic:\r\n {FormatDiagnostics(analyzer, actual)}\r\n"); - } - - if (actual.Severity != expected.Severity) - { - Assert.Fail($"Expected diagnostic severity to be \"{expected.Severity}\" was \"{actual.Severity}\"\r\n\r\nDiagnostic:\r\n {FormatDiagnostics(analyzer, actual)}\r\n"); - } - } - } - - static void VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, Location actual, DiagnosticResultLocation expected) - { - var actualSpan = actual.GetLineSpan(); - - Assert.IsTrue( - actualSpan.Path == expected.Path || (actualSpan.Path != null && actualSpan.Path.Contains("Test0.") && expected.Path.Contains("Test.")), - $"Expected diagnostic to be in file \"{expected.Path}\" was actually in file \"{actualSpan.Path}\"\r\n\r\nDiagnostic:\r\n {FormatDiagnostics(analyzer, diagnostic)}\r\n"); - - var actualPosition = actualSpan.StartLinePosition; - - // Only check line position if there is an actual line in the real diagnostic - if (actualPosition.Line > 0 && actualPosition.Line + 1 != expected.Line) - { - Assert.Fail($"Expected diagnostic to be on line \"{expected.Line}\" was actually on line \"{actualPosition.Line + 1}\"\r\n\r\nDiagnostic:\r\n {FormatDiagnostics(analyzer, diagnostic)}\r\n"); - } - - // Only check character position if there is an actual character position in the real diagnostic - if (actualPosition.Character > 0 && actualPosition.Character + 1 != expected.Character) - { - Assert.Fail($"Expected diagnostic to start at character \"{expected.Character}\" was actually at character \"{actualPosition.Character + 1}\"\r\n\r\nDiagnostic:\r\n {FormatDiagnostics(analyzer, diagnostic)}\r\n"); - } - } - - static Task GetSortedDiagnostics(string[] sources, DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) - { - Project newProject = CreateProject(sources); - - var documents = newProject.Documents.ToArray(); - - if (sources.Length != documents.Length) - { - throw new InvalidOperationException("The number of documents created does not match the number of sources."); - } - - return GetSortedDiagnosticsFromDocuments(analyzer, documents, cancellationToken); - } - - protected static async Task GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, Document[] documents, CancellationToken cancellationToken = default) - { - var diagnostics = new List(); - foreach (var project in new HashSet(documents.Select(document => document.Project))) - { - var compilation = await project.GetCompilationAsync(cancellationToken); - var compilationWithAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create(analyzer), cancellationToken: cancellationToken); - - using (var stream = new System.IO.MemoryStream()) - { - var emitResult = compilation.Emit(stream, cancellationToken: cancellationToken); - if (!emitResult.Success) - { - foreach (var diagnostic in emitResult.Diagnostics) - { - Console.WriteLine(diagnostic.Location.GetMappedLineSpan() + " " + diagnostic.GetMessage()); - } - throw new Exception("Test code did not compile."); - } - } - - foreach (var diagnostic in await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync(cancellationToken)) - { - if (diagnostic.Location == Location.None || diagnostic.Location.IsInMetadata) - { - diagnostics.Add(diagnostic); - } - else - { - foreach (var document in documents) - { - if (await document.GetSyntaxTreeAsync(cancellationToken) == diagnostic.Location.SourceTree) - { - diagnostics.Add(diagnostic); - } - } - } - } - } - - return diagnostics.OrderBy(diagnostic => diagnostic.Location.SourceSpan.Start).ToArray(); - } - - protected static Project CreateProject(params string[] sources) - { - var projectId = ProjectId.CreateNewId("TestProject"); - - var projectInfo = ProjectInfo.Create(projectId, VersionStamp.Create(), "TestProject", "TestProject", LanguageNames.CSharp) - .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - - // The max C# version is controlled by the Roslyn package in use - that's what's used to parse the code in tests - var parseOptions = new CSharpParseOptions(languageVersion: LanguageVersion.CSharp6); - - var solution = new AdhocWorkspace() - .CurrentSolution - .AddProject(projectInfo) - .AddMetadataReference(projectId, CorlibReference) - .AddMetadataReference(projectId, SystemCoreReference) - .AddMetadataReference(projectId, CSharpSymbolsReference) - .AddMetadataReference(projectId, CodeAnalysisReference) - .AddMetadataReference(projectId, TestLib) - .AddMetadataReference(projectId, NServiceBusReference) - .WithProjectParseOptions(projectId, parseOptions); - -#if NETCOREAPP - var netstandard = MetadataReference.CreateFromFile(System.Reflection.Assembly.Load("netstandard").Location); - var systemTasks = MetadataReference.CreateFromFile(System.Reflection.Assembly.Load("System.Threading.Tasks").Location); - var systemRuntime = MetadataReference.CreateFromFile(System.Reflection.Assembly.Load("System.Runtime").Location); - solution = solution.AddMetadataReferences(projectId, new[] - { - netstandard, - systemRuntime, - systemTasks - }); -#endif - var documentIndex = 0; - foreach (var source in sources) - { - var fileName = "Test" + documentIndex + ".cs"; - solution = solution.AddDocument(DocumentId.CreateNewId(projectId, fileName), fileName, SourceText.From(source)); - documentIndex++; - } - - var project = solution.GetProject(projectId); - return project; - } - - static string FormatDiagnostics(DiagnosticAnalyzer analyzer, params Diagnostic[] diagnostics) - { - var builder = new StringBuilder(); - for (var diagnosticIndex = 0; diagnosticIndex < diagnostics.Length; ++diagnosticIndex) - { - builder.AppendLine("// " + diagnostics[diagnosticIndex]); - - var analyzerType = analyzer.GetType(); - var descriptors = analyzer.SupportedDiagnostics; - - foreach (var descriptor in descriptors - .Where(descriptor => descriptor?.Id == diagnostics[diagnosticIndex].Id)) - { - var location = diagnostics[diagnosticIndex].Location; - if (location == Location.None) - { - builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, descriptor.Id); - } - else - { - Assert.IsTrue( - location.IsInSource, - $"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostics[diagnosticIndex]}\r\n"); - - var position = diagnostics[diagnosticIndex].Location.GetLineSpan().StartLinePosition; - - builder.AppendFormat("{0}({1}, {2}, {3}.{4})", - "GetResultAt", - position.Line + 1, - position.Character + 1, - analyzerType.Name, - descriptor.Id); - } - - if (diagnosticIndex != diagnostics.Length - 1) - { - builder.Append(','); - } - - builder.AppendLine(); - break; - } - } - - return builder.ToString(); - } - } -} diff --git a/src/NServiceBus.Core.Analyzer.Tests/Helpers/DocumentExtensions.cs b/src/NServiceBus.Core.Analyzer.Tests/Helpers/DocumentExtensions.cs new file mode 100644 index 0000000000..7c339441cb --- /dev/null +++ b/src/NServiceBus.Core.Analyzer.Tests/Helpers/DocumentExtensions.cs @@ -0,0 +1,45 @@ +namespace NServiceBus.Core.Analyzer.Tests.Helpers +{ + 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.Formatting; + using Microsoft.CodeAnalysis.Simplification; + + static class DocumentExtensions + { + public static async Task> GetCompilerDiagnostics(this Document document, CancellationToken cancellationToken = default) => + (await document.GetSemanticModelAsync(cancellationToken)) + .GetDiagnostics(cancellationToken: cancellationToken) + .Where(diagnostic => diagnostic.Severity != DiagnosticSeverity.Hidden) + .OrderBy(diagnostic => diagnostic.Location.SourceSpan) + .ThenBy(diagnostic => diagnostic.Id); + + public static async Task> GetCodeActions(this Document document, CodeFixProvider codeFix, Diagnostic diagnostic, CancellationToken cancellationToken = default) + { + var actions = new List(); + var context = new CodeFixContext(document, diagnostic, (action, _) => actions.Add(action), cancellationToken); + await codeFix.RegisterCodeFixesAsync(context); + return actions; + } + + public static async Task ApplyChanges(this Document document, CodeAction codeAction, CancellationToken cancellationToken = default) + { + var operations = await codeAction.GetOperationsAsync(cancellationToken); + var solution = operations.OfType().Single().ChangedSolution; + return solution.GetDocument(document.Id); + } + + public static async Task GetCode(this Document document, CancellationToken cancellationToken = default) + { + var simplifiedDoc = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken); + var root = await simplifiedDoc.GetSyntaxRootAsync(cancellationToken); + root = Formatter.Format(root, Formatter.Annotation, simplifiedDoc.Project.Solution.Workspace, cancellationToken: cancellationToken); + return root.GetText().ToString(); + } + } +}