diff --git a/src/AutoFilterer.Generators/AttributeSyntaxReceiver.cs b/src/AutoFilterer.Generators/AttributeSyntaxReceiver.cs deleted file mode 100644 index 2d0a641..0000000 --- a/src/AutoFilterer.Generators/AttributeSyntaxReceiver.cs +++ /dev/null @@ -1,26 +0,0 @@ -using AutoFilterer.Generators.Extensions; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace DotNurse.CodeAnalysis; - -public class AttributeSyntaxReceiver : ISyntaxReceiver - where TAttribute : Attribute -{ - public IList Classes { get; } = new List(); - - public void OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax && - classDeclarationSyntax.AttributeLists.Count > 0 && - classDeclarationSyntax.AttributeLists - .Any(al => al.Attributes - .Any(a => a.Name.ToString().EnsureEndsWith("Attribute").Equals(typeof(TAttribute).Name)))) - { - Classes.Add(classDeclarationSyntax); - } - } -} \ No newline at end of file diff --git a/src/AutoFilterer.Generators/FilterGenerator.cs b/src/AutoFilterer.Generators/FilterGenerator.cs index 62815d6..cac4204 100644 --- a/src/AutoFilterer.Generators/FilterGenerator.cs +++ b/src/AutoFilterer.Generators/FilterGenerator.cs @@ -2,111 +2,130 @@ using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Text; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.Syntax; -using DotNurse.CodeAnalysis; -using AutoFilterer.Generators.Extensions; using Microsoft.CodeAnalysis.CSharp; -using System.Diagnostics; namespace AutoFilterer.Generators; +using Extensions; + [Generator] -public class FilterGenerator : ISourceGenerator +public class FilterGenerator : IIncrementalGenerator { - public void Initialize(GeneratorInitializationContext context) - { - context.RegisterForSyntaxNotifications(() => new AttributeSyntaxReceiver()); - } - - public void Execute(GeneratorExecutionContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { - if (!(context.SyntaxReceiver is AttributeSyntaxReceiver receiver)) - return; + var classDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (s, _) => IsSyntaxTargetForGeneration(s), + transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx)) + .Where(static m => m is not null); - //if (!Debugger.IsAttached) - //{ - // Debugger.Launch(); - //} + var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect()); - foreach (var classSyntax in receiver.Classes) - { - var attribute = classSyntax.AttributeLists.SelectMany(sm => sm.Attributes).FirstOrDefault(x => x.Name.ToString().EnsureEndsWith("Attribute").Equals(typeof(GenerateAutoFilterAttribute).Name)); - var namespaceParam = attribute.ArgumentList?.Arguments.FirstOrDefault(); // Temprorary... Attribute has only one argument for now. + context.RegisterSourceOutput(compilationAndClasses, (spc, source) => Execute(source.Left, source.Right, spc)); + } - var model = context.Compilation.GetSemanticModel(classSyntax.SyntaxTree); - var symbol = model.GetDeclaredSymbol(classSyntax); - var attrs = symbol.GetAttributes(); + private static bool IsSyntaxTargetForGeneration(SyntaxNode node) + { + if (node is not ClassDeclarationSyntax classDeclaration || classDeclaration.AttributeLists.Count == 0) { + return false; + } - var realNamespace = GetNamespaceRecursively(symbol.ContainingNamespace); + return classDeclaration.AttributeLists + .SelectMany(al => al.Attributes) + .Any(a => a.Name.ToString().EnsureEndsWith("Attribute").EndsWith(nameof(GenerateAutoFilterAttribute))); + } - var properties = symbol.GetMembers().OfType() - .Where(x => !x.IsStatic && !x.ContainingType.IsGenericType && x.Kind == SymbolKind.Property); + private static ClassDeclarationSyntax GetSemanticTargetForGeneration(GeneratorSyntaxContext context) + { + var classDeclaration = (ClassDeclarationSyntax)context.Node; - context.AddSource($"{symbol.Name}FilterDto.g.cs", - SourceText.From(GetFilterDtoCode(symbol.Name, properties, namespaceParam?.ToString().Trim('\"') ?? realNamespace), Encoding.UTF8)); + foreach (var attributeList in classDeclaration.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + var attributeSymbol = context.SemanticModel.GetSymbolInfo(attribute).Symbol as IMethodSymbol; + var fullName = attributeSymbol?.ContainingType.ToDisplayString() ?? attribute.Name.ToString(); + if (fullName.EnsureEndsWith("Attribute").EndsWith(nameof(GenerateAutoFilterAttribute))) + { + return classDeclaration; + } + } } + + return null; } - private static Dictionary> GetClassAttributePairs(GeneratorExecutionContext context, - SyntaxReceiver receiver) + private static void Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context) { - var compilation = context.Compilation; - var classSymbols = new Dictionary>(); - foreach (var clazz in receiver.CandidateClasses) + if (classes.IsDefaultOrEmpty) { + return; + } + + foreach (var classSyntax in classes.Distinct()) { - var model = compilation.GetSemanticModel(clazz.SyntaxTree); - var classSymbol = model.GetDeclaredSymbol(clazz); - var attributes = classSymbol.GetAttributes(); - if (attributes.Any(ad => ad.AttributeClass.Name == nameof(GenerateAutoFilterAttribute))) + var model = compilation.GetSemanticModel(classSyntax.SyntaxTree); + if (model.GetDeclaredSymbol(classSyntax) is not { } symbol) { - classSymbols.Add((INamedTypeSymbol)classSymbol, attributes.ToList()); + continue; } - } - return classSymbols; + var attribute = symbol.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.Name == nameof(GenerateAutoFilterAttribute)); + var targetNamespace = attribute?.ConstructorArguments.FirstOrDefault().Value?.ToString().Trim('\"'); // Temporary... Attribute has only one argument for now. + if (string.IsNullOrEmpty(targetNamespace)) { + targetNamespace = GetNamespaceRecursively(symbol.ContainingNamespace); + } + + var properties = symbol.GetMembers() + .OfType() + .Where(x => !x.IsStatic && !x.IsIndexer && x.Kind == SymbolKind.Property); + + var sourceCode = GetFilterDtoCode(symbol.Name, properties, targetNamespace); + + context.AddSource($"{symbol.Name}FilterDto.g.cs", SourceText.From(sourceCode, Encoding.UTF8)); + } } - internal string GetFilterDtoCode(string className, IEnumerable properties, + private static string GetFilterDtoCode(string className, IEnumerable properties, string @namespace = null) { - var start = $@" -using System; -using AutoFilterer; -using AutoFilterer.Attributes; -using AutoFilterer.Types; - -namespace {@namespace ?? "AutoFilterer.Filters"} -{{ - public partial class {className}Filter : PaginationFilterBase - {{ -"; - - var body = new StringBuilder(); + var generatedCode = new StringBuilder(); + generatedCode.AppendLine("using System;"); + generatedCode.AppendLine("using AutoFilterer.Attributes;"); + generatedCode.AppendLine("using AutoFilterer.Types;"); + generatedCode.AppendLine(); + generatedCode.AppendLine($"namespace {@namespace}"); + generatedCode.AppendLine("{"); + generatedCode.AppendLine($"\tpublic partial class {className}Filter : PaginationFilterBase"); + generatedCode.AppendLine("\t{"); foreach (var property in properties) { - string propertyType = property.Type.ToDisplayString(NullableFlowState.None); + var propertyType = property.Type.ToDisplayString(NullableFlowState.None); - if (TypeMapping.Mappings.TryGetValue(propertyType, out var mapped)) + if (TypeMapping.Mappings.TryGetValue(propertyType, out var mapped)) { propertyType = mapped; + } - if (propertyType.Equals(nameof(String), StringComparison.InvariantCultureIgnoreCase)) + if (property.Type.SpecialType == SpecialType.System_String) { - body.AppendLine("\t\t[ToLowerContainsComparison]"); + generatedCode.AppendLine("\t\t[ToLowerContainsComparison]"); } - body.AppendLine($"\t\tpublic virtual {propertyType} {property.Name} {{ get; set; }}"); - } - var end = "\t}\n}"; + generatedCode.AppendLine($"\t\tpublic virtual {propertyType} {property.Name} {{ get; set; }}"); + } + generatedCode.AppendLine("\t}"); + generatedCode.AppendLine("}"); - return start + body.ToString() + end; + return generatedCode.ToString(); } - private string GetNamespaceRecursively(INamespaceSymbol symbol) + private static string GetNamespaceRecursively(INamespaceSymbol symbol) { if (symbol.ContainingNamespace == null) { @@ -115,4 +134,4 @@ private string GetNamespaceRecursively(INamespaceSymbol symbol) return (GetNamespaceRecursively(symbol.ContainingNamespace) + "." + symbol.Name).Trim('.'); } -} +} \ No newline at end of file diff --git a/src/AutoFilterer.Generators/GenerateAutoFilterAttribute.cs b/src/AutoFilterer.Generators/GenerateAutoFilterAttribute.cs index cf7b2bc..f31bbad 100644 --- a/src/AutoFilterer.Generators/GenerateAutoFilterAttribute.cs +++ b/src/AutoFilterer.Generators/GenerateAutoFilterAttribute.cs @@ -9,8 +9,8 @@ public GenerateAutoFilterAttribute() public GenerateAutoFilterAttribute(string @namespace) { - Namespace = @Namespace; + Namespace = @namespace; } public string Namespace { get; } -} +} \ No newline at end of file diff --git a/src/AutoFilterer.Generators/SyntaxReceiver.cs b/src/AutoFilterer.Generators/SyntaxReceiver.cs deleted file mode 100644 index c5ff6de..0000000 --- a/src/AutoFilterer.Generators/SyntaxReceiver.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; - -namespace AutoFilterer.Generators; - -public class SyntaxReceiver : ISyntaxReceiver -{ - public IList CandidateClasses { get; } = new List(); - - public void OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - // any field with at least one attribute is a candidate for property generation - if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax && - classDeclarationSyntax.AttributeLists.Count > 0) - { - CandidateClasses.Add(classDeclarationSyntax); - } - } -}