diff --git a/src/HotChocolate/Core/src/Types.Analyzers/BindMemberAnalyzer.cs b/src/HotChocolate/Core/src/Types.Analyzers/BindMemberAnalyzer.cs index 07044fe2bbb..e0281d14ebd 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/BindMemberAnalyzer.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/BindMemberAnalyzer.cs @@ -197,9 +197,8 @@ private static void ValidateMemberExists( ITypeSymbol objectTypeArg, AttributeSyntax attribute) { - // Check if the member exists on the type - var members = objectTypeArg.GetMembers(memberName); - if (members.IsEmpty) + // Check if the member exists on the type or any of its base types + if (!HasMember(objectTypeArg, memberName)) { var diagnostic = Diagnostic.Create( Errors.BindMemberNotFound, @@ -210,4 +209,21 @@ private static void ValidateMemberExists( context.ReportDiagnostic(diagnostic); } } + + private static bool HasMember(ITypeSymbol type, string memberName) + { + var current = type; + + while (current is not null) + { + if (!current.GetMembers(memberName).IsEmpty) + { + return true; + } + + current = current.BaseType; + } + + return false; + } } diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/BindMemberAnalyzerTests.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/BindMemberAnalyzerTests.cs index 678550e3d51..d1688ee94f2 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/BindMemberAnalyzerTests.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/BindMemberAnalyzerTests.cs @@ -255,6 +255,84 @@ public class Brand enableAnalyzers: true).MatchMarkdownAsync(); } + [Fact] + public async Task BindMember_WithNameof_InheritedMember_NoError() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + using HotChocolate; + using HotChocolate.Types; + using System.Threading.Tasks; + + namespace TestNamespace; + + [ObjectType] + public static partial class DerivedProductNode + { + [BindMember(nameof(DerivedProduct.BrandId))] + public static Task GetBrandAsync(DerivedProduct product) + => Task.FromResult(null); + } + + public class ProductBase + { + public int Id { get; set; } + public int BrandId { get; set; } + } + + public class DerivedProduct : ProductBase + { + public string Name { get; set; } + } + + public class Brand + { + public int Id { get; set; } + public string Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + + [Fact] + public async Task BindMember_WithString_InheritedMember_NoError() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + using HotChocolate; + using HotChocolate.Types; + using System.Threading.Tasks; + + namespace TestNamespace; + + [ObjectType] + public static partial class DerivedProductNode + { + [BindMember("BrandId")] + public static Task GetBrandAsync(DerivedProduct product) + => Task.FromResult(null); + } + + public class ProductBase + { + public int Id { get; set; } + public int BrandId { get; set; } + } + + public class DerivedProduct : ProductBase + { + public string Name { get; set; } + } + + public class Brand + { + public int Id { get; set; } + public string Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + [Fact] public async Task BindMember_MultipleErrors_RaisesMultipleErrors() { diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/BindMemberAnalyzerTests.BindMember_WithNameof_InheritedMember_NoError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/BindMemberAnalyzerTests.BindMember_WithNameof_InheritedMember_NoError.md new file mode 100644 index 00000000000..0502df4023c --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/BindMemberAnalyzerTests.BindMember_WithNameof_InheritedMember_NoError.md @@ -0,0 +1,160 @@ +# BindMember_WithNameof_InheritedMember_NoError + +## DerivedProductNode.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 DerivedProductNode + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var extension = descriptor.Extend(); + var configuration = extension.Configuration; + var thisType = typeof(global::TestNamespace.DerivedProductNode); + var bindingResolver = extension.Context.ParameterBindingResolver; + var resolvers = new __Resolvers(bindingResolver); + + var naming = descriptor.Extend().Context.Naming; + var ignoredFields = new global::System.Collections.Generic.HashSet(); + ignoredFields.Add(naming.GetMemberName("BrandId", global::HotChocolate.Types.MemberKind.ObjectField)); + + foreach(string fieldName in ignoredFields) + { + descriptor.Field(fieldName).Ignore(); + } + + descriptor + .Field(naming.GetMemberName("Brand", global::HotChocolate.Types.MemberKind.ObjectField)) + .ExtendWith(static (field, context) => + { + var configuration = field.Configuration; + var typeInspector = field.Context.TypeInspector; + var bindingResolver = field.Context.ParameterBindingResolver; + var naming = field.Context.Naming; + + configuration.Type = typeInspector.GetTypeRef(typeof(global::TestNamespace.Brand), HotChocolate.Types.TypeContext.Output); + configuration.ResultType = typeof(global::TestNamespace.Brand); + + configuration.SetSourceGeneratorFlags(); + + var bindingInfo = field.Context.ParameterBindingResolver; + var parameter = context.Resolvers.CreateParameterDescriptor_GetBrandAsync_product(); + var parameterInfo = bindingInfo.GetBindingInfo(parameter); + + if(parameterInfo.Kind is global::HotChocolate.Internal.ArgumentKind.Argument) + { + var argumentConfiguration = new global::HotChocolate.Types.Descriptors.Configurations.ArgumentConfiguration + { + Name = naming.GetMemberName("product", global::HotChocolate.Types.MemberKind.Argument), + Type = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(global::TestNamespace.DerivedProduct), HotChocolate.Types.TypeContext.Input), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("global__TestNamespace_DerivedProduct"))), + RuntimeType = typeof(global::TestNamespace.DerivedProduct) + }; + + configuration.Arguments.Add(argumentConfiguration); + } + + configuration.Member = context.ThisType.GetMethod( + "GetBrandAsync", + global::HotChocolate.Utilities.ReflectionUtils.StaticMemberFlags, + new global::System.Type[] + { + typeof(global::TestNamespace.DerivedProduct) + })!; + + var fieldDescriptor = global::HotChocolate.Types.Descriptors.ObjectFieldDescriptor.From(field.Context, configuration); + HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration( + field.Context, + fieldDescriptor, + configuration.Member, + new global::HotChocolate.Types.BindMemberAttribute("BrandId")); + configuration.ConfigurationsAreApplied = true; + fieldDescriptor.CreateConfiguration(); + + configuration.Resolvers = context.Resolvers.GetBrandAsync(); + }, + (Resolvers: resolvers, ThisType: thisType)); + + Configure(descriptor); + } + + static partial void Configure(global::HotChocolate.Types.IObjectTypeDescriptor descriptor); + + private sealed class __Resolvers + { + private readonly global::HotChocolate.Internal.IParameterBinding _binding_GetBrandAsync_product; + + public __Resolvers(global::HotChocolate.Resolvers.ParameterBindingResolver bindingResolver) + { + _binding_GetBrandAsync_product = bindingResolver.GetBinding(CreateParameterDescriptor_GetBrandAsync_product()); + } + + public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetBrandAsync_product() + => new HotChocolate.Internal.ParameterDescriptor( + "product", + typeof(global::TestNamespace.DerivedProduct), + isNullable: false, + []); + + public HotChocolate.Resolvers.FieldResolverDelegates GetBrandAsync() + => new global::HotChocolate.Resolvers.FieldResolverDelegates(resolver: GetBrandAsync); + + private async global::System.Threading.Tasks.ValueTask GetBrandAsync(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = _binding_GetBrandAsync_product.Execute(context); + var result = await global::TestNamespace.DerivedProductNode.GetBrandAsync(args0); + return result; + } + } + } +} + + +``` + +## 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.DerivedProductNode", + () => global::TestNamespace.DerivedProductNode.Initialize)); + builder.AddType>(); + return builder; + } + } +} + +``` + diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/BindMemberAnalyzerTests.BindMember_WithString_InheritedMember_NoError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/BindMemberAnalyzerTests.BindMember_WithString_InheritedMember_NoError.md new file mode 100644 index 00000000000..a2bf87a2c2e --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/BindMemberAnalyzerTests.BindMember_WithString_InheritedMember_NoError.md @@ -0,0 +1,160 @@ +# BindMember_WithString_InheritedMember_NoError + +## DerivedProductNode.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 DerivedProductNode + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var extension = descriptor.Extend(); + var configuration = extension.Configuration; + var thisType = typeof(global::TestNamespace.DerivedProductNode); + var bindingResolver = extension.Context.ParameterBindingResolver; + var resolvers = new __Resolvers(bindingResolver); + + var naming = descriptor.Extend().Context.Naming; + var ignoredFields = new global::System.Collections.Generic.HashSet(); + ignoredFields.Add(naming.GetMemberName("BrandId", global::HotChocolate.Types.MemberKind.ObjectField)); + + foreach(string fieldName in ignoredFields) + { + descriptor.Field(fieldName).Ignore(); + } + + descriptor + .Field(naming.GetMemberName("Brand", global::HotChocolate.Types.MemberKind.ObjectField)) + .ExtendWith(static (field, context) => + { + var configuration = field.Configuration; + var typeInspector = field.Context.TypeInspector; + var bindingResolver = field.Context.ParameterBindingResolver; + var naming = field.Context.Naming; + + configuration.Type = typeInspector.GetTypeRef(typeof(global::TestNamespace.Brand), HotChocolate.Types.TypeContext.Output); + configuration.ResultType = typeof(global::TestNamespace.Brand); + + configuration.SetSourceGeneratorFlags(); + + var bindingInfo = field.Context.ParameterBindingResolver; + var parameter = context.Resolvers.CreateParameterDescriptor_GetBrandAsync_product(); + var parameterInfo = bindingInfo.GetBindingInfo(parameter); + + if(parameterInfo.Kind is global::HotChocolate.Internal.ArgumentKind.Argument) + { + var argumentConfiguration = new global::HotChocolate.Types.Descriptors.Configurations.ArgumentConfiguration + { + Name = naming.GetMemberName("product", global::HotChocolate.Types.MemberKind.Argument), + Type = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(global::TestNamespace.DerivedProduct), HotChocolate.Types.TypeContext.Input), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("global__TestNamespace_DerivedProduct"))), + RuntimeType = typeof(global::TestNamespace.DerivedProduct) + }; + + configuration.Arguments.Add(argumentConfiguration); + } + + configuration.Member = context.ThisType.GetMethod( + "GetBrandAsync", + global::HotChocolate.Utilities.ReflectionUtils.StaticMemberFlags, + new global::System.Type[] + { + typeof(global::TestNamespace.DerivedProduct) + })!; + + var fieldDescriptor = global::HotChocolate.Types.Descriptors.ObjectFieldDescriptor.From(field.Context, configuration); + HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration( + field.Context, + fieldDescriptor, + configuration.Member, + new global::HotChocolate.Types.BindMemberAttribute("BrandId")); + configuration.ConfigurationsAreApplied = true; + fieldDescriptor.CreateConfiguration(); + + configuration.Resolvers = context.Resolvers.GetBrandAsync(); + }, + (Resolvers: resolvers, ThisType: thisType)); + + Configure(descriptor); + } + + static partial void Configure(global::HotChocolate.Types.IObjectTypeDescriptor descriptor); + + private sealed class __Resolvers + { + private readonly global::HotChocolate.Internal.IParameterBinding _binding_GetBrandAsync_product; + + public __Resolvers(global::HotChocolate.Resolvers.ParameterBindingResolver bindingResolver) + { + _binding_GetBrandAsync_product = bindingResolver.GetBinding(CreateParameterDescriptor_GetBrandAsync_product()); + } + + public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetBrandAsync_product() + => new HotChocolate.Internal.ParameterDescriptor( + "product", + typeof(global::TestNamespace.DerivedProduct), + isNullable: false, + []); + + public HotChocolate.Resolvers.FieldResolverDelegates GetBrandAsync() + => new global::HotChocolate.Resolvers.FieldResolverDelegates(resolver: GetBrandAsync); + + private async global::System.Threading.Tasks.ValueTask GetBrandAsync(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = _binding_GetBrandAsync_product.Execute(context); + var result = await global::TestNamespace.DerivedProductNode.GetBrandAsync(args0); + return result; + } + } + } +} + + +``` + +## 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.DerivedProductNode", + () => global::TestNamespace.DerivedProductNode.Initialize)); + builder.AddType>(); + return builder; + } + } +} + +``` +