mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Add support for collection types
This commit is contained in:
@@ -54,6 +54,7 @@ public sealed class ValidateMemberAnalyzerTest
|
||||
{
|
||||
public static void DirtyField<T>(T comp, [ValidateMember]string fieldName) { }
|
||||
public static void DirtyTwoFields<T>(T comp, [ValidateMember]string first, [ValidateMember]string second) { }
|
||||
public static void DirtyFields<T>(T comp, [ValidateMember]ReadOnlySpan<string> fieldNames) { }
|
||||
}
|
||||
|
||||
public sealed class TestCaller
|
||||
@@ -76,21 +77,37 @@ public sealed class ValidateMemberAnalyzerTest
|
||||
TestManager.DirtyTwoFields(testComp, nameof(TestComponent.IntField), nameof(OtherComponent.FloatField));
|
||||
|
||||
TestManager.DirtyTwoFields(testComp, nameof(OtherComponent.FloatField), nameof(OtherComponent.DoubleField));
|
||||
|
||||
TestManager.DirtyFields(testComp, [nameof(TestComponent.IntField), nameof(TestComponent.BoolField)]);
|
||||
|
||||
TestManager.DirtyFields(testComp, [nameof(TestComponent.IntField), nameof(OtherComponent.FloatField)]);
|
||||
|
||||
TestManager.DirtyField(testComp, "foobar");
|
||||
|
||||
TestManager.DirtyFields(testComp, ["foobar", "bizbaz"]);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(31,42): error RA0033: FloatField is not a member of TestComponent
|
||||
VerifyCS.Diagnostic().WithSpan(31, 42, 31, 75).WithArguments("FloatField", "TestComponent"),
|
||||
// /0/Test0.cs(33,43): error RA0033: IntField is not a member of OtherComponent
|
||||
VerifyCS.Diagnostic().WithSpan(33, 43, 33, 73).WithArguments("IntField", "OtherComponent"),
|
||||
// /0/Test0.cs(39,78): error RA0033: FloatField is not a member of TestComponent
|
||||
VerifyCS.Diagnostic().WithSpan(39, 78, 39, 111).WithArguments("FloatField", "TestComponent"),
|
||||
// /0/Test0.cs(41,46): error RA0033: FloatField is not a member of TestComponent
|
||||
VerifyCS.Diagnostic().WithSpan(41, 46, 41, 79).WithArguments("FloatField", "TestComponent"),
|
||||
// /0/Test0.cs(41,81): error RA0033: DoubleField is not a member of TestComponent
|
||||
VerifyCS.Diagnostic().WithSpan(41, 81, 41, 115).WithArguments("DoubleField", "TestComponent")
|
||||
// /0/Test0.cs(32,42): error RA0033: FloatField is not a member of TestComponent
|
||||
VerifyCS.Diagnostic().WithSpan(32, 42, 32, 75).WithArguments("FloatField", "TestComponent"),
|
||||
// /0/Test0.cs(34,43): error RA0033: IntField is not a member of OtherComponent
|
||||
VerifyCS.Diagnostic().WithSpan(34, 43, 34, 73).WithArguments("IntField", "OtherComponent"),
|
||||
// /0/Test0.cs(40,78): error RA0033: FloatField is not a member of TestComponent
|
||||
VerifyCS.Diagnostic().WithSpan(40, 78, 40, 111).WithArguments("FloatField", "TestComponent"),
|
||||
// /0/Test0.cs(42,46): error RA0033: FloatField is not a member of TestComponent
|
||||
VerifyCS.Diagnostic().WithSpan(42, 46, 42, 79).WithArguments("FloatField", "TestComponent"),
|
||||
// /0/Test0.cs(42,81): error RA0033: DoubleField is not a member of TestComponent
|
||||
VerifyCS.Diagnostic().WithSpan(42, 81, 42, 115).WithArguments("DoubleField", "TestComponent"),
|
||||
// /0/Test0.cs(46,43): error RA0033: FloatField is not a member of TestComponent
|
||||
VerifyCS.Diagnostic().WithSpan(46, 43, 46, 110).WithArguments("FloatField", "TestComponent"),
|
||||
// /0/Test0.cs(48,42): error RA0033: foobar is not a member of TestComponent
|
||||
VerifyCS.Diagnostic().WithSpan(48, 42, 48, 50).WithArguments("foobar", "TestComponent"),
|
||||
// /0/Test0.cs(50,43): error RA0033: bizbaz is not a member of TestComponent
|
||||
VerifyCS.Diagnostic().WithSpan(50, 43, 50, 63).WithArguments("bizbaz", "TestComponent"),
|
||||
// /0/Test0.cs(50,43): error RA0033: foobar is not a member of TestComponent
|
||||
VerifyCS.Diagnostic().WithSpan(50, 43, 50, 63).WithArguments("foobar", "TestComponent")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,26 +68,37 @@ public sealed class ValidateMemberAnalyzer : DiagnosticAnalyzer
|
||||
if (!HasAttribute(parameterSymbol, validateMemberAttribute))
|
||||
continue;
|
||||
|
||||
// Get a set containing all the members of the target type and its ancestors
|
||||
members ??= targetType.GetBaseTypesAndThis().SelectMany(n => n.GetMembers()).ToImmutableHashSet(SymbolEqualityComparer.Default);
|
||||
|
||||
if (parameterContext.Expression is CollectionExpressionSyntax collectionExpression)
|
||||
{
|
||||
foreach (var element in collectionExpression.Elements)
|
||||
{
|
||||
if (element is not ExpressionElementSyntax expressionElement)
|
||||
continue;
|
||||
if (context.SemanticModel.GetConstantValue(expressionElement.Expression).Value is not string field)
|
||||
continue;
|
||||
if (!CheckField(field, members))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
ValidateMemberDescriptor,
|
||||
parameterContext.GetLocation(),
|
||||
field,
|
||||
targetType.Name
|
||||
));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the value passed for this parameter.
|
||||
// We use GetConstantValue to resolve compile-time values - i.e. the result of nameof()
|
||||
if (context.SemanticModel.GetConstantValue(parameterContext.Expression).Value is not string fieldName)
|
||||
continue;
|
||||
|
||||
// Get a set containing all the members of the target type and its ancestors
|
||||
members ??= targetType.GetBaseTypesAndThis().SelectMany(n => n.GetMembers()).ToImmutableHashSet(SymbolEqualityComparer.Default);
|
||||
|
||||
// Check each member of the target type to see if it matches our passed in value
|
||||
var found = false;
|
||||
foreach (var member in members)
|
||||
{
|
||||
if (member.Name == fieldName)
|
||||
{
|
||||
found = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// If we didn't find it, report the violation
|
||||
if (!found)
|
||||
if (!CheckField(fieldName, members))
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
ValidateMemberDescriptor,
|
||||
parameterContext.GetLocation(),
|
||||
@@ -97,6 +108,19 @@ public sealed class ValidateMemberAnalyzer : DiagnosticAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckField(string fieldName, ImmutableHashSet<ISymbol> members)
|
||||
{
|
||||
// Check each member of the target type to see if it matches our passed in value
|
||||
foreach (var member in members)
|
||||
{
|
||||
if (member.Name == fieldName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool HasAttribute(ISymbol type, INamedTypeSymbol attributeType)
|
||||
{
|
||||
foreach (var attribute in type.GetAttributes())
|
||||
|
||||
Reference in New Issue
Block a user