Skip to content

Commit fa234e3

Browse files
authored
Fix: Source Generator BindField attribute incorrectly ignores fields instead of configuring them (#8640)
1 parent 9b948de commit fa234e3

6 files changed

Lines changed: 412 additions & 5 deletions

src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,18 @@ ResolverResultKind.TaskAsyncEnumerable or
234234

235235
using (Writer.IncreaseIndent())
236236
{
237-
Writer.WriteIndentedLine(
238-
".Field(naming.GetMemberName(\"{0}\", global::HotChocolate.Types.MemberKind.ObjectField))",
239-
fieldName);
237+
var fieldBinding = resolver.Bindings.FirstOrDefault(b => b.Kind is MemberBindingKind.Field);
238+
239+
if (fieldBinding.Name is not null)
240+
{
241+
Writer.WriteIndentedLine(".Field(\"{0}\")", fieldBinding.Name);
242+
}
243+
else
244+
{
245+
Writer.WriteIndentedLine(
246+
".Field(naming.GetMemberName(\"{0}\", global::HotChocolate.Types.MemberKind.ObjectField))",
247+
fieldName);
248+
}
240249

241250
if (resolver.Kind is ResolverKind.ConnectionResolver)
242251
{

src/HotChocolate/Core/test/Types.Analyzers.Tests/ObjectTypeTests.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,4 +1113,80 @@ public static List<string> GetGreeting(
11131113
}
11141114
""").MatchMarkdownAsync(TestContext.Current.CancellationToken);
11151115
}
1116+
1117+
[Fact]
1118+
public async Task GenerateSource_BindField_MatchesSnapshot()
1119+
{
1120+
await TestHelper.GetGeneratedSourceSnapshot(
1121+
"""
1122+
using System;
1123+
using System.Threading;
1124+
using System.Threading.Tasks;
1125+
using HotChocolate;
1126+
using HotChocolate.Types;
1127+
1128+
namespace TestNamespace;
1129+
1130+
public sealed class LineItem
1131+
{
1132+
public int Id { get; set; }
1133+
public int ProductId { get; set; }
1134+
public int Quantity { get; set; }
1135+
}
1136+
1137+
public sealed class Product
1138+
{
1139+
public int Id { get; set; }
1140+
public string Name { get; set; } = string.Empty;
1141+
}
1142+
1143+
[ObjectType<LineItem>]
1144+
public static partial class LineItemType
1145+
{
1146+
[BindField("product")]
1147+
public static Product GetProduct([Parent] LineItem lineItem)
1148+
=> new Product { Id = lineItem.ProductId, Name = "Test Product" };
1149+
}
1150+
""").MatchMarkdownAsync(TestContext.Current.CancellationToken);
1151+
}
1152+
1153+
[Fact]
1154+
public async Task GenerateSource_BindField_And_BindMember_MatchesSnapshot()
1155+
{
1156+
await TestHelper.GetGeneratedSourceSnapshot(
1157+
"""
1158+
using System;
1159+
using System.Threading;
1160+
using System.Threading.Tasks;
1161+
using HotChocolate;
1162+
using HotChocolate.Types;
1163+
1164+
namespace TestNamespace;
1165+
1166+
public sealed class User
1167+
{
1168+
public int Id { get; set; }
1169+
public string Email { get; set; } = string.Empty;
1170+
public int ProfileId { get; set; }
1171+
}
1172+
1173+
public sealed class Profile
1174+
{
1175+
public int Id { get; set; }
1176+
public string DisplayName { get; set; } = string.Empty;
1177+
}
1178+
1179+
[ObjectType<User>]
1180+
public static partial class UserType
1181+
{
1182+
[BindField("profile")]
1183+
public static Profile GetProfile([Parent] User user)
1184+
=> new Profile { Id = user.ProfileId, DisplayName = "Profile" };
1185+
1186+
[BindMember(nameof(User.Email))]
1187+
public static string GetEmailFormatted([Parent] User user)
1188+
=> $"Email: {user.Email}";
1189+
}
1190+
""").MatchMarkdownAsync(TestContext.Current.CancellationToken);
1191+
}
11161192
}

src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ObjectTypeTests.BindField_BoundProperty_MatchesSnapshot.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ namespace TestNamespace
6969
}
7070

7171
descriptor
72-
.Field(naming.GetMemberName("Greeting", global::HotChocolate.Types.MemberKind.ObjectField))
72+
.Field("greeting")
7373
.ExtendWith(static (field, context) =>
7474
{
7575
var configuration = field.Configuration;

src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ObjectTypeTests.BindField_MultipleAttributes_RaisesError.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ namespace TestNamespace
7070
}
7171

7272
descriptor
73-
.Field(naming.GetMemberName("Greeting", global::HotChocolate.Types.MemberKind.ObjectField))
73+
.Field("greeting")
7474
.ExtendWith(static (field, context) =>
7575
{
7676
var configuration = field.Configuration;
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# GenerateSource_BindField_And_BindMember_MatchesSnapshot
2+
3+
## HotChocolateTypeModule.735550c.g.cs
4+
5+
```csharp
6+
// <auto-generated/>
7+
8+
#nullable enable
9+
#pragma warning disable
10+
11+
using System;
12+
using System.Runtime.CompilerServices;
13+
using HotChocolate;
14+
using HotChocolate.Types;
15+
using HotChocolate.Execution.Configuration;
16+
17+
namespace Microsoft.Extensions.DependencyInjection
18+
{
19+
public static partial class TestsTypesRequestExecutorBuilderExtensions
20+
{
21+
public static IRequestExecutorBuilder AddTestsTypes(this IRequestExecutorBuilder builder)
22+
{
23+
builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd<global::TestNamespace.User>(
24+
"Tests::TestNamespace.UserType",
25+
() => global::TestNamespace.UserType.Initialize));
26+
builder.AddType<ObjectType<global::TestNamespace.User>>();
27+
return builder;
28+
}
29+
}
30+
}
31+
32+
```
33+
34+
## UserType.WaAdMHmlGJHjtEI4nqY7WA.hc.g.cs
35+
36+
```csharp
37+
// <auto-generated/>
38+
39+
#nullable enable
40+
#pragma warning disable
41+
42+
using System;
43+
using System.Runtime.CompilerServices;
44+
using HotChocolate;
45+
using HotChocolate.Types;
46+
using HotChocolate.Execution.Configuration;
47+
using Microsoft.Extensions.DependencyInjection;
48+
using HotChocolate.Internal;
49+
50+
namespace TestNamespace
51+
{
52+
public static partial class UserType
53+
{
54+
internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor<global::TestNamespace.User> descriptor)
55+
{
56+
var extension = descriptor.Extend();
57+
var configuration = extension.Configuration;
58+
var thisType = typeof(global::TestNamespace.UserType);
59+
var bindingResolver = extension.Context.ParameterBindingResolver;
60+
var resolvers = new __Resolvers();
61+
62+
var naming = descriptor.Extend().Context.Naming;
63+
var boundFields = new global::System.Collections.Generic.HashSet<string>();
64+
boundFields.Add("profile");
65+
66+
foreach(string fieldName in boundFields)
67+
{
68+
descriptor.Field(fieldName);
69+
}
70+
71+
var ignoredFields = new global::System.Collections.Generic.HashSet<string>();
72+
ignoredFields.Add(naming.GetMemberName("Email", global::HotChocolate.Types.MemberKind.ObjectField));
73+
74+
foreach(string fieldName in ignoredFields)
75+
{
76+
descriptor.Field(fieldName).Ignore();
77+
}
78+
79+
descriptor
80+
.Field("profile")
81+
.ExtendWith(static (field, context) =>
82+
{
83+
var configuration = field.Configuration;
84+
var typeInspector = field.Context.TypeInspector;
85+
var bindingResolver = field.Context.ParameterBindingResolver;
86+
var naming = field.Context.Naming;
87+
88+
configuration.Type = global::HotChocolate.Types.Descriptors.TypeReference.Create(
89+
typeInspector.GetTypeRef(typeof(global::TestNamespace.Profile), HotChocolate.Types.TypeContext.Output),
90+
new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("global__TestNamespace_Profile")));
91+
configuration.ResultType = typeof(global::TestNamespace.Profile);
92+
configuration.DeclaringType = context.ThisType;
93+
94+
configuration.SetSourceGeneratorFlags();
95+
96+
configuration.Member = context.ThisType.GetMethod(
97+
"GetProfile",
98+
global::HotChocolate.Utilities.ReflectionUtils.StaticMemberFlags,
99+
new global::System.Type[]
100+
{
101+
typeof(global::TestNamespace.User)
102+
})!;
103+
104+
var fieldDescriptor = global::HotChocolate.Types.Descriptors.ObjectFieldDescriptor.From(field.Context, configuration);
105+
HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration(
106+
field.Context,
107+
fieldDescriptor,
108+
configuration.Member,
109+
new global::HotChocolate.Types.BindFieldAttribute("profile"));
110+
configuration.ConfigurationsAreApplied = true;
111+
fieldDescriptor.CreateConfiguration();
112+
113+
configuration.Resolvers = context.Resolvers.GetProfile();
114+
},
115+
(Resolvers: resolvers, ThisType: thisType));
116+
117+
descriptor
118+
.Field(naming.GetMemberName("EmailFormatted", global::HotChocolate.Types.MemberKind.ObjectField))
119+
.ExtendWith(static (field, context) =>
120+
{
121+
var configuration = field.Configuration;
122+
var typeInspector = field.Context.TypeInspector;
123+
var bindingResolver = field.Context.ParameterBindingResolver;
124+
var naming = field.Context.Naming;
125+
126+
configuration.Type = global::HotChocolate.Types.Descriptors.TypeReference.Create(
127+
typeInspector.GetTypeRef(typeof(string), HotChocolate.Types.TypeContext.Output),
128+
new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("string")));
129+
configuration.ResultType = typeof(string);
130+
configuration.DeclaringType = context.ThisType;
131+
132+
configuration.SetSourceGeneratorFlags();
133+
134+
configuration.Member = context.ThisType.GetMethod(
135+
"GetEmailFormatted",
136+
global::HotChocolate.Utilities.ReflectionUtils.StaticMemberFlags,
137+
new global::System.Type[]
138+
{
139+
typeof(global::TestNamespace.User)
140+
})!;
141+
142+
var fieldDescriptor = global::HotChocolate.Types.Descriptors.ObjectFieldDescriptor.From(field.Context, configuration);
143+
HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration(
144+
field.Context,
145+
fieldDescriptor,
146+
configuration.Member,
147+
new global::HotChocolate.Types.BindMemberAttribute("Email"));
148+
configuration.ConfigurationsAreApplied = true;
149+
fieldDescriptor.CreateConfiguration();
150+
151+
configuration.Resolvers = context.Resolvers.GetEmailFormatted();
152+
},
153+
(Resolvers: resolvers, ThisType: thisType));
154+
155+
Configure(descriptor);
156+
}
157+
158+
static partial void Configure(global::HotChocolate.Types.IObjectTypeDescriptor<global::TestNamespace.User> descriptor);
159+
160+
private sealed class __Resolvers
161+
{
162+
public HotChocolate.Resolvers.FieldResolverDelegates GetProfile()
163+
{
164+
return new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: GetProfile);
165+
}
166+
167+
private global::System.Object? GetProfile(global::HotChocolate.Resolvers.IResolverContext context)
168+
{
169+
var args0 = context.Parent<global::TestNamespace.User>();
170+
var result = global::TestNamespace.UserType.GetProfile(args0);
171+
return result;
172+
}
173+
174+
public HotChocolate.Resolvers.FieldResolverDelegates GetEmailFormatted()
175+
{
176+
return new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: GetEmailFormatted);
177+
}
178+
179+
private global::System.Object? GetEmailFormatted(global::HotChocolate.Resolvers.IResolverContext context)
180+
{
181+
var args0 = context.Parent<global::TestNamespace.User>();
182+
var result = global::TestNamespace.UserType.GetEmailFormatted(args0);
183+
return result;
184+
}
185+
}
186+
}
187+
}
188+
189+
190+
```

0 commit comments

Comments
 (0)