-
-
Notifications
You must be signed in to change notification settings - Fork 43
Use IIncrementalGenerator instead of deprecated ISourceGenerator #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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<GenerateAutoFilterAttribute>()); | ||||||
| } | ||||||
|
|
||||||
| public void Execute(GeneratorExecutionContext context) | ||||||
| public void Initialize(IncrementalGeneratorInitializationContext context) | ||||||
| { | ||||||
| if (!(context.SyntaxReceiver is AttributeSyntaxReceiver<GenerateAutoFilterAttribute> 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<IPropertySymbol>() | ||||||
| .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<INamedTypeSymbol, IList<AttributeData>> GetClassAttributePairs(GeneratorExecutionContext context, | ||||||
| SyntaxReceiver receiver) | ||||||
| private static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax> classes, SourceProductionContext context) | ||||||
| { | ||||||
| var compilation = context.Compilation; | ||||||
| var classSymbols = new Dictionary<INamedTypeSymbol, IList<AttributeData>>(); | ||||||
| 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<IPropertySymbol>() | ||||||
| .Where(x => !x.IsStatic && !x.IsIndexer && x.Kind == SymbolKind.Property); | ||||||
|
||||||
| .Where(x => !x.IsStatic && !x.IsIndexer && x.Kind == SymbolKind.Property); | |
| .Where(x => !x.IsStatic && !x.IsIndexer && x.Kind == SymbolKind.Property && !x.ContainingType.IsGenericType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was intentional because excluding the generic classes would break things like Entity<T> where T is int or Guid and used as a generic primary key I think.
I'm open to add it back though
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning null from GetSemanticTargetForGeneration could potentially cause issues with nullability warnings in projects with nullable reference types enabled. Consider using a more explicit pattern like returning a nullable type with the proper annotation or using an Option type pattern.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm this is used in a private method and shouldn't happen anyway because the filter is running first, so it's more of a catch-all situation. The calling method does explicitly filter for
nullafterwards, so I doubt this is an actual issue.Nullable reference types are disabled so far, so returning a nullable type doesn't really add value here either.
That said, I'm open to suggestions and nullable reference types are something I do want to address in a future PR.
The only useful thing I could do would be wrapping the return type in an
Option<T>but that just makes the calling function less readable in my opinion.