Files
RobustToolbox/Robust.Analyzers.Tests/ProxyForAnalyzerTest.cs
T
TayrtahnandGitHub fcc2c01d01 Revert "Revert "Add analyzer & fixer to detect when proxy methods are not used (ProxyForAttribute)"" (#6439)
Revert "Revert "Add analyzer & fixer to detect when proxy methods are not use…"

This reverts commit 1af32c3129.
2026-03-07 11:32:01 -05:00

317 lines
9.2 KiB
C#

using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.ProxyForAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
[TestOf(typeof(ProxyForAnalyzer))]
public sealed class ProxyForAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<ProxyForAnalyzer, DefaultVerifier>()
{
TestState =
{
Sources = { code },
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.Analyzers.ProxyForAttribute.cs"
);
test.TestState.Sources.Add(("TestTypeDefs.cs", TestTypeDefs));
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
private const string TestTypeDefs = """
using Robust.Shared.Analyzers;
public sealed class TargetClass
{
public void DoSomething() { }
public bool TryDoSomething<T>(int foo, out T? bar)
{
bar = default;
return true;
}
}
public abstract partial class ProxyClass
{
protected TargetClass TargetClass = new();
}
""";
[Test]
public async Task TestAutoName()
{
const string code = """
using Robust.Shared.Analyzers;
public abstract partial class ProxyClass
{
[ProxyFor(typeof(TargetClass))]
public void DoSomething()
{
TargetClass.DoSomething();
}
}
public sealed class Tester : ProxyClass
{
public void Good()
{
DoSomething();
}
public void Bad()
{
TargetClass.DoSomething();
}
}
""";
await Verifier(code,
// /0/Test0.cs(18,9): warning RA0037: Use the proxy method DoSomething instead of calling TargetClass.DoSomething directly
VerifyCS.Diagnostic(ProxyForAnalyzer.PreferProxyDescriptor).WithSpan(21, 9, 21, 34).WithArguments("DoSomething", "TargetClass.DoSomething")
);
}
[Test]
public async Task TestSetName()
{
const string code = """
using Robust.Shared.Analyzers;
public abstract partial class ProxyClass
{
[ProxyFor(typeof(TargetClass), nameof(TargetClass.DoSomething))]
public void DoIt()
{
TargetClass.DoSomething();
}
}
public sealed class Tester : ProxyClass
{
public void Good()
{
DoIt();
}
public void Bad()
{
TargetClass.DoSomething();
}
}
""";
await Verifier(code,
// /0/Test0.cs(21,9): warning RA0037: Use the proxy method DoIt instead of calling TargetClass.DoSomething directly
VerifyCS.Diagnostic(ProxyForAnalyzer.PreferProxyDescriptor).WithSpan(21, 9, 21, 34).WithArguments("DoIt", "TargetClass.DoSomething")
);
}
[Test]
public async Task TestGeneric()
{
const string code = """
using Robust.Shared.Analyzers;
public abstract partial class ProxyClass
{
[ProxyFor(typeof(TargetClass))]
public bool TryDoSomething<T>(int foo, out T? bar)
{
return TargetClass.TryDoSomething(foo, out bar);
}
}
public sealed class Tester : ProxyClass
{
public void Good()
{
TryDoSomething<string>(5, out var bar);
}
public void Bad()
{
TargetClass.TryDoSomething<string>(5, out var bar);
}
}
""";
await Verifier(code,
// /0/Test0.cs(21,9): warning RA0037: Use the proxy method TryDoSomething instead of calling TargetClass.TryDoSomething directly
VerifyCS.Diagnostic(ProxyForAnalyzer.PreferProxyDescriptor).WithSpan(21, 9, 21, 59).WithArguments("TryDoSomething", "TargetClass.TryDoSomething")
);
}
[Test]
public async Task TestRedundantMethodName()
{
const string code = """
using Robust.Shared.Analyzers;
public abstract partial class ProxyClass
{
[ProxyFor(typeof(TargetClass), nameof(TargetClass.DoSomething))]
public void DoSomething()
{
TargetClass.DoSomething();
}
}
""";
await Verifier(code,
// /0/Test0.cs(5,36): warning RA0038: Set method name matches the proxy method name and can be omitted
VerifyCS.Diagnostic(ProxyForAnalyzer.RedundantMethodNameDescriptor).WithSpan(5, 36, 5, 67)
);
}
[Test]
public async Task TestNoMatchingSetMethodName()
{
const string code = """
using Robust.Shared.Analyzers;
public abstract partial class ProxyClass
{
[ProxyFor(typeof(TargetClass), "SomeOtherName")]
public void DoSomething()
{
TargetClass.DoSomething();
}
}
""";
await Verifier(code,
// /0/Test0.cs(5,15): error RA0039: Unable to find target method TargetClass.SomeOtherName()
VerifyCS.Diagnostic(ProxyForAnalyzer.TargetMethodNotFoundDescriptor).WithSpan(5, 15, 5, 34).WithArguments("TargetClass.SomeOtherName()")
);
}
[Test]
public async Task TestNoMatchingSignature()
{
const string code = """
using Robust.Shared.Analyzers;
public abstract partial class ProxyClass
{
[ProxyFor(typeof(TargetClass))]
public void DoSomething(int foo)
{
TargetClass.DoSomething();
}
}
""";
await Verifier(code,
// /0/Test0.cs(5,15): error RA0039: Unable to find target method TargetClass.DoSomething(int foo)
VerifyCS.Diagnostic(ProxyForAnalyzer.TargetMethodNotFoundDescriptor).WithSpan(5, 15, 5, 34).WithArguments("TargetClass.DoSomething(int foo)")
);
}
[Test]
public async Task TestIgnoreDelegate()
{
const string code = """
using Robust.Shared.Analyzers;
public abstract partial class ProxyClass
{
[ProxyFor(typeof(TargetClass))]
public void DoSomething()
{
TargetClass.DoSomething();
}
public delegate void ThingDoer(TargetClass TargetClass);
public void RunDelegate(ThingDoer doer) { }
}
public sealed class Tester : ProxyClass
{
public void Test()
{
RunDelegate(target =>
{
target.DoSomething();
});
}
}
""";
await Verifier(code, []);
}
[Test]
public async Task TestIgnoreOtherClassMember()
{
const string code = """
using Robust.Shared.Analyzers;
public abstract partial class ProxyClass
{
[ProxyFor(typeof(TargetClass))]
public void DoSomething()
{
TargetClass.DoSomething();
}
}
public sealed class OtherClass
{
public TargetClass Target;
}
public sealed class Tester : ProxyClass
{
public void Test()
{
var other = new OtherClass();
other.Target.DoSomething();
}
}
""";
await Verifier(code, []);
}
[Test]
public async Task TestNoMatchingAutoMethodName()
{
const string code = """
using Robust.Shared.Analyzers;
public abstract partial class ProxyClass
{
[ProxyFor(typeof(TargetClass))]
public void SomeOtherName()
{
TargetClass.DoSomething();
}
}
""";
await Verifier(code,
// /0/Test0.cs(5,15): error RA0039: Unable to find target method TargetClass.SomeOtherName()
VerifyCS.Diagnostic(ProxyForAnalyzer.TargetMethodNotFoundDescriptor).WithSpan(5, 15, 5, 34).WithArguments("TargetClass.SomeOtherName()")
);
}
}