Add support for collection types

This commit is contained in:
Tayrtahn
2025-02-28 17:09:54 -05:00
parent 40a6d4a89a
commit 2b8f5534bd
2 changed files with 65 additions and 24 deletions

View File

@@ -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")
);
}
}

View File

@@ -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())