diff --git a/.gitignore b/.gitignore index 8a30d25..cca999b 100644 --- a/.gitignore +++ b/.gitignore @@ -396,3 +396,6 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml + +# Snapshot tests +*.received.txt \ No newline at end of file diff --git a/src/QueryByShape.Analyzer/Analyzers/ArgumentAnalyzer.cs b/src/QueryByShape.Analyzer/Analyzers/ArgumentAnalyzer.cs index e1188ee..90091a5 100644 --- a/src/QueryByShape.Analyzer/Analyzers/ArgumentAnalyzer.cs +++ b/src/QueryByShape.Analyzer/Analyzers/ArgumentAnalyzer.cs @@ -40,12 +40,12 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) var attributes = context.ContainingSymbol!.GetAttributes(); var activeAttribute = attributes.Single(a => a.ApplicationSyntaxReference?.SyntaxTree == attributeSyntax.SyntaxTree && a.ApplicationSyntaxReference?.Span == attributeSyntax.Span); - if (activeAttribute.AttributeClass?.Equals(attributeNamedType, SymbolEqualityComparer.Default) != true) + if (activeAttribute.IsAttributeType(attributeNamedType) is false + || activeAttribute.TryGetConstructorArgument(out string? activeName) is false) { return; } - var activeName = activeAttribute.GetConstructorArgument(); ReportInvalidName(activeName, context); ReportDuplicateNames(activeName, activeAttribute, attributeNamedType, attributes, context); @@ -63,7 +63,7 @@ private static void ReportInvalidName(string name, SyntaxNodeAnalysisContext con private static void ReportDuplicateNames(string name, AttributeData activeAttribute, INamedTypeSymbol attributeNamedType, ImmutableArray attributes, SyntaxNodeAnalysisContext context) { - var dupes = attributes.Where(a => a.AttributeClass?.Equals(attributeNamedType, SymbolEqualityComparer.Default) == true && a.GetConstructorArgument() == name); + var dupes = attributes.Where(a => a.IsAttributeType(attributeNamedType) && a.TryGetConstructorArgument(out string? argument) && argument == name); if (dupes.First() != activeAttribute) { diff --git a/src/QueryByShape.Analyzer/Analyzers/VariableAnalyzer.cs b/src/QueryByShape.Analyzer/Analyzers/VariableAnalyzer.cs index 1dbc67f..84de7e4 100644 --- a/src/QueryByShape.Analyzer/Analyzers/VariableAnalyzer.cs +++ b/src/QueryByShape.Analyzer/Analyzers/VariableAnalyzer.cs @@ -40,12 +40,12 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context) var attributes = context.ContainingSymbol!.GetAttributes(); var activeAttribute = attributes.Single(a => a.ApplicationSyntaxReference?.SyntaxTree == attributeSyntax.SyntaxTree && a.ApplicationSyntaxReference?.Span == attributeSyntax.Span); - if (activeAttribute.AttributeClass?.Equals(attributeNamedType, SymbolEqualityComparer.Default) != true) + if (activeAttribute.IsAttributeType(attributeNamedType) is false + || activeAttribute.TryGetConstructorArgument(out string? activeName) is false) { return; } - var activeName = activeAttribute.GetConstructorArgument(); ReportInvalidName(activeName, context); ReportDuplicateNames(activeName, activeAttribute, attributeNamedType, attributes, context); } @@ -73,7 +73,7 @@ private static void ReportInvalidName(string name, SyntaxNodeAnalysisContext con private static void ReportDuplicateNames(string name, AttributeData activeAttribute, INamedTypeSymbol attributeNamedType, ImmutableArray attributes, SyntaxNodeAnalysisContext context) { - var dupes = attributes.Where(a => a.AttributeClass?.Equals(attributeNamedType, SymbolEqualityComparer.Default) == true && a.GetConstructorArgument() == name); + var dupes = attributes.Where(a => a.IsAttributeType(attributeNamedType) && a.TryGetConstructorArgument(out string? argument) && argument == name); if (dupes.First() != activeAttribute) { diff --git a/src/QueryByShape.Analyzer/AttributeNames.cs b/src/QueryByShape.Analyzer/AttributeNames.cs index fabe4ed..ebf23a7 100644 --- a/src/QueryByShape.Analyzer/AttributeNames.cs +++ b/src/QueryByShape.Analyzer/AttributeNames.cs @@ -5,6 +5,7 @@ internal static class AttributeNames public const string JSON_PROPERTY = "System.Text.Json.Serialization.JsonPropertyNameAttribute"; public const string JSON_IGNORE = "System.Text.Json.Serialization.JsonIgnoreAttribute"; public const string QUERY = $"QueryByShape.{nameof(QueryAttribute)}"; + public const string MUTATION = $"QueryByShape.{nameof(MutationAttribute)}"; public const string VARIABLE = $"QueryByShape.{nameof(VariableAttribute)}"; public const string ARGUMENT = $"QueryByShape.{nameof(ArgumentAttribute)}"; public const string ALIAS_OF = $"QueryByShape.{nameof(AliasOfAttribute)}"; diff --git a/src/QueryByShape.Analyzer/CodeAnalysisExtensions.cs b/src/QueryByShape.Analyzer/CodeAnalysisExtensions.cs index 43972a1..8560125 100644 --- a/src/QueryByShape.Analyzer/CodeAnalysisExtensions.cs +++ b/src/QueryByShape.Analyzer/CodeAnalysisExtensions.cs @@ -2,6 +2,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.Json.Serialization; using System.Xml.Linq; @@ -12,8 +13,10 @@ internal static class Extensions { public static string ToFullName(this AttributeData attribute) { - var attributeClass = attribute.AttributeClass ?? throw new NullReferenceException(); - return $"{attributeClass.GetNamespace()}.{attributeClass.Name}"; + return attribute.AttributeClass? + .ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + .Replace("global::", string.Empty) + ?? throw new ArgumentNullException(nameof(attribute)); } public static string ExtractName(this NameSyntax nameSyntax) @@ -47,42 +50,69 @@ public static Location GetLocation(this AttributeData attribute) public static INamedTypeSymbol ResolveNamedType(this Compilation compilation) { - return compilation.GetTypeByMetadataName(typeof(T).FullName)!; + var name = typeof(T).FullName ?? throw new InvalidOperationException("No metadata name for type"); + var symbol = compilation.GetTypeByMetadataName(name); + return symbol ?? throw new InvalidOperationException($"Type '{name}' not found in compilation"); } - public static string GetConstructorArgument(this AttributeData attribute) => GetConstructorArguments(attribute)[0]; - public static string[] GetConstructorArguments(this AttributeData attribute) => attribute.ConstructorArguments.Select(c => c.Value?.ToString() ?? "").ToArray(); - - public static bool TryGetNamedArgument(this AttributeData attribute, string name, out T? value) - { + public static bool TryGetConstructorArgument(this AttributeData attribute, [NotNullWhen(true)] out T? value) + { + if (attribute?.ConstructorArguments.Length > 0 && attribute.ConstructorArguments[0].Value is T argument) + { + value = argument; + return true; + } + value = default; + return false; + } - var arguments = attribute.NamedArguments.Where(a => a.Key == name); - - if (arguments.Any()) + public static bool TryGetConstructorArguments(this AttributeData attribute, [NotNullWhen(true)] out TFirst? first, [NotNullWhen(true)] out TSecond? second) + { + if (attribute?.ConstructorArguments.Length > 1 && attribute.ConstructorArguments[0].Value is TFirst firstArg && attribute.ConstructorArguments[1].Value is TSecond secondArg) { - value = (T?)arguments.First().Value.Value; + first = firstArg; + second = secondArg; return true; } + first = default; + second = default; return false; } - public static string GetNamespace(this INamedTypeSymbol symbol) + public static bool TryGetNamedArgument(this AttributeData attribute, string name, out T? value) { - return string.Join(".", GetNamespace_Internal(symbol.ContainingNamespace)); + foreach (var kv in attribute.NamedArguments) + { + if (kv.Key == name && kv.Value.Value is T t) + { + value = t; + return true; + } + } - static string[] GetNamespace_Internal(INamespaceSymbol symbol, int index = 0) + value = default; + return false; + } + + public static bool IsAttributeType(this AttributeData? attribute, INamedTypeSymbol? targetType) + { + var sourceType = attribute?.AttributeClass; + + if (sourceType is null || targetType is null) { - if (symbol.ContainingNamespace == null) - { - return new string[index]; - } - - var result = GetNamespace_Internal(symbol.ContainingNamespace, index + 1); - result[result.Length - index - 1] = symbol.Name; - return result; + return false; } + + return SymbolEqualityComparer.Default.Equals(sourceType, targetType); + } + + public static string GetNamespace(this INamedTypeSymbol symbol) + { + return symbol.ContainingNamespace + .ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + .Replace("global::", string.Empty); } public static bool TryGetCompatibleGenericBaseType(this ITypeSymbol type, INamedTypeSymbol? baseType, out INamedTypeSymbol? result) @@ -150,6 +180,15 @@ public static bool IsAssignableFrom(this ITypeSymbol? baseType, ITypeSymbol? typ return false; } - public static Location ToTrimmedLocation(this Location location) => Location.Create(location.SourceTree!.FilePath, location.SourceSpan, location.GetLineSpan().Span); + public static Location ToTrimmedLocation(this Location location) + { + if (location == Location.None || location.SourceTree is null) + { + return location; + } + + return Location.Create(location.SourceTree.FilePath, location.SourceSpan, location.GetLineSpan().Span); + } + } } diff --git a/src/QueryByShape.Analyzer/Emitter/QueryEmitter.cs b/src/QueryByShape.Analyzer/Emitter/QueryEmitter.cs index 7a7dd38..b17f140 100644 --- a/src/QueryByShape.Analyzer/Emitter/QueryEmitter.cs +++ b/src/QueryByShape.Analyzer/Emitter/QueryEmitter.cs @@ -1,9 +1,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; -using QueryByShape.Analyzer.Diagnostics; using System.Collections.Generic; using System.Linq; -using System.Net.Http.Headers; using System.Text; using System.Text.Json; @@ -28,14 +26,13 @@ public static void EmitSource(SourceProductionContext ctx, ParseResult result) } private string EmitQuery() { - // variables dynamic or static set? use case - // variables with object type (sorting) - _sb.Append("query "); _sb.Append(FormatName(query.Name ?? query.TypeName)); - EmitParameters(query.Variables?.Select(v => v.DefaultValue == null ? $"{v.Name}:{v.GraphType}" : $"{v.Name}:{v.GraphType} = {JsonSerializer.Serialize(v.DefaultValue)}")); - EmitType(query.Type!, query.Options, 1); + 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); + + EmitType(query.Type, query.Options); return _sb.ToString(); } @@ -50,7 +47,7 @@ private string FormatName(string name) return name; } - private void EmitType(TypeMetadata typeMetadata, QueryOptions options, int depth) + private void EmitType(TypeMetadata typeMetadata, QueryOptions options) { if (typeMetadata.Members is null) { @@ -59,9 +56,9 @@ private void EmitType(TypeMetadata typeMetadata, QueryOptions options, int depth var (members, fragmentMembers) = typeMetadata.Members.Value.Partition(m => m.On?.Count is not > 0); - _sb.AppendLine("{"); + _sb.AppendStartBlock(); - EmitMembers(members, options, depth); + EmitMembers(members, options); if (fragmentMembers.Count > 0) { @@ -71,22 +68,24 @@ private void EmitType(TypeMetadata typeMetadata, QueryOptions options, int depth foreach (var fragment in fragments) { - _sb.AppendLine($"... on {fragment.Key} {{", depth); - EmitMembers(fragment, options, depth + 1); - _sb.AppendLine("}", depth); + _sb.AppendLine(); + _sb.Append($"... on {fragment.Key}"); + _sb.AppendStartBlock(); + EmitMembers(fragment, options); + _sb.AppendEndBlock(); } } - _sb.AppendLine("}", depth - 1); + _sb.AppendEndBlock(); } - private void EmitMembers(IEnumerable members, QueryOptions options, int depth) + private void EmitMembers(IEnumerable members, QueryOptions options) { var filtered = options.IncludeFields == false ? members.Where(m => m.Kind == SymbolKind.Property) : members; foreach (var member in filtered) { - _sb.AppendIndent(depth); + _sb.AppendLine(); _sb.Append(member.OverrideName ?? FormatName(member.Name)); if (member.AliasOf != null) @@ -95,29 +94,14 @@ private void EmitMembers(IEnumerable members, QueryOptions optio _sb.Append(member.AliasOf); } - EmitParameters(member.Arguments?.Select(a => $"{a.Name}:{a.VariableName}")); - + var arguments = member.Arguments?.Select(a => $"{a.Name}:{a.VariableName}").ToArray(); + _sb.AppendParentheses(arguments); + if (member.ChildrenType?.Members?.Count > 0) { - EmitType(member.ChildrenType, options, depth + 1); + EmitType(member.ChildrenType, options); } - else - { - _sb.AppendLine(string.Empty); - } - } - } - - internal void EmitParameters(IEnumerable? items) - { - if (items == null || !items.Any()) - { - return; } - - _sb.Append("("); - _sb.Append(string.Join(",", items)); - _sb.Append(")"); } } } diff --git a/src/QueryByShape.Analyzer/Emitter/SourceBuilder.cs b/src/QueryByShape.Analyzer/Emitter/SourceBuilder.cs index 2224aa8..2ec6815 100644 --- a/src/QueryByShape.Analyzer/Emitter/SourceBuilder.cs +++ b/src/QueryByShape.Analyzer/Emitter/SourceBuilder.cs @@ -1,31 +1,41 @@ -using System.Text; +using System; +using System.Text; +using static System.Net.Mime.MediaTypeNames; namespace QueryByShape.Analyzer { internal class SourceBuilder(SourceFormatting formatting) { - private StringBuilder sb = new StringBuilder(); - - public void Append(string text) + private readonly StringBuilder sb = new StringBuilder(); + private int depth = 0; + + internal void AppendStartBlock() { - sb.Append(text); + sb.Append(" {"); + depth++; } - public void AppendLine(string text, int depth = 0) + internal void AppendEndBlock() + { + depth--; + AppendLine(); + sb.Append("}"); + } + + internal void AppendLine() { - AppendIndent(depth); - if (formatting == SourceFormatting.Minified) { - sb.Append(text); + sb.Append(" "); } else { - sb.AppendLine(text); + sb.AppendLine(); + AppendIndent(); } } - public void AppendIndent(int depth) + internal void AppendIndent() { if (formatting == SourceFormatting.Minified) { @@ -33,10 +43,28 @@ public void AppendIndent(int depth) } else if (depth > 0) { - sb.Append(' ', depth * 4); + sb.Append(' ', depth * 4); + } + } + + 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(); } + } diff --git a/src/QueryByShape.Analyzer/EnumerableExtensions.cs b/src/QueryByShape.Analyzer/EnumerableExtensions.cs index 8624958..8923233 100644 --- a/src/QueryByShape.Analyzer/EnumerableExtensions.cs +++ b/src/QueryByShape.Analyzer/EnumerableExtensions.cs @@ -8,18 +8,6 @@ namespace QueryByShape.Analyzer { public static class EnumerableExtensions { - internal static void Deconstruct(this IList list, out T first, out T second) - { - first = list.Count > 0 ? list[0] : throw new IndexOutOfRangeException(); - second = list.Count > 1 ? list[1] : throw new IndexOutOfRangeException(); - } - - public static void Deconstruct(this IList list, out T first, out T second, out T third) - { - (first, second) = list; - third = list.Count > 2 ? list[2] : throw new IndexOutOfRangeException(); - } - public static (List, List) Partition(this IEnumerable source, Func predicate) { var left = new List(); diff --git a/src/QueryByShape.Analyzer/NamedTypeSymbols.cs b/src/QueryByShape.Analyzer/NamedTypeSymbols.cs index 632dbba..e986ee6 100644 --- a/src/QueryByShape.Analyzer/NamedTypeSymbols.cs +++ b/src/QueryByShape.Analyzer/NamedTypeSymbols.cs @@ -1,53 +1,54 @@ using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editing; -using System; using System.Collections.Generic; using System.Reflection; +using System.Text.Json.Serialization; namespace QueryByShape.Analyzer { - public struct LazySymbol(Func creator) + public class NamedTypeSymbols(Compilation compilation) { - private INamedTypeSymbol? _value = null; - public INamedTypeSymbol Value => _value ?? (_value = creator()); - } - public class NamedTypeSymbols - { - private readonly Compilation _compilation; + public INamedTypeSymbol Delegate => _delegate ??= compilation.GetSpecialType(SpecialType.System_Delegate); + private INamedTypeSymbol? _delegate; - public NamedTypeSymbols(Compilation compilation) - { - _compilation = compilation; - _delegate = new (() => ResolveNamedType(typeof(Delegate).FullName)); - _iEnumerableOfT = new(() => ResolveNamedType(typeof(IEnumerable<>).FullName)); - _iDictionaryOfKV = new (() => ResolveNamedType(typeof(IDictionary<,>).FullName)); - _intPtr = new (() => ResolveNamedType(typeof(IntPtr).FullName)); - _memberInfo = new (() => ResolveNamedType(typeof(MemberInfo).FullName)); - _uIntPtr = new (() => ResolveNamedType(typeof(UIntPtr).FullName)); - } + public INamedTypeSymbol IEnumerableOfT => _iEnumerableOfT ??= compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T); + private INamedTypeSymbol? _iEnumerableOfT; - public INamedTypeSymbol Delegate => _delegate.Value; - private readonly LazySymbol _delegate; + public INamedTypeSymbol IDictionaryOfKV => _iDictionaryOfKV ??= compilation.GetTypeByMetadataName(typeof(IDictionary<,>).FullName)!; + private INamedTypeSymbol? _iDictionaryOfKV; - public INamedTypeSymbol IEnumerableOfT => _iEnumerableOfT.Value; - private readonly LazySymbol _iEnumerableOfT; + public INamedTypeSymbol IntPtr => _intPtr ??= compilation.GetSpecialType(SpecialType.System_IntPtr); + private INamedTypeSymbol? _intPtr; - public INamedTypeSymbol IDictionaryOfKV => _iDictionaryOfKV.Value; - private readonly LazySymbol _iDictionaryOfKV; + public INamedTypeSymbol MemberInfo => _memberInfo ??= compilation.GetTypeByMetadataName(typeof(MemberInfo).FullName)!; + private INamedTypeSymbol? _memberInfo; - public INamedTypeSymbol IntPtr => _intPtr.Value; - private readonly LazySymbol _intPtr; + public INamedTypeSymbol UIntPtr => _uIntPtr ??= compilation.GetSpecialType(SpecialType.System_UIntPtr); + private INamedTypeSymbol? _uIntPtr; - public INamedTypeSymbol MemberInfo => _memberInfo.Value; - private readonly LazySymbol _memberInfo; + public INamedTypeSymbol QueryAttribute => _queryAttribute ??= compilation.GetTypeByMetadataName(typeof(QueryAttribute).FullName)!; + private INamedTypeSymbol? _queryAttribute; - public INamedTypeSymbol UIntPtr => _uIntPtr.Value; - private readonly LazySymbol _uIntPtr; + public INamedTypeSymbol VariableAttribute => _variableAttribute ??= compilation.GetTypeByMetadataName(typeof(VariableAttribute).FullName)!; + private INamedTypeSymbol? _variableAttribute; + + public INamedTypeSymbol ArgumentAttribute => _argumentAttribute ??= compilation.GetTypeByMetadataName(typeof(ArgumentAttribute).FullName)!; + private INamedTypeSymbol? _argumentAttribute; + + public INamedTypeSymbol AliasOfAttribute => _aliasOfAttribute ??= compilation.GetTypeByMetadataName(typeof(AliasOfAttribute).FullName)!; + private INamedTypeSymbol? _aliasOfAttribute; + + public INamedTypeSymbol OnAttribute => _onAttribute ??= compilation.GetTypeByMetadataName(typeof(OnAttribute).FullName)!; + private INamedTypeSymbol? _onAttribute; + + public INamedTypeSymbol MutationAttribute => _mutationAttribute ??= compilation.GetTypeByMetadataName(typeof(MutationAttribute).FullName)!; + private INamedTypeSymbol? _mutationAttribute; + + public INamedTypeSymbol JsonIgnoreAttribute => _jsonIgnoreAttribute ??= compilation.GetTypeByMetadataName(typeof(JsonIgnoreAttribute).FullName)!; + private INamedTypeSymbol? _jsonIgnoreAttribute; + + public INamedTypeSymbol JsonPropertyAttribute => _jsonPropertyAttribute ??= compilation.GetTypeByMetadataName(typeof(JsonPropertyNameAttribute).FullName)!; + private INamedTypeSymbol? _jsonPropertyAttribute; - private INamedTypeSymbol ResolveNamedType(string name) - { - return _compilation.GetTypeByMetadataName(name)!; - } public bool IsPropertySerializable(IPropertySymbol property) { diff --git a/src/QueryByShape.Analyzer/Parser/QueryParser.cs b/src/QueryByShape.Analyzer/Parser/QueryParser.cs index eb6942a..4c0df8c 100644 --- a/src/QueryByShape.Analyzer/Parser/QueryParser.cs +++ b/src/QueryByShape.Analyzer/Parser/QueryParser.cs @@ -7,17 +7,24 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; using System.Text.Json.Serialization; using System.Threading; +using System.Xml.Linq; namespace QueryByShape.Analyzer { internal class QueryParser(NamedTypeSymbols symbols) { - private readonly List _diagnostics = []; - private readonly Dictionary)> _typeCache = []; + private static readonly SymbolEqualityComparer _symbolComparer = SymbolEqualityComparer.Default; + private static readonly StringComparer _stringComparer = StringComparer.Ordinal; + + private readonly List _diagnostics = new(); + private readonly Dictionary)> _typeCache = + new(_symbolComparer); public static EquatableArray Process(ImmutableArray<(TypeDeclarationSyntax declaration, SemanticModel semanticModel)> contexts, NamedTypeSymbols symbols, CancellationToken cancellationToken) { @@ -26,10 +33,7 @@ public static EquatableArray Process(ImmutableArray<(TypeDeclaratio for (int i = 0; i < contexts.Length; i++) { - if (cancellationToken.IsCancellationRequested) - { - throw new OperationCanceledException(); - } + cancellationToken.ThrowIfCancellationRequested(); var (declaration, semanticModel) = contexts[i]; var declaredSymbol = semanticModel.GetDeclaredSymbol(declaration)!; @@ -47,104 +51,114 @@ public ParseResult Parse(TypeDeclarationSyntax typeDeclaration, INamedTypeSymbol //_diagnostics.Add(QueryMustBePartialDiagnostic.Create(declaredSymbol.Name, typeDeclaration.GetLocation())); } - var query = new QueryMetadata(declaredSymbol.Name, declaredSymbol.GetNamespace()); - query.Type = ParseTypeMetadata(declaredSymbol, query.Options, out var queryArguments); + var (types, queryArguments) = ParseTypeMetadata(declaredSymbol); + + var query = new QueryMetadata(declaredSymbol.Name, declaredSymbol.GetNamespace(), types); + UpdateQueryFromAttributes(query, declaredSymbol.GetAttributes()); ValidateVariables(query.Variables, queryArguments); return (query, [.. _diagnostics]); } - public void ValidateVariables(IEnumerable? variables, IList arguments) + public void ValidateVariables(EquatableArray? variables, IList arguments) { - var variableLookup = variables?.ToDictionary(v => v.Name) ?? []; - var variableUsage = variableLookup.Keys.ToHashSet(); + // Nothing to validate + if ((variables is null or { Count: 0 }) && arguments is { Count: 0 }) + { + return; + } + + var vars = variables?.GetArray() ?? Array.Empty(); + + var variableRefs = new ReferenceSet(vars, v => v.Name, StringComparer.Ordinal); foreach (var argument in arguments) { - if (variableLookup.ContainsKey(argument.VariableName) is false) - { - _diagnostics.Add(MissingVariableDiagnostic.CreateMetadata(argument.Name, argument.VariableName, argument.Reference.GetLocation())); - } - else if (variableUsage.Contains(argument.VariableName)) + var varName = argument.VariableName; + + if (variableRefs.TryMarkReferenced(varName) is false) { - variableUsage.Remove(argument.VariableName); + _diagnostics.Add(MissingVariableDiagnostic.CreateMetadata(argument.Name, varName, argument.Reference.GetLocation())); + continue; } } - foreach (var variableName in variableUsage) + // Any remaining variables were not used by any argument + foreach (var variable in variableRefs.GetUnreferenced()) { - _diagnostics.Add(UnusedVariableDiagnostic.CreateMetadata(variableName, variableLookup[variableName].Reference.GetLocation())); + _diagnostics.Add(UnusedVariableDiagnostic.CreateMetadata(variable.Name, variable.Reference.GetLocation())); } } private void UpdateQueryFromAttributes(QueryMetadata query, ImmutableArray attributes) { - Dictionary variables = []; + Dictionary variables = new(_stringComparer); foreach (var attribute in attributes) { - switch (attribute.ToFullName()) + if (attribute.IsAttributeType(symbols.QueryAttribute)) { - case AttributeNames.QUERY: - foreach (var argument in attribute.NamedArguments) + foreach (var argument in attribute.NamedArguments) + { + switch (argument.Key) { - switch (argument.Key) - { - case nameof(QueryAttribute.OperationName): - query.Name = (string)argument.Value.Value!; - break; - case nameof(QueryAttribute.IncludeFields): - query.Options.IncludeFields = bool.Parse(argument.Value.Value!.ToString()); - break; - case nameof(QueryAttribute.PropertyNamingPolicy): - query.Options.PropertyNamingPolicy = (JsonPropertyNaming)Enum.Parse(typeof(JsonPropertyNaming), argument.Value.Value!.ToString()); - break; - case nameof(QueryAttribute.Formatting): - query.Options.Formatting = (SourceFormatting)Enum.Parse(typeof(SourceFormatting), argument.Value.Value!.ToString()); - break; - } + case nameof(QueryAttribute.OperationName): + query.Name = (string)argument.Value.Value!; + break; + case nameof(QueryAttribute.IncludeFields) when argument.Value.Value is bool includeFields: + query.Options.IncludeFields = includeFields; + break; + case nameof(QueryAttribute.PropertyNamingPolicy) when argument.Value.Value is JsonPropertyNaming jsonPropertyNaming: + query.Options.PropertyNamingPolicy = jsonPropertyNaming; + break; + case nameof(QueryAttribute.Formatting) when argument.Value.Value is SourceFormatting sourceFormatting: + query.Options.Formatting = sourceFormatting; + break; } - break; - - case AttributeNames.VARIABLE: - var (variablName, graphType) = attribute.GetConstructorArguments(); + } + + } + else if (attribute.IsAttributeType(symbols.VariableAttribute)) + { + if (attribute.TryGetConstructorArguments(out string? variableName, out string? graphType) + && variables.ContainsKey(variableName) is false) + { var defaultValue = attribute.TryGetNamedArgument(nameof(VariableAttribute.DefaultValue), out var value) ? value : null; - - if (variables.ContainsKey(variablName) == false) - { - variables.Add(variablName, new VariableMetadata(variablName, graphType, defaultValue, attribute.ApplicationSyntaxReference)); - } - - break; + variables[variableName] = new VariableMetadata(variableName, graphType, defaultValue, attribute.ApplicationSyntaxReference); + } } } query.Variables = [.. variables.Values]; } - private TypeMetadata ParseTypeMetadata(INamedTypeSymbol type, QueryOptions options, out List childArguments) - { - INamedTypeSymbol? current = type; - var name = type.ToDisplayString(); - - if (_typeCache.ContainsKey(name)) + private (TypeMetadata, List) ParseTypeMetadata(INamedTypeSymbol type) + { + if (_typeCache.TryGetValue(type, out var cachedType)) { - (var metadata, childArguments) = _typeCache[name]; - return metadata; + return cachedType; } - Dictionary members = []; - childArguments = []; + INamedTypeSymbol? current = type; + + Dictionary members = new(_stringComparer); + + List arguments = []; - while (current?.Name is not null or "Object" or "ValueType") + while (current is not null && current.SpecialType is not SpecialType.System_Object and not SpecialType.System_ValueType) { foreach (var member in current.GetMembers()) { + if ((member.Kind != SymbolKind.Property && member.Kind != SymbolKind.Field) || member.IsImplicitlyDeclared) + { + continue; + } + var memberName = member.Name; var attributes = member.GetAttributes(); - + if (members.TryGetValue(memberName, out var metadata) is false) { metadata = ParseMemberMetadata(member, out var memberType); @@ -157,31 +171,54 @@ private TypeMetadata ParseTypeMetadata(INamedTypeSymbol type, QueryOptions optio if (symbols.TryGetChildrenType(memberType!, out var childrenType)) { - metadata.ChildrenType = ParseTypeMetadata(childrenType!, options, out var innerChildArguments); - childArguments.AddRange(innerChildArguments); + var (childMetadata, childArguments) = ParseTypeMetadata(childrenType!); + metadata.ChildrenType = childMetadata; + arguments.AddRange(childArguments); } UpdateMemberFromBaseAttributes(metadata, attributes); - + if (metadata.Arguments?.Count > 0) { - childArguments.AddRange(metadata.Arguments); + arguments.AddRange(metadata.Arguments); } } - UpdateMemberFromInheritedAttributes(metadata, attributes); + if (metadata != null) + { + UpdateMemberFromInheritedAttributes(metadata, attributes); + + if (metadata.Ignore is true) + { + members[memberName] = null; + } + } } current = current.BaseType; } var typeMembers = members.Values.Where(m => m.IsSerializable && m.Ignore is not true).ToArray(); - var typeMetadata = new TypeMetadata(name, [.. typeMembers]); + var typeMetadata = new TypeMetadata(type.ToDisplayString(), [.. typeMembers]); + + var result = (typeMetadata, arguments); + _typeCache[type] = result; + return result; + } - _typeCache[name] = (typeMetadata, childArguments); - return typeMetadata; + private bool TryGetMemberType(ISymbol member, [NotNullWhen(true)] out INamedTypeSymbol? memberType) + { + memberType = member switch + { + IPropertySymbol property when symbols.IsPropertySerializable(property) => property.Type, + IFieldSymbol field when symbols.IsFieldSerializable(field) => field.Type, + _ => null + } as INamedTypeSymbol; + + return memberType is not null; } + private MemberMetadata ParseMemberMetadata(ISymbol member, out INamedTypeSymbol? memberType) { memberType = member switch @@ -199,31 +236,30 @@ IFieldSymbol field when symbols.IsFieldSerializable(field) => field.Type, private void UpdateMemberFromBaseAttributes(MemberMetadata metadata, ImmutableArray attributes) { - var arguments = new Dictionary(); - var inlineFragments = new HashSet(); + Dictionary arguments = new(_stringComparer); + HashSet inlineFragments = new(_stringComparer); foreach (var attribute in attributes) { - switch (attribute.ToFullName()) + if (attribute.IsAttributeType(symbols.ArgumentAttribute)) { - case AttributeNames.ARGUMENT: - var (name, variableName) = attribute.GetConstructorArguments(); - - if (arguments.ContainsKey(name) == false) - { - var argMetadata = new ArgumentMetadata(name, variableName, attribute.ApplicationSyntaxReference); - arguments.Add(name, argMetadata); - } - break; - - case AttributeNames.ALIAS_OF: - metadata.AliasOf = attribute.GetConstructorArgument(); - break; - - case AttributeNames.ON: - inlineFragments.Add(attribute.GetConstructorArgument()); - break; - + if (attribute.TryGetConstructorArguments(out string? name, out string? variableName) + && arguments.ContainsKey(name) is false) + { + var argMetadata = new ArgumentMetadata(name, variableName, attribute.ApplicationSyntaxReference); + arguments[name] = argMetadata; + } + } + else if (attribute.IsAttributeType(symbols.AliasOfAttribute)) + { + metadata.AliasOf = attribute.TryGetConstructorArgument(out string? alias) ? alias : null; + } + else if (attribute.IsAttributeType(symbols.OnAttribute)) + { + if (attribute.TryGetConstructorArgument(out string? on)) + { + inlineFragments.Add(on); + } } } @@ -235,19 +271,17 @@ private void UpdateMemberFromInheritedAttributes(MemberMetadata metadata, Immuta { foreach (var attribute in attributes) { - switch (attribute.ToFullName()) + if (attribute.IsAttributeType(symbols.JsonPropertyAttribute) && metadata.OverrideName is null) { - case AttributeNames.JSON_PROPERTY when metadata.OverrideName is null: - metadata.OverrideName = attribute.GetConstructorArgument(); - break; - - case AttributeNames.JSON_IGNORE when metadata.Ignore is null: - var ignoreCondition = attribute.TryGetNamedArgument(nameof(JsonIgnoreAttribute.Condition), out var condition) + metadata.OverrideName = attribute.TryGetConstructorArgument(out string? overrideName) ? overrideName : null; + } + else if (attribute.IsAttributeType(symbols.JsonIgnoreAttribute) && metadata.Ignore is null) + { + var ignoreCondition = attribute.TryGetNamedArgument(nameof(JsonIgnoreAttribute.Condition), out var condition) ? (JsonIgnoreCondition)condition : JsonIgnoreCondition.Always; - metadata.Ignore = ignoreCondition is not JsonIgnoreCondition.Never; - break; + metadata.Ignore = ignoreCondition is not JsonIgnoreCondition.Never; } } } diff --git a/src/QueryByShape.Analyzer/QueryMetadata.cs b/src/QueryByShape.Analyzer/QueryMetadata.cs index 17a1e40..0151560 100644 --- a/src/QueryByShape.Analyzer/QueryMetadata.cs +++ b/src/QueryByShape.Analyzer/QueryMetadata.cs @@ -15,7 +15,7 @@ internal record QueryOptions() internal record DiagnosticMetadata(DiagnosticDescriptor Descriptor, Location Location, EquatableArray? MessageArguments = null); - internal record QueryMetadata(string TypeName, string NamespaceName) + internal record QueryMetadata(string TypeName, string NamespaceName, TypeMetadata Type) { public string? Name { get; set; } @@ -23,8 +23,6 @@ internal record QueryMetadata(string TypeName, string NamespaceName) public EquatableArray? Variables { get; set; } - public TypeMetadata? Type { get; set; } - public QueryOptions Options { get; set; } = new QueryOptions(); } diff --git a/src/QueryByShape.Analyzer/ReferenceSet.cs b/src/QueryByShape.Analyzer/ReferenceSet.cs new file mode 100644 index 0000000..eeac431 --- /dev/null +++ b/src/QueryByShape.Analyzer/ReferenceSet.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; + +namespace QueryByShape.Analyzer +{ + /// + /// Tracks a fixed set of values and allows marking keys as referenced. + /// First occurrence of a duplicate key is preserved (first-win). + /// + internal sealed class ReferenceSet + { + private readonly TValue[] _items; + private readonly Dictionary _unreferenced; + private readonly HashSet _initialKeys; + + public ReferenceSet(TValue[] items, Func keySelector, IEqualityComparer comparer) + { + _items = items ?? Array.Empty(); + _unreferenced = new Dictionary(_items.Length, comparer); + _initialKeys = new HashSet(comparer); + + for (var i = 0; i < _items.Length; i++) + { + var key = keySelector(_items[i]); + + // First-win: ignore duplicate keys + if (_initialKeys.Add(key)) + { + _unreferenced[key] = i; + } + } + } + + /// + /// Mark a key as referenced. Returns true if the key existed in the original set; false otherwise. + /// + public bool TryMarkReferenced(TKey key) + { + if (_initialKeys.Contains(key) is false) + { + return false; + } + + _unreferenced.Remove(key); + return true; + } + + /// + /// Returns the items that were not referenced. + /// + public TValue[] GetUnreferenced() + { + if (_unreferenced.Count == 0) + { + return Array.Empty(); + } + + var result = new TValue[_unreferenced.Count]; + var idx = 0; + + foreach (var kv in _unreferenced) + { + result[idx++] = _items[kv.Value]; + } + + return result; + } + } +} diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.ExcludesFieldsWhenNotConfigured.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.ExcludesFieldsWhenNotConfigured.verified.txt index 0299e01..546d854 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.ExcludesFieldsWhenNotConfigured.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.ExcludesFieldsWhenNotConfigured.verified.txt @@ -9,6 +9,6 @@ namespace Tests { public partial class NameQuery { - public static string ToGraphQLQuery() => @"query nameQuery { people { preferredName } }"; + public static string ToGraphQLQuery() => @"query nameQuery { people { preferredName } }"; } } \ No newline at end of file diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesAlias.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesAlias.verified.txt index f989364..ab5917c 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesAlias.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesAlias.verified.txt @@ -9,6 +9,6 @@ namespace Tests { public partial class NameQuery { - public static string ToGraphQLQuery() => @"query nameQuery { people:customers { firstName lastName middleName:middle } }"; + public static string ToGraphQLQuery() => @"query nameQuery { people:customers { firstName lastName middleName:middle } }"; } } \ No newline at end of file diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesArgumentsAndVariables.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesArgumentsAndVariables.verified.txt index bab5ee6..fa899ac 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesArgumentsAndVariables.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesArgumentsAndVariables.verified.txt @@ -9,6 +9,6 @@ namespace Tests { public partial class NameQuery { - public static string ToGraphQLQuery() => @"query nameQuery($id:UUID!) { people(id:$id) { customerId firstName lastName middleName } }"; + public static string ToGraphQLQuery() => @"query nameQuery($id:UUID!) { people(id:$id) { customerId firstName lastName middleName } }"; } } \ No newline at end of file diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesArgumentsAndVariablesWithDefaults.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesArgumentsAndVariablesWithDefaults.verified.txt index d47ac14..812cb61 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesArgumentsAndVariablesWithDefaults.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesArgumentsAndVariablesWithDefaults.verified.txt @@ -9,6 +9,6 @@ namespace Tests { public partial class NameQuery { - public static string ToGraphQLQuery() => @"query nameQuery($id:UUID!,$isActive:Boolean = true) { people(id:$id,isActive:$isActive) { customerId firstName lastName middleName } }"; + public static string ToGraphQLQuery() => @"query nameQuery($id:UUID!,$isActive:Boolean = true) { people(id:$id,isActive:$isActive) { customerId firstName lastName middleName } }"; } } \ No newline at end of file diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesBasicQuery.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesBasicQuery.verified.txt index 75adf4f..e4e6225 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesBasicQuery.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesBasicQuery.verified.txt @@ -9,6 +9,6 @@ namespace Tests { public partial class NameQuery { - public static string ToGraphQLQuery() => @"query nameQuery { people { name } }"; + public static string ToGraphQLQuery() => @"query nameQuery { people { name } }"; } } \ No newline at end of file diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesComplexIEnumarableQuery.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesComplexIEnumarableQuery.verified.txt index 75adf4f..e4e6225 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesComplexIEnumarableQuery.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesComplexIEnumarableQuery.verified.txt @@ -9,6 +9,6 @@ namespace Tests { public partial class NameQuery { - public static string ToGraphQLQuery() => @"query nameQuery { people { name } }"; + public static string ToGraphQLQuery() => @"query nameQuery { people { name } }"; } } \ No newline at end of file diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesFieldsWhenConfigured.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesFieldsWhenConfigured.verified.txt index b33dfc3..ab6ebed 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesFieldsWhenConfigured.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesFieldsWhenConfigured.verified.txt @@ -9,6 +9,6 @@ namespace Tests { public partial class NameQuery { - public static string ToGraphQLQuery() => @"query nameQuery { people { firstName lastName middleName } }"; + public static string ToGraphQLQuery() => @"query nameQuery { people { firstName lastName middleName } }"; } } \ No newline at end of file diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesFragment.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesFragment.verified.txt index d10a4c2..dd7c7b4 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesFragment.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesFragment.verified.txt @@ -9,6 +9,6 @@ namespace Tests { public partial class NameQuery { - public static string ToGraphQLQuery() => @"query nameQuery { people { firstName lastName middleName ... on Employee { employeeId } } }"; + public static string ToGraphQLQuery() => @"query nameQuery { people { firstName lastName middleName ... on Employee { employeeId } } }"; } } \ No newline at end of file diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesKitchenSinkQuery.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesKitchenSinkQuery.verified.txt index a48a033..48c9f0b 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesKitchenSinkQuery.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesKitchenSinkQuery.verified.txt @@ -9,6 +9,6 @@ namespace Tests { public partial class NameQuery { - public static string ToGraphQLQuery() => @"query renamed($id:UUID!) { people(id:$id) { id:identity firstName lastName addressLine1 city state zip } }"; + public static string ToGraphQLQuery() => @"query renamed($id:UUID!) { people(id:$id) { id:identity firstName lastName addressLine1 city state zip } }"; } } \ No newline at end of file diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesMultipleQueriesWithDifferentOptions.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesMultipleQueriesWithDifferentOptions.verified.txt index c69a50f..2ecf52d 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesMultipleQueriesWithDifferentOptions.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesMultipleQueriesWithDifferentOptions.verified.txt @@ -9,7 +9,7 @@ namespace Tests { public partial class FirstQuery { - public static string ToGraphQLQuery() => @"query firstQuery($id:UUID!) { people(id:$id) { customerId firstName lastName middleName addressLine1 city state zipCode } }"; + public static string ToGraphQLQuery() => @"query firstQuery($id:UUID!) { people(id:$id) { customerId firstName lastName middleName addressLine1 city state zipCode } }"; } } @@ -23,6 +23,6 @@ namespace Tests { public partial class SecondQuery { - public static string ToGraphQLQuery() => @"query secondQuery($id:UUID!) { people(id:$id) { customerId firstName lastName middleName } }"; + public static string ToGraphQLQuery() => @"query secondQuery($id:UUID!) { people(id:$id) { customerId firstName lastName middleName } }"; } } \ No newline at end of file diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesMultipleQueriesWithSharedVariables.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesMultipleQueriesWithSharedVariables.verified.txt index ae4068b..b279aa1 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesMultipleQueriesWithSharedVariables.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesMultipleQueriesWithSharedVariables.verified.txt @@ -9,7 +9,7 @@ namespace Tests { public partial class FirstQuery { - public static string ToGraphQLQuery() => @"query firstQuery($id:UUID!,$orderId:UUID!) { people(id:$id) { customerId orders(orderId:$orderId) { orderId orderDate } firstName lastName middleName } }"; + public static string ToGraphQLQuery() => @"query firstQuery($id:UUID!,$orderId:UUID!) { people(id:$id) { customerId orders(orderId:$orderId) { orderId orderDate } firstName lastName middleName } }"; } } @@ -23,6 +23,6 @@ namespace Tests { public partial class SecondQuery { - public static string ToGraphQLQuery() => @"query secondQuery($id:UUID!,$orderId:UUID!) { people(id:$id) { customerId orders(orderId:$orderId) { orderId orderDate } firstName lastName middleName } }"; + public static string ToGraphQLQuery() => @"query secondQuery($id:UUID!,$orderId:UUID!) { people(id:$id) { customerId orders(orderId:$orderId) { orderId orderDate } firstName lastName middleName } }"; } } \ No newline at end of file diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesRecordQuery.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesRecordQuery.verified.txt index 990a152..74398a6 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesRecordQuery.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesRecordQuery.verified.txt @@ -9,6 +9,6 @@ namespace Tests { public partial class NameQuery { - public static string ToGraphQLQuery() => @"query nameQuery { people { name preferredName } }"; + public static string ToGraphQLQuery() => @"query nameQuery { people { name preferredName } }"; } } \ No newline at end of file diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesWithJsonIgnore.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesWithJsonIgnore.verified.txt index b7ae5c4..12b1448 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesWithJsonIgnore.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesWithJsonIgnore.verified.txt @@ -9,6 +9,6 @@ namespace Tests { public partial class NameQuery { - public static string ToGraphQLQuery() => @"query nameQuery { people { firstName lastName } }"; + public static string ToGraphQLQuery() => @"query nameQuery { people { firstName lastName } }"; } } \ No newline at end of file diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesWithJsonPropertyName.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesWithJsonPropertyName.verified.txt index 5185d14..58b2566 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesWithJsonPropertyName.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesWithJsonPropertyName.verified.txt @@ -9,6 +9,6 @@ namespace Tests { public partial class NameQuery { - public static string ToGraphQLQuery() => @"query nameQuery { people { id firstName lastName middle_name } }"; + public static string ToGraphQLQuery() => @"query nameQuery { people { id firstName lastName middle_name } }"; } } \ No newline at end of file diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesWithStructQuery.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesWithStructQuery.verified.txt index 9159aad..1d04066 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesWithStructQuery.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.GeneratesWithStructQuery.verified.txt @@ -9,6 +9,6 @@ namespace Tests { public partial class NameQuery { - public static string ToGraphQLQuery() => @"query nameQuery { people { age } }"; + public static string ToGraphQLQuery() => @"query nameQuery { people { age } }"; } } \ No newline at end of file diff --git a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.SupportsQueryName.verified.txt b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.SupportsQueryName.verified.txt index 63f1b6b..ebbe24f 100644 --- a/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.SupportsQueryName.verified.txt +++ b/tests/QueryByShape.Analyzer.Tests/SourceGenerator/Snapshots/SnapshotTests.SupportsQueryName.verified.txt @@ -9,6 +9,6 @@ namespace Tests { public partial class NameQuery { - public static string ToGraphQLQuery() => @"query renamed { people { name } }"; + public static string ToGraphQLQuery() => @"query renamed { people { name } }"; } } \ No newline at end of file