Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions src/HotChocolate/Core/src/Types.Analyzers/BindMemberAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<DerivedProduct>]
public static partial class DerivedProductNode
{
[BindMember(nameof(DerivedProduct.BrandId))]
public static Task<Brand?> GetBrandAsync(DerivedProduct product)
=> Task.FromResult<Brand?>(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<DerivedProduct>]
public static partial class DerivedProductNode
{
[BindMember("BrandId")]
public static Task<Brand?> GetBrandAsync(DerivedProduct product)
=> Task.FromResult<Brand?>(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()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# BindMember_WithNameof_InheritedMember_NoError

## DerivedProductNode.WaAdMHmlGJHjtEI4nqY7WA.hc.g.cs

```csharp
// <auto-generated/>

#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<global::TestNamespace.DerivedProduct> 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<string>();
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<global::TestNamespace.DerivedProduct> 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<global::System.Object?> GetBrandAsync(global::HotChocolate.Resolvers.IResolverContext context)
{
var args0 = _binding_GetBrandAsync_product.Execute<global::TestNamespace.DerivedProduct>(context);
var result = await global::TestNamespace.DerivedProductNode.GetBrandAsync(args0);
return result;
}
}
}
}


```

## HotChocolateTypeModule.735550c.g.cs

```csharp
// <auto-generated/>

#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<global::TestNamespace.DerivedProduct>(
"Tests::TestNamespace.DerivedProductNode",
() => global::TestNamespace.DerivedProductNode.Initialize));
builder.AddType<ObjectType<global::TestNamespace.DerivedProduct>>();
return builder;
}
}
}

```

Loading
Loading