diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Errors.cs b/src/HotChocolate/Core/src/Types.Analyzers/Errors.cs index d9e1f04867e..8f78932763f 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Errors.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Errors.cs @@ -256,4 +256,13 @@ public static class Errors category: "TypeSystem", DiagnosticSeverity.Error, isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor LookupReturnsNonNullableType = + new( + id: "HC0113", + title: "Lookup Must Return Nullable Type", + messageFormat: "A method or property with the [Lookup] attribute must return a nullable type", + category: "TypeSystem", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/LookupReturnsNonNullableTypeAnalyzer.cs b/src/HotChocolate/Core/src/Types.Analyzers/LookupReturnsNonNullableTypeAnalyzer.cs new file mode 100644 index 00000000000..2ae984f6901 --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/LookupReturnsNonNullableTypeAnalyzer.cs @@ -0,0 +1,112 @@ +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 LookupReturnsNonNullableTypeAnalyzer : DiagnosticAnalyzer +{ + public override ImmutableArray SupportedDiagnostics { get; } = + [Errors.LookupReturnsNonNullableType]; + + 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 (returnType.IsNullableType()) + { + return; + } + + var diagnostic = Diagnostic.Create( + Errors.LookupReturnsNonNullableType, + 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 (propertyType.IsNullableType()) + { + return; + } + + var diagnostic = Diagnostic.Create( + Errors.LookupReturnsNonNullableType, + propertyDeclaration.Type.GetLocation()); + + context.ReportDiagnostic(diagnostic); + } + + 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/LookupReturnsNonNullableTypeCodeFixProvider.cs b/src/HotChocolate/Core/src/Types.Analyzers/LookupReturnsNonNullableTypeCodeFixProvider.cs new file mode 100644 index 00000000000..73450f8c550 --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/LookupReturnsNonNullableTypeCodeFixProvider.cs @@ -0,0 +1,136 @@ +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace HotChocolate.Types.Analyzers; + +[Shared] +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(LookupReturnsNonNullableTypeCodeFixProvider))] +public sealed class LookupReturnsNonNullableTypeCodeFixProvider : CodeFixProvider +{ + public override ImmutableArray FixableDiagnosticIds { get; } = ["HC0113"]; + + public override FixAllProvider GetFixAllProvider() + => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + if (root is null) + { + return; + } + + var diagnostic = context.Diagnostics[0]; + var diagnosticSpan = diagnostic.Location.SourceSpan; + + var node = root.FindNode(diagnosticSpan); + + // Determine the type syntax to make nullable. + TypeSyntax? typeSyntax = null; + + var methodDeclaration = node.AncestorsAndSelf().OfType().FirstOrDefault(); + if (methodDeclaration is not null) + { + typeSyntax = methodDeclaration.ReturnType; + } + else + { + var propertyDeclaration = node.AncestorsAndSelf().OfType().FirstOrDefault(); + if (propertyDeclaration is not null) + { + typeSyntax = propertyDeclaration.Type; + } + } + + if (typeSyntax is null) + { + return; + } + + const string title = "Make return type nullable"; + + context.RegisterCodeFix( + CodeAction.Create( + title: title, + createChangedDocument: c => MakeReturnTypeNullableAsync( + context.Document, + typeSyntax, + c), + equivalenceKey: title), + diagnostic); + } + + private static async Task MakeReturnTypeNullableAsync( + Document document, + TypeSyntax typeSyntax, + CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + if (root is null) + { + return document; + } + + // Unwrap NullableTypeSyntax to handle cases like Task?. + var effectiveType = typeSyntax is NullableTypeSyntax nullableType + ? nullableType.ElementType + : typeSyntax; + + var genericName = FindTaskGenericName(effectiveType); + + TypeSyntax newTypeSyntax; + + if (genericName is not null) + { + var innerType = genericName.TypeArgumentList.Arguments[0]; + + // Guard against double-wrapping. + if (innerType is NullableTypeSyntax) + { + return document; + } + + var nullableInnerType = SyntaxFactory.NullableType(innerType); + var newTypeArgumentList = genericName.TypeArgumentList.WithArguments( + SyntaxFactory.SingletonSeparatedList(nullableInnerType)); + var newGenericName = genericName.WithTypeArgumentList(newTypeArgumentList); + + // Replace the generic name within the effective type to preserve qualification. + newTypeSyntax = effectiveType == genericName + ? newGenericName + : effectiveType.ReplaceNode(genericName, newGenericName); + } + else + { + // Guard against double-wrapping. + if (typeSyntax is NullableTypeSyntax) + { + return document; + } + + newTypeSyntax = SyntaxFactory.NullableType(typeSyntax); + } + + newTypeSyntax = newTypeSyntax.WithTriviaFrom(typeSyntax); + var newRoot = root.ReplaceNode(typeSyntax, newTypeSyntax); + return document.WithSyntaxRoot(newRoot); + } + + private static GenericNameSyntax? FindTaskGenericName(TypeSyntax typeSyntax) + => typeSyntax switch + { + GenericNameSyntax { TypeArgumentList.Arguments.Count: 1 } genericName + when genericName.Identifier.Text is nameof(Task) or nameof(ValueTask) + => genericName, + QualifiedNameSyntax qualifiedName + => FindTaskGenericName(qualifiedName.Right), + AliasQualifiedNameSyntax aliasName + => FindTaskGenericName(aliasName.Name), + _ => null + }; +} diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/LookupReturnsNonNullableTypeAnalyzerTests.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/LookupReturnsNonNullableTypeAnalyzerTests.cs new file mode 100644 index 00000000000..56b811c8bed --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/LookupReturnsNonNullableTypeAnalyzerTests.cs @@ -0,0 +1,286 @@ +namespace HotChocolate.Types; + +public class LookupReturnsNonNullableTypeAnalyzerTests +{ + [Fact] + public async Task Method_NonNullableReturn_RaisesWarning() + { + 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_TaskNonNullableReturn_RaisesWarning() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + #nullable enable + using HotChocolate; + using HotChocolate.Types; + using HotChocolate.Types.Composite; + using System.Threading.Tasks; + + namespace TestNamespace; + + [QueryType] + internal static partial class Query + { + [Lookup] + public static Task GetUserByIdAsync(int id) => default!; + } + + public class User + { + public int Id { get; set; } + public string? Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + + [Fact] + public async Task Method_ValueTaskNonNullableReturn_RaisesWarning() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + #nullable enable + using HotChocolate; + using HotChocolate.Types; + using HotChocolate.Types.Composite; + using System.Threading.Tasks; + + namespace TestNamespace; + + [QueryType] + internal static partial class Query + { + [Lookup] + public static ValueTask GetUserByIdAsync(int id) => default!; + } + + public class User + { + public int Id { get; set; } + public string? Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + + [Fact] + public async Task Property_NonNullableReturn_RaisesWarning() + { + 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 CurrentUser => default!; + } + + public class User + { + public int Id { get; set; } + public string? Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + + [Fact] + public async Task Method_NullableReturn_NoWarning() + { + 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_TaskNullableReturn_NoWarning() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + #nullable enable + using HotChocolate; + using HotChocolate.Types; + using HotChocolate.Types.Composite; + using System.Threading.Tasks; + + namespace TestNamespace; + + [QueryType] + internal static partial class Query + { + [Lookup] + public static Task GetUserByIdAsync(int id) => default!; + } + + public class User + { + public int Id { get; set; } + public string? Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + + [Fact] + public async Task Method_FullyQualifiedTaskNonNullableReturn_RaisesWarning() + { + 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 System.Threading.Tasks.Task GetUserByIdAsync(int id) => default!; + } + + public class User + { + public int Id { get; set; } + public string? Name { get; set; } + } + """], + enableAnalyzers: true).MatchMarkdownAsync(); + } + + [Fact] + public async Task Method_FullyQualifiedTaskNullableReturn_NoWarning() + { + 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 System.Threading.Tasks.Task GetUserByIdAsync(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_NoWarning() + { + await TestHelper.GetGeneratedSourceSnapshot( + [""" + #nullable enable + using HotChocolate; + using HotChocolate.Types; + using HotChocolate.Types.Composite; + + namespace TestNamespace; + + [QueryType] + internal static partial class Query + { + 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 Property_NullableReturn_NoWarning() + { + 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? CurrentUser => 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 d9a444bb10f..7c52f065696 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/TestHelper.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/TestHelper.cs @@ -220,7 +220,8 @@ private static Snapshot CreateSnapshot(CSharpCompilation compilation, GeneratorD new ShareableScopedOnMemberAnalyzer(), new DataAttributeOrderAnalyzer(), new IdAttributeOnRecordParameterAnalyzer(), - new WrongAuthorizationAttributeAnalyzer()); + new WrongAuthorizationAttributeAnalyzer(), + new LookupReturnsNonNullableTypeAnalyzer()); var compilationWithAnalyzers = compilation.WithAnalyzers(analyzers); var analyzerDiagnostics = compilationWithAnalyzers.GetAllDiagnosticsAsync().Result; diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_FullyQualifiedTaskNonNullableReturn_RaisesWarning.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_FullyQualifiedTaskNonNullableReturn_RaisesWarning.md new file mode 100644 index 00000000000..3971315822f --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_FullyQualifiedTaskNonNullableReturn_RaisesWarning.md @@ -0,0 +1,185 @@ +# Method_FullyQualifiedTaskNonNullableReturn_RaisesWarning + +## 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 = 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.NamedTypeNode("global__TestNamespace_User"))); + configuration.ResultType = typeof(global::TestNamespace.User); + + configuration.SetSourceGeneratorFlags(); + + var bindingInfo = field.Context.ParameterBindingResolver; + var parameter = context.Resolvers.CreateParameterDescriptor_GetUserByIdAsync_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( + "GetUserByIdAsync", + 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.GetUserByIdAsync(); + }, + (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_GetUserByIdAsync_id; + + public __Resolvers(global::HotChocolate.Resolvers.ParameterBindingResolver bindingResolver) + { + _binding_GetUserByIdAsync_id = bindingResolver.GetBinding(CreateParameterDescriptor_GetUserByIdAsync_id()); + } + + public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetUserByIdAsync_id() + => new HotChocolate.Internal.ParameterDescriptor( + "id", + typeof(int), + isNullable: false, + []); + + public HotChocolate.Resolvers.FieldResolverDelegates GetUserByIdAsync() + => new global::HotChocolate.Resolvers.FieldResolverDelegates(resolver: GetUserByIdAsync); + + private async global::System.Threading.Tasks.ValueTask GetUserByIdAsync(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = _binding_GetUserByIdAsync_id.Execute(context); + var result = await global::TestNamespace.Query.GetUserByIdAsync(args0); + return result; + } + } + } +} + + +``` + +## Analyzer Diagnostics + +```json +[ + { + "Id": "HC0113", + "Title": "Lookup Must Return Nullable Type", + "Severity": "Warning", + "WarningLevel": 1, + "Location": ": (11,18)-(11,51)", + "MessageFormat": "A method or property with the [Lookup] attribute must return a nullable type", + "Message": "A method or property with the [Lookup] attribute must return a nullable type", + "Category": "TypeSystem", + "CustomTags": [] + } +] +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_FullyQualifiedTaskNullableReturn_NoWarning.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_FullyQualifiedTaskNullableReturn_NoWarning.md new file mode 100644 index 00000000000..c6c7c7f1a69 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_FullyQualifiedTaskNullableReturn_NoWarning.md @@ -0,0 +1,165 @@ +# Method_FullyQualifiedTaskNullableReturn_NoWarning + +## 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_GetUserByIdAsync_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( + "GetUserByIdAsync", + 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.GetUserByIdAsync(); + }, + (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_GetUserByIdAsync_id; + + public __Resolvers(global::HotChocolate.Resolvers.ParameterBindingResolver bindingResolver) + { + _binding_GetUserByIdAsync_id = bindingResolver.GetBinding(CreateParameterDescriptor_GetUserByIdAsync_id()); + } + + public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetUserByIdAsync_id() + => new HotChocolate.Internal.ParameterDescriptor( + "id", + typeof(int), + isNullable: false, + []); + + public HotChocolate.Resolvers.FieldResolverDelegates GetUserByIdAsync() + => new global::HotChocolate.Resolvers.FieldResolverDelegates(resolver: GetUserByIdAsync); + + private async global::System.Threading.Tasks.ValueTask GetUserByIdAsync(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = _binding_GetUserByIdAsync_id.Execute(context); + var result = await global::TestNamespace.Query.GetUserByIdAsync(args0); + return result; + } + } + } +} + + +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_NoLookupAttribute_NoWarning.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_NoLookupAttribute_NoWarning.md new file mode 100644 index 00000000000..d4d31a81953 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_NoLookupAttribute_NoWarning.md @@ -0,0 +1,155 @@ +# Method_NoLookupAttribute_NoWarning + +## 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("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 = 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.NamedTypeNode("global__TestNamespace_User"))); + 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.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__/LookupReturnsNonNullableTypeAnalyzerTests.Method_NonNullableReturn_RaisesWarning.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_NonNullableReturn_RaisesWarning.md new file mode 100644 index 00000000000..caf080f9836 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_NonNullableReturn_RaisesWarning.md @@ -0,0 +1,191 @@ +# Method_NonNullableReturn_RaisesWarning + +## 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 = 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.NamedTypeNode("global__TestNamespace_User"))); + 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; + } + } + } +} + + +``` + +## Analyzer Diagnostics + +```json +[ + { + "Id": "HC0113", + "Title": "Lookup Must Return Nullable Type", + "Severity": "Warning", + "WarningLevel": 1, + "Location": ": (11,18)-(11,22)", + "MessageFormat": "A method or property with the [Lookup] attribute must return a nullable type", + "Message": "A method or property with the [Lookup] attribute must return a nullable type", + "Category": "TypeSystem", + "CustomTags": [] + } +] +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_NullableReturn_NoWarning.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_NullableReturn_NoWarning.md new file mode 100644 index 00000000000..cdd7b18b5a2 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_NullableReturn_NoWarning.md @@ -0,0 +1,171 @@ +# Method_NullableReturn_NoWarning + +## 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__/LookupReturnsNonNullableTypeAnalyzerTests.Method_TaskNonNullableReturn_RaisesWarning.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_TaskNonNullableReturn_RaisesWarning.md new file mode 100644 index 00000000000..89d177635f5 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_TaskNonNullableReturn_RaisesWarning.md @@ -0,0 +1,185 @@ +# Method_TaskNonNullableReturn_RaisesWarning + +## 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 = 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.NamedTypeNode("global__TestNamespace_User"))); + configuration.ResultType = typeof(global::TestNamespace.User); + + configuration.SetSourceGeneratorFlags(); + + var bindingInfo = field.Context.ParameterBindingResolver; + var parameter = context.Resolvers.CreateParameterDescriptor_GetUserByIdAsync_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( + "GetUserByIdAsync", + 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.GetUserByIdAsync(); + }, + (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_GetUserByIdAsync_id; + + public __Resolvers(global::HotChocolate.Resolvers.ParameterBindingResolver bindingResolver) + { + _binding_GetUserByIdAsync_id = bindingResolver.GetBinding(CreateParameterDescriptor_GetUserByIdAsync_id()); + } + + public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetUserByIdAsync_id() + => new HotChocolate.Internal.ParameterDescriptor( + "id", + typeof(int), + isNullable: false, + []); + + public HotChocolate.Resolvers.FieldResolverDelegates GetUserByIdAsync() + => new global::HotChocolate.Resolvers.FieldResolverDelegates(resolver: GetUserByIdAsync); + + private async global::System.Threading.Tasks.ValueTask GetUserByIdAsync(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = _binding_GetUserByIdAsync_id.Execute(context); + var result = await global::TestNamespace.Query.GetUserByIdAsync(args0); + return result; + } + } + } +} + + +``` + +## Analyzer Diagnostics + +```json +[ + { + "Id": "HC0113", + "Title": "Lookup Must Return Nullable Type", + "Severity": "Warning", + "WarningLevel": 1, + "Location": ": (12,18)-(12,28)", + "MessageFormat": "A method or property with the [Lookup] attribute must return a nullable type", + "Message": "A method or property with the [Lookup] attribute must return a nullable type", + "Category": "TypeSystem", + "CustomTags": [] + } +] +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_TaskNullableReturn_NoWarning.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_TaskNullableReturn_NoWarning.md new file mode 100644 index 00000000000..93398a70b29 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_TaskNullableReturn_NoWarning.md @@ -0,0 +1,165 @@ +# Method_TaskNullableReturn_NoWarning + +## 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_GetUserByIdAsync_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( + "GetUserByIdAsync", + 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.GetUserByIdAsync(); + }, + (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_GetUserByIdAsync_id; + + public __Resolvers(global::HotChocolate.Resolvers.ParameterBindingResolver bindingResolver) + { + _binding_GetUserByIdAsync_id = bindingResolver.GetBinding(CreateParameterDescriptor_GetUserByIdAsync_id()); + } + + public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetUserByIdAsync_id() + => new HotChocolate.Internal.ParameterDescriptor( + "id", + typeof(int), + isNullable: false, + []); + + public HotChocolate.Resolvers.FieldResolverDelegates GetUserByIdAsync() + => new global::HotChocolate.Resolvers.FieldResolverDelegates(resolver: GetUserByIdAsync); + + private async global::System.Threading.Tasks.ValueTask GetUserByIdAsync(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = _binding_GetUserByIdAsync_id.Execute(context); + var result = await global::TestNamespace.Query.GetUserByIdAsync(args0); + return result; + } + } + } +} + + +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_ValueTaskNonNullableReturn_RaisesWarning.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_ValueTaskNonNullableReturn_RaisesWarning.md new file mode 100644 index 00000000000..1867af0afee --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Method_ValueTaskNonNullableReturn_RaisesWarning.md @@ -0,0 +1,185 @@ +# Method_ValueTaskNonNullableReturn_RaisesWarning + +## 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 = 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.NamedTypeNode("global__TestNamespace_User"))); + configuration.ResultType = typeof(global::TestNamespace.User); + + configuration.SetSourceGeneratorFlags(); + + var bindingInfo = field.Context.ParameterBindingResolver; + var parameter = context.Resolvers.CreateParameterDescriptor_GetUserByIdAsync_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( + "GetUserByIdAsync", + 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.GetUserByIdAsync(); + }, + (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_GetUserByIdAsync_id; + + public __Resolvers(global::HotChocolate.Resolvers.ParameterBindingResolver bindingResolver) + { + _binding_GetUserByIdAsync_id = bindingResolver.GetBinding(CreateParameterDescriptor_GetUserByIdAsync_id()); + } + + public global::HotChocolate.Internal.ParameterDescriptor CreateParameterDescriptor_GetUserByIdAsync_id() + => new HotChocolate.Internal.ParameterDescriptor( + "id", + typeof(int), + isNullable: false, + []); + + public HotChocolate.Resolvers.FieldResolverDelegates GetUserByIdAsync() + => new global::HotChocolate.Resolvers.FieldResolverDelegates(resolver: GetUserByIdAsync); + + private async global::System.Threading.Tasks.ValueTask GetUserByIdAsync(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = _binding_GetUserByIdAsync_id.Execute(context); + var result = await global::TestNamespace.Query.GetUserByIdAsync(args0); + return result; + } + } + } +} + + +``` + +## Analyzer Diagnostics + +```json +[ + { + "Id": "HC0113", + "Title": "Lookup Must Return Nullable Type", + "Severity": "Warning", + "WarningLevel": 1, + "Location": ": (12,18)-(12,33)", + "MessageFormat": "A method or property with the [Lookup] attribute must return a nullable type", + "Message": "A method or property with the [Lookup] attribute must return a nullable type", + "Category": "TypeSystem", + "CustomTags": [] + } +] +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Property_NonNullableReturn_RaisesWarning.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Property_NonNullableReturn_RaisesWarning.md new file mode 100644 index 00000000000..739ce151daa --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Property_NonNullableReturn_RaisesWarning.md @@ -0,0 +1,149 @@ +# Property_NonNullableReturn_RaisesWarning + +## 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("CurrentUser", 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.NamedTypeNode("global__TestNamespace_User"))); + configuration.ResultType = typeof(global::TestNamespace.User); + + configuration.SetSourceGeneratorFlags(); + + configuration.Member = context.ThisType.GetMethod( + "CurrentUser", + 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.CurrentUser(); + }, + (Resolvers: resolvers, ThisType: thisType)); + + Configure(descriptor); + } + + static partial void Configure(global::HotChocolate.Types.IObjectTypeDescriptor descriptor); + + private sealed class __Resolvers + { + public HotChocolate.Resolvers.FieldResolverDelegates CurrentUser() + => new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: CurrentUser); + + private global::System.Object? CurrentUser(global::HotChocolate.Resolvers.IResolverContext context) + { + var result = global::TestNamespace.Query.CurrentUser; + return result; + } + } + } +} + + +``` + +## Analyzer Diagnostics + +```json +[ + { + "Id": "HC0113", + "Title": "Lookup Must Return Nullable Type", + "Severity": "Warning", + "WarningLevel": 1, + "Location": ": (11,18)-(11,22)", + "MessageFormat": "A method or property with the [Lookup] attribute must return a nullable type", + "Message": "A method or property with the [Lookup] attribute must return a nullable type", + "Category": "TypeSystem", + "CustomTags": [] + } +] +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Property_NullableReturn_NoWarning.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Property_NullableReturn_NoWarning.md new file mode 100644 index 00000000000..3140ef34e32 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/LookupReturnsNonNullableTypeAnalyzerTests.Property_NullableReturn_NoWarning.md @@ -0,0 +1,129 @@ +# Property_NullableReturn_NoWarning + +## 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("CurrentUser", 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(); + + configuration.Member = context.ThisType.GetMethod( + "CurrentUser", + 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.CurrentUser(); + }, + (Resolvers: resolvers, ThisType: thisType)); + + Configure(descriptor); + } + + static partial void Configure(global::HotChocolate.Types.IObjectTypeDescriptor descriptor); + + private sealed class __Resolvers + { + public HotChocolate.Resolvers.FieldResolverDelegates CurrentUser() + => new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: CurrentUser); + + private global::System.Object? CurrentUser(global::HotChocolate.Resolvers.IResolverContext context) + { + var result = global::TestNamespace.Query.CurrentUser; + return result; + } + } + } +} + + +```