From c3ec9cf25ed101e582d8803e7c4bb4b3812e53b0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 5 Jan 2025 15:51:56 +0100 Subject: [PATCH] Handle name colon in forwarded attribute arguments --- .../Models/AttributeInfo.cs | 31 +++++++++++++++---- .../Test_DependencyPropertyGenerator.cs | 9 ++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/AttributeInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/AttributeInfo.cs index 8ac269e7..346403db 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/AttributeInfo.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/AttributeInfo.cs @@ -23,7 +23,7 @@ namespace CommunityToolkit.GeneratedDependencyProperty.Models; /// The values for all named arguments for the attribute. internal sealed record AttributeInfo( string TypeName, - EquatableArray ConstructorArgumentInfo, + EquatableArray<(string? Name, TypedConstantInfo Value)> ConstructorArgumentInfo, EquatableArray<(string Name, TypedConstantInfo Value)> NamedArgumentInfo) { /// @@ -44,7 +44,7 @@ public static bool TryCreate( { string typeName = typeSymbol.GetFullyQualifiedName(); - using ImmutableArrayBuilder constructorArguments = new(); + using ImmutableArrayBuilder<(string?, TypedConstantInfo)> constructorArguments = new(); using ImmutableArrayBuilder<(string, TypedConstantInfo)> namedArguments = new(); foreach (AttributeArgumentSyntax argument in arguments) @@ -65,13 +65,18 @@ public static bool TryCreate( // Try to get the identifier name if the current expression is a named argument expression. If it // isn't, then the expression is a normal attribute constructor argument, so no extra work is needed. - if (argument.NameEquals is { Name.Identifier.ValueText: string argumentName }) + if (argument.NameEquals is { Name.Identifier.ValueText: string nameEqualsName }) { - namedArguments.Add((argumentName, argumentInfo)); + namedArguments.Add((nameEqualsName, argumentInfo)); + } + else if (argument.NameColon is { Name.Identifier.ValueText: string nameColonName }) + { + // This special case also handles named constructor parameters (i.e. '[Test(value: 42)]', not '[Test(Value = 42)]') + constructorArguments.Add((nameColonName, argumentInfo)); } else { - constructorArguments.Add(argumentInfo); + constructorArguments.Add((null, argumentInfo)); } } @@ -86,10 +91,24 @@ public static bool TryCreate( /// public override string ToString() { + // Helper to format constructor parameters + static AttributeArgumentSyntax CreateConstructorArgument(string? name, TypedConstantInfo value) + { + AttributeArgumentSyntax argument = AttributeArgument(ParseExpression(value.ToString())); + + // The name color expression is not guaranteed to be present (in fact, it's more common for it to be missing) + if (name is not null) + { + argument = argument.WithNameColon(NameColon(IdentifierName(name))); + } + + return argument; + } + // Gather the constructor arguments IEnumerable arguments = ConstructorArgumentInfo - .Select(static arg => AttributeArgument(ParseExpression(arg.ToString()))); + .Select(static arg => CreateConstructorArgument(arg.Name, arg.Value)); // Gather the named arguments IEnumerable namedArguments = diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index 6561a782..c1be5536 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -4149,10 +4149,15 @@ public partial {{propertyType}} Name [TestMethod] [DataRow("A", "global::MyNamespace.AAttribute()")] [DataRow("B(42, 10)", "global::MyNamespace.BAttribute(42, 10)")] + [DataRow("B(X: 42, Y: 10)", "global::MyNamespace.BAttribute(X: 42, Y: 10)")] + [DataRow("B(Y: 42, X: 10)", "global::MyNamespace.BAttribute(Y: 42, X: 10)")] + [DataRow("B(42, Y: 10)", "global::MyNamespace.BAttribute(42, Y: 10)")] [DataRow("""C(10, X = "Test", Y = 42)""", """global::MyNamespace.CAttribute(10, X = "Test", Y = 42)""")] + [DataRow("""C(Z: 10, X = "Test", Y = 42)""", """global::MyNamespace.CAttribute(Z: 10, X = "Test", Y = 42)""")] [DataRow("D(Foo.B, typeof(string), new[] { 1, 2, 3 })", "global::MyNamespace.DAttribute(global::MyNamespace.Foo.B, typeof(string), new int[] { 1, 2, 3 })")] [DataRow("D(Foo.B, typeof(string), new int[] { 1, 2, 3 })", "global::MyNamespace.DAttribute(global::MyNamespace.Foo.B, typeof(string), new int[] { 1, 2, 3 })")] [DataRow("D(Foo.B, typeof(string), [1, 2, 3])", "global::MyNamespace.DAttribute(global::MyNamespace.Foo.B, typeof(string), new int[] { 1, 2, 3 })")] + [DataRow("""E(42, Y: 10, Z: "Bob", W = 100)""", """global::MyNamespace.EAttribute(42, Y: 10, Z: "Bob", W = 100)""")] public void SingleProperty_String_WithNoCaching_WithForwardedAttribute( string attributeDefinition, string attributeForwarding) @@ -4179,6 +4184,10 @@ public class CAttribute(int Z) : Attribute public int Y { get; set; } } public class DAttribute(Foo X, Type Y, int[] Z) : Attribute; + public class EAttribute(int X, int Y, string Z) : Attribute + { + public int W { get; set; } + } public enum Foo { A, B } """;