Skip to content

Commit

Permalink
Add ability to use fragments from another csproj.
Browse files Browse the repository at this point in the history
  • Loading branch information
Stanislav Silin committed Jul 21, 2022
1 parent 8a8757d commit 43a1cff
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 14 deletions.
8 changes: 8 additions & 0 deletions src/ZeroQL.SourceGenerators/Descriptors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,33 @@ public record struct GraphQLQueryGenerationContext(
CSharpSyntaxNode Parent,
Dictionary<string, string> 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 };
Expand Down
39 changes: 29 additions & 10 deletions src/ZeroQL.SourceGenerators/Generator/GraphQLQueryGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,11 @@ public static Result<string> 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);
Expand Down Expand Up @@ -328,18 +323,22 @@ private static Result<string> 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;
Expand Down Expand Up @@ -445,6 +444,26 @@ private static Result<string> HandleFieldSelector(GraphQLQueryGenerationContext
return stringBuilder.ToString();
}

private static Result<Compilation> GetCompilation(SyntaxTree syntaxTree, Compilation compilation)
{
if (!compilation.ContainsSyntaxTree(syntaxTree))
{
var newCompilation = compilation.References
.OfType<CompilationReference>()
.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(
Expand Down
4 changes: 2 additions & 2 deletions src/ZeroQL.Tests/SourceGeneration/FragmentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 } } }";
Expand Down

0 comments on commit 43a1cff

Please sign in to comment.