diff --git a/global.json b/global.json index e71d6cc5772..5ce84955149 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,5 @@ { "sdk": { - "version": "8.0.100", - "rollForward": "major" + "version": "8.0.100" } } diff --git a/src/HotChocolate/Core/src/Abstractions/SchemaFactory.cs b/src/HotChocolate/Core/src/Abstractions/SchemaFactory.cs new file mode 100644 index 00000000000..ab08836968c --- /dev/null +++ b/src/HotChocolate/Core/src/Abstractions/SchemaFactory.cs @@ -0,0 +1,11 @@ +using HotChocolate.Language; + +namespace HotChocolate; + +/// +/// Implement this interface to enable design-time services to create the GraphQL type system. +/// +public interface IDesignTimeSchemaDocumentFactory +{ + DocumentNode CreateSchemaDocument(string[] args); +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderDefaultsInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderDefaultsInfo.cs index 19323617545..64e2b8a1fa8 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderDefaultsInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderDefaultsInfo.cs @@ -1,26 +1,20 @@ namespace HotChocolate.Types.Analyzers.Inspectors; -public sealed class DataLoaderDefaultsInfo : ISyntaxInfo, IEquatable +public sealed class DataLoaderDefaultsInfo( + bool? scoped, + bool? isPublic, + bool? isInterfacePublic, + bool registerServices) + : ISyntaxInfo + , IEquatable { - public DataLoaderDefaultsInfo( - bool? scoped, - bool? isPublic, - bool? isInterfacePublic, - bool registerServices) - { - Scoped = scoped; - IsPublic = isPublic; - IsInterfacePublic = isInterfacePublic; - RegisterServices = registerServices; - } - - public bool? Scoped { get; } + public bool? Scoped { get; } = scoped; - public bool? IsPublic { get; } + public bool? IsPublic { get; } = isPublic; - public bool? IsInterfacePublic { get; } + public bool? IsInterfacePublic { get; } = isInterfacePublic; - public bool RegisterServices { get; } + public bool RegisterServices { get; } = registerServices; public bool Equals(DataLoaderDefaultsInfo? other) { diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInspector.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInspector.cs index 8a0dcd6a26c..bba979d86f0 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInspector.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RequestMiddlewareInspector.cs @@ -89,7 +89,7 @@ ctor is not var parameterTypeName = parameter.Type.ToFullyQualified(); if (parameterTypeName.Equals("global::HotChocolate.Schema") || - parameterTypeName.Equals("global::HotChocolate.!Schema")) + parameterTypeName.Equals("global::HotChocolate.ISchema")) { kind = RequestMiddlewareParameterKind.Schema; } @@ -108,26 +108,7 @@ ctor is not invokeParameters.Add(new RequestMiddlewareParameterInfo(kind, parameterTypeName)); } - - - /* - public static IRequestExecutorBuilder UseRequest( - this IRequestExecutorBuilder builder) - where TMiddleware : class - - [InterceptsLocation(@"C:\testapp\Program.cs", line: 4, column: 5)] - public static RouteHandlerBuilder InterceptMapGet( // 👈 The interceptor must - this IEndpointRouteBuilder endpoints, // have the same signature - string pattern, // as the method being - Delegate handler) // intercepted - { - Console.WriteLine($"Intercepted '{pattern}'" ); - - return endpoints.MapGet(pattern, handler); - } - */ - - + syntaxInfo = new RequestMiddlewareInfo( middlewareType.Name, middlewareType.ToFullyQualified(), diff --git a/src/HotChocolate/Core/src/Types/Types/Factories/SchemaSyntaxVisitorContext.cs b/src/HotChocolate/Core/src/Types/Types/Factories/SchemaSyntaxVisitorContext.cs index 05149cc87aa..4aed6b11bdf 100644 --- a/src/HotChocolate/Core/src/Types/Types/Factories/SchemaSyntaxVisitorContext.cs +++ b/src/HotChocolate/Core/src/Types/Types/Factories/SchemaSyntaxVisitorContext.cs @@ -7,13 +7,8 @@ namespace HotChocolate.Types.Factories; -internal class SchemaSyntaxVisitorContext : ISyntaxVisitorContext +internal class SchemaSyntaxVisitorContext(IDescriptorContext directiveContext) { - public SchemaSyntaxVisitorContext(IDescriptorContext directiveContext) - { - DirectiveContext = directiveContext; - } - public List Types { get; } = []; public IReadOnlyCollection? Directives { get; set; } @@ -26,5 +21,5 @@ public SchemaSyntaxVisitorContext(IDescriptorContext directiveContext) public string? Description { get; set; } - public IDescriptorContext DirectiveContext { get; } + public IDescriptorContext DirectiveContext { get; } = directiveContext; } diff --git a/src/HotChocolate/Core/src/Types/Types/Scalars/JsonType.cs b/src/HotChocolate/Core/src/Types/Types/Scalars/JsonType.cs index 34192585f45..cccd91d743f 100644 --- a/src/HotChocolate/Core/src/Types/Types/Scalars/JsonType.cs +++ b/src/HotChocolate/Core/src/Types/Types/Scalars/JsonType.cs @@ -268,14 +268,9 @@ protected override ISyntaxVisitorAction Enter( } } - private sealed class JsonFormatterContext : ISyntaxVisitorContext + private sealed class JsonFormatterContext(Utf8JsonWriter writer) { - public JsonFormatterContext(Utf8JsonWriter writer) - { - Writer = writer; - } - - public Utf8JsonWriter Writer { get; } + public Utf8JsonWriter Writer { get; } = writer; } } } diff --git a/src/HotChocolate/Core/src/Validation/IDocumentValidatorContext.cs b/src/HotChocolate/Core/src/Validation/IDocumentValidatorContext.cs index ec0062cc053..6cbfb07034a 100644 --- a/src/HotChocolate/Core/src/Validation/IDocumentValidatorContext.cs +++ b/src/HotChocolate/Core/src/Validation/IDocumentValidatorContext.cs @@ -10,7 +10,7 @@ namespace HotChocolate.Validation; /// This interface represents the document validation context that can /// be used by validation visitors to build up state. /// -public interface IDocumentValidatorContext : ISyntaxVisitorContext +public interface IDocumentValidatorContext { /// /// Gets the schema on which the validation is executed. diff --git a/src/HotChocolate/Data/src/Data/Filters/Visitor/IFilterVisitorContext.cs b/src/HotChocolate/Data/src/Data/Filters/Visitor/IFilterVisitorContext.cs index 776efabf50a..61bc6fb4e7d 100644 --- a/src/HotChocolate/Data/src/Data/Filters/Visitor/IFilterVisitorContext.cs +++ b/src/HotChocolate/Data/src/Data/Filters/Visitor/IFilterVisitorContext.cs @@ -7,7 +7,7 @@ namespace HotChocolate.Data.Filters; /// /// A context object that is passed along the visitation cycle /// -public interface IFilterVisitorContext : ISyntaxVisitorContext +public interface IFilterVisitorContext { /// /// The already visited types diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryablePagingProjectionOptimizer.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryablePagingProjectionOptimizer.cs index 46487fcbc05..bce60064166 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryablePagingProjectionOptimizer.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryablePagingProjectionOptimizer.cs @@ -155,8 +155,7 @@ private static void CollectSelectionOfNodes( SelectionSetOptimizerContext context, List selections) { - if (context.Selections.Values.FirstOrDefault( - x => x.Field.Name == "nodes") is { } nodeSelection) + if (context.Selections.Values.FirstOrDefault(x => x.Field.Name == "nodes") is { } nodeSelection) { foreach (var nodeField in nodeSelection.SelectionSet!.Selections) { @@ -167,7 +166,7 @@ private static void CollectSelectionOfNodes( } } - private static readonly ISyntaxRewriter _cloneSelectionSetRewriter = + private static readonly ISyntaxRewriter _cloneSelectionSetRewriter = SyntaxRewriter.Create( n => n.Kind is SyntaxKind.SelectionSet ? new SelectionSetNode(((SelectionSetNode)n).Selections) diff --git a/src/HotChocolate/Data/src/Data/Sorting/Visitor/ISortVisitorContext.cs b/src/HotChocolate/Data/src/Data/Sorting/Visitor/ISortVisitorContext.cs index 1f759a10ebf..b457f108b7b 100644 --- a/src/HotChocolate/Data/src/Data/Sorting/Visitor/ISortVisitorContext.cs +++ b/src/HotChocolate/Data/src/Data/Sorting/Visitor/ISortVisitorContext.cs @@ -1,10 +1,8 @@ -using System.Collections.Generic; -using HotChocolate.Language.Visitors; using HotChocolate.Types; namespace HotChocolate.Data.Sorting; -public interface ISortVisitorContext : ISyntaxVisitorContext +public interface ISortVisitorContext { Stack Types { get; } diff --git a/src/HotChocolate/Fusion/HotChocolate.Fusion.sln b/src/HotChocolate/Fusion/HotChocolate.Fusion.sln index 8244356f6a5..9c42ceb037b 100644 --- a/src/HotChocolate/Fusion/HotChocolate.Fusion.sln +++ b/src/HotChocolate/Fusion/HotChocolate.Fusion.sln @@ -25,6 +25,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Fusion.Command EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Fusion.CommandLine.Tests", "test\CommandLine.Tests\HotChocolate.Fusion.CommandLine.Tests.csproj", "{DBD317C2-8485-4A75-8BB7-D70C02B40944}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Fusion.Composition.Analyzers", "src\Composition.Analyzers\HotChocolate.Fusion.Composition.Analyzers.csproj", "{A939BC1B-93A0-40DC-B336-27FF2BC2F704}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Fusion.Composition.Analyzers.Tests", "test\Composition.Analyzers.Tests\HotChocolate.Fusion.Composition.Analyzers.Tests.csproj", "{FD460672-8769-4EB8-87E0-A6A9D5C946C7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -70,6 +74,14 @@ Global {DBD317C2-8485-4A75-8BB7-D70C02B40944}.Debug|Any CPU.Build.0 = Debug|Any CPU {DBD317C2-8485-4A75-8BB7-D70C02B40944}.Release|Any CPU.ActiveCfg = Release|Any CPU {DBD317C2-8485-4A75-8BB7-D70C02B40944}.Release|Any CPU.Build.0 = Release|Any CPU + {A939BC1B-93A0-40DC-B336-27FF2BC2F704}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A939BC1B-93A0-40DC-B336-27FF2BC2F704}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A939BC1B-93A0-40DC-B336-27FF2BC2F704}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A939BC1B-93A0-40DC-B336-27FF2BC2F704}.Release|Any CPU.Build.0 = Release|Any CPU + {FD460672-8769-4EB8-87E0-A6A9D5C946C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FD460672-8769-4EB8-87E0-A6A9D5C946C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD460672-8769-4EB8-87E0-A6A9D5C946C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FD460672-8769-4EB8-87E0-A6A9D5C946C7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {0355AF0F-B91D-4852-8C9F-8E13CE5C88F3} = {748FCFC6-3EE7-4CFD-AFB3-B0F7B1ACD026} @@ -81,5 +93,7 @@ Global {0A07E4BB-0CFE-406C-B3F4-E26D0100F6F9} = {0EF9C546-286E-407F-A02E-731804507FDE} {B4FA97BA-2C36-48F6-ABC4-7088C655E936} = {748FCFC6-3EE7-4CFD-AFB3-B0F7B1ACD026} {DBD317C2-8485-4A75-8BB7-D70C02B40944} = {0EF9C546-286E-407F-A02E-731804507FDE} + {A939BC1B-93A0-40DC-B336-27FF2BC2F704} = {748FCFC6-3EE7-4CFD-AFB3-B0F7B1ACD026} + {FD460672-8769-4EB8-87E0-A6A9D5C946C7} = {0EF9C546-286E-407F-A02E-731804507FDE} EndGlobalSection EndGlobal diff --git a/src/HotChocolate/Fusion/src/CommandLine/Commands/ComposeCommand.cs b/src/HotChocolate/Fusion/src/CommandLine/Commands/ComposeCommand.cs index c309064064c..207eb46ab2d 100644 --- a/src/HotChocolate/Fusion/src/CommandLine/Commands/ComposeCommand.cs +++ b/src/HotChocolate/Fusion/src/CommandLine/Commands/ComposeCommand.cs @@ -280,15 +280,8 @@ private static async Task ResolveSubgraphPackagesAsync( } } - private sealed class ConsoleLog : ICompositionLog + private sealed class ConsoleLog(IConsole console) : ICompositionLog { - private readonly IConsole _console; - - public ConsoleLog(IConsole console) - { - _console = console; - } - public bool HasErrors { get; private set; } public void Write(LogEntry e) @@ -300,15 +293,15 @@ public void Write(LogEntry e) if (e.Code is null) { - _console.WriteLine($"{e.Severity}: {e.Message}"); + console.WriteLine($"{e.Severity}: {e.Message}"); } else if (e.Coordinate is null) { - _console.WriteLine($"{e.Severity}: {e.Code} {e.Message}"); + console.WriteLine($"{e.Severity}: {e.Code} {e.Message}"); } else { - _console.WriteLine($"{e.Severity}: {e.Code} {e.Message} {e.Coordinate}"); + console.WriteLine($"{e.Severity}: {e.Code} {e.Message} {e.Coordinate}"); } } } diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/ConfigurationGenerator.cs b/src/HotChocolate/Fusion/src/Composition.Analyzers/ConfigurationGenerator.cs new file mode 100644 index 00000000000..90322479cd4 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/ConfigurationGenerator.cs @@ -0,0 +1,313 @@ +using System.Collections.Immutable; +using HotChocolate.Fusion.Analyzers.Properties; +using HotChocolate.Types.Analyzers.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace HotChocolate.Types.Analyzers; + +[Generator] +public class ConfigurationGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + context.RegisterPostInitializationOutput(AddInitializationContent); + + var modulesAndTypes = + context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (s, _) => IsRelevant(s), + transform: TryGetProjectClass) + .Where(static t => t is not null); + + var valueProvider = context.CompilationProvider.Combine(modulesAndTypes.Collect()); + + context.RegisterSourceOutput( + valueProvider, + static (context, source) => Execute(context, source.Right!)); + } + + private static bool IsRelevant(SyntaxNode node) + { + if (node is ClassDeclarationSyntax { BaseList.Types.Count: > 0, TypeParameterList: null, }) + { + return true; + } + + if (node is InvocationExpressionSyntax + { + Expression: MemberAccessExpressionSyntax memberAccess, + }) + { + if (memberAccess.Name is GenericNameSyntax { TypeArgumentList.Arguments.Count: 1, } genericName) + { + if (genericName.Identifier.ValueText.Equals("AddFusionGateway", StringComparison.Ordinal)) + { + return true; + } + + if (genericName.Identifier.ValueText.Equals("AddProject", StringComparison.Ordinal)) + { + var current = node; + + while (current.Parent is InvocationExpressionSyntax or MemberAccessExpressionSyntax) + { + current = current.Parent; + } + + if (current.Parent is EqualsValueClauseSyntax) + { + return true; + } + } + } + + if (memberAccess.Name is not GenericNameSyntax && + memberAccess.Name.Identifier.ValueText.Equals("WithSubgraph", StringComparison.Ordinal)) + { + return true; + } + } + + return false; + } + + private static ISyntaxInfo? TryGetProjectClass(GeneratorSyntaxContext context, CancellationToken cancellationToken) + { + if (context.Node is InvocationExpressionSyntax + { + Expression: MemberAccessExpressionSyntax + { + Name: GenericNameSyntax + { + Identifier.ValueText: { } name, + TypeArgumentList: { Arguments.Count: 1, } args, + }, + }, + } invocation) + { + if (name.Equals("AddProject") && + context.SemanticModel.GetTypeInfo(args.Arguments[0]).Type is INamedTypeSymbol subgraphType && + subgraphType.AllInterfaces.Any(t => t.ToFullyQualified().Equals(WellKnownTypeNames.ProjectMetadata))) + { + SyntaxNode current = invocation; + + while (current.Parent is InvocationExpressionSyntax or MemberAccessExpressionSyntax) + { + current = current.Parent; + } + + if (current.Parent is EqualsValueClauseSyntax && + current.Parent.Parent is VariableDeclaratorSyntax variable) + { + return new SubgraphClass( + subgraphType.Name, + subgraphType.ToFullyQualified(), + variable.Identifier.ValueText); + } + } + } + + if (context.Node is InvocationExpressionSyntax + { + Expression: MemberAccessExpressionSyntax + { + Name.Identifier.ValueText: "WithSubgraph", + }, + ArgumentList.Arguments.Count: 1, + } subgraphInvocation) + { + SyntaxNode current = subgraphInvocation; + + while (current is InvocationExpressionSyntax or MemberAccessExpressionSyntax) + { + if (current is InvocationExpressionSyntax invocationSyntax) + { + current = invocationSyntax.Expression; + } + else if (current is MemberAccessExpressionSyntax memberAccessSyntax) + { + current = memberAccessSyntax.Expression; + } + + if (current is InvocationExpressionSyntax + { + Expression: MemberAccessExpressionSyntax + { + Name: GenericNameSyntax + { + Identifier.ValueText: "AddFusionGateway", + TypeArgumentList.Arguments: { Count: 1, } fusionArgs, + }, + }, + } && + context.SemanticModel.GetTypeInfo(fusionArgs[0]).Type is INamedTypeSymbol gatewayType) + { + var argument = subgraphInvocation.ArgumentList.Arguments[0]; + return new GatewayClass( + gatewayType.Name, + gatewayType.ToFullyQualified(), + GetVariableName(argument)); + } + } + } + + + return null; + } + + private static string GetVariableName(ArgumentSyntax argument) + { + SyntaxNode current = argument.Expression; + + while (current is InvocationExpressionSyntax or MemberAccessExpressionSyntax or IdentifierNameSyntax) + { + if (current is InvocationExpressionSyntax invocation) + { + current = invocation.Expression; + } + + if (current is MemberAccessExpressionSyntax memberAccess) + { + current = memberAccess.Expression; + } + + if (current is IdentifierNameSyntax identifier) + { + return identifier.Identifier.ValueText; + } + } + + throw new InvalidOperationException(); + } + + private static void Execute( + SourceProductionContext context, + ImmutableArray syntaxInfos) + { + if (syntaxInfos.Length == 0) + { + return; + } + + var projects = new Dictionary(); + + foreach (var project in syntaxInfos.OfType()) + { + projects[project.VariableName] = project; + } + + var processed = new HashSet(); + var gateways = new List(); + + foreach (var gatewayGroup in syntaxInfos.OfType().GroupBy(t => t.Name)) + { + if (!processed.Add(gatewayGroup.Key)) + { + continue; + } + + var gateway = new GatewayInfo(gatewayGroup.Key, gatewayGroup.First().TypeName); + + foreach (var projectLink in gatewayGroup) + { + if (projects.TryGetValue(projectLink.VariableName, out var project)) + { + gateway.Subgraphs.Add(new SubgraphInfo(project.Name, project.TypeName)); + } + } + + gateways.Add(gateway); + } + + if (gateways.Count == 0) + { + return; + } + + var code = StringBuilderPool.Get(); + using var writer = new CodeWriter(code); + + writer.WriteFileHeader(); + writer.Write(Resources.CliCode); + writer.WriteLine(); + writer.WriteLine(); + writer.WriteIndentedLine("namespace HotChocolate.Fusion.Composition.Tooling"); + writer.WriteIndentedLine("{"); + + using (writer.IncreaseIndent()) + { + writer.WriteIndentedLine("file class GatewayList : List"); + writer.WriteIndentedLine("{"); + + using (writer.IncreaseIndent()) + { + writer.WriteIndentedLine("public GatewayList()"); + + using (writer.IncreaseIndent()) + { + writer.WriteIndentedLine(": base("); + writer.WriteIndentedLine("["); + + using (writer.IncreaseIndent()) + { + foreach (var gateway in gateways) + { + writer.WriteIndentedLine("GatewayInfo.Create<{0}>(", gateway.TypeName); + + using (writer.IncreaseIndent()) + { + writer.WriteIndentedLine("\"{0}\",", gateway.Name); + + var first = true; + + foreach (var project in gateway.Subgraphs) + { + if (first) + { + first = false; + } + else + { + writer.Write(","); + writer.WriteLine(); + } + + writer.WriteIndented( + "SubgraphInfo.Create<{0}>(\"{1}\", \"{2}\")", + project.TypeName, + project.Name, + project.Name); + } + + writer.Write("),"); + writer.WriteLine(); + } + } + } + + writer.WriteIndentedLine("]) { }"); + } + } + + writer.WriteIndentedLine("}"); + } + + writer.WriteIndentedLine("}"); + + context.AddSource("FusionConfiguration.g.cs", code.ToString()); + StringBuilderPool.Return(code); + } + + private static void AddInitializationContent(IncrementalGeneratorPostInitializationContext context) + { + var code = StringBuilderPool.Get(); + using var writer = new CodeWriter(code); + + writer.WriteFileHeader(); + writer.Write(Resources.Extensions); + + context.AddSource("FusionExtensions.g.cs", code.ToString()); + StringBuilderPool.Return(code); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/Helpers/CodeWriter.cs b/src/HotChocolate/Fusion/src/Composition.Analyzers/Helpers/CodeWriter.cs new file mode 100644 index 00000000000..3bef6695746 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/Helpers/CodeWriter.cs @@ -0,0 +1,156 @@ +using System.Text; + +namespace HotChocolate.Types.Analyzers.Helpers; + +public class CodeWriter : TextWriter +{ + private readonly TextWriter _writer; + private readonly bool _disposeWriter; + private bool _disposed; + private int _indent; + + public CodeWriter(TextWriter writer) + { + _writer = writer; + _disposeWriter = false; + } + + public CodeWriter(StringBuilder text) + { + _writer = new StringWriter(text); + _disposeWriter = true; + } + + public override Encoding Encoding { get; } = Encoding.UTF8; + + public static string Indent { get; } = new(' ', 4); + + public override void Write(char value) => + _writer.Write(value); + + public void WriteStringValue(string value) + { + Write('"'); + Write(value); + Write('"'); + } + + public void WriteIndent() + { + if (_indent > 0) + { + var spaces = _indent * 4; + for (var i = 0; i < spaces; i++) + { + Write(' '); + } + } + } + + public string GetIndentString() + { + if (_indent > 0) + { + return new string(' ', _indent * 4); + } + return string.Empty; + } + + public void WriteIndentedLine(string format, params object?[] args) + { + WriteIndent(); + + if (args.Length == 0) + { + Write(format); + } + else + { + Write(format, args); + } + + WriteLine(); + } + + public void WriteIndented(string format, params object?[] args) + { + WriteIndent(); + + if (args.Length == 0) + { + Write(format); + } + else + { + Write(format, args); + } + } + + public void WriteSpace() => Write(' '); + + public IDisposable IncreaseIndent() + { + _indent++; + return new Block(DecreaseIndent); + } + + public void DecreaseIndent() + { + if (_indent > 0) + { + _indent--; + } + } + + public IDisposable WriteBraces() + { + WriteLeftBrace(); + WriteLine(); + +#pragma warning disable CA2000 + var indent = IncreaseIndent(); +#pragma warning restore CA2000 + + return new Block(() => + { + WriteLine(); + indent.Dispose(); + WriteIndent(); + WriteRightBrace(); + }); + } + + public void WriteLeftBrace() => Write('{'); + + public void WriteRightBrace() => Write('}'); + + public override void Flush() + { + base.Flush(); + _writer.Flush(); + } + + protected override void Dispose(bool disposing) + { + if (!_disposed && _disposeWriter) + { + if (disposing) + { + _writer.Dispose(); + } + _disposed = true; + } + } + + private sealed class Block : IDisposable + { + private readonly Action _decrease; + + public Block(Action close) + { + _decrease = close; + } + + public void Dispose() => _decrease(); + } +} \ No newline at end of file diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/Helpers/CodeWriterExtensions.cs b/src/HotChocolate/Fusion/src/Composition.Analyzers/Helpers/CodeWriterExtensions.cs new file mode 100644 index 00000000000..dd20afd63ad --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/Helpers/CodeWriterExtensions.cs @@ -0,0 +1,43 @@ +namespace HotChocolate.Types.Analyzers.Helpers; + +public static class CodeWriterExtensions +{ + public static void WriteGeneratedAttribute(this CodeWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + +#if DEBUG + writer.WriteIndentedLine( + "[global::System.CodeDom.Compiler.GeneratedCode(" + + "\"HotChocolate\", \"11.0.0\")]"); +#else + var version = typeof(CodeWriter).Assembly.GetName().Version!.ToString(); + writer.WriteIndentedLine( + "[global::System.CodeDom.Compiler.GeneratedCode(" + + $"\"HotChocolate\", \"{version}\")]"); +#endif + } + + public static void WriteFileHeader(this CodeWriter writer) + { + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } + + writer.WriteIndentedLine("// "); + writer.WriteLine(); + writer.WriteIndentedLine("#nullable enable"); + writer.WriteLine(); + } + + public static CodeWriter WriteComment(this CodeWriter writer, string comment) + { + writer.Write("// "); + writer.WriteLine(comment); + return writer; + } +} \ No newline at end of file diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/Helpers/StringBuilderPool.cs b/src/HotChocolate/Fusion/src/Composition.Analyzers/Helpers/StringBuilderPool.cs new file mode 100644 index 00000000000..2f8fe5b7e15 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/Helpers/StringBuilderPool.cs @@ -0,0 +1,20 @@ +using System.Text; + +namespace HotChocolate.Types.Analyzers.Helpers; + +public static class StringBuilderPool +{ + private static StringBuilder? _stringBuilder; + + public static StringBuilder Get() + { + var stringBuilder = Interlocked.Exchange(ref _stringBuilder, null); + return stringBuilder ?? new StringBuilder(); + } + + public static void Return(StringBuilder stringBuilder) + { + stringBuilder.Clear(); + Interlocked.CompareExchange(ref _stringBuilder, stringBuilder, null); + } +} diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/Helpers/SymbolExtensions.cs b/src/HotChocolate/Fusion/src/Composition.Analyzers/Helpers/SymbolExtensions.cs new file mode 100644 index 00000000000..0473d15b7cc --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/Helpers/SymbolExtensions.cs @@ -0,0 +1,9 @@ +using Microsoft.CodeAnalysis; + +namespace HotChocolate.Types.Analyzers.Helpers; + +public static class SymbolExtensions +{ + public static string ToFullyQualified(this ITypeSymbol typeSymbol) + => typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); +} diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/HotChocolate.Fusion.Analyzers.targets b/src/HotChocolate/Fusion/src/Composition.Analyzers/HotChocolate.Fusion.Analyzers.targets new file mode 100644 index 00000000000..340becf594f --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/HotChocolate.Fusion.Analyzers.targets @@ -0,0 +1,5 @@ + + + + + diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/HotChocolate.Fusion.Composition.Analyzers.csproj b/src/HotChocolate/Fusion/src/Composition.Analyzers/HotChocolate.Fusion.Composition.Analyzers.csproj new file mode 100644 index 00000000000..e79fd6da3d6 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/HotChocolate.Fusion.Composition.Analyzers.csproj @@ -0,0 +1,46 @@ + + + + netstandard2.0 + netstandard2.0 + enable + enable + false + false + true + + + + HotChocolate.Fusion.Analyzers + HotChocolate.Fusion.Analyzers + HotChocolate.Fusion.Analyzers + This package provides source generators for HotChocolate Fusion. + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + True + True + Resources.resx + + + + diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/Models/GatewayClass.cs b/src/HotChocolate/Fusion/src/Composition.Analyzers/Models/GatewayClass.cs new file mode 100644 index 00000000000..67e7e4c8960 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/Models/GatewayClass.cs @@ -0,0 +1,10 @@ +namespace HotChocolate.Types.Analyzers; + +public sealed class GatewayClass(string name, string typeName, string variableName) : ISyntaxInfo +{ + public string Name { get; } = name; + + public string TypeName { get; } = typeName; + + public string VariableName { get; } = variableName; +} \ No newline at end of file diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/Models/GatewayInfo.cs b/src/HotChocolate/Fusion/src/Composition.Analyzers/Models/GatewayInfo.cs new file mode 100644 index 00000000000..8b316fc5faf --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/Models/GatewayInfo.cs @@ -0,0 +1,10 @@ +namespace HotChocolate.Types.Analyzers; + +public class GatewayInfo(string name, string typeName) +{ + public string Name { get; } = name; + + public string TypeName { get; } = typeName; + + public List Subgraphs { get; } = new(); +} \ No newline at end of file diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/Models/ISyntaxInfo.cs b/src/HotChocolate/Fusion/src/Composition.Analyzers/Models/ISyntaxInfo.cs new file mode 100644 index 00000000000..097a6e47f90 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/Models/ISyntaxInfo.cs @@ -0,0 +1,6 @@ +namespace HotChocolate.Types.Analyzers; + +public interface ISyntaxInfo +{ + string Name { get; } +} \ No newline at end of file diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/Models/SubgraphClass.cs b/src/HotChocolate/Fusion/src/Composition.Analyzers/Models/SubgraphClass.cs new file mode 100644 index 00000000000..54bf0182bc6 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/Models/SubgraphClass.cs @@ -0,0 +1,10 @@ +namespace HotChocolate.Types.Analyzers; + +public sealed class SubgraphClass(string name, string typeName, string variableName) : ISyntaxInfo +{ + public string Name { get; } = name; + + public string TypeName { get; } = typeName; + + public string VariableName { get; } = variableName; +} \ No newline at end of file diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/Models/SubgraphInfo.cs b/src/HotChocolate/Fusion/src/Composition.Analyzers/Models/SubgraphInfo.cs new file mode 100644 index 00000000000..f937a6d0f0c --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/Models/SubgraphInfo.cs @@ -0,0 +1,8 @@ +namespace HotChocolate.Types.Analyzers; + +public class SubgraphInfo(string name, string typeName) +{ + public string Name { get; } = name; + + public string TypeName { get; } = typeName; +} \ No newline at end of file diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/Properties/Resources.Designer.cs b/src/HotChocolate/Fusion/src/Composition.Analyzers/Properties/Resources.Designer.cs new file mode 100644 index 00000000000..5ae122945da --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/Properties/Resources.Designer.cs @@ -0,0 +1,60 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace HotChocolate.Fusion.Analyzers.Properties { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("HotChocolate.Fusion.Analyzers.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static string CliCode { + get { + return ResourceManager.GetString("CliCode", resourceCulture); + } + } + + internal static string Extensions { + get { + return ResourceManager.GetString("Extensions", resourceCulture); + } + } + } +} diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/Properties/Resources.resx b/src/HotChocolate/Fusion/src/Composition.Analyzers/Properties/Resources.resx new file mode 100644 index 00000000000..0362e573398 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/Properties/Resources.resx @@ -0,0 +1,663 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + using System; +using System.Runtime.CompilerServices; +using System.Buffers; +using System.Diagnostics; +using System.Net.Mime; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using HotChocolate.Execution.Configuration; +using HotChocolate.Fusion; +using HotChocolate.Fusion.Composition; +using HotChocolate.Fusion.Composition.Features; +using HotChocolate.Fusion.Composition.Tooling; +using HotChocolate.Language; +using HotChocolate.Language.Visitors; +using HotChocolate.Skimmed.Serialization; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Fusion.Composition.Tooling +{ + + internal static class FusionGatewayConfgHelper + { + public static async Task ConfigureAsync(CancellationToken cancellationToken = default) + { + ExportSubgraphSchemaDocs(); + await EnsureSubgraphHasConfigAsync(cancellationToken); + await ComposeAsync(cancellationToken); + } + + private static void ExportSubgraphSchemaDocs() + { + var processed = new HashSet<string>(); + + foreach (var gateway in new GatewayList()) + { + foreach (var subgraph in gateway.Subgraphs) + { + if (!processed.Add(subgraph.Path)) + { + continue; + } + + Console.WriteLine("Expoorting schema document for subgraph {0} ...", subgraph.Name); + + var workingDirectory = System.IO.Path.GetDirectoryName(subgraph.Path)!; + + var processStartInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = "dotnet run --no-build --no-restore -- schema export --output schema.graphql", + WorkingDirectory = workingDirectory, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + }; + + using (var process = Process.Start(processStartInfo)!) + { + string output = process.StandardOutput.ReadToEnd(); + string errors = process.StandardError.ReadToEnd(); + + process.WaitForExit(); + + if (!string.IsNullOrEmpty(output)) + { + Console.WriteLine(output); + } + + if (!string.IsNullOrEmpty(errors)) + { + Console.WriteLine(errors); + } + + if (process.ExitCode != 0) + { + Console.WriteLine( + "{0}(1,1): error HF1002: ; Failed to export schema document for subgraph {1} ...", + subgraph.Path, + subgraph.Name); + Environment.Exit(-255); + } + } + } + } + } + + private static async Task EnsureSubgraphHasConfigAsync(CancellationToken cancellationToken = default) + { + foreach (var gateway in new GatewayList()) + { + foreach (var project in gateway.Subgraphs) + { + var projectRoot = System.IO.Path.GetDirectoryName(project.Path)!; + var configFile = System.IO.Path.Combine(projectRoot, Defaults.ConfigFile); + + if (File.Exists(configFile)) + { + continue; + } + + var config = new SubgraphConfigurationDto(project.Name); + var configJson = PackageHelper.FormatSubgraphConfig(config); + await File.WriteAllTextAsync(configFile, configJson, cancellationToken); + } + } + } + + private static async Task ComposeAsync(CancellationToken cancellationToken = default) + { + foreach (var gateway in new GatewayList()) + { + await ComposeGatewayAsync(gateway.Path, gateway.Subgraphs.Select(t => t.Path), cancellationToken); + } + } + + private static async Task ComposeGatewayAsync( + string gatewayProject, + IEnumerable<string> subgraphProjects, + CancellationToken cancellationToken = default) + { + var gatewayDirectory = System.IO.Path.GetDirectoryName(gatewayProject)!; + var packageFileName = System.IO.Path.Combine(gatewayDirectory, $"gateway{Extensions.FusionPackage}"); + var packageFile = new FileInfo(packageFileName); + var settingsFileName = System.IO.Path.Combine(gatewayDirectory, "gateway-settings.json"); + var settingsFile = new FileInfo(settingsFileName); + var subgraphDirectories = subgraphProjects.Select(t => System.IO.Path.GetDirectoryName(t)!).ToArray(); + + // Ensure Gateway Project Directory Exists. + if (!Directory.Exists(gatewayDirectory)) + { + Directory.CreateDirectory(gatewayDirectory); + } + + await using var package = FusionGraphPackage.Open(packageFile.FullName); + var subgraphConfigs = + (await package.GetSubgraphConfigurationsAsync(cancellationToken)).ToDictionary(t => t.Name); + await ResolveSubgraphPackagesAsync(subgraphDirectories, subgraphConfigs, cancellationToken); + + using var settingsJson = settingsFile.Exists + ? JsonDocument.Parse(await File.ReadAllTextAsync(settingsFile.FullName, cancellationToken)) + : await package.GetFusionGraphSettingsAsync(cancellationToken); + var settings = settingsJson.Deserialize<PackageSettings>() ?? new PackageSettings(); + + var features = settings.CreateFeatures(); + + var composer = new FusionGraphComposer( + settings.FusionTypePrefix, + settings.FusionTypeSelf, + () => new ConsoleLog()); + + var fusionGraph = await composer.TryComposeAsync(subgraphConfigs.Values, features, cancellationToken); + + if (fusionGraph is null) + { + Console.WriteLine("Fusion graph composition failed."); + return; + } + + var fusionGraphDoc = Utf8GraphQLParser.Parse(SchemaFormatter.FormatAsString(fusionGraph)); + var typeNames = FusionTypeNames.From(fusionGraphDoc); + var rewriter = new FusionGraphConfigurationToSchemaRewriter(); + var schemaDoc = (DocumentNode)rewriter.Rewrite(fusionGraphDoc, typeNames)!; + + using var updateSettingsJson = JsonSerializer.SerializeToDocument( + settings, + new JsonSerializerOptions(JsonSerializerDefaults.Web)); + + await package.SetFusionGraphAsync(fusionGraphDoc, cancellationToken); + await package.SetFusionGraphSettingsAsync(updateSettingsJson, cancellationToken); + await package.SetSchemaAsync(schemaDoc, cancellationToken); + + foreach (var config in subgraphConfigs.Values) + { + await package.SetSubgraphConfigurationAsync(config, cancellationToken); + } + + Console.WriteLine("Fusion graph composed."); + } + + private static async Task ResolveSubgraphPackagesAsync( + IReadOnlyList<string> subgraphDirectories, + IDictionary<string, SubgraphConfiguration> subgraphConfigs, + CancellationToken cancellationToken) + { + for (var i = 0; i < subgraphDirectories.Count; i++) + { + var path = subgraphDirectories[i]; + + if (!Directory.Exists(path)) + { + continue; + } + + var configFile = System.IO.Path.Combine(path, Defaults.ConfigFile); + var schemaFile = System.IO.Path.Combine(path, Defaults.SchemaFile); + var extensionFile = System.IO.Path.Combine(path, Defaults.ExtensionFile); + + if (!File.Exists(configFile) || !File.Exists(schemaFile)) + { + continue; + } + + var conf = await PackageHelper.LoadSubgraphConfigAsync(configFile, cancellationToken); + var schema = await File.ReadAllTextAsync(schemaFile, cancellationToken); + var extensions = Array.Empty<string>(); + + if (File.Exists(extensionFile)) + { + extensions = [await File.ReadAllTextAsync(extensionFile, cancellationToken),]; + } + + subgraphConfigs[conf.Name] = + new SubgraphConfiguration( + conf.Name, + schema, + extensions, + conf.Clients, + conf.Extensions); + } + } + } + + file static class Defaults + { + public const string SchemaFile = "schema.graphql"; + public const string ExtensionFile = "schema.extensions.graphql"; + public const string ConfigFile = "subgraph-config.json"; + } + + file static class Extensions + { + public const string FusionPackage = ".fgp"; + } + + file class PackageSettings + { + private Feature? _reEncodeIds; + private Feature? _nodeField; + private TagDirective? _tagDirective; + private Transport? _transport; + + [JsonPropertyName("fusionTypePrefix")] + [JsonPropertyOrder(10)] + public string? FusionTypePrefix { get; set; } + + [JsonPropertyName("fusionTypeSelf")] + [JsonPropertyOrder(11)] + public bool FusionTypeSelf { get; set; } + + public Transport Transport + { + get => _transport ??= new(); + set => _transport = value; + } + + [JsonPropertyName("nodeField")] + [JsonPropertyOrder(12)] + public Feature NodeField + { + get => _nodeField ??= new(); + set => _nodeField = value; + } + + [JsonPropertyName("reEncodeIds")] + [JsonPropertyOrder(13)] + public Feature ReEncodeIds + { + get => _reEncodeIds ??= new(); + set => _reEncodeIds = value; + } + + [JsonPropertyName("tagDirective")] + [JsonPropertyOrder(14)] + public TagDirective TagDirective + { + get => _tagDirective ??= new(); + set => _tagDirective = value; + } + + public FusionFeatureCollection CreateFeatures() + { + var features = new List<IFusionFeature> + { + new TransportFeature + { + DefaultClientName = Transport.DefaultClientName, + }, + }; + + if (NodeField.Enabled) + { + features.Add(FusionFeatures.NodeField); + } + + if (ReEncodeIds.Enabled) + { + features.Add(FusionFeatures.ReEncodeIds); + } + + if (TagDirective.Enabled) + { + features.Add( + FusionFeatures.TagDirective( + TagDirective.Exclude, + TagDirective.MakePublic)); + } + + return new FusionFeatureCollection(features); + } + } + + file class Feature + { + [JsonPropertyName("enabled")] + [JsonPropertyOrder(10)] + public bool Enabled { get; set; } + } + + file sealed class TagDirective : Feature + { + private string[]? _exclude; + + [JsonPropertyName("makePublic")] + [JsonPropertyOrder(100)] + public bool MakePublic { get; set; } + + [JsonPropertyName("exclude")] + [JsonPropertyOrder(101)] + public string[] Exclude + { + get => _exclude ?? Array.Empty<string>(); + set => _exclude = value; + } + } + + file sealed class Transport + { + [JsonPropertyName("defaultClientName")] + [JsonPropertyOrder(10)] + public string? DefaultClientName { get; set; } = "Fusion"; + } + + file sealed class ConsoleLog : ICompositionLog + { + public bool HasErrors { get; private set; } + + public void Write(LogEntry e) + { + if (e.Severity is LogSeverity.Error) + { + HasErrors = true; + } + + if (e.Code is null) + { + Console.WriteLine($"{e.Severity}: {e.Message}"); + } + else if (e.Coordinate is null) + { + Console.WriteLine($"{e.Severity}: {e.Code} {e.Message}"); + } + else + { + Console.WriteLine($"{e.Severity}: {e.Code} {e.Message} {e.Coordinate}"); + } + } + } + + file sealed class SubgraphConfigurationDto( + string name, + IReadOnlyList<IClientConfiguration>? clients = null, + JsonElement? extensions = null) + { + public string Name { get; } = name; + + public IReadOnlyList<IClientConfiguration> Clients { get; } = clients ?? Array.Empty<IClientConfiguration>(); + + public JsonElement? Extensions { get; } = extensions; + } + + file static class PackageHelper + { + public static async Task<SubgraphConfigurationDto> LoadSubgraphConfigAsync( + string filename, + CancellationToken ct) + { + await using var stream = File.OpenRead(filename); + return await ParseSubgraphConfigAsync(stream, ct); + } + + private static async Task<SubgraphConfigurationDto> ParseSubgraphConfigAsync( + Stream stream, + CancellationToken ct) + { + using var document = await JsonDocument.ParseAsync(stream, cancellationToken: ct); + var configs = new List<IClientConfiguration>(); + var subgraph = default(string?); + var extensions = default(JsonElement?); + + foreach (var property in document.RootElement.EnumerateObject()) + { + switch (property.Name) + { + case "subgraph": + subgraph = property.Value.GetString(); + break; + + case "http": + configs.Add(ReadHttpClientConfiguration(property.Value)); + break; + + case "websocket": + configs.Add(ReadWebSocketClientConfiguration(property.Value)); + break; + + case "extensions": + extensions = property.Value.SafeClone(); + break; + + default: + throw new NotSupportedException( + $"Configuration property `{property.Value}` is not supported."); + } + } + + if (string.IsNullOrEmpty(subgraph)) + { + throw new InvalidOperationException("No subgraph name was specified."); + } + + return new SubgraphConfigurationDto(subgraph, configs, extensions); + } + + private static HttpClientConfiguration ReadHttpClientConfiguration( + JsonElement element) + { + var baseAddress = new Uri(element.GetProperty("baseAddress").GetString()!); + var clientName = default(string?); + + if (element.TryGetProperty("clientName", out var clientNameElement)) + { + clientName = clientNameElement.GetString(); + } + + return new HttpClientConfiguration(baseAddress, clientName); + } + + private static WebSocketClientConfiguration ReadWebSocketClientConfiguration( + JsonElement element) + { + var baseAddress = new Uri(element.GetProperty("baseAddress").GetString()!); + var clientName = default(string?); + + if (element.TryGetProperty("clientName", out var clientNameElement)) + { + clientName = clientNameElement.GetString(); + } + + return new WebSocketClientConfiguration(baseAddress, clientName); + } + + private static JsonElement SafeClone(this JsonElement element) + { + var writer = new ArrayBufferWriter<byte>(); + using var jsonWriter = new Utf8JsonWriter(writer); + + element.WriteTo(jsonWriter); + jsonWriter.Flush(); + var reader = new Utf8JsonReader(writer.WrittenSpan, true, default); + + return JsonElement.ParseValue(ref reader); + } + + public static string FormatSubgraphConfig( + SubgraphConfigurationDto subgraphConfig) + { + var buffer = new ArrayBufferWriter<byte>(); + using var writer = new Utf8JsonWriter(buffer); + + writer.WriteStartObject(); + writer.WriteString("subgraph", subgraphConfig.Name); + + foreach (var client in subgraphConfig.Clients) + { + switch (client) + { + case HttpClientConfiguration config: + writer.WriteStartObject("http"); + writer.WriteString("baseAddress", config.BaseAddress.ToString()); + + if (config.ClientName is not null) + { + writer.WriteString("clientName", config.ClientName); + } + + writer.WriteEndObject(); + break; + + case WebSocketClientConfiguration config: + writer.WriteStartObject("websocket"); + writer.WriteString("baseAddress", config.BaseAddress.ToString()); + + if (config.ClientName is not null) + { + writer.WriteString("clientName", config.ClientName); + } + + writer.WriteEndObject(); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(client)); + } + } + + if (subgraphConfig.Extensions is not null) + { + writer.WritePropertyName("extensions"); + subgraphConfig.Extensions.Value.WriteTo(writer); + } + + writer.WriteEndObject(); + writer.Flush(); + + return Encoding.UTF8.GetString(buffer.WrittenSpan); + } + } + + file sealed class FusionGraphConfigurationToSchemaRewriter + : SyntaxRewriter<FusionTypeNames> + { + public DocumentNode Rewrite(DocumentNode fusionGraph) + { + var typeNames = FusionTypeNames.From(fusionGraph); + var schemaDoc = (DocumentNode?)Rewrite(fusionGraph, typeNames); + + if (schemaDoc is null) + { + throw new InvalidOperationException(); + } + + return schemaDoc; + } + + protected override DirectiveNode? RewriteDirective(DirectiveNode node, FusionTypeNames context) + => context.IsFusionDirective(node.Name.Value) + ? null + : base.RewriteDirective(node, context); + } + + file sealed class SubgraphInfo(string name, string path, string variableName) + { + public string Name { get; } = name; + + public string Path { get; } = path; + + public string VariableName { get; } = variableName; + + public static SubgraphInfo Create<TProject>(string name, string variableName) + where TProject : IProjectMetadata, new() + => new(name, new TProject().ProjectPath, variableName); + } + + file sealed class GatewayInfo(string name, string path, IReadOnlyList<SubgraphInfo> subgraphs) + { + public string Name { get; } = name; + + public string Path { get; } = path; + + public IReadOnlyList<SubgraphInfo> Subgraphs { get; } = subgraphs; + + public static GatewayInfo Create<TProject>(string name, params SubgraphInfo[] projects) + where TProject : IProjectMetadata, new() + => new(name, new TProject().ProjectPath, projects); + } +} + + + using Microsoft.Extensions.DependencyInjection; + +namespace Aspire.Hosting +{ + internal static class FusionExtensions + { + public static DistributedApplication Compose(this DistributedApplication application) + { + var options = application.Services.GetRequiredService<DistributedApplicationOptions>(); + + if (options.Args is ["compose"]) + { + global::HotChocolate.Fusion.Composition.Tooling.FusionGatewayConfgHelper.ConfigureAsync().Wait(); + Environment.Exit(0); + } + + return application; + } + + public static IResourceBuilder<FusionGatewayResource> AddFusionGateway<TProject>( + this IDistributedApplicationBuilder builder, + string name) + where TProject : IProjectMetadata, new() + => new FusionGatewayResourceBuilder(builder.AddProject<TProject>(name)); + + public static IResourceBuilder<FusionGatewayResource> WithSubgraph( + this IResourceBuilder<FusionGatewayResource> builder, + IResourceBuilder<ProjectResource> subgraphProject) + => builder.WithReference(subgraphProject.GetEndpoint("http")); + + public static IResourceBuilder<FusionGatewayResource> WithSubgraph( + this IResourceBuilder<FusionGatewayResource> builder, + EndpointReference subgraphEndpoint) + => builder.WithReference(subgraphEndpoint); + } + + internal sealed class FusionGatewayResource(ProjectResource projectResource) + : Resource(projectResource.Name) + , IResourceWithEnvironment + , IResourceWithServiceDiscovery + { + public ProjectResource ProjectResource { get; } = projectResource; + } + + internal sealed class FusionGatewayResourceBuilder( + IResourceBuilder<ProjectResource> projectResourceBuilder) + : IResourceBuilder<FusionGatewayResource> + { + public IResourceBuilder<FusionGatewayResource> WithAnnotation<TAnnotation>(TAnnotation annotation) + where TAnnotation : IResourceAnnotation + { + projectResourceBuilder.WithAnnotation(annotation); + return this; + } + + public IDistributedApplicationBuilder ApplicationBuilder => projectResourceBuilder.ApplicationBuilder; + + public FusionGatewayResource Resource { get; } = new(projectResourceBuilder.Resource); + } +} + + + \ No newline at end of file diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/Properties/launchSettings.json b/src/HotChocolate/Fusion/src/Composition.Analyzers/Properties/launchSettings.json new file mode 100644 index 00000000000..8af0cdb9a16 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Generators": { + "commandName": "DebugRoslynComponent", + "targetProject": "../../test/Analyzers.Tests/HotChocolate.Fusion.Analyzers.Tests.csproj" + } + } +} \ No newline at end of file diff --git a/src/HotChocolate/Fusion/src/Composition.Analyzers/WellKnownTypeNames.cs b/src/HotChocolate/Fusion/src/Composition.Analyzers/WellKnownTypeNames.cs new file mode 100644 index 00000000000..69bbb2e7267 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Composition.Analyzers/WellKnownTypeNames.cs @@ -0,0 +1,6 @@ +namespace HotChocolate.Types.Analyzers; + +public static class WellKnownTypeNames +{ + public const string ProjectMetadata = "global::Aspire.Hosting.IProjectMetadata"; +} \ No newline at end of file diff --git a/src/HotChocolate/Fusion/src/Composition/Extensions/SchemaExtensions.cs b/src/HotChocolate/Fusion/src/Composition/Extensions/SchemaExtensions.cs index 003d60d95c6..870ce74169e 100644 --- a/src/HotChocolate/Fusion/src/Composition/Extensions/SchemaExtensions.cs +++ b/src/HotChocolate/Fusion/src/Composition/Extensions/SchemaExtensions.cs @@ -81,7 +81,7 @@ protected override ISyntaxVisitorAction Enter( } } - private sealed class FieldVariableNameContext : ISyntaxVisitorContext + private sealed class FieldVariableNameContext { public StringBuilder Name { get; } = new(); } diff --git a/src/HotChocolate/Fusion/src/Core/Execution/Nodes/QueryPlan.cs b/src/HotChocolate/Fusion/src/Core/Execution/Nodes/QueryPlan.cs index e95e9cf4124..52ae77426d9 100644 --- a/src/HotChocolate/Fusion/src/Core/Execution/Nodes/QueryPlan.cs +++ b/src/HotChocolate/Fusion/src/Core/Execution/Nodes/QueryPlan.cs @@ -337,7 +337,7 @@ protected override ISyntaxVisitorAction Enter( public static ExportPathVisitor Instance { get; } = new(); } - private sealed class ExportPathVisitorContext : ISyntaxVisitorContext + private sealed class ExportPathVisitorContext { public Queue Path { get; } = new(); } diff --git a/src/HotChocolate/Fusion/src/Core/Execution/Nodes/ReformatVariableRewriter.cs b/src/HotChocolate/Fusion/src/Core/Execution/Nodes/ReformatVariableRewriter.cs index adcdcd4d8af..66d91725859 100644 --- a/src/HotChocolate/Fusion/src/Core/Execution/Nodes/ReformatVariableRewriter.cs +++ b/src/HotChocolate/Fusion/src/Core/Execution/Nodes/ReformatVariableRewriter.cs @@ -5,7 +5,7 @@ namespace HotChocolate.Fusion.Execution.Nodes; -internal sealed class ReformatVariableRewriter : SyntaxRewriter, ISyntaxVisitorContext +internal sealed class ReformatVariableRewriter : SyntaxRewriter { private static readonly ReformatVariableRewriter _instance = new(); diff --git a/src/HotChocolate/Fusion/src/Core/Execution/Pipeline/OperationExecutionMiddleware.cs b/src/HotChocolate/Fusion/src/Core/Execution/Pipeline/OperationExecutionMiddleware.cs index 8d704a9d5df..eb51be262b8 100644 --- a/src/HotChocolate/Fusion/src/Core/Execution/Pipeline/OperationExecutionMiddleware.cs +++ b/src/HotChocolate/Fusion/src/Core/Execution/Pipeline/OperationExecutionMiddleware.cs @@ -1,7 +1,5 @@ using HotChocolate.Execution; -using HotChocolate.Execution.Caching; using HotChocolate.Execution.DependencyInjection; -using HotChocolate.Execution.Instrumentation; using HotChocolate.Execution.Processing; using HotChocolate.Fetching; using HotChocolate.Fusion.Clients; diff --git a/src/HotChocolate/Fusion/src/Core/Metadata/FusionGraphConfigurationToSchemaRewriter.cs b/src/HotChocolate/Fusion/src/Core/Metadata/FusionGraphConfigurationToSchemaRewriter.cs index dcb427e8b8b..ed078df3415 100644 --- a/src/HotChocolate/Fusion/src/Core/Metadata/FusionGraphConfigurationToSchemaRewriter.cs +++ b/src/HotChocolate/Fusion/src/Core/Metadata/FusionGraphConfigurationToSchemaRewriter.cs @@ -22,9 +22,7 @@ public DocumentNode Rewrite(DocumentNode fusionGraph) return schemaDoc; } - protected override DirectiveNode? RewriteDirective( - DirectiveNode node, - Context context) + protected override DirectiveNode? RewriteDirective(DirectiveNode node, Context context) { if (context.TypeNames.IsFusionDirective(node.Name.Value)) { @@ -34,13 +32,8 @@ public DocumentNode Rewrite(DocumentNode fusionGraph) return base.RewriteDirective(node, context); } - internal sealed class Context : ISyntaxVisitorContext + internal sealed class Context(FusionTypeNames typeNames) { - public Context(FusionTypeNames typeNames) - { - TypeNames = typeNames; - } - - public FusionTypeNames TypeNames { get; } + public FusionTypeNames TypeNames { get; } = typeNames; } } diff --git a/src/HotChocolate/Fusion/src/Core/Metadata/ResolverDefinition.FetchRewriterContext.cs b/src/HotChocolate/Fusion/src/Core/Metadata/ResolverDefinition.FetchRewriterContext.cs index 7b221eeaaf2..4fa26741f36 100644 --- a/src/HotChocolate/Fusion/src/Core/Metadata/ResolverDefinition.FetchRewriterContext.cs +++ b/src/HotChocolate/Fusion/src/Core/Metadata/ResolverDefinition.FetchRewriterContext.cs @@ -1,42 +1,32 @@ using HotChocolate.Language; -using HotChocolate.Language.Visitors; namespace HotChocolate.Fusion.Metadata; internal sealed partial class ResolverDefinition { - private sealed class FetchRewriterContext : ISyntaxVisitorContext + private sealed class FetchRewriterContext( + FragmentSpreadNode? placeholder, + IReadOnlyDictionary variables, + SelectionSetNode? selectionSet, + string? responseName, + IReadOnlyList? unspecifiedArguments) { - public FetchRewriterContext( - FragmentSpreadNode? placeholder, - IReadOnlyDictionary variables, - SelectionSetNode? selectionSet, - string? responseName, - IReadOnlyList? unspecifiedArguments) - { - Placeholder = placeholder; - Variables = variables; - SelectionSet = selectionSet; - ResponseName = responseName; - UnspecifiedArguments = unspecifiedArguments; - } - - public string? ResponseName { get; } + public string? ResponseName { get; } = responseName; public Stack Path { get; } = new(); - public FragmentSpreadNode? Placeholder { get; } + public FragmentSpreadNode? Placeholder { get; } = placeholder; public bool PlaceholderFound { get; set; } - public IReadOnlyDictionary Variables { get; } + public IReadOnlyDictionary Variables { get; } = variables; /// /// An optional list of arguments that weren't explicitly specified in the original query. /// - public IReadOnlyList? UnspecifiedArguments { get; } + public IReadOnlyList? UnspecifiedArguments { get; } = unspecifiedArguments; - public SelectionSetNode? SelectionSet { get; } + public SelectionSetNode? SelectionSet { get; } = selectionSet; public IReadOnlyList SelectionPath { get; set; } = Array.Empty(); } diff --git a/src/HotChocolate/Fusion/src/Core/Planning/RequestFormatters/RequestDocumentFormatter.cs b/src/HotChocolate/Fusion/src/Core/Planning/RequestFormatters/RequestDocumentFormatter.cs index 86a5c38d37b..7e0f91df47e 100644 --- a/src/HotChocolate/Fusion/src/Core/Planning/RequestFormatters/RequestDocumentFormatter.cs +++ b/src/HotChocolate/Fusion/src/Core/Planning/RequestFormatters/RequestDocumentFormatter.cs @@ -576,7 +576,7 @@ public static IEnumerable Collect(IValueNode node) } } - private class VariableVisitorContext : ISyntaxVisitorContext + private sealed class VariableVisitorContext { public HashSet VariableNodes { get; } = new(SyntaxComparer.BySyntax); } diff --git a/src/HotChocolate/Fusion/test/Composition.Analyzers.Tests/HotChocolate.Fusion.Composition.Analyzers.Tests.csproj b/src/HotChocolate/Fusion/test/Composition.Analyzers.Tests/HotChocolate.Fusion.Composition.Analyzers.Tests.csproj new file mode 100644 index 00000000000..6d318b9ee74 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Composition.Analyzers.Tests/HotChocolate.Fusion.Composition.Analyzers.Tests.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + true + + + + + + + + + + + + + + + + diff --git a/src/HotChocolate/Fusion/test/Composition.Analyzers.Tests/Program.cs b/src/HotChocolate/Fusion/test/Composition.Analyzers.Tests/Program.cs new file mode 100644 index 00000000000..373ca42bff6 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Composition.Analyzers.Tests/Program.cs @@ -0,0 +1,78 @@ + +namespace HotChocolate.Fusion.Analyzers.Tests; + +public class Program +{ + public static void Foo(string[] args) + { + var builder = DistributedApplication.CreateBuilder(args); + + // resources + var redis = builder.AddRedisContainer("redis"); + + var postgres = builder + .AddPostgresContainer("postgres") + .WithPgAdmin() + .WithAnnotation( + new ContainerImageAnnotation + { + Image = "ankane/pgvector", + Tag = "latest", + }); + + var rabbitMq = builder + .AddRabbitMQContainer("event-bus") + .WithEndpoint(containerPort: 58812, name: "management"); + + var catalogDb = postgres.AddDatabase("CatalogDB"); + var identityDb = postgres.AddDatabase("IdentityDB"); + var orderingDb = postgres.AddDatabase("OrderingDB"); + + // APIs + var identityApi = builder + .AddProject("identity-api") + .WithReference(identityDb) + .WithLaunchProfile("https"); + + var identityHttpsEndpoint = identityApi.GetEndpoint("https"); + + var catalogApi = builder + .AddProject("catalog-api") + .WithReference(catalogDb) + .WithEnvironment("Identity__Url", identityHttpsEndpoint); + + var basketApi = builder + .AddProject("basket-api") + .WithReference(redis) + .WithEnvironment("Identity__Url", identityHttpsEndpoint) + .WithReference(catalogApi.GetEndpoint("http")); + + var orderingApi = builder + .AddProject("ordering-api") + .WithReference(rabbitMq) + .WithReference(orderingDb) + .WithReference(catalogApi.GetEndpoint("http")) + .WithEnvironment("Identity__Url", identityHttpsEndpoint); + + var purchaseApi = builder + .AddProject("ordering-api") + .WithReference(rabbitMq) + .WithReference(orderingDb) + .WithReference(catalogApi.GetEndpoint("http")) + .WithEnvironment("Identity__Url", identityHttpsEndpoint); + + // Fusion + builder + .AddFusionGateway("gateway") + .WithSubgraph(basketApi) + .WithSubgraph(identityApi.GetEndpoint("http")) + .WithSubgraph(catalogApi) + .WithSubgraph(orderingApi) + .WithSubgraph(purchaseApi) + .WithEnvironment("Identity__Url", identityHttpsEndpoint); + + + builder.Build().Compose().Run(); + } +} + diff --git a/src/HotChocolate/Fusion/test/Composition.Analyzers.Tests/Projects.cs b/src/HotChocolate/Fusion/test/Composition.Analyzers.Tests/Projects.cs new file mode 100644 index 00000000000..7129c351a1a --- /dev/null +++ b/src/HotChocolate/Fusion/test/Composition.Analyzers.Tests/Projects.cs @@ -0,0 +1,37 @@ +namespace Projects; + +[global::System.Diagnostics.DebuggerDisplay("Type = {GetType().Name,nq}, ProjectPath = {ProjectPath}")] +public class eShop_Basket_API : global::Aspire.Hosting.IProjectMetadata +{ + public string ProjectPath => """/Users/michael/local/webshop-workshop/src/Basket.API/eShop.Basket.API.csproj"""; +} + +[global::System.Diagnostics.DebuggerDisplay("Type = {GetType().Name,nq}, ProjectPath = {ProjectPath}")] +public class eShop_Identity_API : global::Aspire.Hosting.IProjectMetadata +{ + public string ProjectPath => """/Users/michael/local/webshop-workshop/src/Identity.API/eShop.Identity.API.csproj"""; +} + +[global::System.Diagnostics.DebuggerDisplay("Type = {GetType().Name,nq}, ProjectPath = {ProjectPath}")] +public class eShop_Catalog_API : global::Aspire.Hosting.IProjectMetadata +{ + public string ProjectPath => """/Users/michael/local/webshop-workshop/src/Catalog.API/eShop.Catalog.API.csproj"""; +} + +[global::System.Diagnostics.DebuggerDisplay("Type = {GetType().Name,nq}, ProjectPath = {ProjectPath}")] +public class eShop_Ordering_API : global::Aspire.Hosting.IProjectMetadata +{ + public string ProjectPath => """/Users/michael/local/webshop-workshop/src/Ordering.API/eShop.Ordering.API.csproj"""; +} + +[global::System.Diagnostics.DebuggerDisplay("Type = {GetType().Name,nq}, ProjectPath = {ProjectPath}")] +public class eShop_Purchase_API : global::Aspire.Hosting.IProjectMetadata +{ + public string ProjectPath => """/Users/michael/local/webshop-workshop/src/Purchase.API/eShop.Purchase.API.csproj"""; +} + +[global::System.Diagnostics.DebuggerDisplay("Type = {GetType().Name,nq}, ProjectPath = {ProjectPath}")] +public class eShop_Gateway : global::Aspire.Hosting.IProjectMetadata +{ + public string ProjectPath => """/Users/michael/local/webshop-workshop/src/Gateway/eShop.Gateway.csproj"""; +} diff --git a/src/HotChocolate/Fusion/test/Composition.Analyzers.Tests/Test.txt b/src/HotChocolate/Fusion/test/Composition.Analyzers.Tests/Test.txt new file mode 100644 index 00000000000..cf5bc32552c --- /dev/null +++ b/src/HotChocolate/Fusion/test/Composition.Analyzers.Tests/Test.txt @@ -0,0 +1,618 @@ +// + +#nullable enable + +using System; +using System.Runtime.CompilerServices; +using System.Buffers; +using System.Diagnostics; +using System.Net.Mime; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using HotChocolate.Execution.Configuration; +using HotChocolate.Fusion; +using HotChocolate.Fusion.Composition; +using HotChocolate.Fusion.Composition.Features; +using HotChocolate.Fusion.Composition.Tooling; +using HotChocolate.Language; +using HotChocolate.Language.Visitors; +using HotChocolate.Skimmed.Serialization; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Fusion.Composition.Tooling +{ + + internal static class FusionGatewayConfgHelper + { + public static async Task ConfigureAsync(CancellationToken cancellationToken = default) + { + ExportSubgraphSchemaDocs(); + await EnsureSubgraphHasConfigAsync(cancellationToken); + await ComposeAsync(cancellationToken); + } + + private static void ExportSubgraphSchemaDocs() + { + var processed = new HashSet(); + + foreach (var gateway in new GatewayList()) + { + foreach (var subgraph in gateway.Subgraphs) + { + if (!processed.Add(subgraph.Path)) + { + continue; + } + + Console.WriteLine("Expoorting schema document for subgraph {0} ...", subgraph.Name); + + var workingDirectory = System.IO.Path.GetDirectoryName(subgraph.Path)!; + + var processStartInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = "dotnet run --no-build --no-restore -- schema export --output schema.graphql", + WorkingDirectory = workingDirectory, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + }; + + using (var process = Process.Start(processStartInfo)!) + { + string output = process.StandardOutput.ReadToEnd(); + string errors = process.StandardError.ReadToEnd(); + + process.WaitForExit(); + + if (!string.IsNullOrEmpty(output)) + { + Console.WriteLine(output); + } + + if (!string.IsNullOrEmpty(errors)) + { + Console.WriteLine(errors); + } + + if (process.ExitCode != 0) + { + Console.WriteLine( + "{0}(1,1): error HF1002: ; Failed to export schema document for subgraph {1} ...", + subgraph.Path, + subgraph.Name); + Environment.Exit(-255); + } + } + } + } + } + + private static async Task EnsureSubgraphHasConfigAsync(CancellationToken cancellationToken = default) + { + foreach (var gateway in new GatewayList()) + { + foreach (var project in gateway.Subgraphs) + { + var projectRoot = System.IO.Path.GetDirectoryName(project.Path)!; + var configFile = System.IO.Path.Combine(projectRoot, Defaults.ConfigFile); + + if (File.Exists(configFile)) + { + continue; + } + + var config = new SubgraphConfigurationDto(project.Name); + var configJson = PackageHelper.FormatSubgraphConfig(config); + await File.WriteAllTextAsync(configFile, configJson, cancellationToken); + } + } + } + + private static async Task ComposeAsync(CancellationToken cancellationToken = default) + { + foreach (var gateway in new GatewayList()) + { + await ComposeGatewayAsync(gateway.Path, gateway.Subgraphs.Select(t => t.Path), cancellationToken); + } + } + + private static async Task ComposeGatewayAsync( + string gatewayProject, + IEnumerable subgraphProjects, + CancellationToken cancellationToken = default) + { + var gatewayDirectory = System.IO.Path.GetDirectoryName(gatewayProject)!; + var packageFileName = System.IO.Path.Combine(gatewayDirectory, $"gateway{Extensions.FusionPackage}"); + var packageFile = new FileInfo(packageFileName); + var settingsFileName = System.IO.Path.Combine(gatewayDirectory, "gateway-settings.json"); + var settingsFile = new FileInfo(settingsFileName); + var subgraphDirectories = subgraphProjects.Select(t => System.IO.Path.GetDirectoryName(t)!).ToArray(); + + // Ensure Gateway Project Directory Exists. + if (!Directory.Exists(gatewayDirectory)) + { + Directory.CreateDirectory(gatewayDirectory); + } + + await using var package = FusionGraphPackage.Open(packageFile.FullName); + var subgraphConfigs = + (await package.GetSubgraphConfigurationsAsync(cancellationToken)).ToDictionary(t => t.Name); + await ResolveSubgraphPackagesAsync(subgraphDirectories, subgraphConfigs, cancellationToken); + + using var settingsJson = settingsFile.Exists + ? JsonDocument.Parse(await File.ReadAllTextAsync(settingsFile.FullName, cancellationToken)) + : await package.GetFusionGraphSettingsAsync(cancellationToken); + var settings = settingsJson.Deserialize() ?? new PackageSettings(); + + var features = settings.CreateFeatures(); + + var composer = new FusionGraphComposer( + settings.FusionTypePrefix, + settings.FusionTypeSelf, + () => new ConsoleLog()); + + var fusionGraph = await composer.TryComposeAsync(subgraphConfigs.Values, features, cancellationToken); + + if (fusionGraph is null) + { + Console.WriteLine("Fusion graph composition failed."); + return; + } + + var fusionGraphDoc = Utf8GraphQLParser.Parse(SchemaFormatter.FormatAsString(fusionGraph)); + var typeNames = FusionTypeNames.From(fusionGraphDoc); + var rewriter = new FusionGraphConfigurationToSchemaRewriter(); + var schemaDoc = (DocumentNode)rewriter.Rewrite(fusionGraphDoc, typeNames)!; + + using var updateSettingsJson = JsonSerializer.SerializeToDocument( + settings, + new JsonSerializerOptions(JsonSerializerDefaults.Web)); + + await package.SetFusionGraphAsync(fusionGraphDoc, cancellationToken); + await package.SetFusionGraphSettingsAsync(updateSettingsJson, cancellationToken); + await package.SetSchemaAsync(schemaDoc, cancellationToken); + + foreach (var config in subgraphConfigs.Values) + { + await package.SetSubgraphConfigurationAsync(config, cancellationToken); + } + + Console.WriteLine("Fusion graph composed."); + } + + private static async Task ResolveSubgraphPackagesAsync( + IReadOnlyList subgraphDirectories, + IDictionary subgraphConfigs, + CancellationToken cancellationToken) + { + for (var i = 0; i < subgraphDirectories.Count; i++) + { + var path = subgraphDirectories[i]; + + if (!Directory.Exists(path)) + { + continue; + } + + var configFile = System.IO.Path.Combine(path, Defaults.ConfigFile); + var schemaFile = System.IO.Path.Combine(path, Defaults.SchemaFile); + var extensionFile = System.IO.Path.Combine(path, Defaults.ExtensionFile); + + if (!File.Exists(configFile) || !File.Exists(schemaFile)) + { + continue; + } + + var conf = await PackageHelper.LoadSubgraphConfigAsync(configFile, cancellationToken); + var schema = await File.ReadAllTextAsync(schemaFile, cancellationToken); + var extensions = Array.Empty(); + + if (File.Exists(extensionFile)) + { + extensions = [await File.ReadAllTextAsync(extensionFile, cancellationToken),]; + } + + subgraphConfigs[conf.Name] = + new SubgraphConfiguration( + conf.Name, + schema, + extensions, + conf.Clients, + conf.Extensions); + } + } + } + + file static class Defaults + { + public const string SchemaFile = "schema.graphql"; + public const string ExtensionFile = "schema.extensions.graphql"; + public const string ConfigFile = "subgraph-config.json"; + } + + file static class Extensions + { + public const string FusionPackage = ".fgp"; + } + + file class PackageSettings + { + private Feature? _reEncodeIds; + private Feature? _nodeField; + private TagDirective? _tagDirective; + private Transport? _transport; + + [JsonPropertyName("fusionTypePrefix")] + [JsonPropertyOrder(10)] + public string? FusionTypePrefix { get; set; } + + [JsonPropertyName("fusionTypeSelf")] + [JsonPropertyOrder(11)] + public bool FusionTypeSelf { get; set; } + + public Transport Transport + { + get => _transport ??= new(); + set => _transport = value; + } + + [JsonPropertyName("nodeField")] + [JsonPropertyOrder(12)] + public Feature NodeField + { + get => _nodeField ??= new(); + set => _nodeField = value; + } + + [JsonPropertyName("reEncodeIds")] + [JsonPropertyOrder(13)] + public Feature ReEncodeIds + { + get => _reEncodeIds ??= new(); + set => _reEncodeIds = value; + } + + [JsonPropertyName("tagDirective")] + [JsonPropertyOrder(14)] + public TagDirective TagDirective + { + get => _tagDirective ??= new(); + set => _tagDirective = value; + } + + public FusionFeatureCollection CreateFeatures() + { + var features = new List + { + new TransportFeature + { + DefaultClientName = Transport.DefaultClientName, + }, + }; + + if (NodeField.Enabled) + { + features.Add(FusionFeatures.NodeField); + } + + if (ReEncodeIds.Enabled) + { + features.Add(FusionFeatures.ReEncodeIds); + } + + if (TagDirective.Enabled) + { + features.Add( + FusionFeatures.TagDirective( + TagDirective.Exclude, + TagDirective.MakePublic)); + } + + return new FusionFeatureCollection(features); + } + } + + file class Feature + { + [JsonPropertyName("enabled")] + [JsonPropertyOrder(10)] + public bool Enabled { get; set; } + } + + file sealed class TagDirective : Feature + { + private string[]? _exclude; + + [JsonPropertyName("makePublic")] + [JsonPropertyOrder(100)] + public bool MakePublic { get; set; } + + [JsonPropertyName("exclude")] + [JsonPropertyOrder(101)] + public string[] Exclude + { + get => _exclude ?? Array.Empty(); + set => _exclude = value; + } + } + + file sealed class Transport + { + [JsonPropertyName("defaultClientName")] + [JsonPropertyOrder(10)] + public string? DefaultClientName { get; set; } = "Fusion"; + } + + file sealed class ConsoleLog : ICompositionLog + { + public bool HasErrors { get; private set; } + + public void Write(LogEntry e) + { + if (e.Severity is LogSeverity.Error) + { + HasErrors = true; + } + + if (e.Code is null) + { + Console.WriteLine($"{e.Severity}: {e.Message}"); + } + else if (e.Coordinate is null) + { + Console.WriteLine($"{e.Severity}: {e.Code} {e.Message}"); + } + else + { + Console.WriteLine($"{e.Severity}: {e.Code} {e.Message} {e.Coordinate}"); + } + } + } + + file sealed class SubgraphConfigurationDto( + string name, + IReadOnlyList? clients = null, + JsonElement? extensions = null) + { + public string Name { get; } = name; + + public IReadOnlyList Clients { get; } = clients ?? Array.Empty(); + + public JsonElement? Extensions { get; } = extensions; + } + + file static class PackageHelper + { + public static async Task LoadSubgraphConfigAsync( + string filename, + CancellationToken ct) + { + await using var stream = File.OpenRead(filename); + return await ParseSubgraphConfigAsync(stream, ct); + } + + private static async Task ParseSubgraphConfigAsync( + Stream stream, + CancellationToken ct) + { + using var document = await JsonDocument.ParseAsync(stream, cancellationToken: ct); + var configs = new List(); + var subgraph = default(string?); + var extensions = default(JsonElement?); + + foreach (var property in document.RootElement.EnumerateObject()) + { + switch (property.Name) + { + case "subgraph": + subgraph = property.Value.GetString(); + break; + + case "http": + configs.Add(ReadHttpClientConfiguration(property.Value)); + break; + + case "websocket": + configs.Add(ReadWebSocketClientConfiguration(property.Value)); + break; + + case "extensions": + extensions = property.Value.SafeClone(); + break; + + default: + throw new NotSupportedException( + $"Configuration property `{property.Value}` is not supported."); + } + } + + if (string.IsNullOrEmpty(subgraph)) + { + throw new InvalidOperationException("No subgraph name was specified."); + } + + return new SubgraphConfigurationDto(subgraph, configs, extensions); + } + + private static HttpClientConfiguration ReadHttpClientConfiguration( + JsonElement element) + { + var baseAddress = new Uri(element.GetProperty("baseAddress").GetString()!); + var clientName = default(string?); + + if (element.TryGetProperty("clientName", out var clientNameElement)) + { + clientName = clientNameElement.GetString(); + } + + return new HttpClientConfiguration(baseAddress, clientName); + } + + private static WebSocketClientConfiguration ReadWebSocketClientConfiguration( + JsonElement element) + { + var baseAddress = new Uri(element.GetProperty("baseAddress").GetString()!); + var clientName = default(string?); + + if (element.TryGetProperty("clientName", out var clientNameElement)) + { + clientName = clientNameElement.GetString(); + } + + return new WebSocketClientConfiguration(baseAddress, clientName); + } + + private static JsonElement SafeClone(this JsonElement element) + { + var writer = new ArrayBufferWriter(); + using var jsonWriter = new Utf8JsonWriter(writer); + + element.WriteTo(jsonWriter); + jsonWriter.Flush(); + var reader = new Utf8JsonReader(writer.WrittenSpan, true, default); + + return JsonElement.ParseValue(ref reader); + } + + public static string FormatSubgraphConfig( + SubgraphConfigurationDto subgraphConfig) + { + var buffer = new ArrayBufferWriter(); + using var writer = new Utf8JsonWriter(buffer); + + writer.WriteStartObject(); + writer.WriteString("subgraph", subgraphConfig.Name); + + foreach (var client in subgraphConfig.Clients) + { + switch (client) + { + case HttpClientConfiguration config: + writer.WriteStartObject("http"); + writer.WriteString("baseAddress", config.BaseAddress.ToString()); + + if (config.ClientName is not null) + { + writer.WriteString("clientName", config.ClientName); + } + + writer.WriteEndObject(); + break; + + case WebSocketClientConfiguration config: + writer.WriteStartObject("websocket"); + writer.WriteString("baseAddress", config.BaseAddress.ToString()); + + if (config.ClientName is not null) + { + writer.WriteString("clientName", config.ClientName); + } + + writer.WriteEndObject(); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(client)); + } + } + + if (subgraphConfig.Extensions is not null) + { + writer.WritePropertyName("extensions"); + subgraphConfig.Extensions.Value.WriteTo(writer); + } + + writer.WriteEndObject(); + writer.Flush(); + + return Encoding.UTF8.GetString(buffer.WrittenSpan); + } + } + + file sealed class FusionGraphConfigurationToSchemaRewriter + : SyntaxRewriter + { + public DocumentNode Rewrite(DocumentNode fusionGraph) + { + var typeNames = FusionTypeNames.From(fusionGraph); + var schemaDoc = (DocumentNode?)Rewrite(fusionGraph, typeNames); + + if (schemaDoc is null) + { + throw new InvalidOperationException(); + } + + return schemaDoc; + } + + protected override DirectiveNode? RewriteDirective(DirectiveNode node, FusionTypeNames context) + => context.IsFusionDirective(node.Name.Value) + ? null + : base.RewriteDirective(node, context); + } + + file sealed class SubgraphInfo(string name, string path, string variableName) + { + public string Name { get; } = name; + + public string Path { get; } = path; + + public string VariableName { get; } = variableName; + + public static SubgraphInfo Create(string name, string variableName) + where TProject : IProjectMetadata, new() + => new(name, new TProject().ProjectPath, variableName); + } + + file sealed class GatewayInfo(string name, string path, IReadOnlyList subgraphs) + { + public string Name { get; } = name; + + public string Path { get; } = path; + + public IReadOnlyList Subgraphs { get; } = subgraphs; + + public static GatewayInfo Create(string name, params SubgraphInfo[] projects) + where TProject : IProjectMetadata, new() + => new(name, new TProject().ProjectPath, projects); + } +} + +namespace Aspire.Hosting +{ + public static class Extensions + { + public static DistributedApplication Compose(this DistributedApplication application) + { + var options = application.Services.GetRequiredService(); + + if (options.Args is ["compose"]) + { + FusionGatewayConfgHelper.ConfigureAsync().Wait(); + Environment.Exit(0); + } + + return application; + } + } +} + +namespace HotChocolate.Fusion.Composition.Tooling +{ +file class GatewayList : List +{ + public GatewayList() + : base( + [ + GatewayInfo.Create( + "gateway", + SubgraphInfo.Create("purchase-api", "purchaseApi"), + SubgraphInfo.Create("ordering-api", "orderingApi"), + SubgraphInfo.Create("catalog-api", "catalogApi"), + SubgraphInfo.Create("identity-api", "identityApi"), + SubgraphInfo.Create("basket-api", "basketApi")) + ]) { } +} +} \ No newline at end of file diff --git a/src/HotChocolate/Fusion/test/Directory.Build.props b/src/HotChocolate/Fusion/test/Directory.Build.props index 036ac8b42b1..4290ce4eb6f 100644 --- a/src/HotChocolate/Fusion/test/Directory.Build.props +++ b/src/HotChocolate/Fusion/test/Directory.Build.props @@ -3,6 +3,7 @@ false + true enable enable false diff --git a/src/HotChocolate/Language/src/Language.Visitors/Contracts/INavigatorContext.cs b/src/HotChocolate/Language/src/Language.Visitors/Contracts/INavigatorContext.cs index 1010d22b21e..689e6d5bf7b 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/Contracts/INavigatorContext.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/Contracts/INavigatorContext.cs @@ -3,7 +3,7 @@ namespace HotChocolate.Language.Visitors; /// /// A visitor context that contains a syntax navigator. /// -public interface INavigatorContext : ISyntaxVisitorContext +public interface INavigatorContext { /// /// Gets the associated from the current context. diff --git a/src/HotChocolate/Language/src/Language.Visitors/Contracts/ISyntaxRewriter.cs b/src/HotChocolate/Language/src/Language.Visitors/Contracts/ISyntaxRewriter.cs index 8e364aa0208..3feee422033 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/Contracts/ISyntaxRewriter.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/Contracts/ISyntaxRewriter.cs @@ -7,7 +7,7 @@ namespace HotChocolate.Language.Visitors; /// /// The context type. /// -public interface ISyntaxRewriter where TContext : ISyntaxVisitorContext +public interface ISyntaxRewriter { /// /// Rewrite the syntax node. diff --git a/src/HotChocolate/Language/src/Language.Visitors/Contracts/ISyntaxVisitorContext.cs b/src/HotChocolate/Language/src/Language.Visitors/Contracts/ISyntaxVisitorContext.cs deleted file mode 100644 index 22e142b8120..00000000000 --- a/src/HotChocolate/Language/src/Language.Visitors/Contracts/ISyntaxVisitorContext.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace HotChocolate.Language.Visitors; - -public interface ISyntaxVisitorContext -{ -} diff --git a/src/HotChocolate/Language/src/Language.Visitors/Contracts/ISyntaxVisitor~1.cs b/src/HotChocolate/Language/src/Language.Visitors/Contracts/ISyntaxVisitor~1.cs index a770cdc258b..26d6ed2a624 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/Contracts/ISyntaxVisitor~1.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/Contracts/ISyntaxVisitor~1.cs @@ -1,6 +1,6 @@ namespace HotChocolate.Language.Visitors; -public interface ISyntaxVisitor where TContext : ISyntaxVisitorContext +public interface ISyntaxVisitor { ISyntaxVisitorAction Visit(ISyntaxNode node, TContext context); } diff --git a/src/HotChocolate/Language/src/Language.Visitors/DelegateSyntaxRewriter.cs b/src/HotChocolate/Language/src/Language.Visitors/DelegateSyntaxRewriter.cs index 02884cf720b..e4d70c62967 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/DelegateSyntaxRewriter.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/DelegateSyntaxRewriter.cs @@ -1,10 +1,6 @@ -using System; - namespace HotChocolate.Language.Visitors; -internal sealed class DelegateSyntaxRewriter - : SyntaxRewriter - where TContext : ISyntaxVisitorContext +internal sealed class DelegateSyntaxRewriter : SyntaxRewriter { private readonly RewriteSyntaxNode _rewrite; private readonly Func _enter; @@ -15,9 +11,9 @@ public DelegateSyntaxRewriter( Func? enter = null, Action? leave = null) { - _rewrite = rewrite ?? new RewriteSyntaxNode(static (node, _) => node); - _enter = enter ?? new Func(static (_, ctx) => ctx); - _leave = leave ?? new Action(static (_, _) => { }); + _rewrite = rewrite ?? (static (node, _) => node); + _enter = enter ?? (static (_, ctx) => ctx); + _leave = leave ?? (static (_, _) => { }); } protected override TContext OnEnter( diff --git a/src/HotChocolate/Language/src/Language.Visitors/DelegateSyntaxVisitor.cs b/src/HotChocolate/Language/src/Language.Visitors/DelegateSyntaxVisitor.cs index 31ce56d419c..c9ce3e732af 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/DelegateSyntaxVisitor.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/DelegateSyntaxVisitor.cs @@ -1,8 +1,6 @@ namespace HotChocolate.Language.Visitors; -internal sealed class DelegateSyntaxVisitor - : SyntaxVisitor - where TContext : ISyntaxVisitorContext +internal sealed class DelegateSyntaxVisitor : SyntaxVisitor { private readonly VisitSyntaxNode _enter; private readonly VisitSyntaxNode _leave; @@ -14,8 +12,8 @@ public DelegateSyntaxVisitor( SyntaxVisitorOptions options = default) : base(defaultResult ?? Skip, options) { - _enter = enter ?? new VisitSyntaxNode((_, _) => DefaultAction); - _leave = leave ?? new VisitSyntaxNode((_, _) => DefaultAction); + _enter = enter ?? ((_, _) => DefaultAction); + _leave = leave ?? ((_, _) => DefaultAction); } protected override ISyntaxVisitorAction Enter(ISyntaxNode node, TContext context) diff --git a/src/HotChocolate/Language/src/Language.Visitors/RewriteSyntaxNode.cs b/src/HotChocolate/Language/src/Language.Visitors/RewriteSyntaxNode.cs index 662a817408d..856e4f84794 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/RewriteSyntaxNode.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/RewriteSyntaxNode.cs @@ -1,6 +1,3 @@ namespace HotChocolate.Language.Visitors; -public delegate ISyntaxNode? RewriteSyntaxNode( - ISyntaxNode node, - TContext context) - where TContext : ISyntaxVisitorContext; +public delegate ISyntaxNode? RewriteSyntaxNode(ISyntaxNode node, TContext context); diff --git a/src/HotChocolate/Language/src/Language.Visitors/SyntaxRewriter.cs b/src/HotChocolate/Language/src/Language.Visitors/SyntaxRewriter.cs index 860b706db5b..8ac057924c4 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/SyntaxRewriter.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/SyntaxRewriter.cs @@ -1,5 +1,3 @@ -using System; - namespace HotChocolate.Language.Visitors; /// @@ -7,16 +5,15 @@ namespace HotChocolate.Language.Visitors; /// public static class SyntaxRewriter { - public static ISyntaxRewriter Create( + public static ISyntaxRewriter Create( Func rewrite) - => new DelegateSyntaxRewriter( + => new DelegateSyntaxRewriter( rewrite: (node, _) => rewrite(node)); public static ISyntaxRewriter Create( RewriteSyntaxNode? rewrite = null, Func? enter = null, Action? leave = null) - where TContext : ISyntaxVisitorContext => new DelegateSyntaxRewriter(rewrite, enter, leave); public static ISyntaxRewriter CreateWithNavigator( diff --git a/src/HotChocolate/Language/src/Language.Visitors/SyntaxRewriter~1.cs b/src/HotChocolate/Language/src/Language.Visitors/SyntaxRewriter~1.cs index f621e2f64df..e8d59a8697d 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/SyntaxRewriter~1.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/SyntaxRewriter~1.cs @@ -10,9 +10,7 @@ namespace HotChocolate.Language.Visitors; /// /// The context type. /// -public class SyntaxRewriter - : ISyntaxRewriter - where TContext : ISyntaxVisitorContext +public class SyntaxRewriter : ISyntaxRewriter { public virtual ISyntaxNode? Rewrite(ISyntaxNode node, TContext context) { diff --git a/src/HotChocolate/Language/src/Language.Visitors/SyntaxVisitor.cs b/src/HotChocolate/Language/src/Language.Visitors/SyntaxVisitor.cs index d81721f1e1a..55e6f320e85 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/SyntaxVisitor.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/SyntaxVisitor.cs @@ -1,8 +1,6 @@ -using System; - namespace HotChocolate.Language.Visitors; -public class SyntaxVisitor : SyntaxVisitor +public class SyntaxVisitor : SyntaxVisitor { public SyntaxVisitor(SyntaxVisitorOptions options = default) : base(options) @@ -16,17 +14,17 @@ public SyntaxVisitor( { } - public static ISyntaxVisitor Create( + public static ISyntaxVisitor Create( Func? enter = null, Func? leave = null, ISyntaxVisitorAction? defaultAction = null, SyntaxVisitorOptions options = default) - => new DelegateSyntaxVisitor( + => new DelegateSyntaxVisitor( enter is not null - ? new VisitSyntaxNode((n, _) => enter(n)) + ? new VisitSyntaxNode((n, _) => enter(n)) : null, leave is not null - ? new VisitSyntaxNode((n, _) => leave(n)) + ? new VisitSyntaxNode((n, _) => leave(n)) : null, defaultAction, options); @@ -36,7 +34,6 @@ public static ISyntaxVisitor Create( VisitSyntaxNode? leave = null, ISyntaxVisitorAction? defaultAction = null, SyntaxVisitorOptions options = default) - where TContext : ISyntaxVisitorContext { defaultAction ??= Skip; @@ -73,7 +70,7 @@ public static ISyntaxVisitor CreateWithNavigator( context.Navigator.Pop(); return leave(node, context); } - : (node, context) => + : (_, context) => { context.Navigator.Pop(); return defaultAction; diff --git a/src/HotChocolate/Language/src/Language.Visitors/SyntaxVisitor~1.cs b/src/HotChocolate/Language/src/Language.Visitors/SyntaxVisitor~1.cs index e41439e7279..a9bb3b4bc2e 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/SyntaxVisitor~1.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/SyntaxVisitor~1.cs @@ -1,8 +1,6 @@ namespace HotChocolate.Language.Visitors; -public partial class SyntaxVisitor - : ISyntaxVisitor - where TContext : ISyntaxVisitorContext +public partial class SyntaxVisitor : ISyntaxVisitor { private readonly SyntaxVisitorOptions _options; diff --git a/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker.Enter.cs b/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker.Enter.cs index 1ac868807a6..babb654619e 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker.Enter.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker.Enter.cs @@ -1,12 +1,8 @@ -using System; - namespace HotChocolate.Language.Visitors; public partial class SyntaxWalker { - protected override ISyntaxVisitorAction Enter( - ISyntaxNode node, - ISyntaxVisitorContext context) + protected override ISyntaxVisitorAction Enter(ISyntaxNode node, object? context) { switch (node.Kind) { @@ -100,196 +96,196 @@ protected override ISyntaxVisitorAction Enter( protected virtual ISyntaxVisitorAction Enter( NameNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( DocumentNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( OperationDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( VariableDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( VariableNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( SelectionSetNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( FieldNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( ArgumentNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( FragmentSpreadNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( InlineFragmentNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( FragmentDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( DirectiveNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( NamedTypeNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( ListTypeNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( NonNullTypeNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( ListValueNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( ObjectValueNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( ObjectFieldNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( IValueNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( SchemaDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( OperationTypeDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( ScalarTypeDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( ObjectTypeDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( FieldDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( InputValueDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( InterfaceTypeDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( UnionTypeDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( EnumTypeDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( EnumValueDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( InputObjectTypeDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( DirectiveDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( SchemaExtensionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( ScalarTypeExtensionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( ObjectTypeExtensionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( InterfaceTypeExtensionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( UnionTypeExtensionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( EnumTypeExtensionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( InputObjectTypeExtensionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Enter( SchemaCoordinateNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; } diff --git a/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker.Leave.cs b/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker.Leave.cs index f25342a4a4e..9cf727f0938 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker.Leave.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker.Leave.cs @@ -1,12 +1,10 @@ -using System; - namespace HotChocolate.Language.Visitors; public partial class SyntaxWalker { protected override ISyntaxVisitorAction Leave( ISyntaxNode node, - ISyntaxVisitorContext context) + object? context) { switch (node.Kind) { @@ -99,196 +97,196 @@ protected override ISyntaxVisitorAction Leave( } protected virtual ISyntaxVisitorAction Leave( NameNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( DocumentNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( OperationDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( VariableDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( VariableNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( SelectionSetNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( FieldNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( ArgumentNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( FragmentSpreadNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( InlineFragmentNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( FragmentDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( DirectiveNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( NamedTypeNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( ListTypeNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( NonNullTypeNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( ListValueNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( ObjectValueNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( ObjectFieldNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( IValueNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( SchemaDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( OperationTypeDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( ScalarTypeDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( ObjectTypeDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( FieldDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( InputValueDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( InterfaceTypeDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( UnionTypeDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( EnumTypeDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( EnumValueDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( InputObjectTypeDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( DirectiveDefinitionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( SchemaExtensionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( ScalarTypeExtensionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( ObjectTypeExtensionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( InterfaceTypeExtensionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( UnionTypeExtensionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( EnumTypeExtensionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( InputObjectTypeExtensionNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; protected virtual ISyntaxVisitorAction Leave( SchemaCoordinateNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; } diff --git a/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker~1.Enter.cs b/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker~1.Enter.cs index 826fb56c728..b386d9a23b5 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker~1.Enter.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker~1.Enter.cs @@ -290,6 +290,6 @@ protected virtual ISyntaxVisitorAction Enter( protected virtual ISyntaxVisitorAction Enter( SchemaCoordinateNode node, - ISyntaxVisitorContext context) => + object? context) => DefaultAction; } diff --git a/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker~1.Leave.cs b/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker~1.Leave.cs index 112c5bd9e68..3565cb02bc5 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker~1.Leave.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker~1.Leave.cs @@ -289,6 +289,6 @@ protected virtual ISyntaxVisitorAction Leave( protected virtual ISyntaxVisitorAction Leave( SchemaCoordinateNode node, - ISyntaxVisitorContext context) => + TContext context) => DefaultAction; } diff --git a/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker~1.cs b/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker~1.cs index 46da15e02a2..b49845bedae 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker~1.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/SyntaxWalker~1.cs @@ -1,8 +1,6 @@ namespace HotChocolate.Language.Visitors; -public partial class SyntaxWalker - : SyntaxVisitor - where TContext : ISyntaxVisitorContext +public partial class SyntaxWalker : SyntaxVisitor { protected SyntaxWalker(SyntaxVisitorOptions options = default) : base(Continue, options) diff --git a/src/HotChocolate/Language/src/Language.Visitors/Utilities/SyntaxRewriterExtensions.cs b/src/HotChocolate/Language/src/Language.Visitors/Utilities/SyntaxRewriterExtensions.cs index 6dadb1cbaf0..490264bdc84 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/Utilities/SyntaxRewriterExtensions.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/Utilities/SyntaxRewriterExtensions.cs @@ -2,20 +2,16 @@ namespace HotChocolate.Language.Visitors; public static class SyntaxRewriterExtensions { - private static readonly EmptySyntaxVisitorContext _empty = new(); + private static readonly object? _empty = new(); public static ISyntaxNode? Rewrite( - this ISyntaxRewriter rewriter, + this ISyntaxRewriter rewriter, ISyntaxNode node) => rewriter.Rewrite(node, _empty); public static T? Rewrite( - this ISyntaxRewriter rewriter, + this ISyntaxRewriter rewriter, T node) where T : ISyntaxNode => (T?)rewriter.Rewrite(node, _empty); - - private sealed class EmptySyntaxVisitorContext : ISyntaxVisitorContext - { - } } diff --git a/src/HotChocolate/Language/src/Language.Visitors/Utilities/SyntaxVisitorExtensions.cs b/src/HotChocolate/Language/src/Language.Visitors/Utilities/SyntaxVisitorExtensions.cs index 9cb45949dd0..b4068ba9844 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/Utilities/SyntaxVisitorExtensions.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/Utilities/SyntaxVisitorExtensions.cs @@ -2,14 +2,10 @@ namespace HotChocolate.Language.Visitors; public static class SyntaxVisitorExtensions { - private static readonly EmptySyntaxVisitorContext _empty = new(); + private static readonly object _empty = new(); public static ISyntaxVisitorAction Visit( - this ISyntaxVisitor visitor, + this ISyntaxVisitor visitor, ISyntaxNode node) => visitor.Visit(node, _empty); - - private sealed class EmptySyntaxVisitorContext : ISyntaxVisitorContext - { - } } diff --git a/src/HotChocolate/Language/src/Language.Visitors/VisitSyntaxNode.cs b/src/HotChocolate/Language/src/Language.Visitors/VisitSyntaxNode.cs index ff363e81867..b9e312e9ab2 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/VisitSyntaxNode.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/VisitSyntaxNode.cs @@ -1,10 +1,3 @@ namespace HotChocolate.Language.Visitors; -public delegate ISyntaxVisitorAction VisitSyntaxNode( - ISyntaxNode node, - ISyntaxVisitorContext context); - -public delegate ISyntaxVisitorAction VisitSyntaxNode( - ISyntaxNode node, - TContext context) - where TContext : ISyntaxVisitorContext; +public delegate ISyntaxVisitorAction VisitSyntaxNode(ISyntaxNode node, TContext context); diff --git a/src/HotChocolate/Language/test/Language.Tests/Visitors/SchemaCoordinateVisitorTests.cs b/src/HotChocolate/Language/test/Language.Tests/Visitors/SchemaCoordinateVisitorTests.cs index 183d6c03ba5..260c4db2371 100644 --- a/src/HotChocolate/Language/test/Language.Tests/Visitors/SchemaCoordinateVisitorTests.cs +++ b/src/HotChocolate/Language/test/Language.Tests/Visitors/SchemaCoordinateVisitorTests.cs @@ -75,30 +75,19 @@ public static void VisitAllNodes_With_Generic_Walker() s => Assert.Equal("ghi", s)); } - public class CustomSyntaxWalker : SyntaxWalker + public class CustomSyntaxWalker(List list) + : SyntaxWalker(new() { VisitNames = true, }) { - private readonly List _list; - - public CustomSyntaxWalker(List list) - : base(new() { VisitNames = true, }) + protected override ISyntaxVisitorAction Enter(NameNode node, object context) { - _list = list; - } - - protected override ISyntaxVisitorAction Enter(NameNode node, ISyntaxVisitorContext context) - { - _list.Add(node.Value); + list.Add(node.Value); return DefaultAction; } } - public class CustomGenericSyntaxWalker : SyntaxWalker + public class CustomGenericSyntaxWalker() + : SyntaxWalker(new SyntaxVisitorOptions { VisitNames = true, }) { - public CustomGenericSyntaxWalker() - : base(new() { VisitNames = true, }) - { - } - protected override ISyntaxVisitorAction Enter(NameNode node, CustomContext context) { context.List.Add(node.Value); @@ -106,13 +95,8 @@ protected override ISyntaxVisitorAction Enter(NameNode node, CustomContext conte } } - public class CustomContext : ISyntaxVisitorContext + public class CustomContext(List list) { - public CustomContext(List list) - { - List = list; - } - - public List List { get; } + public List List { get; } = list; } } diff --git a/src/HotChocolate/Skimmed/src/Skimmed/Refactor.cs b/src/HotChocolate/Skimmed/src/Skimmed/Refactor.cs index 5d7b2416683..d87b153b361 100644 --- a/src/HotChocolate/Skimmed/src/Skimmed/Refactor.cs +++ b/src/HotChocolate/Skimmed/src/Skimmed/Refactor.cs @@ -402,19 +402,13 @@ private sealed class ValueRewriter : SyntaxRewriter } } - private class RewriterContext : ISyntaxVisitorContext + private class RewriterContext(string value) { - public RewriterContext(string value) - { - Value = value; - } - - public string Value { get; } + public string Value { get; } = value; } } - private sealed class RemoveInputFieldRewriter - : SchemaVisitor<(InputObjectType Type, InputField Field)> + private sealed class RemoveInputFieldRewriter : SchemaVisitor<(InputObjectType Type, InputField Field)> { public override void VisitInputField(InputField field, (InputObjectType Type, InputField Field) context) { @@ -457,14 +451,9 @@ private sealed class ValueRewriter : SyntaxRewriter } } - private class RewriterContext : ISyntaxVisitorContext + private class RewriterContext(string name) { - public RewriterContext(string name) - { - Name = name; - } - - public string Name { get; } + public string Name { get; } = name; } } } \ No newline at end of file diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/EntityIdRewriter.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/EntityIdRewriter.cs index 0c86f6bb2f6..f19129e9370 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/EntityIdRewriter.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/EntityIdRewriter.cs @@ -115,14 +115,9 @@ public static DocumentNode Rewrite(DocumentNode document, ISchema schema) return rewriter.RewriteDocument(document, new Context(schema))!; } - public class Context : ISyntaxVisitorContext + public class Context(ISchema schema) { - public Context(ISchema schema) - { - Schema = schema; - } - - public ISchema Schema { get; } + public ISchema Schema { get; } = schema; public Stack Types { get; } = new(); diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/ExtractOperationContext.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/ExtractOperationContext.cs index 87f6a490bfe..e8b66b8f26e 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/ExtractOperationContext.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/ExtractOperationContext.cs @@ -6,7 +6,7 @@ namespace StrawberryShake.CodeGeneration.Utilities; -internal sealed class ExtractOperationContext : ISyntaxVisitorContext +internal sealed class ExtractOperationContext { private readonly DocumentNode _document; private int _index = -1; diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/FragmentRewriter.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/FragmentRewriter.cs index 788450604c9..99bfb2a3221 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/FragmentRewriter.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/FragmentRewriter.cs @@ -56,7 +56,7 @@ public static DocumentNode Rewrite(DocumentNode document) return rewriter.RewriteDocument(document, context)!; } - internal sealed class Context : ISyntaxVisitorContext + internal sealed class Context { public HashSet Deferred { get; } = []; } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/RemoveClientDirectivesRewriter.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/RemoveClientDirectivesRewriter.cs index 4be6ce22398..44ca144d254 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/RemoveClientDirectivesRewriter.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/RemoveClientDirectivesRewriter.cs @@ -5,11 +5,11 @@ namespace StrawberryShake.CodeGeneration.Utilities; -internal sealed class RemoveClientDirectivesRewriter : SyntaxRewriter +internal sealed class RemoveClientDirectivesRewriter : SyntaxRewriter { private const string _returns = "returns"; - protected override FieldNode RewriteField(FieldNode node, ISyntaxVisitorContext context) + protected override FieldNode RewriteField(FieldNode node, object? context) { var current = node; diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/RemovedUnusedFragmentRewriter.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/RemovedUnusedFragmentRewriter.cs index 6cbcd14ba06..809fa340702 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/RemovedUnusedFragmentRewriter.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/RemovedUnusedFragmentRewriter.cs @@ -46,7 +46,7 @@ public static DocumentNode Rewrite(DocumentNode document) return rewriter.RewriteDocument(document, context); } - internal sealed class Context : ISyntaxVisitorContext + internal sealed class Context { public HashSet Used { get; } = []; } diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/TypeNameQueryRewriter.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/TypeNameQueryRewriter.cs index 8fb0fe27837..8222755ce49 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/TypeNameQueryRewriter.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/TypeNameQueryRewriter.cs @@ -87,7 +87,7 @@ internal sealed class TypeNameQueryRewriter : SyntaxRewriter Nodes { get; } = new(); }