diff --git a/src/ZeroQL.SourceGenerators/Descriptors.cs b/src/ZeroQL.SourceGenerators/Descriptors.cs index 51638bc..f3d1f2c 100644 --- a/src/ZeroQL.SourceGenerators/Descriptors.cs +++ b/src/ZeroQL.SourceGenerators/Descriptors.cs @@ -46,6 +46,14 @@ public class Descriptors description: "Only field selectors and fragments are allowed inside the query.", isEnabledByDefault: true); + public static DiagnosticDescriptor FragmentsWithoutSyntaxTree = new( + nameof(FragmentsWithoutSyntaxTree), + "Looks like, this fragment is defined in pre-compiled assembly. Such fragment can't be used in the query.", + "Looks like, this fragment is defined in pre-compiled assembly. Such fragment can't be used in the query.", + "ZeroQL", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + public static DiagnosticDescriptor GraphQLQueryPreview = new( nameof(GraphQLQueryPreview), string.Empty, diff --git a/src/ZeroQL.SourceGenerators/Generator/GraphQLQueryGenerationContext.cs b/src/ZeroQL.SourceGenerators/Generator/GraphQLQueryGenerationContext.cs index 762384c..a6d2da6 100644 --- a/src/ZeroQL.SourceGenerators/Generator/GraphQLQueryGenerationContext.cs +++ b/src/ZeroQL.SourceGenerators/Generator/GraphQLQueryGenerationContext.cs @@ -10,10 +10,33 @@ public record struct GraphQLQueryGenerationContext( CSharpSyntaxNode Parent, Dictionary AvailableVariables, SemanticModel SemanticModel, - INamedTypeSymbol FieldSelectorAttribute, - INamedTypeSymbol FragmentAttribute, CancellationToken CancellationToken) { + private INamedTypeSymbol? fragmentAttribute = null; + private INamedTypeSymbol? fieldSelectorAttribute = null; + private SemanticModel semanticModel = SemanticModel; + + public INamedTypeSymbol FragmentAttribute + { + get => fragmentAttribute ??= SemanticModel.Compilation.GetTypeByMetadataName(SourceGeneratorInfo.GraphQLFragmentAttribute)!; + } + + public INamedTypeSymbol FieldSelectorAttribute + { + get => fieldSelectorAttribute ??= SemanticModel.Compilation.GetTypeByMetadataName(SourceGeneratorInfo.GraphQLFieldSelectorAttribute)!; + } + + public SemanticModel SemanticModel + { + readonly get => semanticModel; + set + { + semanticModel = value; + fieldSelectorAttribute = null; + fragmentAttribute = null; + } + } + public GraphQLQueryGenerationContext WithParent(CSharpSyntaxNode parent) { return this with { Parent = parent }; diff --git a/src/ZeroQL.SourceGenerators/Generator/GraphQLQueryGenerator.cs b/src/ZeroQL.SourceGenerators/Generator/GraphQLQueryGenerator.cs index 24339fa..8eb3199 100644 --- a/src/ZeroQL.SourceGenerators/Generator/GraphQLQueryGenerator.cs +++ b/src/ZeroQL.SourceGenerators/Generator/GraphQLQueryGenerator.cs @@ -28,16 +28,11 @@ public static Result Generate(SemanticModel semanticModel, ExpressionSyn o => $"{inputs.VariablesName}.{o.Name}", o => "$" + o.Name.FirstToLower()); - var fieldSelectorAttribute = semanticModel.Compilation.GetTypeByMetadataName(SourceGeneratorInfo.GraphQLFieldSelectorAttribute)!; - var fragmentAttribute = semanticModel.Compilation.GetTypeByMetadataName(SourceGeneratorInfo.GraphQLFragmentAttribute)!; - var generationContext = new GraphQLQueryGenerationContext( inputs.QueryName, lambda, availableVariables, semanticModel, - fieldSelectorAttribute, - fragmentAttribute, cancellationToken); var body = GenerateQuery(generationContext, lambda.Body); @@ -328,18 +323,22 @@ private static Result HandleFragment(GraphQLQueryGenerationContext gener { if (method.DeclaringSyntaxReferences.IsEmpty) { - return Failed(invocation); + return Failed(invocation, Descriptors.FragmentsWithoutSyntaxTree); } - var fragment = method.DeclaringSyntaxReferences.First().GetSyntax(); + var fragment = method.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(); if (fragment is not MethodDeclarationSyntax methodDeclaration) { return Failed(invocation); } - var newSemanticModel = generationContext.SemanticModel.Compilation - .GetSemanticModel(fragment.SyntaxTree); - + var compilation = GetCompilation(fragment.SyntaxTree, generationContext.SemanticModel.Compilation); + if (compilation.Error) + { + return Failed(invocation, Descriptors.FragmentsWithoutSyntaxTree); + } + + var newSemanticModel = compilation.Value.GetSemanticModel(fragment.SyntaxTree); generationContext = generationContext with { SemanticModel = newSemanticModel }; var parameters = methodDeclaration.ParameterList.Parameters; @@ -445,6 +444,26 @@ private static Result HandleFieldSelector(GraphQLQueryGenerationContext return stringBuilder.ToString(); } + private static Result GetCompilation(SyntaxTree syntaxTree, Compilation compilation) + { + if (!compilation.ContainsSyntaxTree(syntaxTree)) + { + var newCompilation = compilation.References + .OfType() + .FirstOrDefault(o => o.Compilation.ContainsSyntaxTree(syntaxTree)) + ?.Compilation; + + if (newCompilation is null) + { + return new Error("Failed to find compilation"); + } + + return newCompilation; + } + + return compilation; + } + private static Error Failed(CSharpSyntaxNode node, DiagnosticDescriptor? descriptor = null) { var diagnostic = Diagnostic.Create( diff --git a/src/ZeroQL.Tests/SourceGeneration/FragmentTests.cs b/src/ZeroQL.Tests/SourceGeneration/FragmentTests.cs index a92ffb7..c0b38b3 100644 --- a/src/ZeroQL.Tests/SourceGeneration/FragmentTests.cs +++ b/src/ZeroQL.Tests/SourceGeneration/FragmentTests.cs @@ -105,8 +105,8 @@ public async Task CanApplyFragmentWithConstantArgument() response.Data.Role.Should().Be("Admin"); } - [Fact(Skip = "Figure out how to support this")] - public async Task CanLoadFragmentWithoutSyntaxTree() + [Fact] + public async Task CanLoadFragmentFromDifferentProject() { var csharpQuery = "static q => q.Me(o => o.AsUserFromDifferentAssembly())"; var graphqlQuery = @"query { me { firstName lastName role { name } } }";