Files
RobustToolbox/Robust.Analyzers.Tests/PrototypeAnalyzerTest.cs
Tayrtahn c1737a540f Analyzer & Fixer for redundant Prototype type strings (#5718)
* Add Prototype analyzer

* Add Prototype fixer

* Early return after finding prototype attribute

* Add PrototypeEndsWithPrototypeRule diagnostic

* Oops. Uncomment parallelizable.

* Rework to ignore redundancy for non-literal string values

* Allow redundancy when removal would expose class name not ending in "Prototype"

* Promote PrototypeEndsWithPrototypeRule from warning to error, since it causes a runtime error.

* No need to get the symbol to get the class identifier

* Minor cleanup

* A little more cleanup

* More specific location for redundant name

* Refactor redundant name fixer so argument order is no longer important

* Add failing test

* Use symbol analysis to fix alias handling

* Oops! We have to go back to the previous syntax-based approach.

Now it's a hybrid.

Also fixed tests to not copy the prototype definitions.

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-12-17 18:15:32 +01:00

178 lines
6.1 KiB
C#

using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PrototypeAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
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<PrototypeAnalyzer>()
{
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")
);
}
}