Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
43375ce
Attribute, analyzer, and tests
Tayrtahn Jun 15, 2025
bebf41d
Add attribute to EntitySystem methods
Tayrtahn Jun 15, 2025
6be4407
Code fixer for redundant method name
Tayrtahn Jun 16, 2025
3de093b
Code fixer for proxy substitution
Tayrtahn Jun 16, 2025
15292b3
Comments
Tayrtahn Jun 16, 2025
4d1a559
Improve error message when target method is not found
Tayrtahn Jun 16, 2025
1939d11
Modify AddComp signature to match AddComponent (Component -> IComponent)
Tayrtahn Jun 16, 2025
a65ea71
Revert "Modify AddComp signature to match AddComponent (Component -> …
Tayrtahn Jun 16, 2025
c747db5
Check that proxy and target have matching type constraints
Tayrtahn Jun 16, 2025
5e0af23
Reapply "Modify AddComp signature to match AddComponent (Component ->…
Tayrtahn Jun 16, 2025
d61d53c
Boost efficiency by only searching for proxy methods once.
Tayrtahn Jun 17, 2025
0b81ee1
Revert unrelated change
Tayrtahn Jun 17, 2025
ef3e987
Fine, I'll do it properly
Tayrtahn Jun 17, 2025
09b0fea
Merge branch 'master' into analyzer/proxyfor
Tayrtahn Jun 20, 2025
d63b7e1
Misleading method name
Tayrtahn Jun 20, 2025
5208e79
Preserve trivia when when replacing method
Tayrtahn Jun 20, 2025
de31f76
Prevent flagging method calls on non-member variables
Tayrtahn Jun 20, 2025
b205219
Prevent flagging method calls on other classes
Tayrtahn Jun 20, 2025
cb0c7a8
Merge branch 'analyzer/proxyfor' of github.com:Tayrtahn/RobustToolbox…
Tayrtahn Jun 20, 2025
fac3d6e
Merge branch 'master' into analyzer/proxyfor
Tayrtahn Jun 26, 2025
c930c3f
Switch to new HasAttribute helper
Tayrtahn Jun 27, 2025
2ea7099
comment fix
Tayrtahn Jun 27, 2025
dafa55b
Switch ProxyMethod from struct to class
Tayrtahn Jun 27, 2025
de34e64
Kill TryGetAttributeSyntax
Tayrtahn Jun 27, 2025
7324cfb
Move member owner check to helper
Tayrtahn Jun 27, 2025
7856909
Use helper for proxy method search
Tayrtahn Jun 27, 2025
b6e1fd1
meh
Tayrtahn Jul 20, 2025
2db4dce
Merge branch 'master' into analyzer/proxyfor
Tayrtahn Jan 30, 2026
e31ac02
A little bit of documentation never hurt anybody
Tayrtahn Jan 30, 2026
9adef07
Test attributes
Tayrtahn Jan 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
316 changes: 316 additions & 0 deletions Robust.Analyzers.Tests/ProxyForAnalyzerTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
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()")
);
}
}
Loading
Loading