From 3f62f0629a972d1fb38c0e9e85824728e5c7a7e9 Mon Sep 17 00:00:00 2001 From: Glen Date: Wed, 15 Apr 2026 15:41:49 +0200 Subject: [PATCH] Add analyzer "Lookup Must Not Return List Type" --- .../Core/src/Types.Analyzers/Errors.cs | 9 + .../LookupReturnsListTypeAnalyzer.cs | 115 ++++++++++ .../LookupReturnsNonNullableTypeAnalyzer.cs | 10 + .../LookupReturnsListTypeAnalyzerTests.cs | 205 ++++++++++++++++++ .../test/Types.Analyzers.Tests/TestHelper.cs | 3 +- ...zerTests.Method_ArrayReturn_RaisesError.md | 191 ++++++++++++++++ ...ts.Method_IEnumerableReturn_RaisesError.md | 191 ++++++++++++++++ ...yzerTests.Method_ListReturn_RaisesError.md | 191 ++++++++++++++++ ...rTests.Method_NoLookupAttribute_NoError.md | 155 +++++++++++++ ...alyzerTests.Method_SingleReturn_NoError.md | 171 +++++++++++++++ ...Tests.Method_TaskListReturn_RaisesError.md | 186 ++++++++++++++++ ...erTests.Property_ListReturn_RaisesError.md | 149 +++++++++++++ 12 files changed, 1575 insertions(+), 1 deletion(-) create mode 100644 src/HotChocolate/Core/src/Types.Analyzers/LookupReturnsListTypeAnalyzer.cs create mode 100644 src/HotChocolate/Core/test/Types.Analyzers.Tests/LookupReturnsListTypeAnalyzerTests.cs create mode 100644 src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_ArrayReturn_RaisesError.md create mode 100644 src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_IEnumerableReturn_RaisesError.md create mode 100644 src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_ListReturn_RaisesError.md create mode 100644 src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_NoLookupAttribute_NoError.md create mode 100644 src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_SingleReturn_NoError.md create mode 100644 src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_TaskListReturn_RaisesError.md create mode 100644 src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Property_ListReturn_RaisesError.md diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Errors.cs b/src/HotChocolate/Core/src/Types.Analyzers/Errors.cs index 8f78932763f..f5bfb1af270 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Errors.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Errors.cs @@ -265,4 +265,13 @@ public static class Errors category: "TypeSystem", DiagnosticSeverity.Warning, isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor LookupReturnsListType = + new( + id: "HC0114", + title: "Lookup Must Not Return List Type", + messageFormat: "A method or property with the [Lookup] attribute must not return a list type", + category: "TypeSystem", + DiagnosticSeverity.Error, + isEnabledByDefault: true); } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/LookupReturnsListTypeAnalyzer.cs b/src/HotChocolate/Core/src/Types.Analyzers/LookupReturnsListTypeAnalyzer.cs new file mode 100644 index 00000000000..e9812c9395e --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/LookupReturnsListTypeAnalyzer.cs @@ -0,0 +1,115 @@ +using System.Collections.Immutable; +using HotChocolate.Types.Analyzers.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace HotChocolate.Types.Analyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class LookupReturnsListTypeAnalyzer : DiagnosticAnalyzer +{ + public override ImmutableArray SupportedDiagnostics { get; } = + [Errors.LookupReturnsListType]; + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration); + context.RegisterSyntaxNodeAction(AnalyzePropertyDeclaration, SyntaxKind.PropertyDeclaration); + } + + private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) + { + var methodDeclaration = (MethodDeclarationSyntax)context.Node; + + if (!HasLookupAttribute(context, methodDeclaration.AttributeLists)) + { + return; + } + + var methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodDeclaration); + if (methodSymbol is null) + { + return; + } + + var returnType = context.Compilation.IsTaskOrValueTask(methodSymbol.ReturnType, out var innerType) + ? innerType + : methodSymbol.ReturnType; + + if (!IsListType(returnType)) + { + return; + } + + var diagnostic = Diagnostic.Create( + Errors.LookupReturnsListType, + methodDeclaration.ReturnType.GetLocation()); + + context.ReportDiagnostic(diagnostic); + } + + private static void AnalyzePropertyDeclaration(SyntaxNodeAnalysisContext context) + { + var propertyDeclaration = (PropertyDeclarationSyntax)context.Node; + + if (!HasLookupAttribute(context, propertyDeclaration.AttributeLists)) + { + return; + } + + var propertySymbol = context.SemanticModel.GetDeclaredSymbol(propertyDeclaration); + if (propertySymbol is null) + { + return; + } + + var propertyType = context.Compilation.IsTaskOrValueTask(propertySymbol.Type, out var innerType) + ? innerType + : propertySymbol.Type; + + if (!IsListType(propertyType)) + { + return; + } + + var diagnostic = Diagnostic.Create( + Errors.LookupReturnsListType, + propertyDeclaration.Type.GetLocation()); + + context.ReportDiagnostic(diagnostic); + } + + private static bool IsListType(ITypeSymbol typeSymbol) + => typeSymbol is IArrayTypeSymbol || typeSymbol.IsListType(out _); + + private static bool HasLookupAttribute( + SyntaxNodeAnalysisContext context, + SyntaxList attributeLists) + { + var semanticModel = context.SemanticModel; + + foreach (var attributeList in attributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + var symbolInfo = semanticModel.GetSymbolInfo(attribute); + if (symbolInfo.Symbol is not IMethodSymbol attributeSymbol) + { + continue; + } + + var attributeType = attributeSymbol.ContainingType; + if (attributeType.ToDisplayString() == WellKnownAttributes.LookupAttribute) + { + return true; + } + } + } + + return false; + } +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/LookupReturnsNonNullableTypeAnalyzer.cs b/src/HotChocolate/Core/src/Types.Analyzers/LookupReturnsNonNullableTypeAnalyzer.cs index 2ae984f6901..eacb75dfdd2 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/LookupReturnsNonNullableTypeAnalyzer.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/LookupReturnsNonNullableTypeAnalyzer.cs @@ -40,6 +40,11 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) ? innerType : methodSymbol.ReturnType; + if (returnType is IArrayTypeSymbol || returnType.IsListType(out _)) + { + return; + } + if (returnType.IsNullableType()) { return; @@ -71,6 +76,11 @@ private static void AnalyzePropertyDeclaration(SyntaxNodeAnalysisContext context ? innerType : propertySymbol.Type; + if (propertyType is IArrayTypeSymbol || propertyType.IsListType(out _)) + { + return; + } + if (propertyType.IsNullableType()) { return; diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/LookupReturnsListTypeAnalyzerTests.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/LookupReturnsListTypeAnalyzerTests.cs new file mode 100644 index 00000000000..34bd9efb62c --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/LookupReturnsListTypeAnalyzerTests.cs @@ -0,0 +1,205 @@ +namespace HotChocolate.Types; + +public class LookupReturnsListTypeAnalyzerTests +{ + [Fact] + public async Task Method_ListReturn_RaisesError() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + #nullable enable + using HotChocolate; + using HotChocolate.Types; + using HotChocolate.Types.Composite; + using System.Collections.Generic; + + namespace TestNamespace; + + [QueryType] + internal static partial class Query + { + [Lookup] + public static List GetUsersById(int id) => default!; + } + + public class User + { + public int Id { get; set; } + public string? Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + + [Fact] + public async Task Method_ArrayReturn_RaisesError() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + #nullable enable + using HotChocolate; + using HotChocolate.Types; + using HotChocolate.Types.Composite; + + namespace TestNamespace; + + [QueryType] + internal static partial class Query + { + [Lookup] + public static User?[] GetUsersById(int id) => default!; + } + + public class User + { + public int Id { get; set; } + public string? Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + + [Fact] + public async Task Method_IEnumerableReturn_RaisesError() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + #nullable enable + using HotChocolate; + using HotChocolate.Types; + using HotChocolate.Types.Composite; + using System.Collections.Generic; + + namespace TestNamespace; + + [QueryType] + internal static partial class Query + { + [Lookup] + public static IEnumerable GetUsersById(int id) => default!; + } + + public class User + { + public int Id { get; set; } + public string? Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + + [Fact] + public async Task Method_TaskListReturn_RaisesError() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + #nullable enable + using HotChocolate; + using HotChocolate.Types; + using HotChocolate.Types.Composite; + using System.Collections.Generic; + using System.Threading.Tasks; + + namespace TestNamespace; + + [QueryType] + internal static partial class Query + { + [Lookup] + public static Task> GetUsersByIdAsync(int id) => default!; + } + + public class User + { + public int Id { get; set; } + public string? Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + + [Fact] + public async Task Property_ListReturn_RaisesError() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + #nullable enable + using HotChocolate; + using HotChocolate.Types; + using HotChocolate.Types.Composite; + using System.Collections.Generic; + + namespace TestNamespace; + + [QueryType] + internal static partial class Query + { + [Lookup] + public static List AllUsers => default!; + } + + public class User + { + public int Id { get; set; } + public string? Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + + [Fact] + public async Task Method_SingleReturn_NoError() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + #nullable enable + using HotChocolate; + using HotChocolate.Types; + using HotChocolate.Types.Composite; + + namespace TestNamespace; + + [QueryType] + internal static partial class Query + { + [Lookup] + public static User? GetUserById(int id) => default; + } + + public class User + { + public int Id { get; set; } + public string? Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + + [Fact] + public async Task Method_NoLookupAttribute_NoError() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + #nullable enable + using HotChocolate; + using HotChocolate.Types; + using HotChocolate.Types.Composite; + using System.Collections.Generic; + + namespace TestNamespace; + + [QueryType] + internal static partial class Query + { + public static List GetUsersById(int id) => default!; + } + + public class User + { + public int Id { get; set; } + public string? Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } +} diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/TestHelper.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/TestHelper.cs index 7c52f065696..6737b92db28 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/TestHelper.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/TestHelper.cs @@ -221,7 +221,8 @@ private static Snapshot CreateSnapshot(CSharpCompilation compilation, GeneratorD new DataAttributeOrderAnalyzer(), new IdAttributeOnRecordParameterAnalyzer(), new WrongAuthorizationAttributeAnalyzer(), - new LookupReturnsNonNullableTypeAnalyzer()); + new LookupReturnsNonNullableTypeAnalyzer(), + new LookupReturnsListTypeAnalyzer()); var compilationWithAnalyzers = compilation.WithAnalyzers(analyzers); var analyzerDiagnostics = compilationWithAnalyzers.GetAllDiagnosticsAsync().Result; diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_ArrayReturn_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_ArrayReturn_RaisesError.md new file mode 100644 index 00000000000..4dd861b9b22 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_ArrayReturn_RaisesError.md @@ -0,0 +1,191 @@ +# Method_ArrayReturn_RaisesError + +## 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.AddSourceSchemaDefaults(); + builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd( + "Tests::TestNamespace.Query", + global::HotChocolate.Types.OperationTypeNames.Query, + () => global::TestNamespace.Query.Initialize)); + builder.ConfigureSchema( + b => b.TryAddRootType( + () => new global::HotChocolate.Types.ObjectType( + d => d.Name(global::HotChocolate.Types.OperationTypeNames.Query)), + HotChocolate.Language.OperationType.Query)); + return builder; + } + } +} + +``` + +## Query.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 +{ + internal static partial class Query + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var extension = descriptor.Extend(); + var configuration = extension.Configuration; + var thisType = typeof(global::TestNamespace.Query); + var bindingResolver = extension.Context.ParameterBindingResolver; + var resolvers = new __Resolvers(bindingResolver); + + HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration( + extension.Context, + descriptor, + null, + new global::HotChocolate.Types.QueryTypeAttribute()); + configuration.ConfigurationsAreApplied = true; + + var naming = descriptor.Extend().Context.Naming; + + descriptor + .Field(naming.GetMemberName("UsersById", 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 = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(global::TestNamespace.User), HotChocolate.Types.TypeContext.Output), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.ListTypeNode(new global::HotChocolate.Language.NamedTypeNode("global__TestNamespace_User")))); + configuration.ResultType = typeof(global::TestNamespace.User?[]); + + configuration.SetSourceGeneratorFlags(); + + var bindingInfo = field.Context.ParameterBindingResolver; + var parameter = context.Resolvers.CreateParameterDescriptor_GetUsersById_id(); + 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("id", global::HotChocolate.Types.MemberKind.Argument), + Type = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(int), HotChocolate.Types.TypeContext.Input), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("int"))), + RuntimeType = typeof(int) + }; + + configuration.Arguments.Add(argumentConfiguration); + } + + configuration.Member = context.ThisType.GetMethod( + "GetUsersById", + global::HotChocolate.Utilities.ReflectionUtils.StaticMemberFlags, + new global::System.Type[] + { + typeof(int) + })!; + + var fieldDescriptor = global::HotChocolate.Types.Descriptors.ObjectFieldDescriptor.From(field.Context, configuration); + HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration( + field.Context, + fieldDescriptor, + configuration.Member, + new global::HotChocolate.Types.Composite.LookupAttribute()); + configuration.ConfigurationsAreApplied = true; + fieldDescriptor.CreateConfiguration(); + + configuration.Resolvers = context.Resolvers.GetUsersById(); + }, + (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_GetUsersById_id; + + public __Resolvers(global::HotChocolate.Resolvers.ParameterBindingResolver bindingResolver) + { + _binding_GetUsersById_id = bindingResolver.GetBinding(CreateParameterDescriptor_GetUsersById_id()); + } + + public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetUsersById_id() + => new HotChocolate.Internal.ParameterDescriptor( + "id", + typeof(int), + isNullable: false, + []); + + public HotChocolate.Resolvers.FieldResolverDelegates GetUsersById() + { + var isPureResolver = _binding_GetUsersById_id.IsPure; + + return isPureResolver + ? new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: GetUsersById) + : new global::HotChocolate.Resolvers.FieldResolverDelegates(resolver: c => new(GetUsersById(c))); + } + + private global::System.Object? GetUsersById(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = _binding_GetUsersById_id.Execute(context); + var result = global::TestNamespace.Query.GetUsersById(args0); + return result; + } + } + } +} + + +``` + +## Analyzer Diagnostics + +```json +[ + { + "Id": "HC0114", + "Title": "Lookup Must Not Return List Type", + "Severity": "Error", + "WarningLevel": 0, + "Location": ": (11,18)-(11,25)", + "MessageFormat": "A method or property with the [Lookup] attribute must not return a list type", + "Message": "A method or property with the [Lookup] attribute must not return a list type", + "Category": "TypeSystem", + "CustomTags": [] + } +] +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_IEnumerableReturn_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_IEnumerableReturn_RaisesError.md new file mode 100644 index 00000000000..ebc5c58b336 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_IEnumerableReturn_RaisesError.md @@ -0,0 +1,191 @@ +# Method_IEnumerableReturn_RaisesError + +## 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.AddSourceSchemaDefaults(); + builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd( + "Tests::TestNamespace.Query", + global::HotChocolate.Types.OperationTypeNames.Query, + () => global::TestNamespace.Query.Initialize)); + builder.ConfigureSchema( + b => b.TryAddRootType( + () => new global::HotChocolate.Types.ObjectType( + d => d.Name(global::HotChocolate.Types.OperationTypeNames.Query)), + HotChocolate.Language.OperationType.Query)); + return builder; + } + } +} + +``` + +## Query.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 +{ + internal static partial class Query + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var extension = descriptor.Extend(); + var configuration = extension.Configuration; + var thisType = typeof(global::TestNamespace.Query); + var bindingResolver = extension.Context.ParameterBindingResolver; + var resolvers = new __Resolvers(bindingResolver); + + HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration( + extension.Context, + descriptor, + null, + new global::HotChocolate.Types.QueryTypeAttribute()); + configuration.ConfigurationsAreApplied = true; + + var naming = descriptor.Extend().Context.Naming; + + descriptor + .Field(naming.GetMemberName("UsersById", 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 = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(global::TestNamespace.User), HotChocolate.Types.TypeContext.Output), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.ListTypeNode(new global::HotChocolate.Language.NamedTypeNode("global__TestNamespace_User")))); + configuration.ResultType = typeof(global::System.Collections.Generic.IEnumerable); + + configuration.SetSourceGeneratorFlags(); + + var bindingInfo = field.Context.ParameterBindingResolver; + var parameter = context.Resolvers.CreateParameterDescriptor_GetUsersById_id(); + 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("id", global::HotChocolate.Types.MemberKind.Argument), + Type = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(int), HotChocolate.Types.TypeContext.Input), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("int"))), + RuntimeType = typeof(int) + }; + + configuration.Arguments.Add(argumentConfiguration); + } + + configuration.Member = context.ThisType.GetMethod( + "GetUsersById", + global::HotChocolate.Utilities.ReflectionUtils.StaticMemberFlags, + new global::System.Type[] + { + typeof(int) + })!; + + var fieldDescriptor = global::HotChocolate.Types.Descriptors.ObjectFieldDescriptor.From(field.Context, configuration); + HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration( + field.Context, + fieldDescriptor, + configuration.Member, + new global::HotChocolate.Types.Composite.LookupAttribute()); + configuration.ConfigurationsAreApplied = true; + fieldDescriptor.CreateConfiguration(); + + configuration.Resolvers = context.Resolvers.GetUsersById(); + }, + (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_GetUsersById_id; + + public __Resolvers(global::HotChocolate.Resolvers.ParameterBindingResolver bindingResolver) + { + _binding_GetUsersById_id = bindingResolver.GetBinding(CreateParameterDescriptor_GetUsersById_id()); + } + + public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetUsersById_id() + => new HotChocolate.Internal.ParameterDescriptor( + "id", + typeof(int), + isNullable: false, + []); + + public HotChocolate.Resolvers.FieldResolverDelegates GetUsersById() + { + var isPureResolver = _binding_GetUsersById_id.IsPure; + + return isPureResolver + ? new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: GetUsersById) + : new global::HotChocolate.Resolvers.FieldResolverDelegates(resolver: c => new(GetUsersById(c))); + } + + private global::System.Object? GetUsersById(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = _binding_GetUsersById_id.Execute(context); + var result = global::TestNamespace.Query.GetUsersById(args0); + return result; + } + } + } +} + + +``` + +## Analyzer Diagnostics + +```json +[ + { + "Id": "HC0114", + "Title": "Lookup Must Not Return List Type", + "Severity": "Error", + "WarningLevel": 0, + "Location": ": (12,18)-(12,36)", + "MessageFormat": "A method or property with the [Lookup] attribute must not return a list type", + "Message": "A method or property with the [Lookup] attribute must not return a list type", + "Category": "TypeSystem", + "CustomTags": [] + } +] +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_ListReturn_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_ListReturn_RaisesError.md new file mode 100644 index 00000000000..70f7ae57e26 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_ListReturn_RaisesError.md @@ -0,0 +1,191 @@ +# Method_ListReturn_RaisesError + +## 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.AddSourceSchemaDefaults(); + builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd( + "Tests::TestNamespace.Query", + global::HotChocolate.Types.OperationTypeNames.Query, + () => global::TestNamespace.Query.Initialize)); + builder.ConfigureSchema( + b => b.TryAddRootType( + () => new global::HotChocolate.Types.ObjectType( + d => d.Name(global::HotChocolate.Types.OperationTypeNames.Query)), + HotChocolate.Language.OperationType.Query)); + return builder; + } + } +} + +``` + +## Query.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 +{ + internal static partial class Query + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var extension = descriptor.Extend(); + var configuration = extension.Configuration; + var thisType = typeof(global::TestNamespace.Query); + var bindingResolver = extension.Context.ParameterBindingResolver; + var resolvers = new __Resolvers(bindingResolver); + + HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration( + extension.Context, + descriptor, + null, + new global::HotChocolate.Types.QueryTypeAttribute()); + configuration.ConfigurationsAreApplied = true; + + var naming = descriptor.Extend().Context.Naming; + + descriptor + .Field(naming.GetMemberName("UsersById", 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 = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(global::TestNamespace.User), HotChocolate.Types.TypeContext.Output), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.ListTypeNode(new global::HotChocolate.Language.NamedTypeNode("global__TestNamespace_User")))); + configuration.ResultType = typeof(global::System.Collections.Generic.List); + + configuration.SetSourceGeneratorFlags(); + + var bindingInfo = field.Context.ParameterBindingResolver; + var parameter = context.Resolvers.CreateParameterDescriptor_GetUsersById_id(); + 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("id", global::HotChocolate.Types.MemberKind.Argument), + Type = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(int), HotChocolate.Types.TypeContext.Input), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("int"))), + RuntimeType = typeof(int) + }; + + configuration.Arguments.Add(argumentConfiguration); + } + + configuration.Member = context.ThisType.GetMethod( + "GetUsersById", + global::HotChocolate.Utilities.ReflectionUtils.StaticMemberFlags, + new global::System.Type[] + { + typeof(int) + })!; + + var fieldDescriptor = global::HotChocolate.Types.Descriptors.ObjectFieldDescriptor.From(field.Context, configuration); + HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration( + field.Context, + fieldDescriptor, + configuration.Member, + new global::HotChocolate.Types.Composite.LookupAttribute()); + configuration.ConfigurationsAreApplied = true; + fieldDescriptor.CreateConfiguration(); + + configuration.Resolvers = context.Resolvers.GetUsersById(); + }, + (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_GetUsersById_id; + + public __Resolvers(global::HotChocolate.Resolvers.ParameterBindingResolver bindingResolver) + { + _binding_GetUsersById_id = bindingResolver.GetBinding(CreateParameterDescriptor_GetUsersById_id()); + } + + public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetUsersById_id() + => new HotChocolate.Internal.ParameterDescriptor( + "id", + typeof(int), + isNullable: false, + []); + + public HotChocolate.Resolvers.FieldResolverDelegates GetUsersById() + { + var isPureResolver = _binding_GetUsersById_id.IsPure; + + return isPureResolver + ? new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: GetUsersById) + : new global::HotChocolate.Resolvers.FieldResolverDelegates(resolver: c => new(GetUsersById(c))); + } + + private global::System.Object? GetUsersById(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = _binding_GetUsersById_id.Execute(context); + var result = global::TestNamespace.Query.GetUsersById(args0); + return result; + } + } + } +} + + +``` + +## Analyzer Diagnostics + +```json +[ + { + "Id": "HC0114", + "Title": "Lookup Must Not Return List Type", + "Severity": "Error", + "WarningLevel": 0, + "Location": ": (12,18)-(12,29)", + "MessageFormat": "A method or property with the [Lookup] attribute must not return a list type", + "Message": "A method or property with the [Lookup] attribute must not return a list type", + "Category": "TypeSystem", + "CustomTags": [] + } +] +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_NoLookupAttribute_NoError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_NoLookupAttribute_NoError.md new file mode 100644 index 00000000000..faa8849df29 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_NoLookupAttribute_NoError.md @@ -0,0 +1,155 @@ +# Method_NoLookupAttribute_NoError + +## 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.Query", + global::HotChocolate.Types.OperationTypeNames.Query, + () => global::TestNamespace.Query.Initialize)); + builder.ConfigureSchema( + b => b.TryAddRootType( + () => new global::HotChocolate.Types.ObjectType( + d => d.Name(global::HotChocolate.Types.OperationTypeNames.Query)), + HotChocolate.Language.OperationType.Query)); + return builder; + } + } +} + +``` + +## Query.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 +{ + internal static partial class Query + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var extension = descriptor.Extend(); + var configuration = extension.Configuration; + var thisType = typeof(global::TestNamespace.Query); + var bindingResolver = extension.Context.ParameterBindingResolver; + var resolvers = new __Resolvers(bindingResolver); + + HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration( + extension.Context, + descriptor, + null, + new global::HotChocolate.Types.QueryTypeAttribute()); + configuration.ConfigurationsAreApplied = true; + + var naming = descriptor.Extend().Context.Naming; + + descriptor + .Field(naming.GetMemberName("UsersById", 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 = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(global::TestNamespace.User), HotChocolate.Types.TypeContext.Output), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.ListTypeNode(new global::HotChocolate.Language.NamedTypeNode("global__TestNamespace_User")))); + configuration.ResultType = typeof(global::System.Collections.Generic.List); + + configuration.SetSourceGeneratorFlags(); + + var bindingInfo = field.Context.ParameterBindingResolver; + var parameter = context.Resolvers.CreateParameterDescriptor_GetUsersById_id(); + 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("id", global::HotChocolate.Types.MemberKind.Argument), + Type = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(int), HotChocolate.Types.TypeContext.Input), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("int"))), + RuntimeType = typeof(int) + }; + + configuration.Arguments.Add(argumentConfiguration); + } + + configuration.Resolvers = context.Resolvers.GetUsersById(); + }, + (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_GetUsersById_id; + + public __Resolvers(global::HotChocolate.Resolvers.ParameterBindingResolver bindingResolver) + { + _binding_GetUsersById_id = bindingResolver.GetBinding(CreateParameterDescriptor_GetUsersById_id()); + } + + public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetUsersById_id() + => new HotChocolate.Internal.ParameterDescriptor( + "id", + typeof(int), + isNullable: false, + []); + + public HotChocolate.Resolvers.FieldResolverDelegates GetUsersById() + { + var isPureResolver = _binding_GetUsersById_id.IsPure; + + return isPureResolver + ? new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: GetUsersById) + : new global::HotChocolate.Resolvers.FieldResolverDelegates(resolver: c => new(GetUsersById(c))); + } + + private global::System.Object? GetUsersById(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = _binding_GetUsersById_id.Execute(context); + var result = global::TestNamespace.Query.GetUsersById(args0); + return result; + } + } + } +} + + +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_SingleReturn_NoError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_SingleReturn_NoError.md new file mode 100644 index 00000000000..1ab739d96dd --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_SingleReturn_NoError.md @@ -0,0 +1,171 @@ +# Method_SingleReturn_NoError + +## 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.AddSourceSchemaDefaults(); + builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd( + "Tests::TestNamespace.Query", + global::HotChocolate.Types.OperationTypeNames.Query, + () => global::TestNamespace.Query.Initialize)); + builder.ConfigureSchema( + b => b.TryAddRootType( + () => new global::HotChocolate.Types.ObjectType( + d => d.Name(global::HotChocolate.Types.OperationTypeNames.Query)), + HotChocolate.Language.OperationType.Query)); + return builder; + } + } +} + +``` + +## Query.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 +{ + internal static partial class Query + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var extension = descriptor.Extend(); + var configuration = extension.Configuration; + var thisType = typeof(global::TestNamespace.Query); + var bindingResolver = extension.Context.ParameterBindingResolver; + var resolvers = new __Resolvers(bindingResolver); + + HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration( + extension.Context, + descriptor, + null, + new global::HotChocolate.Types.QueryTypeAttribute()); + configuration.ConfigurationsAreApplied = true; + + var naming = descriptor.Extend().Context.Naming; + + descriptor + .Field(naming.GetMemberName("UserById", 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.User), HotChocolate.Types.TypeContext.Output); + configuration.ResultType = typeof(global::TestNamespace.User); + + configuration.SetSourceGeneratorFlags(); + + var bindingInfo = field.Context.ParameterBindingResolver; + var parameter = context.Resolvers.CreateParameterDescriptor_GetUserById_id(); + 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("id", global::HotChocolate.Types.MemberKind.Argument), + Type = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(int), HotChocolate.Types.TypeContext.Input), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("int"))), + RuntimeType = typeof(int) + }; + + configuration.Arguments.Add(argumentConfiguration); + } + + configuration.Member = context.ThisType.GetMethod( + "GetUserById", + global::HotChocolate.Utilities.ReflectionUtils.StaticMemberFlags, + new global::System.Type[] + { + typeof(int) + })!; + + var fieldDescriptor = global::HotChocolate.Types.Descriptors.ObjectFieldDescriptor.From(field.Context, configuration); + HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration( + field.Context, + fieldDescriptor, + configuration.Member, + new global::HotChocolate.Types.Composite.LookupAttribute()); + configuration.ConfigurationsAreApplied = true; + fieldDescriptor.CreateConfiguration(); + + configuration.Resolvers = context.Resolvers.GetUserById(); + }, + (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_GetUserById_id; + + public __Resolvers(global::HotChocolate.Resolvers.ParameterBindingResolver bindingResolver) + { + _binding_GetUserById_id = bindingResolver.GetBinding(CreateParameterDescriptor_GetUserById_id()); + } + + public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetUserById_id() + => new HotChocolate.Internal.ParameterDescriptor( + "id", + typeof(int), + isNullable: false, + []); + + public HotChocolate.Resolvers.FieldResolverDelegates GetUserById() + { + var isPureResolver = _binding_GetUserById_id.IsPure; + + return isPureResolver + ? new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: GetUserById) + : new global::HotChocolate.Resolvers.FieldResolverDelegates(resolver: c => new(GetUserById(c))); + } + + private global::System.Object? GetUserById(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = _binding_GetUserById_id.Execute(context); + var result = global::TestNamespace.Query.GetUserById(args0); + return result; + } + } + } +} + + +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_TaskListReturn_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_TaskListReturn_RaisesError.md new file mode 100644 index 00000000000..ec16006d038 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Method_TaskListReturn_RaisesError.md @@ -0,0 +1,186 @@ +# Method_TaskListReturn_RaisesError + +## 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.AddSourceSchemaDefaults(); + builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd( + "Tests::TestNamespace.Query", + global::HotChocolate.Types.OperationTypeNames.Query, + () => global::TestNamespace.Query.Initialize)); + builder.ConfigureSchema( + b => b.TryAddRootType( + () => new global::HotChocolate.Types.ObjectType( + d => d.Name(global::HotChocolate.Types.OperationTypeNames.Query)), + HotChocolate.Language.OperationType.Query)); + return builder; + } + } +} + +``` + +## Query.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 +{ + internal static partial class Query + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var extension = descriptor.Extend(); + var configuration = extension.Configuration; + var thisType = typeof(global::TestNamespace.Query); + var bindingResolver = extension.Context.ParameterBindingResolver; + var resolvers = new __Resolvers(bindingResolver); + + HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration( + extension.Context, + descriptor, + null, + new global::HotChocolate.Types.QueryTypeAttribute()); + configuration.ConfigurationsAreApplied = true; + + var naming = descriptor.Extend().Context.Naming; + + descriptor + .Field(naming.GetMemberName("UsersById", 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 = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(global::TestNamespace.User), HotChocolate.Types.TypeContext.Output), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.ListTypeNode(new global::HotChocolate.Language.NamedTypeNode("global__TestNamespace_User")))); + configuration.ResultType = typeof(global::System.Collections.Generic.List); + + configuration.SetSourceGeneratorFlags(); + + var bindingInfo = field.Context.ParameterBindingResolver; + var parameter = context.Resolvers.CreateParameterDescriptor_GetUsersByIdAsync_id(); + 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("id", global::HotChocolate.Types.MemberKind.Argument), + Type = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(int), HotChocolate.Types.TypeContext.Input), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("int"))), + RuntimeType = typeof(int) + }; + + configuration.Arguments.Add(argumentConfiguration); + } + + configuration.Member = context.ThisType.GetMethod( + "GetUsersByIdAsync", + global::HotChocolate.Utilities.ReflectionUtils.StaticMemberFlags, + new global::System.Type[] + { + typeof(int) + })!; + + var fieldDescriptor = global::HotChocolate.Types.Descriptors.ObjectFieldDescriptor.From(field.Context, configuration); + HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration( + field.Context, + fieldDescriptor, + configuration.Member, + new global::HotChocolate.Types.Composite.LookupAttribute()); + configuration.ConfigurationsAreApplied = true; + fieldDescriptor.CreateConfiguration(); + + configuration.Resolvers = context.Resolvers.GetUsersByIdAsync(); + configuration.ResultPostProcessor = global::HotChocolate.Execution.ListPostProcessor.Default; + }, + (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_GetUsersByIdAsync_id; + + public __Resolvers(global::HotChocolate.Resolvers.ParameterBindingResolver bindingResolver) + { + _binding_GetUsersByIdAsync_id = bindingResolver.GetBinding(CreateParameterDescriptor_GetUsersByIdAsync_id()); + } + + public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetUsersByIdAsync_id() + => new HotChocolate.Internal.ParameterDescriptor( + "id", + typeof(int), + isNullable: false, + []); + + public HotChocolate.Resolvers.FieldResolverDelegates GetUsersByIdAsync() + => new global::HotChocolate.Resolvers.FieldResolverDelegates(resolver: GetUsersByIdAsync); + + private async global::System.Threading.Tasks.ValueTask GetUsersByIdAsync(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = _binding_GetUsersByIdAsync_id.Execute(context); + var result = await global::TestNamespace.Query.GetUsersByIdAsync(args0); + return result; + } + } + } +} + + +``` + +## Analyzer Diagnostics + +```json +[ + { + "Id": "HC0114", + "Title": "Lookup Must Not Return List Type", + "Severity": "Error", + "WarningLevel": 0, + "Location": ": (13,18)-(13,35)", + "MessageFormat": "A method or property with the [Lookup] attribute must not return a list type", + "Message": "A method or property with the [Lookup] attribute must not return a list type", + "Category": "TypeSystem", + "CustomTags": [] + } +] +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Property_ListReturn_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Property_ListReturn_RaisesError.md new file mode 100644 index 00000000000..74ddf00d7e4 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsListTypeAnalyzerTests.Property_ListReturn_RaisesError.md @@ -0,0 +1,149 @@ +# Property_ListReturn_RaisesError + +## 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.AddSourceSchemaDefaults(); + builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd( + "Tests::TestNamespace.Query", + global::HotChocolate.Types.OperationTypeNames.Query, + () => global::TestNamespace.Query.Initialize)); + builder.ConfigureSchema( + b => b.TryAddRootType( + () => new global::HotChocolate.Types.ObjectType( + d => d.Name(global::HotChocolate.Types.OperationTypeNames.Query)), + HotChocolate.Language.OperationType.Query)); + return builder; + } + } +} + +``` + +## Query.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 +{ + internal static partial class Query + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var extension = descriptor.Extend(); + var configuration = extension.Configuration; + var thisType = typeof(global::TestNamespace.Query); + var bindingResolver = extension.Context.ParameterBindingResolver; + var resolvers = new __Resolvers(); + + HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration( + extension.Context, + descriptor, + null, + new global::HotChocolate.Types.QueryTypeAttribute()); + configuration.ConfigurationsAreApplied = true; + + var naming = descriptor.Extend().Context.Naming; + + descriptor + .Field(naming.GetMemberName("AllUsers", 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 = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(global::TestNamespace.User), HotChocolate.Types.TypeContext.Output), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.ListTypeNode(new global::HotChocolate.Language.NamedTypeNode("global__TestNamespace_User")))); + configuration.ResultType = typeof(global::System.Collections.Generic.List); + + configuration.SetSourceGeneratorFlags(); + + configuration.Member = context.ThisType.GetMethod( + "AllUsers", + global::HotChocolate.Utilities.ReflectionUtils.StaticMemberFlags, + global::System.Array.Empty()); + + var fieldDescriptor = global::HotChocolate.Types.Descriptors.ObjectFieldDescriptor.From(field.Context, configuration); + HotChocolate.Internal.ConfigurationHelper.ApplyConfiguration( + field.Context, + fieldDescriptor, + configuration.Member, + new global::HotChocolate.Types.Composite.LookupAttribute()); + configuration.ConfigurationsAreApplied = true; + fieldDescriptor.CreateConfiguration(); + + configuration.Resolvers = context.Resolvers.AllUsers(); + }, + (Resolvers: resolvers, ThisType: thisType)); + + Configure(descriptor); + } + + static partial void Configure(global::HotChocolate.Types.IObjectTypeDescriptor descriptor); + + private sealed class __Resolvers + { + public HotChocolate.Resolvers.FieldResolverDelegates AllUsers() + => new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: AllUsers); + + private global::System.Object? AllUsers(global::HotChocolate.Resolvers.IResolverContext context) + { + var result = global::TestNamespace.Query.AllUsers; + return result; + } + } + } +} + + +``` + +## Analyzer Diagnostics + +```json +[ + { + "Id": "HC0114", + "Title": "Lookup Must Not Return List Type", + "Severity": "Error", + "WarningLevel": 0, + "Location": ": (12,18)-(12,29)", + "MessageFormat": "A method or property with the [Lookup] attribute must not return a list type", + "Message": "A method or property with the [Lookup] attribute must not return a list type", + "Category": "TypeSystem", + "CustomTags": [] + } +] +```