diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Errors.cs b/src/HotChocolate/Core/src/Types.Analyzers/Errors.cs index cdfdea45351..a1214571b84 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Errors.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Errors.cs @@ -25,7 +25,7 @@ public static class Errors public static readonly DiagnosticDescriptor ObjectTypePartialKeywordMissing = new( - id: "HC00XX", + id: "HC0080", title: "Partial Keyword Missing.", messageFormat: "A split object type class needs to be a partial class.", category: "TypeSystem", @@ -34,7 +34,7 @@ public static class Errors public static readonly DiagnosticDescriptor ObjectTypeStaticKeywordMissing = new( - id: "HC00XX", + id: "HC0081", title: "Static Keyword Missing.", messageFormat: "A split object type class needs to be a static class.", category: "TypeSystem", diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/DataLoaderSyntaxGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/DataLoaderFileBuilder.cs similarity index 98% rename from src/HotChocolate/Core/src/Types.Analyzers/Generators/DataLoaderSyntaxGenerator.cs rename to src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/DataLoaderFileBuilder.cs index 4b6df65d077..501feb026c0 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Generators/DataLoaderSyntaxGenerator.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/DataLoaderFileBuilder.cs @@ -4,15 +4,15 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; -namespace HotChocolate.Types.Analyzers.Generators; +namespace HotChocolate.Types.Analyzers.FileBuilders; -public sealed class DataLoaderSyntaxGenerator : IDisposable +public sealed class DataLoaderFileBuilder : IDisposable { private StringBuilder _sb; private CodeWriter _writer; private bool _disposed; - public DataLoaderSyntaxGenerator() + public DataLoaderFileBuilder() { _sb = StringBuilderPool.Get(); _writer = new CodeWriter(_sb); diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ModuleSyntaxGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/ModuleFileBuilder.cs similarity index 97% rename from src/HotChocolate/Core/src/Types.Analyzers/Generators/ModuleSyntaxGenerator.cs rename to src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/ModuleFileBuilder.cs index 3c0a58fbeac..9eecbcc42af 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ModuleSyntaxGenerator.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/ModuleFileBuilder.cs @@ -1,10 +1,11 @@ using System.Text; using HotChocolate.Types.Analyzers.Helpers; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis.Text; -namespace HotChocolate.Types.Analyzers.Generators; +namespace HotChocolate.Types.Analyzers.FileBuilders; -public sealed class ModuleSyntaxGenerator : IDisposable +public sealed class ModuleFileBuilder : IDisposable { private readonly string _moduleName; private readonly string _ns; @@ -12,7 +13,7 @@ public sealed class ModuleSyntaxGenerator : IDisposable private CodeWriter _writer; private bool _disposed; - public ModuleSyntaxGenerator(string moduleName, string ns) + public ModuleFileBuilder(string moduleName, string ns) { _moduleName = moduleName; _ns = ns; diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ObjectTypeExtensionSyntaxGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/ObjectTypeExtensionFileBuilder.cs similarity index 97% rename from src/HotChocolate/Core/src/Types.Analyzers/Generators/ObjectTypeExtensionSyntaxGenerator.cs rename to src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/ObjectTypeExtensionFileBuilder.cs index 014ef3bd959..a8a46f34fed 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ObjectTypeExtensionSyntaxGenerator.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/ObjectTypeExtensionFileBuilder.cs @@ -1,11 +1,11 @@ using System.Text; using HotChocolate.Types.Analyzers.Helpers; -using HotChocolate.Types.Analyzers.Inspectors; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis; -namespace HotChocolate.Types.Analyzers.Generators; +namespace HotChocolate.Types.Analyzers.FileBuilders; -public sealed class ObjectTypeExtensionSyntaxGenerator(StringBuilder sb, string ns) +public sealed class ObjectTypeExtensionFileBuilder(StringBuilder sb, string ns) { private readonly CodeWriter _writer = new(sb); diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/OperationFieldSyntaxGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/OperationFieldFileBuilder.cs similarity index 94% rename from src/HotChocolate/Core/src/Types.Analyzers/Generators/OperationFieldSyntaxGenerator.cs rename to src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/OperationFieldFileBuilder.cs index a9f9093e826..87522f2dcc0 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Generators/OperationFieldSyntaxGenerator.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/OperationFieldFileBuilder.cs @@ -1,18 +1,18 @@ using System.Text; using HotChocolate.Types.Analyzers.Helpers; -using HotChocolate.Types.Analyzers.Inspectors; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis.Text; -namespace HotChocolate.Types.Analyzers.Generators; +namespace HotChocolate.Types.Analyzers.FileBuilders; -public sealed class OperationFieldSyntaxGenerator: IDisposable +public sealed class OperationFieldFileBuilder : IDisposable { private StringBuilder _sb; private CodeWriter _writer; private bool _first = true; private bool _disposed; - public OperationFieldSyntaxGenerator() + public OperationFieldFileBuilder() { _sb = StringBuilderPool.Get(); _writer = new CodeWriter(_sb); diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/RequestMiddlewareSyntaxGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/RequestMiddlewareFileBuilder.cs similarity index 97% rename from src/HotChocolate/Core/src/Types.Analyzers/Generators/RequestMiddlewareSyntaxGenerator.cs rename to src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/RequestMiddlewareFileBuilder.cs index 46e4169bf28..75d8889fad7 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Generators/RequestMiddlewareSyntaxGenerator.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/RequestMiddlewareFileBuilder.cs @@ -1,12 +1,12 @@ using System.Text; using HotChocolate.Types.Analyzers.Helpers; -using HotChocolate.Types.Analyzers.Inspectors; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis.Text; using static HotChocolate.Types.Analyzers.WellKnownTypes; -namespace HotChocolate.Types.Analyzers.Generators; +namespace HotChocolate.Types.Analyzers.FileBuilders; -public sealed class RequestMiddlewareSyntaxGenerator : IDisposable +public sealed class RequestMiddlewareFileBuilder : IDisposable { private readonly string _moduleName; private readonly string _ns; @@ -15,7 +15,7 @@ public sealed class RequestMiddlewareSyntaxGenerator : IDisposable private CodeWriter _writer; private bool _disposed; - public RequestMiddlewareSyntaxGenerator(string moduleName, string ns) + public RequestMiddlewareFileBuilder(string moduleName, string ns) { _moduleName = moduleName; _ns = ns; diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ResolverSyntaxGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/ResolverFileBuilder.cs similarity index 72% rename from src/HotChocolate/Core/src/Types.Analyzers/Generators/ResolverSyntaxGenerator.cs rename to src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/ResolverFileBuilder.cs index 16d9fdf17f9..59f2acca36e 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ResolverSyntaxGenerator.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/ResolverFileBuilder.cs @@ -1,10 +1,14 @@ +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Text; using HotChocolate.Types.Analyzers.Helpers; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace HotChocolate.Types.Analyzers.Generators; +namespace HotChocolate.Types.Analyzers.FileBuilders; -public sealed class ResolverSyntaxGenerator(StringBuilder sb) +public sealed class ResolverFileBuilder(StringBuilder sb) { private readonly CodeWriter _writer = new(sb); @@ -59,6 +63,7 @@ public bool AddResolverDeclarations(IEnumerable resolvers) { _writer.WriteLine(); } + first = false; _writer.WriteIndentedLine( @@ -72,7 +77,7 @@ public bool AddResolverDeclarations(IEnumerable resolvers) return !first; } - public void AddParameterInitializer(IEnumerable resolvers) + public void AddParameterInitializer(IEnumerable resolvers, ILocalTypeLookup typeLookup) { _writer.WriteIndentedLine( "public static void InitializeBindings(global::{0} bindingResolver)", @@ -83,65 +88,79 @@ public void AddParameterInitializer(IEnumerable resolvers) var first = true; foreach (var resolver in resolvers) { - if (!resolver.Skip && resolver.Method is not null) + if (resolver is not { Skip: false, Method: not null }) + { + continue; + } + + if (first) { - if (first) + _writer.WriteIndentedLine("if (_bindingsInitialized)"); + _writer.WriteIndentedLine("{"); + using (_writer.IncreaseIndent()) { - _writer.WriteIndentedLine("if (_bindingsInitialized)"); - _writer.WriteIndentedLine("{"); - using (_writer.IncreaseIndent()) - { - _writer.WriteIndentedLine("return;"); - } - _writer.WriteIndentedLine("}"); - _writer.WriteIndentedLine("_bindingsInitialized = true;"); - _writer.WriteLine(); - _writer.WriteIndentedLine( - "const global::{0} bindingFlags =", - WellKnownTypes.BindingFlags); - _writer.WriteIndentedLine( - " global::{0}.Public", - WellKnownTypes.BindingFlags); - _writer.WriteIndentedLine( - " | global::{0}.NonPublic", - WellKnownTypes.BindingFlags); - _writer.WriteIndentedLine( - " | global::{0}.Static;", - WellKnownTypes.BindingFlags); - _writer.WriteIndentedLine( - "var type = typeof(global::{0});", - resolver.Method.ContainingType.ToDisplayString()); - first = false; + _writer.WriteIndentedLine("return;"); } + _writer.WriteIndentedLine("}"); + _writer.WriteIndentedLine("_bindingsInitialized = true;"); _writer.WriteLine(); _writer.WriteIndentedLine( - "var resolver_{0}_{1} = type.GetMethod(\"{1}\", bindingFlags, [{2}])!;", - resolver.Name.TypeName, - resolver.Name.MemberName, - string.Join(", ", resolver.Method.Parameters.Select( - p => $"typeof(global::{p.Type.ToDisplayString()})"))); + "const global::{0} bindingFlags =", + WellKnownTypes.BindingFlags); + _writer.WriteIndentedLine( + " global::{0}.Public", + WellKnownTypes.BindingFlags); + _writer.WriteIndentedLine( + " | global::{0}.NonPublic", + WellKnownTypes.BindingFlags); + _writer.WriteIndentedLine( + " | global::{0}.Static;", + WellKnownTypes.BindingFlags); + _writer.WriteIndentedLine( + "var type = typeof(global::{0});", + resolver.Method.ContainingType.ToDisplayString()); + first = false; + } + _writer.WriteLine(); + _writer.WriteIndentedLine( + "var resolver_{0}_{1} = type.GetMethod(\"{1}\", bindingFlags, [{2}])!;", + resolver.Name.TypeName, + resolver.Name.MemberName, + string.Join(", ", resolver.Method.Parameters.Select( + p => $"typeof(global::{ToDisplayString(p.Type, resolver, typeLookup)})"))); + + _writer.WriteIndentedLine( + "var parameters_{0}_{1} = resolver_{0}_{1}.GetParameters();", + resolver.Name.TypeName, + resolver.Name.MemberName); + + for (var i = 0; i < resolver.Method.Parameters.Length; i++) + { _writer.WriteIndentedLine( - "var parameters_{0}_{1} = resolver_{0}_{1}.GetParameters();", + "_args_{0}_{1}[{2}] = bindingResolver.GetBinding(parameters_{0}_{1}[{2}]);", resolver.Name.TypeName, resolver.Name.MemberName, - string.Join(", ", resolver.Method.Parameters.Select(p => $"typeof({p.Type.ToDisplayString()})"))); - - for (var i = 0; i < resolver.Method.Parameters.Length; i++) - { - _writer.WriteIndentedLine( - "_args_{0}_{1}[{2}] = bindingResolver.GetBinding(parameters_{0}_{1}[{2}]);", - resolver.Name.TypeName, - resolver.Name.MemberName, - i); - } + i); } } } + _writer.WriteIndentedLine("}"); } + private static string ToDisplayString(ITypeSymbol type, ResolverInfo resolver, ILocalTypeLookup typeLookup) + { + if (type.TypeKind is TypeKind.Error && + typeLookup.TryGetTypeName(type, resolver, out var typeDisplayName)) + { + return typeDisplayName; + } + + return type.ToDisplayString(); + } + public void AddResolver(ResolverName resolverName, ISymbol member, Compilation compilation) { if (member is IMethodSymbol method) @@ -223,6 +242,7 @@ private void AddStaticStandardResolver( WellKnownTypes.Object); } } + _writer.WriteIndentedLine("}"); } @@ -247,6 +267,7 @@ private void AddStaticPureResolver(ResolverName resolverName, IMethodSymbol meth _writer.WriteIndentedLine("return result;"); } + _writer.WriteIndentedLine("}"); } @@ -271,6 +292,7 @@ private void AddStaticPropertyResolver(ResolverName resolverName, ISymbol method WellKnownTypes.ValueTask, WellKnownTypes.Object); } + _writer.WriteIndentedLine("}"); } @@ -450,6 +472,7 @@ private string GetResolverArguments(IMethodSymbol method) { arguments.Append(", "); } + arguments.Append($"args{i}"); } @@ -478,7 +501,8 @@ private static string ConvertDefaultValueToString(object? defaultValue, ITypeSym return defaultValue.ToString().ToLower(); } - if (type.SpecialType == SpecialType.System_Double || type.SpecialType == SpecialType.System_Single) + if (type.SpecialType == SpecialType.System_Double || + type.SpecialType == SpecialType.System_Single) { return $"{defaultValue}d"; } @@ -488,7 +512,8 @@ private static string ConvertDefaultValueToString(object? defaultValue, ITypeSym return $"{defaultValue}m"; } - if (type.SpecialType == SpecialType.System_Int64 || type.SpecialType == SpecialType.System_UInt64) + if (type.SpecialType == SpecialType.System_Int64 || + type.SpecialType == SpecialType.System_UInt64) { return $"{defaultValue}L"; } @@ -496,3 +521,92 @@ private static string ConvertDefaultValueToString(object? defaultValue, ITypeSym return defaultValue.ToString(); } } + +public interface ILocalTypeLookup +{ + bool TryGetTypeName(ITypeSymbol type, ResolverInfo resolver, [NotNullWhen(true)] out string? typeDisplayName); +} + +public sealed class DefaultLocalTypeLookup(ImmutableArray syntaxInfos) : ILocalTypeLookup +{ + private Dictionary>? _typeNameLookup; + + public bool TryGetTypeName(ITypeSymbol type, ResolverInfo resolver, [NotNullWhen(true)] out string? typeDisplayName) + { + var typeNameLookup = GetTypeNameLookup(); + + if(!typeNameLookup.TryGetValue(type.Name, out var typeNames)) + { + typeDisplayName = null; + return false; + } + + if(typeNames.Count == 1) + { + typeDisplayName = typeNames[0]; + return true; + } + + if (resolver.Method is not null) + { + foreach (var namespaceString in GetContainingNamespaces(resolver.Method)) + { + if (typeNames.Contains($"{namespaceString}.{type.Name}")) + { + typeDisplayName = typeNames[0]; + return true; + } + } + } + + typeDisplayName = type.Name; + return true; + } + + private Dictionary> GetTypeNameLookup() + { + if (_typeNameLookup is null) + { + _typeNameLookup = new Dictionary>(); + foreach (var dataLoaderInfo in syntaxInfos.OfType()) + { + if (!_typeNameLookup.TryGetValue(dataLoaderInfo.Name, out var typeNames)) + { + typeNames = []; + _typeNameLookup[dataLoaderInfo.Name] = typeNames; + } + + typeNames.Add(dataLoaderInfo.FullName); + + if (!_typeNameLookup.TryGetValue(dataLoaderInfo.InterfaceName, out typeNames)) + { + typeNames = []; + _typeNameLookup[dataLoaderInfo.InterfaceName] = typeNames; + } + + typeNames.Add(dataLoaderInfo.InterfaceFullName); + } + } + + return _typeNameLookup; + } + + private IEnumerable GetContainingNamespaces(IMethodSymbol methodSymbol) + { + var namespaces = new HashSet(); + var syntaxTree = methodSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.SyntaxTree; + + if (syntaxTree != null) + { + var root = syntaxTree.GetRoot(); + var namespaceDeclarations = root.DescendantNodes().OfType(); + + foreach (var namespaceDeclaration in namespaceDeclarations) + { + namespaces.Add(namespaceDeclaration.Name.ToString()); + } + } + + return namespaces; + } +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Filters/AssemblyAttributeList.cs b/src/HotChocolate/Core/src/Types.Analyzers/Filters/AssemblyAttributeList.cs new file mode 100644 index 00000000000..48f7ba9684a --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Filters/AssemblyAttributeList.cs @@ -0,0 +1,14 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace HotChocolate.Types.Analyzers.Filters; + +public sealed class AssemblyAttributeList : ISyntaxFilter +{ + private AssemblyAttributeList() { } + + public static AssemblyAttributeList Instance { get; } = new(); + + public bool IsMatch(SyntaxNode node) + => node is AttributeListSyntax; +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Filters/ClassWithBaseClass.cs b/src/HotChocolate/Core/src/Types.Analyzers/Filters/ClassWithBaseClass.cs new file mode 100644 index 00000000000..ce33081ee4b --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Filters/ClassWithBaseClass.cs @@ -0,0 +1,14 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace HotChocolate.Types.Analyzers.Filters; + +public sealed class ClassWithBaseClass : ISyntaxFilter +{ + private ClassWithBaseClass() { } + + public static ClassWithBaseClass Instance { get; } = new(); + + public bool IsMatch(SyntaxNode node) + => node is ClassDeclarationSyntax { BaseList.Types.Count: > 0 }; +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Filters/ISyntaxFilter.cs b/src/HotChocolate/Core/src/Types.Analyzers/Filters/ISyntaxFilter.cs new file mode 100644 index 00000000000..5de7298396b --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Filters/ISyntaxFilter.cs @@ -0,0 +1,8 @@ +using Microsoft.CodeAnalysis; + +namespace HotChocolate.Types.Analyzers.Filters; + +public interface ISyntaxFilter +{ + bool IsMatch(SyntaxNode node); +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Filters/MethodWithAttribute.cs b/src/HotChocolate/Core/src/Types.Analyzers/Filters/MethodWithAttribute.cs new file mode 100644 index 00000000000..155f60ab8bb --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Filters/MethodWithAttribute.cs @@ -0,0 +1,14 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace HotChocolate.Types.Analyzers.Filters; + +public sealed class MethodWithAttribute : ISyntaxFilter +{ + private MethodWithAttribute() { } + + public static MethodWithAttribute Instance { get; } = new(); + + public bool IsMatch(SyntaxNode node) + => node is MethodDeclarationSyntax { AttributeLists.Count: > 0 }; +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Filters/MiddlewareMethod.cs b/src/HotChocolate/Core/src/Types.Analyzers/Filters/MiddlewareMethod.cs new file mode 100644 index 00000000000..783c5fd10d3 --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Filters/MiddlewareMethod.cs @@ -0,0 +1,21 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace HotChocolate.Types.Analyzers.Filters; + +internal sealed class MiddlewareMethod : ISyntaxFilter +{ + private MiddlewareMethod() { } + + public static MiddlewareMethod Instance { get; } = new(); + + public bool IsMatch(SyntaxNode node) + => node is InvocationExpressionSyntax + { + Expression: MemberAccessExpressionSyntax + { + Name.Identifier.ValueText: var method, + }, + } && + (method.Equals("UseRequest") || method.Equals("UseField") || method.Equals("Use")); +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Filters/SyntaxFilterBuilder.cs b/src/HotChocolate/Core/src/Types.Analyzers/Filters/SyntaxFilterBuilder.cs new file mode 100644 index 00000000000..cc3a31aa7c1 --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Filters/SyntaxFilterBuilder.cs @@ -0,0 +1,43 @@ +using Microsoft.CodeAnalysis; + +namespace HotChocolate.Types.Analyzers.Filters; + +public sealed class SyntaxFilterBuilder +{ + private readonly List _filters = []; + + public SyntaxFilterBuilder Add(ISyntaxFilter filter) + { + if (!_filters.Contains(filter)) + { + _filters.Add(filter); + } + return this; + } + + public SyntaxFilterBuilder AddRange(IEnumerable filters) + { + foreach (var filter in filters) + { + if (!_filters.Contains(filter)) + { + _filters.Add(filter); + } + } + return this; + } + + public Func Build() + { + Func current = _ => false; + + for (var i = _filters.Count - 1; i >= 0; i--) + { + var filter = _filters[i]; + var parent = current; + current = node => filter.IsMatch(node) || parent(node); + } + + return current; + } +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Filters/TypeWithAttribute.cs b/src/HotChocolate/Core/src/Types.Analyzers/Filters/TypeWithAttribute.cs new file mode 100644 index 00000000000..7923077748a --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Filters/TypeWithAttribute.cs @@ -0,0 +1,14 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace HotChocolate.Types.Analyzers.Filters; + +public sealed class TypeWithAttribute : ISyntaxFilter +{ + private TypeWithAttribute() { } + + public static TypeWithAttribute Instance { get; } = new(); + + public bool IsMatch(SyntaxNode node) + => node is BaseTypeDeclarationSyntax { AttributeLists.Count: > 0 }; +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ISyntaxGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/Generators/ISyntaxGenerator.cs new file mode 100644 index 00000000000..4c463d08802 --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Generators/ISyntaxGenerator.cs @@ -0,0 +1,13 @@ +using System.Collections.Immutable; +using HotChocolate.Types.Analyzers.Models; +using Microsoft.CodeAnalysis; + +namespace HotChocolate.Types.Analyzers.Generators; + +public interface ISyntaxGenerator +{ + void Generate( + SourceProductionContext context, + Compilation compilation, + ImmutableArray syntaxInfos); +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/MiddlewareGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/Generators/MiddlewareGenerator.cs new file mode 100644 index 00000000000..cb9f8675b74 --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Generators/MiddlewareGenerator.cs @@ -0,0 +1,57 @@ +using System.Collections.Immutable; +using HotChocolate.Types.Analyzers.FileBuilders; +using HotChocolate.Types.Analyzers.Helpers; +using HotChocolate.Types.Analyzers.Models; +using Microsoft.CodeAnalysis; + +namespace HotChocolate.Types.Analyzers.Generators; + +public sealed class MiddlewareGenerator : ISyntaxGenerator +{ + private const string _namespace = "HotChocolate.Execution.Generated"; + + public void Generate( + SourceProductionContext context, + Compilation compilation, + ImmutableArray syntaxInfos) + { + if (syntaxInfos.IsEmpty) + { + return; + } + + var module = syntaxInfos.GetModuleInfo(compilation.AssemblyName, out var defaultModule); + + // if there is only the module info we do not need to generate a module. + if (!defaultModule && syntaxInfos.Length == 1) + { + return; + } + + using var generator = new RequestMiddlewareFileBuilder(module.ModuleName, _namespace); + + generator.WriteHeader(); + generator.WriteBeginNamespace(); + + generator.WriteBeginClass(); + + var i = 0; + foreach (var syntaxInfo in syntaxInfos) + { + if (syntaxInfo is not RequestMiddlewareInfo middleware) + { + continue; + } + + generator.WriteFactory(i, middleware); + generator.WriteInterceptMethod(i, middleware.Location); + i++; + } + + generator.WriteEndClass(); + + generator.WriteEndNamespace(); + + context.AddSource(WellKnownFileNames.MiddlewareFile, generator.ToSourceText()); + } +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/SymbolExtensions.cs b/src/HotChocolate/Core/src/Types.Analyzers/Generators/SymbolExtensions.cs deleted file mode 100644 index 241c85fd476..00000000000 --- a/src/HotChocolate/Core/src/Types.Analyzers/Generators/SymbolExtensions.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; - -namespace HotChocolate.Types.Analyzers.Generators; - -public static class SymbolExtensions -{ - public static bool IsParent(this IParameterSymbol parameter) - => parameter.IsThis || - parameter - .GetAttributes() - .Any(static t => t.AttributeClass?.ToDisplayString() == WellKnownAttributes.ParentAttribute); - - public static bool IsCancellationToken(this IParameterSymbol parameter) - => parameter.Type.ToDisplayString() == WellKnownTypes.CancellationToken; - - public static bool IsClaimsPrincipal(this IParameterSymbol parameter) - => parameter.Type.ToDisplayString() == WellKnownTypes.ClaimsPrincipal; - - public static bool IsDocumentNode(this IParameterSymbol parameter) - => parameter.Type.ToDisplayString() == WellKnownTypes.DocumentNode; - - public static bool IsEventMessage(this IParameterSymbol parameter) - => parameter.Type.ToDisplayString() == WellKnownAttributes.EnumTypeAttribute; - - public static bool IsFieldNode(this IParameterSymbol parameter) - => parameter.Type.ToDisplayString() == WellKnownAttributes.FieldNode; - - public static bool IsOutputField(this IParameterSymbol parameterSymbol, Compilation compilation) - { - var type = compilation.GetTypeByMetadataName(WellKnownTypes.OutputField); - - if (type == null) - { - return false; - } - - return compilation.ClassifyConversion(parameterSymbol.Type, type).IsImplicit; - } - - public static bool IsGlobalState( - this IParameterSymbol parameter, - [NotNullWhen(true)] out string? key) - { - key = null; - - foreach (var attributeData in parameter.GetAttributes()) - { - if (attributeData.AttributeClass?.ToDisplayString() == "HotChocolate.GlobalStateAttribute") - { - if (attributeData.ConstructorArguments.Length == 1 && - attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Primitive && - attributeData.ConstructorArguments[0].Value is string keyValue) - { - key = keyValue; - return true; - } - - foreach (var namedArg in attributeData.NamedArguments) - { - if (namedArg.Key == "Key" && namedArg.Value.Value is string namedKeyValue) - { - key = namedKeyValue; - return true; - } - } - - key = parameter.Name; - return true; - } - } - - return false; - } - - public static bool IsScopedState( - this IParameterSymbol parameter, - [NotNullWhen(true)] out string? key) - { - key = null; - - foreach (var attributeData in parameter.GetAttributes()) - { - if (attributeData.AttributeClass?.ToDisplayString() == "HotChocolate.ScopedStateAttribute") - { - if (attributeData.ConstructorArguments.Length == 1 && - attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Primitive && - attributeData.ConstructorArguments[0].Value is string keyValue) - { - key = keyValue; - return true; - } - - foreach (var namedArg in attributeData.NamedArguments) - { - if (namedArg.Key == "Key" && namedArg.Value.Value is string namedKeyValue) - { - key = namedKeyValue; - return true; - } - } - - key = parameter.Name; - return true; - } - } - - return false; - } - - public static bool IsLocalState( - this IParameterSymbol parameter, - [NotNullWhen(true)] out string? key) - { - key = null; - - foreach (var attributeData in parameter.GetAttributes()) - { - if (attributeData.AttributeClass?.ToDisplayString() == "HotChocolate.LocalStateAttribute") - { - if (attributeData.ConstructorArguments.Length == 1 && - attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Primitive && - attributeData.ConstructorArguments[0].Value is string keyValue) - { - key = keyValue; - return true; - } - - foreach (var namedArg in attributeData.NamedArguments) - { - if (namedArg.Key == "Key" && namedArg.Value.Value is string namedKeyValue) - { - key = namedKeyValue; - return true; - } - } - - key = parameter.Name; - return true; - } - } - - return false; - } - - public static bool IsNonNullable(this IParameterSymbol parameter) - { - if (parameter.Type.NullableAnnotation != NullableAnnotation.NotAnnotated) - { - return false; - } - - if (parameter.Type is INamedTypeSymbol namedTypeSymbol && - namedTypeSymbol.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T) - { - return false; - } - - return true; - } - - public static ResolverResultKind GetResultKind(this IMethodSymbol method) - { - const string task = $"{WellKnownTypes.Task}<"; - const string valueTask = $"{WellKnownTypes.ValueTask}<"; - const string taskEnumerable = $"{WellKnownTypes.Task}<{WellKnownTypes.AsyncEnumerable}<"; - const string valueTaskEnumerable = $"{WellKnownTypes.ValueTask}<{WellKnownTypes.AsyncEnumerable}<"; - - if (method.ReturnsVoid || method.ReturnsByRef || method.ReturnsByRefReadonly) - { - return ResolverResultKind.Invalid; - } - - var returnType = method.ReturnType.ToDisplayString(); - - if (returnType.Equals(WellKnownTypes.Task) || - returnType.Equals(WellKnownTypes.ValueTask)) - { - return ResolverResultKind.Invalid; - } - - if (returnType.StartsWith(task) || - returnType.StartsWith(valueTask)) - { - if (returnType.StartsWith(taskEnumerable) || - returnType.StartsWith(valueTaskEnumerable)) - { - return ResolverResultKind.TaskAsyncEnumerable; - } - - return ResolverResultKind.Task; - } - - if (returnType.StartsWith(WellKnownTypes.Executable)) - { - return ResolverResultKind.Executable; - } - - if (returnType.StartsWith(WellKnownTypes.Queryable)) - { - return ResolverResultKind.Queryable; - } - - if (returnType.StartsWith(WellKnownTypes.AsyncEnumerable)) - { - return ResolverResultKind.AsyncEnumerable; - } - - return ResolverResultKind.Pure; - } -} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/TypeModuleGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/Generators/TypeModuleSyntaxGenerator.cs similarity index 80% rename from src/HotChocolate/Core/src/Types.Analyzers/TypeModuleGenerator.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Generators/TypeModuleSyntaxGenerator.cs index 46df6265e81..0e542b6a745 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/TypeModuleGenerator.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Generators/TypeModuleSyntaxGenerator.cs @@ -1,79 +1,20 @@ -using System.Collections.Immutable; -using HotChocolate.Types.Analyzers.Generators; +using System.Collections.Immutable; +using HotChocolate.Types.Analyzers.FileBuilders; using HotChocolate.Types.Analyzers.Helpers; using HotChocolate.Types.Analyzers.Inspectors; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using static System.StringComparison; -using static HotChocolate.Types.Analyzers.WellKnownFileNames; -using static HotChocolate.Types.Analyzers.WellKnownTypes; -using TypeInfo = HotChocolate.Types.Analyzers.Inspectors.TypeInfo; +using TypeInfo = HotChocolate.Types.Analyzers.Models.TypeInfo; -namespace HotChocolate.Types.Analyzers; +namespace HotChocolate.Types.Analyzers.Generators; -[Generator] -public class TypeModuleGenerator : IIncrementalGenerator +public sealed class TypeModuleSyntaxGenerator : ISyntaxGenerator { - private static readonly ISyntaxInspector[] _inspectors = - [ - new TypeAttributeInspector(), - new ClassBaseClassInspector(), - new ModuleInspector(), - new DataLoaderInspector(), - new DataLoaderDefaultsInspector(), - new OperationInspector(), - new ObjectTypeExtensionInfoInspector(), - ]; - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var modulesAndTypes = - context.SyntaxProvider - .CreateSyntaxProvider( - predicate: static (s, _) => IsRelevant(s), - transform: TryGetModuleOrType) - .Where(static t => t is not null)! - .WithComparer(SyntaxInfoComparer.Default); - - var valueProvider = context.CompilationProvider.Combine(modulesAndTypes.Collect()); - - context.RegisterSourceOutput( - valueProvider, - static (context, source) => Execute(context, source.Left, source.Right)); - } - - private static bool IsRelevant(SyntaxNode node) - => IsTypeWithAttribute(node) || - IsClassWithBaseClass(node) || - IsAssemblyAttributeList(node) || - IsMethodWithAttribute(node); - - private static bool IsClassWithBaseClass(SyntaxNode node) - => node is ClassDeclarationSyntax { BaseList.Types.Count: > 0, }; - - private static bool IsTypeWithAttribute(SyntaxNode node) - => node is BaseTypeDeclarationSyntax { AttributeLists.Count: > 0, }; - - private static bool IsMethodWithAttribute(SyntaxNode node) - => node is MethodDeclarationSyntax { AttributeLists.Count: > 0, }; - - private static bool IsAssemblyAttributeList(SyntaxNode node) - => node is AttributeListSyntax; - - private static ISyntaxInfo? TryGetModuleOrType( - GeneratorSyntaxContext context, - CancellationToken cancellationToken) - { - for (var i = 0; i < _inspectors.Length; i++) - { - if (_inspectors[i].TryHandle(context, out var syntaxInfo)) - { - return syntaxInfo; - } - } - - return null; - } + public void Generate( + SourceProductionContext context, + Compilation compilation, + ImmutableArray syntaxInfos) + => Execute(context, compilation, syntaxInfos); private static void Execute( SourceProductionContext context, @@ -105,7 +46,7 @@ private static void WriteConfiguration( List syntaxInfos, ModuleInfo module) { - using var generator = new ModuleSyntaxGenerator(module.ModuleName, "Microsoft.Extensions.DependencyInjection"); + using var generator = new ModuleFileBuilder(module.ModuleName, "Microsoft.Extensions.DependencyInjection"); generator.WriteHeader(); generator.WriteBeginNamespace(); @@ -211,7 +152,7 @@ private static void WriteConfiguration( generator.WriteEndClass(); generator.WriteEndNamespace(); - context.AddSource(TypeModuleFile, generator.ToSourceText()); + context.AddSource(WellKnownFileNames.TypeModuleFile, generator.ToSourceText()); } private static void WriteDataLoader( @@ -254,7 +195,7 @@ private static void WriteDataLoader( dataLoaders.Add(dataLoader); } - using var generator = new DataLoaderSyntaxGenerator(); + using var generator = new DataLoaderFileBuilder(); generator.WriteHeader(); foreach (var group in dataLoaders.GroupBy(t => t.Namespace)) @@ -311,7 +252,7 @@ private static void WriteDataLoader( generator.WriteEndNamespace(); } - context.AddSource(DataLoaderFile, generator.ToSourceText()); + context.AddSource(WellKnownFileNames.DataLoaderFile, generator.ToSourceText()); } private static void WriteOperationTypes( @@ -334,7 +275,7 @@ private static void WriteOperationTypes( return; } - using var generator = new OperationFieldSyntaxGenerator(); + using var generator = new OperationFieldFileBuilder(); generator.WriteHeader(); generator.WriteBeginNamespace("Microsoft.Extensions.DependencyInjection"); @@ -353,11 +294,11 @@ private static void WriteOperationTypes( generator.WriteEndNamespace(); - context.AddSource(RootTypesFile, generator.ToSourceText()); + context.AddSource(WellKnownFileNames.RootTypesFile, generator.ToSourceText()); } private static void GenerateDataLoader( - DataLoaderSyntaxGenerator generator, + DataLoaderFileBuilder generator, DataLoaderInfo dataLoader, DataLoaderDefaultsInfo defaults, DataLoaderKind kind, @@ -423,12 +364,12 @@ private static void InspectDataLoaderParameters( private static bool IsKeysArgument(ITypeSymbol type) => type is INamedTypeSymbol { IsGenericType: true, TypeArguments.Length: 1, } nt && - ReadOnlyList.Equals(ToTypeNameNoGenerics(nt), Ordinal); + WellKnownTypes.ReadOnlyList.Equals(ToTypeNameNoGenerics(nt), StringComparison.Ordinal); private static ITypeSymbol ExtractKeyType(ITypeSymbol type) { if (type is INamedTypeSymbol { IsGenericType: true, TypeArguments.Length: 1, } namedType && - ReadOnlyList.Equals(ToTypeNameNoGenerics(namedType), Ordinal)) + WellKnownTypes.ReadOnlyList.Equals(ToTypeNameNoGenerics(namedType), StringComparison.Ordinal)) { return namedType.TypeArguments[0]; } @@ -438,7 +379,7 @@ private static ITypeSymbol ExtractKeyType(ITypeSymbol type) private static bool IsCancellationToken(string typeName) => string.Equals(typeName, WellKnownTypes.CancellationToken) || - string.Equals(typeName, GlobalCancellationToken); + string.Equals(typeName, WellKnownTypes.GlobalCancellationToken); private static bool IsReturnTypeDictionary(ITypeSymbol returnType, ITypeSymbol keyType) { @@ -463,7 +404,7 @@ private static bool IsReturnTypeLookup(ITypeSymbol returnType, ITypeSymbol keyTy { var resultType = namedType.TypeArguments[0]; - if (ToTypeNameNoGenerics(resultType).Equals(Lookup, Ordinal) && + if (ToTypeNameNoGenerics(resultType).Equals(WellKnownTypes.Lookup, StringComparison.Ordinal) && resultType is INamedTypeSymbol { TypeArguments.Length: 2, } dictionaryType && dictionaryType.TypeArguments[0].Equals(keyType, SymbolEqualityComparer.Default)) { @@ -475,11 +416,11 @@ private static bool IsReturnTypeLookup(ITypeSymbol returnType, ITypeSymbol keyTy private static bool IsReadOnlyDictionary(ITypeSymbol type) { - if (!ToTypeNameNoGenerics(type).Equals(ReadOnlyDictionary, Ordinal)) + if (!ToTypeNameNoGenerics(type).Equals(WellKnownTypes.ReadOnlyDictionary, StringComparison.Ordinal)) { foreach (var interfaceSymbol in type.Interfaces) { - if (ToTypeNameNoGenerics(interfaceSymbol).Equals(ReadOnlyDictionary, Ordinal)) + if (ToTypeNameNoGenerics(interfaceSymbol).Equals(WellKnownTypes.ReadOnlyDictionary, StringComparison.Ordinal)) { return true; } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/TypesGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/Generators/TypesSyntaxGenerator.cs similarity index 66% rename from src/HotChocolate/Core/src/Types.Analyzers/TypesGenerator.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Generators/TypesSyntaxGenerator.cs index 76e14bd29c4..e2432f0e2f5 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/TypesGenerator.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Generators/TypesSyntaxGenerator.cs @@ -1,61 +1,20 @@ using System.Collections.Immutable; using System.Text; -using HotChocolate.Types.Analyzers.Generators; +using HotChocolate.Types.Analyzers.FileBuilders; using HotChocolate.Types.Analyzers.Helpers; using HotChocolate.Types.Analyzers.Inspectors; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace HotChocolate.Types.Analyzers; +namespace HotChocolate.Types.Analyzers.Generators; -[Generator] -public class TypesGenerator : IIncrementalGenerator +public sealed class TypesSyntaxGenerator : ISyntaxGenerator { - private static readonly ISyntaxInspector[] _inspectors = - [ - new ObjectTypeExtensionInfoInspector(), - ]; - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var modulesAndTypes = - context.SyntaxProvider - .CreateSyntaxProvider( - predicate: static (s, _) => IsRelevant(s), - transform: TryGetModuleOrType) - .Where(static t => t is not null)! - .WithComparer(SyntaxInfoComparer.Default); - - var valueProvider = context.CompilationProvider.Combine(modulesAndTypes.Collect()); - - context.RegisterSourceOutput( - valueProvider, - static (context, source) => Execute(context, source.Left, source.Right)); - } - - private static bool IsRelevant(SyntaxNode node) - => IsClassWithAttribute(node) || IsAssemblyAttributeList(node); - - private static bool IsClassWithAttribute(SyntaxNode node) - => node is ClassDeclarationSyntax { AttributeLists.Count: > 0, }; - - private static bool IsAssemblyAttributeList(SyntaxNode node) - => node is AttributeListSyntax; - - private static ISyntaxInfo? TryGetModuleOrType( - GeneratorSyntaxContext context, - CancellationToken cancellationToken) - { - for (var i = 0; i < _inspectors.Length; i++) - { - if (_inspectors[i].TryHandle(context, out var syntaxInfo)) - { - return syntaxInfo; - } - } - - return null; - } + public void Generate( + SourceProductionContext context, + Compilation compilation, + ImmutableArray syntaxInfos) + => Execute(context, compilation, syntaxInfos); private static void Execute( SourceProductionContext context, @@ -88,7 +47,7 @@ private static void WriteTypes( .OfType() .GroupBy(t => t.Type.ContainingNamespace.ToDisplayString())) { - var generator = new ObjectTypeExtensionSyntaxGenerator(sb, group.Key); + var generator = new ObjectTypeExtensionFileBuilder(sb, group.Key); if (firstNamespace) { @@ -136,7 +95,9 @@ private static void WriteResolvers( ImmutableArray syntaxInfos, StringBuilder sb) { - var generator = new ResolverSyntaxGenerator(sb); + var localTypeLookup = new DefaultLocalTypeLookup(syntaxInfos); + + var generator = new ResolverFileBuilder(sb); generator.WriteHeader(); var firstNamespace = true; @@ -161,8 +122,9 @@ private static void WriteResolvers( } firstClass = false; - var resolverInfos = objectTypeExtension.Members.Select( - m => CreateResolverInfo(objectTypeExtension, m)); + var resolverInfos = objectTypeExtension.Members + .Select(m => CreateResolverInfo(objectTypeExtension, m)) + .ToList(); generator.WriteBeginClass(objectTypeExtension.Type.Name + "Resolvers"); @@ -171,7 +133,7 @@ private static void WriteResolvers( sb.AppendLine(); } - generator.AddParameterInitializer(resolverInfos); + generator.AddParameterInitializer(resolverInfos, localTypeLookup); foreach (var member in objectTypeExtension.Members) { diff --git a/src/HotChocolate/Core/src/Types.Analyzers/GraphQLServerGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/GraphQLServerGenerator.cs new file mode 100644 index 00000000000..436dd7b23ce --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/GraphQLServerGenerator.cs @@ -0,0 +1,98 @@ +using System.Collections.Immutable; +using HotChocolate.Types.Analyzers.Filters; +using HotChocolate.Types.Analyzers.Generators; +using HotChocolate.Types.Analyzers.Inspectors; +using HotChocolate.Types.Analyzers.Models; +using Microsoft.CodeAnalysis; + +namespace HotChocolate.Types.Analyzers; + +[Generator] +public class GraphQLServerGenerator : IIncrementalGenerator +{ + private static readonly ISyntaxInspector[] _inspectors = + [ + new TypeAttributeInspector(), + new ClassBaseClassInspector(), + new ModuleInspector(), + new DataLoaderInspector(), + new DataLoaderDefaultsInspector(), + new OperationInspector(), + new ObjectTypeExtensionInfoInspector(), + new ObjectTypeExtensionInfoInspector(), + new RequestMiddlewareInspector(), + ]; + + private static readonly ISyntaxGenerator[] _generators = + [ + new TypeModuleSyntaxGenerator(), + new TypesSyntaxGenerator(), + new MiddlewareGenerator(), + ]; + + private static readonly Func _predicate; + + static GraphQLServerGenerator() + { + var filterBuilder = new SyntaxFilterBuilder(); + + foreach (var inspector in _inspectors) + { + filterBuilder.AddRange(inspector.Filters); + } + + _predicate = filterBuilder.Build(); + } + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var syntaxInfos = + context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (s, _) => Predicate(s), + transform: static (ctx, _) => Transform(ctx)) + .WhereNotNull() + .WithComparer(SyntaxInfoComparer.Default) + .Collect(); + + var valueProvider = context.CompilationProvider.Combine(syntaxInfos); + + context.RegisterSourceOutput( + valueProvider, + static (context, source) => Execute(context, source.Left, source.Right)); + } + + private static bool Predicate(SyntaxNode node) + => _predicate(node); + + private static ISyntaxInfo? Transform(GeneratorSyntaxContext context) + { + for (var i = 0; i < _inspectors.Length; i++) + { + if (_inspectors[i].TryHandle(context, out var syntaxInfo)) + { + return syntaxInfo; + } + } + + return null; + } + + private static void Execute( + SourceProductionContext context, + Compilation compilation, + ImmutableArray syntaxInfos) + { + for (var i = 0; i < _generators.Length; i++) + { + _generators[i].Generate(context, compilation, syntaxInfos); + } + } +} + +file static class Extensions +{ + public static IncrementalValuesProvider WhereNotNull( + this IncrementalValuesProvider source) + => source.Where(static t => t is not null)!; +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Helpers/GeneratorUtils.cs b/src/HotChocolate/Core/src/Types.Analyzers/Helpers/GeneratorUtils.cs index 8c4ee3d95ed..d8a07bb51c4 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Helpers/GeneratorUtils.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Helpers/GeneratorUtils.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using HotChocolate.Types.Analyzers.Inspectors; +using HotChocolate.Types.Analyzers.Models; namespace HotChocolate.Types.Analyzers.Helpers; diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Helpers/SymbolExtensions.cs b/src/HotChocolate/Core/src/Types.Analyzers/Helpers/SymbolExtensions.cs index 0473d15b7cc..2dfef84c631 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Helpers/SymbolExtensions.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Helpers/SymbolExtensions.cs @@ -1,4 +1,7 @@ +using System.Diagnostics.CodeAnalysis; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; namespace HotChocolate.Types.Analyzers.Helpers; @@ -6,4 +9,202 @@ public static class SymbolExtensions { public static string ToFullyQualified(this ITypeSymbol typeSymbol) => typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + public static bool IsParent(this IParameterSymbol parameter) + => parameter.IsThis || + parameter + .GetAttributes() + .Any(static t => t.AttributeClass?.ToDisplayString() == WellKnownAttributes.ParentAttribute); + + public static bool IsCancellationToken(this IParameterSymbol parameter) + => parameter.Type.ToDisplayString() == WellKnownTypes.CancellationToken; + + public static bool IsClaimsPrincipal(this IParameterSymbol parameter) + => parameter.Type.ToDisplayString() == WellKnownTypes.ClaimsPrincipal; + + public static bool IsDocumentNode(this IParameterSymbol parameter) + => parameter.Type.ToDisplayString() == WellKnownTypes.DocumentNode; + + public static bool IsEventMessage(this IParameterSymbol parameter) + => parameter.Type.ToDisplayString() == WellKnownAttributes.EnumTypeAttribute; + + public static bool IsFieldNode(this IParameterSymbol parameter) + => parameter.Type.ToDisplayString() == WellKnownAttributes.FieldNode; + + public static bool IsOutputField(this IParameterSymbol parameterSymbol, Compilation compilation) + { + var type = compilation.GetTypeByMetadataName(WellKnownTypes.OutputField); + return type != null && compilation.ClassifyConversion(parameterSymbol.Type, type).IsImplicit; + } + + public static bool IsGlobalState( + this IParameterSymbol parameter, + [NotNullWhen(true)] out string? key) + { + key = null; + + foreach (var attributeData in parameter.GetAttributes()) + { + if (attributeData.AttributeClass?.ToDisplayString() == "HotChocolate.GlobalStateAttribute") + { + if (attributeData.ConstructorArguments.Length == 1 && + attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Primitive && + attributeData.ConstructorArguments[0].Value is string keyValue) + { + key = keyValue; + return true; + } + + foreach (var namedArg in attributeData.NamedArguments) + { + if (namedArg.Key == "Key" && namedArg.Value.Value is string namedKeyValue) + { + key = namedKeyValue; + return true; + } + } + + key = parameter.Name; + return true; + } + } + + return false; + } + + public static bool IsScopedState( + this IParameterSymbol parameter, + [NotNullWhen(true)] out string? key) + { + key = null; + + foreach (var attributeData in parameter.GetAttributes()) + { + if (attributeData.AttributeClass?.ToDisplayString() == "HotChocolate.ScopedStateAttribute") + { + if (attributeData.ConstructorArguments.Length == 1 && + attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Primitive && + attributeData.ConstructorArguments[0].Value is string keyValue) + { + key = keyValue; + return true; + } + + foreach (var namedArg in attributeData.NamedArguments) + { + if (namedArg is { Key: "Key", Value.Value: string namedKeyValue }) + { + key = namedKeyValue; + return true; + } + } + + key = parameter.Name; + return true; + } + } + + return false; + } + + public static bool IsLocalState( + this IParameterSymbol parameter, + [NotNullWhen(true)] out string? key) + { + key = null; + + foreach (var attributeData in parameter.GetAttributes()) + { + if (attributeData.AttributeClass?.ToDisplayString() == "HotChocolate.LocalStateAttribute") + { + if (attributeData.ConstructorArguments.Length == 1 && + attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Primitive && + attributeData.ConstructorArguments[0].Value is string keyValue) + { + key = keyValue; + return true; + } + + foreach (var namedArg in attributeData.NamedArguments) + { + if (namedArg.Key == "Key" && namedArg.Value.Value is string namedKeyValue) + { + key = namedKeyValue; + return true; + } + } + + key = parameter.Name; + return true; + } + } + + return false; + } + + public static bool IsNonNullable(this IParameterSymbol parameter) + { + if (parameter.Type.NullableAnnotation != NullableAnnotation.NotAnnotated) + { + return false; + } + + if (parameter.Type is INamedTypeSymbol namedTypeSymbol && + namedTypeSymbol.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T) + { + return false; + } + + return true; + } + + public static ResolverResultKind GetResultKind(this IMethodSymbol method) + { + const string task = $"{WellKnownTypes.Task}<"; + const string valueTask = $"{WellKnownTypes.ValueTask}<"; + const string taskEnumerable = $"{WellKnownTypes.Task}<{WellKnownTypes.AsyncEnumerable}<"; + const string valueTaskEnumerable = $"{WellKnownTypes.ValueTask}<{WellKnownTypes.AsyncEnumerable}<"; + + if (method.ReturnsVoid || method.ReturnsByRef || method.ReturnsByRefReadonly) + { + return ResolverResultKind.Invalid; + } + + var returnType = method.ReturnType.ToDisplayString(); + + if (returnType.Equals(WellKnownTypes.Task) || + returnType.Equals(WellKnownTypes.ValueTask)) + { + return ResolverResultKind.Invalid; + } + + if (returnType.StartsWith(task) || + returnType.StartsWith(valueTask)) + { + if (returnType.StartsWith(taskEnumerable) || + returnType.StartsWith(valueTaskEnumerable)) + { + return ResolverResultKind.TaskAsyncEnumerable; + } + + return ResolverResultKind.Task; + } + + if (returnType.StartsWith(WellKnownTypes.Executable)) + { + return ResolverResultKind.Executable; + } + + if (returnType.StartsWith(WellKnownTypes.Queryable)) + { + return ResolverResultKind.Queryable; + } + + if (returnType.StartsWith(WellKnownTypes.AsyncEnumerable)) + { + return ResolverResultKind.AsyncEnumerable; + } + + return ResolverResultKind.Pure; + } } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ClassBaseClassInspector.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ClassBaseClassInspector.cs index c386a136134..a0c5ec8c700 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ClassBaseClassInspector.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ClassBaseClassInspector.cs @@ -1,12 +1,17 @@ using System.Diagnostics.CodeAnalysis; +using HotChocolate.Types.Analyzers.Filters; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using TypeInfo = HotChocolate.Types.Analyzers.Models.TypeInfo; namespace HotChocolate.Types.Analyzers.Inspectors; public class ClassBaseClassInspector : ISyntaxInspector { + public IReadOnlyList Filters => [ClassWithBaseClass.Instance]; + public bool TryHandle( GeneratorSyntaxContext context, [NotNullWhen(true)] out ISyntaxInfo? syntaxInfo) diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderDefaultsInspector.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderDefaultsInspector.cs index 271c83d6f38..9cc242a5104 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderDefaultsInspector.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderDefaultsInspector.cs @@ -1,5 +1,7 @@ using System.Diagnostics.CodeAnalysis; +using HotChocolate.Types.Analyzers.Filters; using HotChocolate.Types.Analyzers.Helpers; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using static System.StringComparison; @@ -9,6 +11,8 @@ namespace HotChocolate.Types.Analyzers.Inspectors; public class DataLoaderDefaultsInspector : ISyntaxInspector { + public IReadOnlyList Filters => [AssemblyAttributeList.Instance]; + public bool TryHandle( GeneratorSyntaxContext context, [NotNullWhen(true)] out ISyntaxInfo? syntaxInfo) diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderInspector.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderInspector.cs index 4e99fd5733d..4f77a6e73ce 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderInspector.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderInspector.cs @@ -1,4 +1,6 @@ using System.Diagnostics.CodeAnalysis; +using HotChocolate.Types.Analyzers.Filters; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -8,6 +10,8 @@ namespace HotChocolate.Types.Analyzers.Inspectors; public sealed class DataLoaderInspector : ISyntaxInspector { + public IReadOnlyList Filters => [MethodWithAttribute.Instance]; + public bool TryHandle( GeneratorSyntaxContext context, [NotNullWhen(true)] out ISyntaxInfo? syntaxInfo) @@ -45,4 +49,4 @@ public bool TryHandle( syntaxInfo = null; return false; } -} \ No newline at end of file +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ISyntaxInspector.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ISyntaxInspector.cs index 0b52257576d..7bb6ad67bfa 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ISyntaxInspector.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ISyntaxInspector.cs @@ -1,4 +1,6 @@ using System.Diagnostics.CodeAnalysis; +using HotChocolate.Types.Analyzers.Filters; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis; namespace HotChocolate.Types.Analyzers.Inspectors; @@ -9,6 +11,11 @@ namespace HotChocolate.Types.Analyzers.Inspectors; /// public interface ISyntaxInspector { + /// + /// Gets the filters that is used to determine in what kinds of syntax nodes the inspector is interested. + /// + IReadOnlyList Filters { get; } + /// /// /// Inspects the current syntax node and if the current inspector can handle diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ModuleInspector.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ModuleInspector.cs index 32cf948fd30..2d97354de7f 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ModuleInspector.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ModuleInspector.cs @@ -1,4 +1,6 @@ using System.Diagnostics.CodeAnalysis; +using HotChocolate.Types.Analyzers.Filters; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using static System.StringComparison; @@ -8,6 +10,8 @@ namespace HotChocolate.Types.Analyzers.Inspectors; public class ModuleInspector : ISyntaxInspector { + public IReadOnlyList Filters => [AssemblyAttributeList.Instance]; + public bool TryHandle( GeneratorSyntaxContext context, [NotNullWhen(true)] out ISyntaxInfo? syntaxInfo) diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ObjectTypeExtensionInfoInspector.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ObjectTypeExtensionInfoInspector.cs index 43f3b3b41a8..1ed06a8e478 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ObjectTypeExtensionInfoInspector.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ObjectTypeExtensionInfoInspector.cs @@ -1,5 +1,7 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using HotChocolate.Types.Analyzers.Filters; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -10,6 +12,8 @@ namespace HotChocolate.Types.Analyzers.Inspectors; public class ObjectTypeExtensionInfoInspector : ISyntaxInspector { + public IReadOnlyList Filters => [TypeWithAttribute.Instance]; + public bool TryHandle(GeneratorSyntaxContext context, [NotNullWhen(true)] out ISyntaxInfo? syntaxInfo) { if (context.Node is ClassDeclarationSyntax { AttributeLists.Count: > 0, } possibleType) diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/OperationInspector.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/OperationInspector.cs index 90ffeff82fd..21c2feac90b 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/OperationInspector.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/OperationInspector.cs @@ -1,4 +1,6 @@ using System.Diagnostics.CodeAnalysis; +using HotChocolate.Types.Analyzers.Filters; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -7,6 +9,8 @@ namespace HotChocolate.Types.Analyzers.Inspectors; public sealed class OperationInspector : ISyntaxInspector { + public IReadOnlyList Filters => [MethodWithAttribute.Instance]; + public bool TryHandle( GeneratorSyntaxContext context, [NotNullWhen(true)] out ISyntaxInfo? syntaxInfo) @@ -27,7 +31,7 @@ public bool TryHandle( var attributeContainingTypeSymbol = attributeSymbol.ContainingType; var fullName = attributeContainingTypeSymbol.ToDisplayString(); var operationType = ParseOperationType(fullName); - + if(operationType == OperationType.No) { continue; @@ -42,7 +46,7 @@ public bool TryHandle( { continue; } - + syntaxInfo = new OperationInfo( operationType, methodSymbol.ContainingType.ToDisplayString(), @@ -62,17 +66,17 @@ private OperationType ParseOperationType(string attributeName) { return OperationType.Query; } - + if (attributeName.Equals(WellKnownAttributes.MutationAttribute, StringComparison.Ordinal)) { return OperationType.Mutation; } - + if (attributeName.Equals(WellKnownAttributes.SubscriptionAttribute, StringComparison.Ordinal)) { - return OperationType.Subscription; + return OperationType.Subscription; } return OperationType.No; } -} \ No newline at end of file +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInspector.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInspector.cs index 6277a7a98d4..1c0ed7b856e 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInspector.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInspector.cs @@ -1,6 +1,8 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using HotChocolate.Types.Analyzers.Filters; using HotChocolate.Types.Analyzers.Helpers; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -8,6 +10,8 @@ namespace HotChocolate.Types.Analyzers.Inspectors; internal sealed class RequestMiddlewareInspector : ISyntaxInspector { + public IReadOnlyList Filters => [MiddlewareMethod.Instance]; + public bool TryHandle(GeneratorSyntaxContext context, [NotNullWhen(true)] out ISyntaxInfo? syntaxInfo) { if (context.Node is InvocationExpressionSyntax diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeAttributeInspector.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeAttributeInspector.cs index 9ad14d613ca..658fd559f6a 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeAttributeInspector.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeAttributeInspector.cs @@ -1,14 +1,19 @@ using System.Diagnostics.CodeAnalysis; +using HotChocolate.Types.Analyzers.Filters; +using HotChocolate.Types.Analyzers.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using static System.StringComparison; using static HotChocolate.Types.Analyzers.WellKnownAttributes; +using TypeInfo = HotChocolate.Types.Analyzers.Models.TypeInfo; namespace HotChocolate.Types.Analyzers.Inspectors; public sealed class TypeAttributeInspector : ISyntaxInspector { + public IReadOnlyList Filters => [TypeWithAttribute.Instance]; + public bool TryHandle( GeneratorSyntaxContext context, [NotNullWhen(true)] out ISyntaxInfo? syntaxInfo) diff --git a/src/HotChocolate/Core/src/Types.Analyzers/MiddlewareGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/MiddlewareGenerator.cs deleted file mode 100644 index 08e342bd971..00000000000 --- a/src/HotChocolate/Core/src/Types.Analyzers/MiddlewareGenerator.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Collections.Immutable; -using HotChocolate.Types.Analyzers.Generators; -using HotChocolate.Types.Analyzers.Helpers; -using HotChocolate.Types.Analyzers.Inspectors; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace HotChocolate.Types.Analyzers; - -[Generator] -public class MiddlewareGenerator : IIncrementalGenerator -{ - private const string _namespace = "HotChocolate.Execution.Generated"; - - private static readonly ISyntaxInspector[] _inspectors = - [ - new ModuleInspector(), - new RequestMiddlewareInspector(), - ]; - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var modulesAndTypes = - context.SyntaxProvider - .CreateSyntaxProvider( - predicate: static (s, _) => IsRelevant(s), - transform: TryGetModuleOrType) - .Where(static t => t is not null)! - .WithComparer(SyntaxInfoComparer.Default); - - var valueProvider = context.CompilationProvider.Combine(modulesAndTypes.Collect()); - - context.RegisterSourceOutput( - valueProvider, - static (context, source) => Execute(context, source.Left, source.Right)); - } - - private static bool IsRelevant(SyntaxNode node) - => IsMiddlewareMethod(node) || IsAssemblyAttributeList(node); - - private static bool IsMiddlewareMethod(SyntaxNode node) - => node is InvocationExpressionSyntax - { - Expression: MemberAccessExpressionSyntax - { - Name.Identifier.ValueText: var method, - }, - } && - (method.Equals("UseRequest") || method.Equals("UseField") || method.Equals("Use")); - - private static bool IsAssemblyAttributeList(SyntaxNode node) - => node is AttributeListSyntax; - - private static ISyntaxInfo? TryGetModuleOrType( - GeneratorSyntaxContext context, - CancellationToken cancellationToken) - { - for (var i = 0; i < _inspectors.Length; i++) - { - if (_inspectors[i].TryHandle(context, out var syntaxInfo)) - { - return syntaxInfo; - } - } - - return null; - } - - private static void Execute( - SourceProductionContext context, - Compilation compilation, - ImmutableArray syntaxInfos) - { - if (syntaxInfos.IsEmpty) - { - return; - } - - var module = syntaxInfos.GetModuleInfo(compilation.AssemblyName, out var defaultModule); - - // if there is only the module info we do not need to generate a module. - if (!defaultModule && syntaxInfos.Length == 1) - { - return; - } - - using var generator = new RequestMiddlewareSyntaxGenerator(module.ModuleName, _namespace); - - generator.WriteHeader(); - generator.WriteBeginNamespace(); - - generator.WriteBeginClass(); - - var i = 0; - foreach (var syntaxInfo in syntaxInfos) - { - if (syntaxInfo is not RequestMiddlewareInfo middleware) - { - continue; - } - - generator.WriteFactory(i, middleware); - generator.WriteInterceptMethod(i, middleware.Location); - i++; - } - - generator.WriteEndClass(); - - generator.WriteEndNamespace(); - - context.AddSource(WellKnownFileNames.MiddlewareFile, generator.ToSourceText()); - } -} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/AggregateInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/AggregateInfo.cs similarity index 100% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/AggregateInfo.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/AggregateInfo.cs diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderDefaultsInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/DataLoaderDefaultsInfo.cs similarity index 96% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderDefaultsInfo.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/DataLoaderDefaultsInfo.cs index 64e2b8a1fa8..5ea5a1f3026 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderDefaultsInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/DataLoaderDefaultsInfo.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.Types.Analyzers.Inspectors; +namespace HotChocolate.Types.Analyzers.Models; public sealed class DataLoaderDefaultsInfo( bool? scoped, diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/DataLoaderInfo.cs similarity index 98% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderInfo.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/DataLoaderInfo.cs index 06a9b5c6f0b..2bb7b70e1f1 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/DataLoaderInfo.cs @@ -2,7 +2,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace HotChocolate.Types.Analyzers.Inspectors; +namespace HotChocolate.Types.Analyzers.Models; public sealed class DataLoaderInfo : ISyntaxInfo, IEquatable { diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ISyntaxInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/ISyntaxInfo.cs similarity index 54% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ISyntaxInfo.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/ISyntaxInfo.cs index accf67983f4..143affaf2bf 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ISyntaxInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/ISyntaxInfo.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.Types.Analyzers.Inspectors; +namespace HotChocolate.Types.Analyzers.Models; public interface ISyntaxInfo : IEquatable { diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ModuleInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/ModuleInfo.cs similarity index 95% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ModuleInfo.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/ModuleInfo.cs index 60044dc4796..8a0aa4aafa2 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ModuleInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/ModuleInfo.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.Types.Analyzers.Inspectors; +namespace HotChocolate.Types.Analyzers.Models; public sealed class ModuleInfo : ISyntaxInfo, IEquatable { diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ModuleOptions.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/ModuleOptions.cs similarity index 73% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ModuleOptions.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/ModuleOptions.cs index ce4eaba940b..2bf7cf22d55 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ModuleOptions.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/ModuleOptions.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.Types.Analyzers.Inspectors; +namespace HotChocolate.Types.Analyzers.Models; [Flags] public enum ModuleOptions diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ObjectTypeExtensionInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/ObjectTypeExtensionInfo.cs similarity index 97% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ObjectTypeExtensionInfo.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/ObjectTypeExtensionInfo.cs index cb217ec1712..baa310e427d 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ObjectTypeExtensionInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/ObjectTypeExtensionInfo.cs @@ -3,7 +3,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace HotChocolate.Types.Analyzers.Inspectors; +namespace HotChocolate.Types.Analyzers.Models; public sealed class ObjectTypeExtensionInfo : ISyntaxInfo, IEquatable { diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/OperationInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/OperationInfo.cs similarity index 96% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/OperationInfo.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/OperationInfo.cs index d2616268ea3..fa01cac61b0 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/OperationInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/OperationInfo.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.Types.Analyzers.Inspectors; +namespace HotChocolate.Types.Analyzers.Models; public sealed class OperationInfo(OperationType type, string typeName, string methodName) : ISyntaxInfo { diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/OperationRegistrationInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/OperationRegistrationInfo.cs similarity index 95% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/OperationRegistrationInfo.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/OperationRegistrationInfo.cs index 2f7671c4c76..7188e0eed55 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/OperationRegistrationInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/OperationRegistrationInfo.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.Types.Analyzers.Inspectors; +namespace HotChocolate.Types.Analyzers.Models; public sealed class OperationRegistrationInfo(OperationType type, string typeName) : ISyntaxInfo { diff --git a/src/HotChocolate/Core/src/Types.Analyzers/OperationType.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/OperationType.cs similarity index 69% rename from src/HotChocolate/Core/src/Types.Analyzers/OperationType.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/OperationType.cs index 16f3ac9bd5f..e168d58d91e 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/OperationType.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/OperationType.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.Types.Analyzers; +namespace HotChocolate.Types.Analyzers.Models; [Flags] public enum OperationType diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RegisterDataLoaderInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/RegisterDataLoaderInfo.cs similarity index 96% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RegisterDataLoaderInfo.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/RegisterDataLoaderInfo.cs index 4f09bb96d38..42965381e43 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RegisterDataLoaderInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/RegisterDataLoaderInfo.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.Types.Analyzers.Inspectors; +namespace HotChocolate.Types.Analyzers.Models; public sealed class RegisterDataLoaderInfo : ISyntaxInfo, IEquatable { diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/RequestMiddlewareInfo.cs similarity index 98% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInfo.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/RequestMiddlewareInfo.cs index 890fa13c7fd..b37ede03fb3 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/RequestMiddlewareInfo.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.Types.Analyzers.Inspectors; +namespace HotChocolate.Types.Analyzers.Models; public sealed class RequestMiddlewareInfo( string name, diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareParameterInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/RequestMiddlewareParameterInfo.cs similarity index 96% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareParameterInfo.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/RequestMiddlewareParameterInfo.cs index 69015d99ede..80ae5906c22 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareParameterInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/RequestMiddlewareParameterInfo.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.Types.Analyzers.Inspectors; +namespace HotChocolate.Types.Analyzers.Models; public sealed class RequestMiddlewareParameterInfo( RequestMiddlewareParameterKind kind, diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareParameterKind.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/RequestMiddlewareParameterKind.cs similarity index 69% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareParameterKind.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/RequestMiddlewareParameterKind.cs index 8115652716c..3f6ca4517d2 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareParameterKind.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/RequestMiddlewareParameterKind.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.Types.Analyzers.Inspectors; +namespace HotChocolate.Types.Analyzers.Models; public enum RequestMiddlewareParameterKind { diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ResolverName.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/ResolverInfo.cs similarity index 50% rename from src/HotChocolate/Core/src/Types.Analyzers/Generators/ResolverName.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/ResolverInfo.cs index 9e5cf671592..237bf8878ac 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ResolverName.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/ResolverInfo.cs @@ -1,13 +1,7 @@ +using HotChocolate.Types.Analyzers.Helpers; using Microsoft.CodeAnalysis; -namespace HotChocolate.Types.Analyzers.Generators; - -public readonly struct ResolverName(string typeName, string memberName) -{ - public readonly string TypeName = typeName; - - public readonly string MemberName = memberName; -} +namespace HotChocolate.Types.Analyzers.Models; public readonly struct ResolverInfo(ResolverName resolverName, IMethodSymbol? methodSymbol) { @@ -17,7 +11,7 @@ public readonly struct ResolverInfo(ResolverName resolverName, IMethodSymbol? me public readonly int ParameterCount = methodSymbol?.Parameters.Length ?? 0; - public readonly bool Skip => + public bool Skip => ParameterCount == 0 || - (ParameterCount == 1 && (Method?.Parameters[0]?.IsParent() ?? false)); + (ParameterCount == 1 && (Method?.Parameters[0].IsParent() ?? false)); } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Models/ResolverName.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/ResolverName.cs new file mode 100644 index 00000000000..a1c097719df --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/ResolverName.cs @@ -0,0 +1,8 @@ +namespace HotChocolate.Types.Analyzers.Models; + +public readonly struct ResolverName(string typeName, string memberName) +{ + public readonly string TypeName = typeName; + + public readonly string MemberName = memberName; +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ResolverResultKind.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/ResolverResultKind.cs similarity index 73% rename from src/HotChocolate/Core/src/Types.Analyzers/Generators/ResolverResultKind.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/ResolverResultKind.cs index 77b4b504d12..970bec101e7 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ResolverResultKind.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/ResolverResultKind.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.Types.Analyzers.Generators; +namespace HotChocolate.Types.Analyzers.Models; public enum ResolverResultKind { diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/SyntaxInfoComparer.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/SyntaxInfoComparer.cs similarity index 90% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/SyntaxInfoComparer.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/SyntaxInfoComparer.cs index 5f25567afe1..af04ea0eb8d 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/SyntaxInfoComparer.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/SyntaxInfoComparer.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.Types.Analyzers.Inspectors; +namespace HotChocolate.Types.Analyzers.Models; internal sealed class SyntaxInfoComparer : IEqualityComparer { diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeExtensionInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/TypeExtensionInfo.cs similarity index 96% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeExtensionInfo.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/TypeExtensionInfo.cs index d9f29cd3734..748d9240011 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeExtensionInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/TypeExtensionInfo.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.Types.Analyzers.Inspectors; +namespace HotChocolate.Types.Analyzers.Models; public sealed class TypeExtensionInfo : ISyntaxInfo, IEquatable { diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Models/TypeInfo.cs similarity index 95% rename from src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeInfo.cs rename to src/HotChocolate/Core/src/Types.Analyzers/Models/TypeInfo.cs index bd16ab07821..e7a47828207 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Models/TypeInfo.cs @@ -1,4 +1,4 @@ -namespace HotChocolate.Types.Analyzers.Inspectors; +namespace HotChocolate.Types.Analyzers.Models; public sealed class TypeInfo : ISyntaxInfo, IEquatable { diff --git a/src/HotChocolate/Core/src/Types.Analyzers/StringConstants.cs b/src/HotChocolate/Core/src/Types.Analyzers/StringConstants.cs deleted file mode 100644 index 7b7e436b426..00000000000 --- a/src/HotChocolate/Core/src/Types.Analyzers/StringConstants.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace HotChocolate.Types.Analyzers; - -internal static class StringConstants -{ - public const string Indent = " "; -} diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/HotChocolate.Types.Analyzers.Tests.csproj b/src/HotChocolate/Core/test/Types.Analyzers.Tests/HotChocolate.Types.Analyzers.Tests.csproj index d8f49264f3b..af5cb69d8fb 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/HotChocolate.Types.Analyzers.Tests.csproj +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/HotChocolate.Types.Analyzers.Tests.csproj @@ -7,8 +7,8 @@ false $(InterceptorsPreviewNamespaces);HotChocolate.Execution.Generated - true - $(BaseIntermediateOutputPath)\GeneratedFiles + diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/Types/AuthorNode.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/Types/AuthorNode.cs index 1e537d7db95..ac6be3c76aa 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/Types/AuthorNode.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/Types/AuthorNode.cs @@ -14,6 +14,11 @@ public static async Task> GetBooksAsync( CancellationToken cancellationToken) => await repository.GetBooksByAuthorAsync(author.Id, cancellationToken); + public static async Task GetSomeInfo( + [Parent] Author author, + ISomeInfoByIdDataLoader dataLoader, + CancellationToken cancellationToken) + => await dataLoader.LoadAsync(author.Id, cancellationToken); [Query] public static string QueryFieldCollocatedWithAuthor() => "hello"; diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/Types/DataLoaders.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/Types/DataLoaders.cs new file mode 100644 index 00000000000..213a4e0c2bd --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/Types/DataLoaders.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace HotChocolate.Types; + +public static class DataLoaders +{ + [DataLoader] + public static async Task> GetSomeInfoById( + IReadOnlyList keys) + => await Task.FromResult(keys.ToDictionary(k => k, k => k + " - some info")); +} diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/IntegrationTests.Schema.graphql b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/IntegrationTests.Schema.graphql index b1d6102b463..27bc489c47b 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/IntegrationTests.Schema.graphql +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/IntegrationTests.Schema.graphql @@ -22,6 +22,7 @@ type Address { type Author implements Entity & Node { books("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): BooksConnection + someInfo: String! id: ID! name: String! address: Address