diff --git a/Robust.Analyzers.Tests/ByRefEventAnalyzerTest.cs b/Robust.Analyzers.Tests/ByRefEventAnalyzerTest.cs new file mode 100644 index 00000000000..cb4d7b45de2 --- /dev/null +++ b/Robust.Analyzers.Tests/ByRefEventAnalyzerTest.cs @@ -0,0 +1,114 @@ +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; +using NUnit.Framework; +using VerifyCS = + Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier; + +namespace Robust.Analyzers.Tests; + +[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)] +[TestFixture, TestOf(typeof(ByRefEventAnalyzer))] +public sealed class ByRefEventAnalyzerTest +{ + private const string EventBusDef = """ + namespace Robust.Shared.GameObjects; + + public readonly struct EntityUid; + + public sealed class EntitySystem + { + public void RaiseLocalEvent(EntityUid uid, ref TEvent args, bool broadcast = false) + where TEvent : notnull { } + + public void RaiseLocalEvent(EntityUid uid, TEvent args, bool broadcast = false) + where TEvent : notnull { } + } + + public sealed class EntityEventBus + { + public void RaiseLocalEvent(EntityUid uid, ref TEvent args, bool broadcast = false) + where TEvent : notnull { } + + public void RaiseLocalEvent(EntityUid uid, TEvent args, bool broadcast = false) + where TEvent : notnull { } + } + """; + + private static Task Verifier(string code, params DiagnosticResult[] expected) + { + var test = new CSharpAnalyzerTest() + { + TestState = + { + Sources = { code } + }, + }; + + TestHelper.AddEmbeddedSources( + test.TestState, + "Robust.Shared.GameObjects.EventBusAttributes.cs" + ); + + test.TestState.Sources.Add(("EntityEventBus.cs", EventBusDef)); + + // ExpectedDiagnostics cannot be set, so we need to AddRange here... + test.TestState.ExpectedDiagnostics.AddRange(expected); + + return test.RunAsync(); + } + + [Test] + public async Task TestSuccess() + { + const string code = """ + using Robust.Shared.GameObjects; + + [ByRefEvent] + public readonly struct RefEvent; + public readonly struct ValueEvent; + + public static class Foo + { + public static void Bar(EntityEventBus bus) + { + bus.RaiseLocalEvent(default(EntityUid), new ValueEvent()); + var refEv = new RefEvent(); + bus.RaiseLocalEvent(default(EntityUid), ref refEv); + } + } + """; + + await Verifier(code); + } + + [Test] + public async Task TestWrong() + { + const string code = """ + using Robust.Shared.GameObjects; + + [ByRefEvent] + public readonly struct RefEvent; + public readonly struct ValueEvent; + + public static class Foo + { + public static void Bar(EntityEventBus bus) + { + bus.RaiseLocalEvent(default(EntityUid), new RefEvent()); + var valueEv = new ValueEvent(); + bus.RaiseLocalEvent(default(EntityUid), ref valueEv); + } + } + """; + + await Verifier( + code, + // /0/Test0.cs(11,49): error RA0015: Tried to raise a by-ref event 'RefEvent' by value + VerifyCS.Diagnostic(ByRefEventAnalyzer.ByRefEventRaisedByValueRule).WithSpan(11, 49, 11, 63).WithArguments("RefEvent"), + // /0/Test0.cs(13,49): error RA0016: Tried to raise a value event 'ValueEvent' by-ref + VerifyCS.Diagnostic(ByRefEventAnalyzer.ByValueEventRaisedByRefRule).WithSpan(13, 49, 13, 60).WithArguments("ValueEvent") + ); + } +} diff --git a/Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj b/Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj index 7cfb0d6817e..c9d10900f75 100644 --- a/Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj +++ b/Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/Robust.Analyzers/ByRefEventAnalyzer.cs b/Robust.Analyzers/ByRefEventAnalyzer.cs index 62236bc12fa..d5c2cad796a 100644 --- a/Robust.Analyzers/ByRefEventAnalyzer.cs +++ b/Robust.Analyzers/ByRefEventAnalyzer.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; @@ -24,7 +24,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer "Make sure that methods subscribing to a ref event have the ref keyword for the event argument." ); - private static readonly DiagnosticDescriptor ByRefEventRaisedByValueRule = new( + public static readonly DiagnosticDescriptor ByRefEventRaisedByValueRule = new( Diagnostics.IdByRefEventRaisedByValue, "By-ref event raised by value", "Tried to raise a by-ref event '{0}' by value", @@ -34,7 +34,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer "Make sure to use the ref keyword when raising ref events." ); - private static readonly DiagnosticDescriptor ByValueEventRaisedByRefRule = new( + public static readonly DiagnosticDescriptor ByValueEventRaisedByRefRule = new( Diagnostics.IdValueEventRaisedByRef, "Value event raised by-ref", "Tried to raise a value event '{0}' by-ref",