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
76 changes: 76 additions & 0 deletions src/QueryByShape.Analyzer/Analyzers/ArgumentAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using QueryByShape.Analyzer.Diagnostics;
using System;
using System.Collections.Immutable;
using System.Linq;


namespace QueryByShape.Analyzer.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ArgumentAnalyzer : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [InvalidArgumentNameDiagnostic.Descriptor, DuplicateArgumentDiagnostic.Descriptor];

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.Attribute);
}

private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
if (context.Node is not AttributeSyntax attributeSyntax)
{
return;
}

var name = attributeSyntax.Name.ExtractName();

if (name is not "Argument" or nameof(ArgumentAttribute))
{
return;
}

var attributeNamedType = context.Compilation.ResolveNamedType<ArgumentAttribute>();
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)
{
return;
}

var activeName = activeAttribute.GetConstructorArgument();
ReportInvalidName(activeName, context);
ReportDuplicateNames(activeName, activeAttribute, attributeNamedType, attributes, context);

}

private static void ReportInvalidName(string name, SyntaxNodeAnalysisContext context)
{
if (GraphQLHelpers.IsValidName(name.AsSpan(), out var problems) == false)
{
context.ReportDiagnostic(
InvalidArgumentNameDiagnostic.Create(name, [.. problems], context.Node.GetLocation())
);
}
}

private static void ReportDuplicateNames(string name, AttributeData activeAttribute, INamedTypeSymbol attributeNamedType, ImmutableArray<AttributeData> attributes, SyntaxNodeAnalysisContext context)
{
var dupes = attributes.Where(a => a.AttributeClass?.Equals(attributeNamedType, SymbolEqualityComparer.Default) == true && a.GetConstructorArgument() == name);

if (dupes.First() != activeAttribute)
{
context.ReportDiagnostic(
DuplicateArgumentDiagnostic.Create(name, activeAttribute.GetLocation())
);
}
}
}
}
72 changes: 72 additions & 0 deletions src/QueryByShape.Analyzer/Analyzers/QueryAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using QueryByShape.Analyzer.Diagnostics;
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.InteropServices;
using System.Xml.Linq;


namespace QueryByShape.Analyzer.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class QueryAnalyzer : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [InvalidOperationNameDiagnostic.Descriptor];

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.Attribute);
}

private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
if (context.Node is not AttributeSyntax attributeSyntax)
{
return;
}

var name = attributeSyntax.Name.ExtractName();

if (name is not "Query" or nameof(QueryAttribute))
{
return;
}

var attributeNamedType = context.Compilation.ResolveNamedType<QueryAttribute>();
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)
{
return;
}

ReportInvalidName(activeAttribute, context);
}

private static void ReportInvalidName(AttributeData attribute, SyntaxNodeAnalysisContext context)
{
var arguments = attribute.NamedArguments.Where(n => n.Key == nameof(QueryAttribute.OperationName)).ToArray();

if (arguments.Length == 0)
{
return;
}

var operationName = arguments[0].Value.Value as string;

if (GraphQLHelpers.IsValidName(operationName.AsSpan(), out var problems) == false)
{
context.ReportDiagnostic(
InvalidOperationNameDiagnostic.Create(operationName!, [.. problems], context.Node.GetLocation())
);
}
}
}
}
68 changes: 68 additions & 0 deletions src/QueryByShape.Analyzer/Analyzers/QueryDeclarationAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using QueryByShape.Analyzer.Diagnostics;
using System;
using System.Collections.Immutable;
using System.Linq;

namespace QueryByShape.Analyzer.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class QueryDeclarationAnalyzer : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [QueryMustImplementDiagnostic.Descriptor, QueryMustBePartialDiagnostic.Descriptor];

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration);

}

private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
if (context.Node is not TypeDeclarationSyntax typeSyntax)
{
return;
}

var attributeNamedType = context.Compilation.ResolveNamedType<QueryAttribute>();
var symbol = context.ContainingSymbol as INamedTypeSymbol;
var attributes = symbol!.GetAttributes();
var isQuery = attributes.Any(a => a.AttributeClass?.Equals(attributeNamedType, SymbolEqualityComparer.Default) == true);

if (isQuery == false)
{
return;
}

ReportNotImplementing(symbol, context);
ReportNotPartial(typeSyntax, symbol, context);
}

private static void ReportNotImplementing(INamedTypeSymbol symbol, SyntaxNodeAnalysisContext context)
{
var interfaceType = context.Compilation.GetTypeByMetadataName("QueryByShape.IGeneratedQuery")!;

if (interfaceType.IsAssignableFrom(symbol) == false)
{
context.ReportDiagnostic(
QueryMustImplementDiagnostic.Create(symbol.Name, symbol.Locations[0])
);
}
}

private static void ReportNotPartial(TypeDeclarationSyntax typeSyntax, INamedTypeSymbol symbol, SyntaxNodeAnalysisContext context)
{
if (typeSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)) == false)
{
context.ReportDiagnostic(
QueryMustBePartialDiagnostic.Create(symbol.Name, symbol.Locations[0])
);
}
}
}
}
86 changes: 86 additions & 0 deletions src/QueryByShape.Analyzer/Analyzers/VariableAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using QueryByShape.Analyzer.Diagnostics;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace QueryByShape.Analyzer.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class VariableAnalyzer : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [InvalidVariableNameDiagnostic.Descriptor, DuplicateVariableDiagnostic.Descriptor];

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.Attribute);
}

private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
if (context.Node is not AttributeSyntax attributeSyntax)
{
return;
}

var name = attributeSyntax.Name.ExtractName();

if (name is not "Variable" or nameof(VariableAttribute))
{
return;
}

var attributeNamedType = context.Compilation.ResolveNamedType<VariableAttribute>();
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)
{
return;
}

var activeName = activeAttribute.GetConstructorArgument();
ReportInvalidName(activeName, context);
ReportDuplicateNames(activeName, activeAttribute, attributeNamedType, attributes, context);
}

private static void ReportInvalidName(string name, SyntaxNodeAnalysisContext context)
{
var problems = new List<string>();

if (name[0] != '$')
{
problems.Add("Must start with $");
}
else
{
GraphQLHelpers.IsValidName(name.AsSpan()[1..], out problems);
}

if (problems.Count > 0)
{
context.ReportDiagnostic(
InvalidVariableNameDiagnostic.Create(name, [.. problems], context.Node.GetLocation())
);
}
}

private static void ReportDuplicateNames(string name, AttributeData activeAttribute, INamedTypeSymbol attributeNamedType, ImmutableArray<AttributeData> attributes, SyntaxNodeAnalysisContext context)
{
var dupes = attributes.Where(a => a.AttributeClass?.Equals(attributeNamedType, SymbolEqualityComparer.Default) == true && a.GetConstructorArgument() == name);

if (dupes.First() != activeAttribute)
{
context.ReportDiagnostic(
DuplicateVariableDiagnostic.Create(name, activeAttribute.GetLocation())
);
}
}
}
}
Loading