diff --git a/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs index a6f72ce21f3..414465c2582 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs @@ -57,7 +57,7 @@ protected virtual void WriteResolverBindings(IOutputTypeInfo type) return; } - if (type.Resolvers.Any(t => t.Bindings.Length > 0)) + if (type.Resolvers.SelectMany(t => t.Bindings).Any(b => b.Kind is MemberBindingKind.Property)) { Writer.WriteLine(); Writer.WriteIndentedLine("var naming = descriptor.Extend().Context.Naming;"); @@ -65,13 +65,7 @@ protected virtual void WriteResolverBindings(IOutputTypeInfo type) foreach (var binding in type.Resolvers.SelectMany(t => t.Bindings)) { - if (binding.Kind is MemberBindingKind.Field) - { - Writer.WriteIndentedLine( - "ignoredFields.Add(\"{0}\");", - binding.Name); - } - else if (binding.Kind is MemberBindingKind.Property) + if (binding.Kind is MemberBindingKind.Property) { Writer.WriteIndentedLine( "ignoredFields.Add(naming.GetMemberName(\"{0}\", global::{1}.ObjectField));", @@ -98,12 +92,23 @@ protected virtual void WriteResolverBindings(IOutputTypeInfo type) using (Writer.IncreaseIndent()) { - Writer.WriteIndentedLine( - ".Field(thisType.GetMember(\"{0}\", global::{1})[0])", - resolver.Member.Name, - resolver.IsStatic - ? WellKnownTypes.StaticMemberFlags - : WellKnownTypes.InstanceMemberFlags); + // Check if this resolver has a field binding (BindField attribute) + var fieldBinding = resolver.Bindings.FirstOrDefault(b => b.Kind is MemberBindingKind.Field); + if (fieldBinding.Name is not null) + { + // For BindField, configure the bound field name instead of creating a new field from the method + Writer.WriteIndentedLine(".Field(\"{0}\")", fieldBinding.Name); + } + else + { + // For regular resolvers, create field from method name + Writer.WriteIndentedLine( + ".Field(thisType.GetMember(\"{0}\", global::{1})[0])", + resolver.Member.Name, + resolver.IsStatic + ? WellKnownTypes.StaticMemberFlags + : WellKnownTypes.InstanceMemberFlags); + } if (resolver.Kind is ResolverKind.ConnectionResolver) { diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/ObjectTypeTests.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/ObjectTypeTests.cs index 24a9f098f0d..08733eb47a6 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/ObjectTypeTests.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/ObjectTypeTests.cs @@ -40,4 +40,80 @@ internal static partial class BookNode } """).MatchMarkdownAsync(); } + + [Fact] + public async Task GenerateSource_BindField_MatchesSnapshot() + { + await TestHelper.GetGeneratedSourceSnapshot( + """ + using System; + using System.Threading; + using System.Threading.Tasks; + using HotChocolate; + using HotChocolate.Types; + + namespace TestNamespace; + + public sealed class LineItem + { + public int Id { get; set; } + public int ProductId { get; set; } + public int Quantity { get; set; } + } + + public sealed class Product + { + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + } + + [ObjectType] + public static partial class LineItemType + { + [BindField("product")] + public static Product GetProduct([Parent] LineItem lineItem) + => new Product { Id = lineItem.ProductId, Name = "Test Product" }; + } + """).MatchMarkdownAsync(); + } + + [Fact] + public async Task GenerateSource_BindField_And_BindMember_MatchesSnapshot() + { + await TestHelper.GetGeneratedSourceSnapshot( + """ + using System; + using System.Threading; + using System.Threading.Tasks; + using HotChocolate; + using HotChocolate.Types; + + namespace TestNamespace; + + public sealed class User + { + public int Id { get; set; } + public string Email { get; set; } = string.Empty; + public int ProfileId { get; set; } + } + + public sealed class Profile + { + public int Id { get; set; } + public string DisplayName { get; set; } = string.Empty; + } + + [ObjectType] + public static partial class UserType + { + [BindField("profile")] + public static Profile GetProfile([Parent] User user) + => new Profile { Id = user.ProfileId, DisplayName = "Profile" }; + + [BindMember(nameof(User.Email))] + public static string GetEmailFormatted([Parent] User user) + => $"Email: {user.Email}"; + } + """).MatchMarkdownAsync(); + } } diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ObjectTypeTests.GenerateSource_BindField_And_BindMember_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ObjectTypeTests.GenerateSource_BindField_And_BindMember_MatchesSnapshot.md new file mode 100644 index 00000000000..1c540c50396 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ObjectTypeTests.GenerateSource_BindField_And_BindMember_MatchesSnapshot.md @@ -0,0 +1,123 @@ +# GenerateSource_BindField_And_BindMember_MatchesSnapshot + +## HotChocolateTypeModule.735550c.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +using System; +using System.Runtime.CompilerServices; +using HotChocolate; +using HotChocolate.Types; +using HotChocolate.Execution.Configuration; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class TestsTypesRequestExecutorBuilderExtensions + { + public static IRequestExecutorBuilder AddTestsTypes(this IRequestExecutorBuilder builder) + { + builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd( + "Tests::TestNamespace.UserType", + () => global::TestNamespace.UserType.Initialize)); + builder.AddType>(); + return builder; + } + } +} + +``` + +## UserType.WaAdMHmlGJHjtEI4nqY7WA.hc.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +using System; +using System.Runtime.CompilerServices; +using HotChocolate; +using HotChocolate.Types; +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Internal; + +namespace TestNamespace +{ + public static partial class UserType + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var thisType = typeof(global::TestNamespace.UserType); + var bindingResolver = descriptor.Extend().Context.ParameterBindingResolver; + var resolvers = new __Resolvers(); + + var naming = descriptor.Extend().Context.Naming; + var ignoredFields = new global::System.Collections.Generic.HashSet(); + ignoredFields.Add(naming.GetMemberName("Email", global::HotChocolate.Types.MemberKind.ObjectField)); + + foreach(string fieldName in ignoredFields) + { + descriptor.Field(fieldName).Ignore(); + } + + descriptor + .Field("profile") + .ExtendWith(static (c, r) => + { + c.Configuration.SetSourceGeneratorFlags(); + c.Configuration.Resolvers = r.GetProfile(); + }, + resolvers); + + descriptor + .Field(thisType.GetMember("GetEmailFormatted", global::HotChocolate.Utilities.ReflectionUtils.StaticMemberFlags)[0]) + .ExtendWith(static (c, r) => + { + c.Configuration.SetSourceGeneratorFlags(); + c.Configuration.Resolvers = r.GetEmailFormatted(); + }, + resolvers); + + Configure(descriptor); + } + + static partial void Configure(global::HotChocolate.Types.IObjectTypeDescriptor descriptor); + + private sealed class __Resolvers + { + public HotChocolate.Resolvers.FieldResolverDelegates GetProfile() + { + return new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: GetProfile); + } + + private global::System.Object? GetProfile(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = context.Parent(); + var result = global::TestNamespace.UserType.GetProfile(args0); + return result; + } + + public HotChocolate.Resolvers.FieldResolverDelegates GetEmailFormatted() + { + return new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: GetEmailFormatted); + } + + private global::System.Object? GetEmailFormatted(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = context.Parent(); + var result = global::TestNamespace.UserType.GetEmailFormatted(args0); + return result; + } + } + } +} + + +``` + diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ObjectTypeTests.GenerateSource_BindField_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ObjectTypeTests.GenerateSource_BindField_MatchesSnapshot.md new file mode 100644 index 00000000000..52033423dc6 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ObjectTypeTests.GenerateSource_BindField_MatchesSnapshot.md @@ -0,0 +1,93 @@ +# GenerateSource_BindField_MatchesSnapshot + +## HotChocolateTypeModule.735550c.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +using System; +using System.Runtime.CompilerServices; +using HotChocolate; +using HotChocolate.Types; +using HotChocolate.Execution.Configuration; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class TestsTypesRequestExecutorBuilderExtensions + { + public static IRequestExecutorBuilder AddTestsTypes(this IRequestExecutorBuilder builder) + { + builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd( + "Tests::TestNamespace.LineItemType", + () => global::TestNamespace.LineItemType.Initialize)); + builder.AddType>(); + return builder; + } + } +} + +``` + +## LineItemType.WaAdMHmlGJHjtEI4nqY7WA.hc.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +using System; +using System.Runtime.CompilerServices; +using HotChocolate; +using HotChocolate.Types; +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Internal; + +namespace TestNamespace +{ + public static partial class LineItemType + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var thisType = typeof(global::TestNamespace.LineItemType); + var bindingResolver = descriptor.Extend().Context.ParameterBindingResolver; + var resolvers = new __Resolvers(); + + descriptor + .Field("product") + .ExtendWith(static (c, r) => + { + c.Configuration.SetSourceGeneratorFlags(); + c.Configuration.Resolvers = r.GetProduct(); + }, + resolvers); + + Configure(descriptor); + } + + static partial void Configure(global::HotChocolate.Types.IObjectTypeDescriptor descriptor); + + private sealed class __Resolvers + { + public HotChocolate.Resolvers.FieldResolverDelegates GetProduct() + { + return new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: GetProduct); + } + + private global::System.Object? GetProduct(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = context.Parent(); + var result = global::TestNamespace.LineItemType.GetProduct(args0); + return result; + } + } + } +} + + +``` +