Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions QueryByShape.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32516.85
# Visual Studio Version 18
VisualStudioVersion = 18.0.11201.2 d18.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QueryByShape", "src\QueryByShape\QueryByShape.csproj", "{0B6A344B-FCCE-428D-A2A7-AF1409A86C7F}"
EndProject
Expand All @@ -19,6 +19,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{6D
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StarWars", "examples\StarWars\StarWars.csproj", "{E26A1C68-EC4D-4A3C-9EEF-7C1DBCA8AC1F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QueryByShape.Analyzer.Benchmark", "tests\QueryByShape.Analyzer.Benchmark\QueryByShape.Analyzer.Benchmark.csproj", "{49638B8D-2B15-4AE7-ABA0-B296645D631C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -49,13 +51,18 @@ Global
{E26A1C68-EC4D-4A3C-9EEF-7C1DBCA8AC1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E26A1C68-EC4D-4A3C-9EEF-7C1DBCA8AC1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E26A1C68-EC4D-4A3C-9EEF-7C1DBCA8AC1F}.Release|Any CPU.Build.0 = Release|Any CPU
{49638B8D-2B15-4AE7-ABA0-B296645D631C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{49638B8D-2B15-4AE7-ABA0-B296645D631C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{49638B8D-2B15-4AE7-ABA0-B296645D631C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{49638B8D-2B15-4AE7-ABA0-B296645D631C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{70312BAD-B5F9-4FFA-86DF-A4B38B830C7C} = {91D9FD32-6EC7-4E31-978F-124B427C126B}
{E26A1C68-EC4D-4A3C-9EEF-7C1DBCA8AC1F} = {6D432921-65BB-40FE-856A-C087B5ACC0A2}
{49638B8D-2B15-4AE7-ABA0-B296645D631C} = {91D9FD32-6EC7-4E31-978F-124B427C126B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2182D04A-84B1-4FB0-BF83-FD2D148552DF}
Expand Down
63 changes: 63 additions & 0 deletions src/QueryByShape.Analyzer/ArrayBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;

namespace QueryByShape.Analyzer
{
internal class ArrayBuilder<T>
{
private const int DefaultCapacity = 8;
private T[] _items;

public ArrayBuilder(int capacity = DefaultCapacity)
{
_items = new T[capacity];
}

public int Count { get; set; }

private void EnsureCapacity(int additional)
{
int requiredCapacity = this.Count + additional;
if (requiredCapacity <= _items.Length)
{
return;
}

int newCapacity = Math.Max(_items.Length * 2, requiredCapacity);
Array.Resize(ref _items, newCapacity);
}

public void Add(T item)
{
EnsureCapacity(1);
_items[this.Count++] = item;
}

public void AddRange(T[] items)
{
EnsureCapacity(items.Length);

int offset = this.Count;
this.Count += items.Length;

Array.Copy(items, 0, _items, offset, items.Length);
}


public T[] ToArray()
{
if (this.Count == 0)
{
return Array.Empty<T>();
}

T[] result = new T[this.Count];
Array.Copy(_items, result, this.Count);
return result;
}

public void Clear()
{
this.Count = 0;
}
}
}
10 changes: 5 additions & 5 deletions src/QueryByShape.Analyzer/Emitter/GeneratorTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ internal static class GeneratorTemplate
{
public static string Build(string query, string namespaceName, string className)
{
return string.Format(Output, DateTime.Now.ToLongTimeString(), namespaceName, className, query);
return string.Format(Output, namespaceName, className, query);
}

public static string Output = """
// <auto-generated/> {0}
// <auto-generated/>

#nullable enable annotations
#nullable disable warnings

// Suppress warnings about [Obsolete] member usage in generated code.
// #pragma warning disable CS0612, CS0618

namespace {1}
namespace {0}
{{
public partial class {2}
public partial class {1}
{{
public static string ToGraphQLQuery() => @"{3}";
public static string ToGraphQLQuery() => @"{2}";
}}
}}
""";
Expand Down
118 changes: 91 additions & 27 deletions src/QueryByShape.Analyzer/Emitter/QueryEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,50 @@ public static void EmitSource(SourceProductionContext ctx, ParseResult result)
var (query, diagnostics) = result;
var emitter = new QueryEmitter(query);

if (diagnostics.Any(d => d.Descriptor.DefaultSeverity is DiagnosticSeverity.Error) is false)
if (diagnostics?.Any(d => d.Descriptor.DefaultSeverity is DiagnosticSeverity.Error) == true)
{
string graphQlQuery = emitter.EmitQuery();
var generatedClass = GeneratorTemplate.Build(graphQlQuery, query.NamespaceName, query.TypeName);
ctx.AddSource($"QueryByShape.{query.TypeFullName}.Query.g.cs", SourceText.From(generatedClass, Encoding.UTF8));
return;
}

string graphQlQuery = emitter.EmitQuery();
var generatedClass = GeneratorTemplate.Build(graphQlQuery, query.NamespaceName, query.TypeName);
ctx.AddSource($"QueryByShape.{query.TypeFullName}.Query.g.cs", SourceText.From(generatedClass, Encoding.UTF8));
}
private string EmitQuery()
{
_sb.Append("query ");
_sb.Append(FormatName(query.Name ?? query.TypeName));

var variables = query.Variables?.Select(v => v.DefaultValue == null ? $"{v.Name}:{v.GraphType}" : $"{v.Name}:{v.GraphType} = {JsonSerializer.Serialize(v.DefaultValue)}").ToArray();
_sb.AppendParentheses(variables);

if (query.Variables?.Count > 0)
{
var variables = query.Variables.Value;
_sb.Append("(");

for (int i = 0; i < variables.Count; i++)
{
var variable = variables[i];

if (i != 0)
{
_sb.Append(",");
}

_sb.Append(variable.Name);
_sb.Append(":");
_sb.Append(variable.GraphType);

if (variable.DefaultValue != null)
{
_sb.Append(" = ");
_sb.Append(JsonSerializer.Serialize(variable.DefaultValue));
}
}

_sb.Append(")");
}

EmitType(query.Type, query.Options);

return _sb.ToString();
}

Expand All @@ -49,41 +76,59 @@ private string FormatName(string name)

private void EmitType(TypeMetadata typeMetadata, QueryOptions options)
{
if (typeMetadata.Members is null)
var members = typeMetadata.Members;

var withoutFragments = new List<MemberMetadata>(members.Count);
var withFragments = new Dictionary<string, List<MemberMetadata>>();

foreach (var member in typeMetadata.Members)
{
return;
}
if (member.Ignore == true || (member.Kind == SymbolKind.Field && !options.IncludeFields))
{
continue;
}

var (members, fragmentMembers) = typeMetadata.Members.Value.Partition(m => m.On?.Count is not > 0);
if (member.On?.Count > 0)
{
foreach (var fragment in member.On)
{
if (!withFragments.TryGetValue(fragment, out var fragmentMembers))
{
fragmentMembers = new List<MemberMetadata>();
withFragments[fragment] = fragmentMembers;
}

fragmentMembers.Add(member);
}
}
else
{
withoutFragments.Add(member);
}
}

_sb.AppendStartBlock();

EmitMembers(members, options);
EmitMembers(withoutFragments, options);

if (fragmentMembers.Count > 0)
if (withFragments.Count > 0)
{
var fragments = fragmentMembers
.SelectMany(m => m.On!.Value, (m, o) => (fragment: o, member: m))
.GroupBy(m => m.fragment, m => m.member);

foreach (var fragment in fragments)
foreach (var fragment in withFragments)
{
_sb.AppendLine();
_sb.Append($"... on {fragment.Key}");
_sb.AppendStartBlock();
EmitMembers(fragment, options);
EmitMembers(fragment.Value, options);
_sb.AppendEndBlock();
}
}

_sb.AppendEndBlock();
}

private void EmitMembers(IEnumerable<MemberMetadata> members, QueryOptions options)
private void EmitMembers(List<MemberMetadata> members, QueryOptions options)
{
var filtered = options.IncludeFields == false ? members.Where(m => m.Kind == SymbolKind.Property) : members;

foreach (var member in filtered)
foreach (var member in members)
{
_sb.AppendLine();
_sb.Append(member.OverrideName ?? FormatName(member.Name));
Expand All @@ -94,10 +139,29 @@ private void EmitMembers(IEnumerable<MemberMetadata> members, QueryOptions optio
_sb.Append(member.AliasOf);
}

var arguments = member.Arguments?.Select(a => $"{a.Name}:{a.VariableName}").ToArray();
_sb.AppendParentheses(arguments);
if (member.Arguments?.Count > 0)
{
var arguments = member.Arguments.Value;
_sb.Append("(");

for (int i = 0; i < arguments.Count; i++)
{
var argument = arguments[i];

if (i != 0)
{
_sb.Append(",");
}

_sb.Append(argument.Name);
_sb.Append(":");
_sb.Append(argument.VariableName);
}

_sb.Append(")");
}

if (member.ChildrenType?.Members?.Count > 0)
if (member.ChildrenType?.Members.Count > 0)
{
EmitType(member.ChildrenType, options);
}
Expand Down
13 changes: 1 addition & 12 deletions src/QueryByShape.Analyzer/Emitter/SourceBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Text;
using static Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser;
using static System.Net.Mime.MediaTypeNames;

namespace QueryByShape.Analyzer
Expand Down Expand Up @@ -52,18 +53,6 @@ internal void Append(string text)
sb.Append(text);
}

internal void AppendParentheses(string[]? values)
{
if (values == null || values.Length == 0)
{
return;
}

sb.Append("(");
sb.AppendJoin(",", values);
sb.Append(")");
}

public override string ToString() => sb.ToString();
}

Expand Down
33 changes: 0 additions & 33 deletions src/QueryByShape.Analyzer/EnumerableExtensions.cs

This file was deleted.

Loading