11using System . Text ;
22using AttributeSourceGenerator . Common ;
3+ using AttributeSourceGenerator . Models ;
34using Microsoft . CodeAnalysis ;
45using Microsoft . CodeAnalysis . CSharp ;
56using Microsoft . CodeAnalysis . CSharp . Syntax ;
67using Microsoft . CodeAnalysis . Text ;
78
9+ // ReSharper disable CheckNamespace
10+
811namespace AttributeSourceGenerator ;
912
13+ /// <summary>Provides a base class for incremental source generators that generate source using marker attributes.</summary>
1014public abstract class AttributeIncrementalGeneratorBase : IIncrementalGenerator
1115{
12- protected abstract string AttributeFullName { get ; }
13- protected abstract string AttributeSource { get ; }
14- protected abstract FilterType AttributeFilter { get ; }
15- protected abstract Func < Symbol , string > GenerateSourceForSymbol { get ; }
16+ private readonly AttributeIncrementalGeneratorConfiguration _configuration ;
1617
17- public void Initialize ( IncrementalGeneratorInitializationContext context )
18+ /// <summary>Initializes a new instance of the <see cref="AttributeIncrementalGeneratorBase" /> class with the given configuration initializer.</summary>
19+ /// <param name="configuration">The configuration for the generator.</param>
20+ protected AttributeIncrementalGeneratorBase ( AttributeIncrementalGeneratorConfiguration configuration )
1821 {
19- context . RegisterPostInitializationOutput ( AddSource ) ;
20-
21- var pipeline = context . SyntaxProvider . ForAttributeWithMetadataName ( AttributeFullName , Filter , Transform ) ;
22-
23- context . RegisterSourceOutput ( pipeline , GenerateSource ) ;
22+ _configuration = configuration ?? throw new ArgumentNullException ( nameof ( configuration ) ) ;
2423 }
2524
26- private void AddSource ( IncrementalGeneratorPostInitializationContext ctx )
25+ /// <summary>Initializes a new instance of the <see cref="AttributeIncrementalGeneratorBase" /> class with the given configuration initializer.</summary>
26+ /// <param name="initializer">A function that provides the configuration for the generator.</param>
27+ protected AttributeIncrementalGeneratorBase ( Func < AttributeIncrementalGeneratorConfiguration > initializer )
2728 {
28- ctx . AddSource ( $ " { AttributeFullName } .g.cs" , SourceText . From ( AttributeSource , Encoding . UTF8 ) ) ;
29- }
29+ if ( initializer is null )
30+ throw new ArgumentNullException ( nameof ( initializer ) ) ;
3031
31- private bool Filter ( SyntaxNode syntaxNode , CancellationToken _ )
32- {
33- return AttributeFilter switch
34- {
35- FilterType . Interface => syntaxNode is InterfaceDeclarationSyntax ,
36- FilterType . Class => syntaxNode is ClassDeclarationSyntax ,
37- FilterType . Record => syntaxNode is RecordDeclarationSyntax syntax && syntax . Kind ( ) == SyntaxKind . ClassDeclaration ,
38- FilterType . Struct => syntaxNode is StructDeclarationSyntax ,
39- FilterType . RecordStruct => syntaxNode is RecordDeclarationSyntax syntax && syntax . Kind ( ) == SyntaxKind . StructDeclaration ,
40- FilterType . Method => syntaxNode is MethodDeclarationSyntax ,
41- _ => true ,
42- } ;
32+ _configuration = initializer ( ) ;
4333 }
4434
45- private static Symbol Transform ( GeneratorAttributeSyntaxContext context , CancellationToken _ )
35+ /// <summary>Initializes the incremental generator.</summary>
36+ /// <param name="context">The initialization context.</param>
37+ public void Initialize ( IncrementalGeneratorInitializationContext context )
4638 {
47- var symbol = context . TargetSymbol ;
48- var containingDeclarations = BuildHierarchy ( symbol ) ;
49- var symbolName = symbol . Name ;
50- return new Symbol ( symbolName , containingDeclarations ) ;
51- }
39+ context . RegisterPostInitializationOutput ( initializationContext => AddSource ( initializationContext , _configuration . AttributeFullyQualifiedName , _configuration . AttributeSource ) ) ;
5240
53- private void GenerateSource ( SourceProductionContext context , Symbol symbol )
54- {
55- var sourceText = GenerateSourceForSymbol ( symbol ) ;
56- context . AddSource ( $ "{ symbol . FullName } .g.cs", sourceText ) ;
41+ var pipeline = context . SyntaxProvider . ForAttributeWithMetadataName ( _configuration . AttributeFullyQualifiedName , ( syntaxNode , _ ) => Filter ( syntaxNode , _configuration . SymbolFilter ) , ( syntaxContext , _ ) => Transform ( syntaxContext ) ) ;
42+
43+ context . RegisterSourceOutput ( pipeline , ( productionContext , symbol ) => GenerateSourceForSymbol ( productionContext , symbol , _configuration . SourceGenerator ) ) ;
5744 }
5845
59- private static EquatableReadOnlyList < Declaration > BuildHierarchy ( ISymbol symbol )
46+ /// <summary>Adds a source file to the output.</summary>
47+ /// <param name="context">The post-initialization context.</param>
48+ /// <param name="name">The name of the source file.</param>
49+ /// <param name="source">The source code for the file.</param>
50+ private static void AddSource ( IncrementalGeneratorPostInitializationContext context , string name , string ? source )
6051 {
61- var declarations = new Stack < Declaration > ( ) ;
62- BuildContainingSymbolHierarchy ( symbol , in declarations ) ;
63- return declarations . ToEquatableReadOnlyList ( ) ;
52+ if ( source ? . Length > 0 )
53+ context . AddSource ( $ "{ name } .g.cs", SourceText . From ( source , Encoding . UTF8 ) ) ;
6454 }
6555
66- private static void BuildContainingSymbolHierarchy ( ISymbol symbol , in Stack < Declaration > declarations )
56+ /// <summary>Determines whether a syntax node should be included based on the filter settings.</summary>
57+ /// <param name="syntaxNode">The syntax node to filter.</param>
58+ /// <param name="filterType">The filter configuration.</param>
59+ /// <returns><see langword="true" /> if the syntax node should be included, otherwise <see langword="false" />.</returns>
60+ private static bool Filter ( SyntaxNode syntaxNode , FilterType filterType )
6761 {
68- if ( symbol . ContainingType is not null )
69- BuildTypeHierarchy ( symbol . ContainingType , in declarations ) ;
70- else if ( symbol . ContainingNamespace is not null )
71- BuildNamespaceHierarchy ( symbol . ContainingNamespace , declarations ) ;
62+ var filter = filterType == FilterType . None ? FilterType . All : filterType ;
63+
64+ if ( filter . HasFlag ( FilterType . Interface ) && syntaxNode is InterfaceDeclarationSyntax )
65+ return true ;
66+ if ( filter . HasFlag ( FilterType . Class ) && syntaxNode is ClassDeclarationSyntax )
67+ return true ;
68+ if ( filter . HasFlag ( FilterType . Record ) && syntaxNode is RecordDeclarationSyntax recordDeclaration && recordDeclaration . Kind ( ) == SyntaxKind . ClassDeclaration )
69+ return true ;
70+ if ( filter . HasFlag ( FilterType . Struct ) && syntaxNode is StructDeclarationSyntax )
71+ return true ;
72+ if ( filter . HasFlag ( FilterType . RecordStruct ) && syntaxNode is RecordDeclarationSyntax recordStructDeclaration && recordStructDeclaration . Kind ( ) == SyntaxKind . StructDeclaration )
73+ return true ;
74+ if ( filter . HasFlag ( FilterType . Method ) && syntaxNode is MethodDeclarationSyntax )
75+ return true ;
76+
77+ return false ;
7278 }
7379
74- private static void BuildTypeHierarchy ( INamedTypeSymbol symbol , in Stack < Declaration > declarations )
80+ /// <summary>Transforms a generator attribute syntax context into a symbol for source generation.</summary>
81+ /// <param name="context">The generator attribute syntax context.</param>
82+ /// <returns>The transformed symbol.</returns>
83+ private static Symbol Transform ( GeneratorAttributeSyntaxContext context )
7584 {
76- DeclarationType ? declarationType = null ;
77-
78-
79- if ( symbol . IsReferenceType )
80- {
81- if ( symbol . TypeKind == TypeKind . Interface )
82- declarationType = DeclarationType . Interface ;
83- else if ( symbol . IsRecord )
84- declarationType = DeclarationType . Record ;
85- else
86- declarationType = DeclarationType . Class ;
87- }
88- else if ( symbol . IsValueType )
85+ var targetSymbol = context . TargetSymbol ;
86+ if ( targetSymbol is not INamedTypeSymbol && targetSymbol is not IMethodSymbol )
87+ throw new InvalidOperationException ( $ "{ nameof ( AttributeIncrementalGeneratorBase ) } unexpectedly tried to transform a { nameof ( context . TargetSymbol ) } that was not an { nameof ( INamedTypeSymbol ) } or a { nameof ( IMethodSymbol ) } .") ;
88+
89+ var markerAttribute = context . GetMarkerAttribute ( ) ;
90+ var containingDeclarations = targetSymbol . GetContainingDeclarations ( ) ;
91+ var symbolType = targetSymbol . GetSymbolType ( ) ;
92+ var symbolName = targetSymbol . Name ;
93+ EquatableReadOnlyList < string > genericTypeParameters ;
94+ EquatableReadOnlyList < ConstructorParameter > constructorParameters ;
95+ string returnType ;
96+ switch ( targetSymbol )
8997 {
90- if ( symbol . IsRecord )
91- declarationType = DeclarationType . RecordStruct ;
92- else
93- declarationType = DeclarationType . Struct ;
98+ case INamedTypeSymbol namedTypeSymbol :
99+ genericTypeParameters = namedTypeSymbol . GetGenericTypeParameters ( ) ;
100+ constructorParameters = EquatableReadOnlyList < ConstructorParameter > . Empty ;
101+ returnType = "" ;
102+ break ;
103+ case IMethodSymbol methodSymbol :
104+ genericTypeParameters = methodSymbol . GetGenericTypeParameters ( ) ;
105+ constructorParameters = methodSymbol . GetConstructorParameters ( ) ;
106+ returnType = methodSymbol . ReturnType . ToDisplayString ( ) ;
107+ break ;
108+ default :
109+ genericTypeParameters = EquatableReadOnlyList < string > . Empty ;
110+ constructorParameters = EquatableReadOnlyList < ConstructorParameter > . Empty ;
111+ returnType = "" ;
112+ break ;
94113 }
95114
96- if ( declarationType is null )
97- return ;
115+ var symbol = new Symbol ( markerAttribute , containingDeclarations , symbolType , symbolName , genericTypeParameters , constructorParameters , returnType ) ;
98116
99- var genericParameters = EquatableReadOnlyList < string > . Empty ;
100- if ( symbol . IsGenericType )
101- {
102- var typeParameters = new List < string > ( ) ;
103- foreach ( var typeParameter in symbol . TypeParameters )
104- typeParameters . Add ( typeParameter . Name ) ;
105- genericParameters = new EquatableReadOnlyList < string > ( typeParameters ) ;
106- }
107-
108- var typeDeclaration = new Declaration ( declarationType . Value , symbol . Name , genericParameters ) ;
109- declarations . Push ( typeDeclaration ) ;
110-
111- BuildContainingSymbolHierarchy ( symbol , declarations ) ;
117+ return symbol ;
112118 }
113119
114- private static void BuildNamespaceHierarchy ( INamespaceSymbol symbol , in Stack < Declaration > declarations )
120+ /// <summary>Generates source code for a given symbol.</summary>
121+ /// <param name="context">The source production context.</param>
122+ /// <param name="symbol">The symbol to generate source for.</param>
123+ /// <param name="generate">A function that generates the source code for a symbol.</param>
124+ private static void GenerateSourceForSymbol ( SourceProductionContext context , Symbol symbol , Func < Symbol , string > generate )
115125 {
116- if ( ! symbol . IsGlobalNamespace )
117- {
118- var namespaceDeclaration = new Declaration ( DeclarationType . Namespace , symbol . Name , EquatableReadOnlyList < string > . Empty ) ;
119- declarations . Push ( namespaceDeclaration ) ;
120- }
121-
122- if ( symbol . ContainingNamespace is not null && ! symbol . ContainingNamespace . IsGlobalNamespace )
123- BuildNamespaceHierarchy ( symbol . ContainingNamespace , declarations ) ;
126+ var sourceText = generate ( symbol ) ;
127+ context . AddSource ( $ "{ symbol . FullyQualifiedName } .g.cs", sourceText ) ;
124128 }
125- }
129+ }
0 commit comments