22using Microsoft . CodeAnalysis . Text ;
33using System ;
44using System . Collections . Generic ;
5+ using System . Collections . Immutable ;
56using System . Linq ;
67using System . Text ;
7- using System . Threading . Tasks ;
88using Microsoft . CodeAnalysis . CSharp . Syntax ;
9- using DotNurse . CodeAnalysis ;
10- using AutoFilterer . Generators . Extensions ;
119using Microsoft . CodeAnalysis . CSharp ;
12- using System . Diagnostics ;
1310
1411namespace AutoFilterer . Generators ;
1512
13+ using Extensions ;
14+
1615[ Generator ]
17- public class FilterGenerator : ISourceGenerator
16+ public class FilterGenerator : IIncrementalGenerator
1817{
19- public void Initialize ( GeneratorInitializationContext context )
20- {
21- context . RegisterForSyntaxNotifications ( ( ) => new AttributeSyntaxReceiver < GenerateAutoFilterAttribute > ( ) ) ;
22- }
23-
24- public void Execute ( GeneratorExecutionContext context )
18+ public void Initialize ( IncrementalGeneratorInitializationContext context )
2519 {
26- if ( ! ( context . SyntaxReceiver is AttributeSyntaxReceiver < GenerateAutoFilterAttribute > receiver ) )
27- return ;
20+ var classDeclarations = context . SyntaxProvider
21+ . CreateSyntaxProvider (
22+ predicate : static ( s , _ ) => IsSyntaxTargetForGeneration ( s ) ,
23+ transform : static ( ctx , _ ) => GetSemanticTargetForGeneration ( ctx ) )
24+ . Where ( static m => m is not null ) ;
2825
29- //if (!Debugger.IsAttached)
30- //{
31- // Debugger.Launch();
32- //}
26+ var compilationAndClasses = context . CompilationProvider . Combine ( classDeclarations . Collect ( ) ) ;
3327
34- foreach ( var classSyntax in receiver . Classes )
35- {
36- var attribute = classSyntax . AttributeLists . SelectMany ( sm => sm . Attributes ) . FirstOrDefault ( x => x . Name . ToString ( ) . EnsureEndsWith ( "Attribute" ) . Equals ( typeof ( GenerateAutoFilterAttribute ) . Name ) ) ;
37- var namespaceParam = attribute . ArgumentList ? . Arguments . FirstOrDefault ( ) ; // Temprorary... Attribute has only one argument for now.
28+ context . RegisterSourceOutput ( compilationAndClasses , ( spc , source ) => Execute ( source . Left , source . Right , spc ) ) ;
29+ }
3830
39- var model = context . Compilation . GetSemanticModel ( classSyntax . SyntaxTree ) ;
40- var symbol = model . GetDeclaredSymbol ( classSyntax ) ;
41- var attrs = symbol . GetAttributes ( ) ;
31+ private static bool IsSyntaxTargetForGeneration ( SyntaxNode node )
32+ {
33+ if ( node is not ClassDeclarationSyntax classDeclaration || classDeclaration . AttributeLists . Count == 0 ) {
34+ return false ;
35+ }
4236
43- var realNamespace = GetNamespaceRecursively ( symbol . ContainingNamespace ) ;
37+ return classDeclaration . AttributeLists
38+ . SelectMany ( al => al . Attributes )
39+ . Any ( a => a . Name . ToString ( ) . EnsureEndsWith ( "Attribute" ) . EndsWith ( nameof ( GenerateAutoFilterAttribute ) ) ) ;
40+ }
4441
45- var properties = symbol . GetMembers ( ) . OfType < IPropertySymbol > ( )
46- . Where ( x => ! x . IsStatic && ! x . ContainingType . IsGenericType && x . Kind == SymbolKind . Property ) ;
42+ private static ClassDeclarationSyntax GetSemanticTargetForGeneration ( GeneratorSyntaxContext context )
43+ {
44+ var classDeclaration = ( ClassDeclarationSyntax ) context . Node ;
4745
48- context . AddSource ( $ "{ symbol . Name } FilterDto.g.cs",
49- SourceText . From ( GetFilterDtoCode ( symbol . Name , properties , namespaceParam ? . ToString ( ) . Trim ( '\" ' ) ?? realNamespace ) , Encoding . UTF8 ) ) ;
46+ foreach ( var attributeList in classDeclaration . AttributeLists )
47+ {
48+ foreach ( var attribute in attributeList . Attributes )
49+ {
50+ var attributeSymbol = context . SemanticModel . GetSymbolInfo ( attribute ) . Symbol as IMethodSymbol ;
51+ var fullName = attributeSymbol ? . ContainingType . ToDisplayString ( ) ?? attribute . Name . ToString ( ) ;
52+ if ( fullName . EnsureEndsWith ( "Attribute" ) . EndsWith ( nameof ( GenerateAutoFilterAttribute ) ) )
53+ {
54+ return classDeclaration ;
55+ }
56+ }
5057 }
58+
59+ return null ;
5160 }
5261
53- private static Dictionary < INamedTypeSymbol , IList < AttributeData > > GetClassAttributePairs ( GeneratorExecutionContext context ,
54- SyntaxReceiver receiver )
62+ private static void Execute ( Compilation compilation , ImmutableArray < ClassDeclarationSyntax > classes , SourceProductionContext context )
5563 {
56- var compilation = context . Compilation ;
57- var classSymbols = new Dictionary < INamedTypeSymbol , IList < AttributeData > > ( ) ;
58- foreach ( var clazz in receiver . CandidateClasses )
64+ if ( classes . IsDefaultOrEmpty ) {
65+ return ;
66+ }
67+
68+ foreach ( var classSyntax in classes . Distinct ( ) )
5969 {
60- var model = compilation . GetSemanticModel ( clazz . SyntaxTree ) ;
61- var classSymbol = model . GetDeclaredSymbol ( clazz ) ;
62- var attributes = classSymbol . GetAttributes ( ) ;
63- if ( attributes . Any ( ad => ad . AttributeClass . Name == nameof ( GenerateAutoFilterAttribute ) ) )
70+ var model = compilation . GetSemanticModel ( classSyntax . SyntaxTree ) ;
71+ if ( model . GetDeclaredSymbol ( classSyntax ) is not { } symbol )
6472 {
65- classSymbols . Add ( ( INamedTypeSymbol ) classSymbol , attributes . ToList ( ) ) ;
73+ continue ;
6674 }
67- }
6875
69- return classSymbols ;
76+ var attribute = symbol . GetAttributes ( )
77+ . FirstOrDefault ( a => a . AttributeClass ? . Name == nameof ( GenerateAutoFilterAttribute ) ) ;
78+ var targetNamespace = attribute ? . ConstructorArguments . FirstOrDefault ( ) . Value ? . ToString ( ) . Trim ( '\" ' ) ; // Temporary... Attribute has only one argument for now.
79+ if ( string . IsNullOrEmpty ( targetNamespace ) ) {
80+ targetNamespace = GetNamespaceRecursively ( symbol . ContainingNamespace ) ;
81+ }
82+
83+ var properties = symbol . GetMembers ( )
84+ . OfType < IPropertySymbol > ( )
85+ . Where ( x => ! x . IsStatic && ! x . IsIndexer && x . Kind == SymbolKind . Property ) ;
86+
87+ var sourceCode = GetFilterDtoCode ( symbol . Name , properties , targetNamespace ) ;
88+
89+ context . AddSource ( $ "{ symbol . Name } FilterDto.g.cs", SourceText . From ( sourceCode , Encoding . UTF8 ) ) ;
90+ }
7091 }
7192
72- internal string GetFilterDtoCode ( string className , IEnumerable < IPropertySymbol > properties ,
93+ private static string GetFilterDtoCode ( string className , IEnumerable < IPropertySymbol > properties ,
7394 string @namespace = null )
7495 {
75- var start = $@ "
76- using System;
77- using AutoFilterer;
78- using AutoFilterer.Attributes;
79- using AutoFilterer.Types;
80-
81- namespace { @namespace ?? "AutoFilterer.Filters" }
82- {{
83- public partial class { className } Filter : PaginationFilterBase
84- {{
85- " ;
86-
87- var body = new StringBuilder ( ) ;
96+ var generatedCode = new StringBuilder ( ) ;
97+ generatedCode . AppendLine ( "using System;" ) ;
98+ generatedCode . AppendLine ( "using AutoFilterer.Attributes;" ) ;
99+ generatedCode . AppendLine ( "using AutoFilterer.Types;" ) ;
100+ generatedCode . AppendLine ( ) ;
101+ generatedCode . AppendLine ( $ "namespace { @namespace } ") ;
102+ generatedCode . AppendLine ( "{" ) ;
103+ generatedCode . AppendLine ( $ "\t public partial class { className } Filter : PaginationFilterBase") ;
104+ generatedCode . AppendLine ( "\t {" ) ;
88105
89106 foreach ( var property in properties )
90107 {
91- string propertyType = property . Type . ToDisplayString ( NullableFlowState . None ) ;
108+ var propertyType = property . Type . ToDisplayString ( NullableFlowState . None ) ;
92109
93- if ( TypeMapping . Mappings . TryGetValue ( propertyType , out var mapped ) )
110+ if ( TypeMapping . Mappings . TryGetValue ( propertyType , out var mapped ) ) {
94111 propertyType = mapped ;
112+ }
95113
96- if ( propertyType . Equals ( nameof ( String ) , StringComparison . InvariantCultureIgnoreCase ) )
114+ if ( property . Type . SpecialType == SpecialType . System_String )
97115 {
98- body . AppendLine ( "\t \t [ToLowerContainsComparison]" ) ;
116+ generatedCode . AppendLine ( "\t \t [ToLowerContainsComparison]" ) ;
99117 }
100- body . AppendLine ( $ "\t \t public virtual { propertyType } { property . Name } {{ get; set; }}") ;
101- }
102118
103- var end = "\t }\n }" ;
119+ generatedCode . AppendLine ( $ "\t \t public virtual { propertyType } { property . Name } {{ get; set; }}") ;
120+ }
104121
122+ generatedCode . AppendLine ( "\t }" ) ;
123+ generatedCode . AppendLine ( "}" ) ;
105124
106- return start + body . ToString ( ) + end ;
125+ return generatedCode . ToString ( ) ;
107126 }
108127
109- private string GetNamespaceRecursively ( INamespaceSymbol symbol )
128+ private static string GetNamespaceRecursively ( INamespaceSymbol symbol )
110129 {
111130 if ( symbol . ContainingNamespace == null )
112131 {
@@ -115,4 +134,4 @@ private string GetNamespaceRecursively(INamespaceSymbol symbol)
115134
116135 return ( GetNamespaceRecursively ( symbol . ContainingNamespace ) + "." + symbol . Name ) . Trim ( '.' ) ;
117136 }
118- }
137+ }
0 commit comments