Skip to content

Commit

Permalink
Added analyzer to validate union syntax.
Browse files Browse the repository at this point in the history
  • Loading branch information
byme8 committed Dec 10, 2022
1 parent af3489e commit 8925099
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 2 deletions.
77 changes: 77 additions & 0 deletions src/ZeroQL.SourceGenerators/Analyzers/QueryOnSyntaxAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace ZeroQL.SourceGenerators.Analyzers;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class QueryOnSyntaxAnalyzer : DiagnosticAnalyzer
{
#pragma warning disable RS1026
public override void Initialize(AnalysisContext context)
#pragma warning restore RS1026
{
#if !DEBUG
context.EnableConcurrentExecution();
#endif
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze |
GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.RegisterSyntaxNodeAction(Handle, SyntaxKind.InvocationExpression);
}

private void Handle(SyntaxNodeAnalysisContext context)
{
if (context.Node is not InvocationExpressionSyntax
{
Expression: MemberAccessExpressionSyntax { Name.Identifier.ValueText: "On" } memberAccess
} invocation)
{
return;
}

var possibleMethod = context.SemanticModel.GetSymbolInfo(invocation);
if (possibleMethod.Symbol is not IMethodSymbol method)
{
return;
}

var onMethod = context.Compilation.GetTypeByMetadataName("ZeroQL.GraphQLSyntaxExtensions")?
.GetMembers("On")
.OfType<IMethodSymbol>()
.FirstOrDefault();

if (!SymbolEqualityComparer.Default.Equals(onMethod, method.ReducedFrom))
{
return;
}

var typeArgument = method.TypeArguments.FirstOrDefault();
if (typeArgument is null)
{
return;
}

var type = context.SemanticModel.GetTypeInfo(memberAccess.Expression);
if (type.Type is not INamedTypeSymbol targetType)
{
return;
}

if (typeArgument.AllInterfaces.Contains(targetType))
{
return;
}

context.ReportDiagnostic(Diagnostic.Create(
Descriptors.GraphQLQueryInvalidUnionType,
memberAccess.Name.Identifier.GetLocation(),
typeArgument.Name,
targetType.Name));
}

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
= ImmutableArray.Create(Descriptors.GraphQLQueryInvalidUnionType);
}
8 changes: 8 additions & 0 deletions src/ZeroQL.SourceGenerators/Descriptors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,12 @@ public class Descriptors
"ZeroQL",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static DiagnosticDescriptor GraphQLQueryInvalidUnionType = new(
nameof(GraphQLQueryInvalidUnionType),
"Invalid union type.",
"Type {0} has to implement {1} interface.",
"ZeroQL",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
}
2 changes: 1 addition & 1 deletion src/ZeroQL.Tests/Core/TestExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public static async Task<Project> RemoveSyntaxTreesFromReferences(this Project p
return project;
}

public static async Task<Diagnostic[]?> ApplyAnalyzer(this Project project, DiagnosticAnalyzer analyzer)
public static async Task<Diagnostic[]> ApplyAnalyzer(this Project project, DiagnosticAnalyzer analyzer)
{
var compilation = await project.GetCompilationAsync();
var analyzerResults = await compilation!
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
Id: GraphQLQueryInvalidUnionType,
Highlighted: On<ImageContent>,
Message: Type ImageContent has to implement IFigure interface.
}
]
31 changes: 30 additions & 1 deletion src/ZeroQL.Tests/SourceGeneration/OnSyntaxTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Microsoft.CodeAnalysis;
using ZeroQL.SourceGenerators.Analyzers;
using ZeroQL.Tests.Core;
using ZeroQL.Tests.Data;

Expand Down Expand Up @@ -32,7 +34,7 @@ public async Task Interfaces()

await Verify(result);
}

[Fact]
public async Task Union()
{
Expand All @@ -54,4 +56,31 @@ public async Task Union()

await Verify(result);
}

[Fact]
public async Task AppliedToWrongType()
{
var csharpQuery = """
static q => q.Figures(
o => new
{
Circle = o.On<ImageContent>()
.Select(oo => new { oo.ImageUrl, oo.Height }),
})
""";

var project = await TestProject.Project
.ReplacePartOfDocumentAsync("Program.cs", (TestProject.ME_QUERY, csharpQuery));

var diagnostics = await project.ApplyAnalyzer(new QueryOnSyntaxAnalyzer());

await Verify(diagnostics
.Where(o => o.Severity == DiagnosticSeverity.Error)
.Select(o => new
{
o.Descriptor.Id,
Highlighted = o.Location.SourceTree!.GetRoot().FindNode(o.Location.SourceSpan).ToString(),
Message = o.GetMessage()
}));
}
}

0 comments on commit 8925099

Please sign in to comment.