using System.Threading.Tasks; 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(PrototypeAnalyzer))] public sealed class PrototypeAnalyzerTest { private static Task Verifier(string code, params DiagnosticResult[] expected) { var test = new RTAnalyzerTest() { TestState = { Sources = { code } }, }; TestHelper.AddEmbeddedSources( test.TestState, "Robust.Shared.Prototypes.Attributes.cs", "Robust.Shared.Prototypes.IPrototype.cs", "Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute.cs" ); // ExpectedDiagnostics cannot be set, so we need to AddRange here... test.TestState.ExpectedDiagnostics.AddRange(expected); return test.RunAsync(); } [Test] public async Task RedundantTypeTest() { const string code = """ using Robust.Shared.Prototypes; [Prototype] public sealed partial class GoodAutoPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; } [Prototype("someOtherName")] public sealed partial class GoodUnmatchedPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; } [Prototype("badMatched")] public sealed partial class BadMatchedPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; } [Prototype(ProtoName)] public sealed partial class GoodNonLiteralMatchedPrototype : IPrototype { public const string ProtoName = "goodNonLiteralMatched"; [IdDataField] public string ID { get; private set; } = default!; } [Prototype(ProtoName)] public sealed partial class GoodNonLiteralUnmatchedPrototype : IPrototype { public const string ProtoName = "someOtherNameEntirely"; [IdDataField] public string ID { get; private set; } = default!; } [Prototype("goodDoesNotEndWithPrototypeWord")] public sealed partial class GoodDoesNotEndWithPrototypeWord : IPrototype { [IdDataField] public string ID { get; private set; } = default!; } """; await Verifier(code, // /0/Test0.cs(9,2): warning RA0033: Prototype BadMatchedPrototype has explicitly set type "badMatched" that matches autogenerated value VerifyCS.Diagnostic(PrototypeAnalyzer.PrototypeRedundantTypeRule).WithSpan(17, 12, 17, 24).WithArguments("BadMatchedPrototype", "badMatched") ); } [Test] public async Task AliasTest() { const string code = """ using Robust.Shared.Prototypes; using PPrototypeAttribute = Robust.Shared.Prototypes.PrototypeAttribute; [PPrototype("badMatched")] public sealed partial class BadMatchedPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; } """; await Verifier(code, // /0/Test0.cs(4,2): warning RA0042: Prototype BadMatchedPrototype has explicitly set type "badMatched" that matches autogenerated value VerifyCS.Diagnostic(PrototypeAnalyzer.PrototypeRedundantTypeRule).WithSpan(4, 13, 4, 25).WithArguments("BadMatchedPrototype", "badMatched") ); } [Test] public async Task MoreAttributesTest() { const string code = """ using System; using Robust.Shared.Prototypes; using PPrototypeAttribute = Robust.Shared.Prototypes.PrototypeAttribute; [FooBarAttribute] [PPrototype("badMatched")] public sealed partial class BadMatchedPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; } [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class FooBarAttribute : Attribute; """; await Verifier(code, // /0/Test0.cs(4,2): warning RA0042: Prototype BadMatchedPrototype has explicitly set type "badMatched" that matches autogenerated value VerifyCS.Diagnostic(PrototypeAnalyzer.PrototypeRedundantTypeRule).WithSpan(6, 13, 6, 25).WithArguments("BadMatchedPrototype", "badMatched") ); } [Test] public async Task NameEndsWithPrototypeTest() { const string code = """ using Robust.Shared.Prototypes; [Prototype] public sealed partial class GoodAutoPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; } [Prototype("ThisIsFine")] public sealed partial class GoodManual : IPrototype { [IdDataField] public string ID { get; private set; } = default!; } [Prototype] public sealed partial class BadAuto : IPrototype { [IdDataField] public string ID { get; private set; } = default!; } """; await Verifier(code, // /0/Test0.cs(18,29): error RA0043: Prototype BadAuto does not end with the word Prototype VerifyCS.Diagnostic(PrototypeAnalyzer.PrototypeEndsWithPrototypeRule).WithSpan(18, 29, 18, 36).WithArguments("BadAuto") ); } }