1- using System . Collections . Immutable ;
21using System . Linq ;
32using System . Text ;
43using Microsoft . CodeAnalysis ;
5- using Microsoft . CodeAnalysis . CSharp ;
64using Microsoft . CodeAnalysis . CSharp . Syntax ;
5+ using Microsoft . CodeAnalysis . Text ;
76
87namespace Bunit . Web . Stubs ;
98
@@ -13,61 +12,49 @@ namespace Bunit.Web.Stubs;
1312[ Generator ]
1413public class StubGenerator : IIncrementalGenerator
1514{
15+ private const string AttributeFullQualifiedName = "Bunit.StubAttribute" ;
16+
1617 /// <inheritdoc/>
1718 public void Initialize ( IncrementalGeneratorInitializationContext context )
1819 {
19- var classDeclarations = context . SyntaxProvider
20- . CreateSyntaxProvider (
21- predicate : static ( s , _ ) => s is ClassDeclarationSyntax { AttributeLists . Count : > 0 } ,
20+ context . RegisterPostInitializationOutput ( ctx => ctx . AddSource (
21+ "StubAttribute.g.cs" ,
22+ SourceText . From ( StubAttributeGenerator . StubAttribute , Encoding . UTF8 ) ) ) ;
23+
24+ var classesToStub = context . SyntaxProvider
25+ . ForAttributeWithMetadataName (
26+ AttributeFullQualifiedName ,
27+ predicate : static ( s , _ ) => s is ClassDeclarationSyntax ,
2228 transform : static ( ctx , _ ) => GetStubClassInfo ( ctx ) )
2329 . Where ( static m => m is not null ) ;
2430
25- var compilationAndClasses = context . CompilationProvider . Combine ( classDeclarations . Collect ( ) ) ;
2631
27- context . RegisterSourceOutput ( compilationAndClasses , static ( spc , source ) => Execute ( source . Item2 , spc ) ) ;
32+ context . RegisterSourceOutput (
33+ classesToStub ,
34+ static ( spc , source ) => Execute ( source , spc ) ) ;
2835 }
2936
30- private static StubClassInfo GetStubClassInfo ( GeneratorSyntaxContext context )
37+ private static StubClassInfo GetStubClassInfo ( GeneratorAttributeSyntaxContext context )
3138 {
32- var classDeclarationSyntax = ( ClassDeclarationSyntax ) context . Node ;
33-
34- // Check if the class is partial
35- if ( ! classDeclarationSyntax . Modifiers . Any ( SyntaxKind . PartialKeyword ) )
36- {
37- return null ;
38- }
39-
40- // Find the StubAttribute on the class
41- foreach ( var attribute in classDeclarationSyntax . AttributeLists . SelectMany ( a => a . Attributes ) )
39+ foreach ( var attribute in context . TargetSymbol . GetAttributes ( ) )
4240 {
43- var attributeSymbol =
44- ModelExtensions . GetSymbolInfo ( context . SemanticModel , attribute ) . Symbol as IMethodSymbol ;
45- if ( attributeSymbol is null || ! IsStubAttribute ( attributeSymbol ) )
41+ if ( context . TargetSymbol is not ITypeSymbol stubbedType ||
42+ ! ImplementsInterface ( stubbedType , "Microsoft.AspNetCore.Components.IComponent" ) )
4643 {
4744 continue ;
4845 }
4946
50- if ( attribute . ArgumentList ? . Arguments is not [ { Expression : TypeOfExpressionSyntax typeOfExpression } ] )
51- {
52- continue ;
53- }
54-
55- var typeSymbol = ModelExtensions . GetTypeInfo ( context . SemanticModel , typeOfExpression . Type ) . Type ;
56- if ( typeSymbol == null )
57- {
58- continue ;
59- }
47+ var namespaceName = stubbedType . ContainingNamespace . ToDisplayString ( ) ;
48+ var className = context . TargetSymbol . Name ;
6049
61- if ( ! ImplementsInterface ( typeSymbol , "Microsoft.AspNetCore.Components.IComponent" ) )
50+ // TODO: Check for the name not the first
51+ var originalTypeToStub = attribute . ConstructorArguments . FirstOrDefault ( ) . Value ;
52+ if ( originalTypeToStub is not ITypeSymbol originalType )
6253 {
6354 continue ;
6455 }
6556
66- var namespaceSyntax = classDeclarationSyntax . Parent as NamespaceDeclarationSyntax ;
67- var namespaceName = namespaceSyntax ? . Name . ToString ( ) ;
68- var className = classDeclarationSyntax . Identifier . ValueText ;
69-
70- return new StubClassInfo { ClassName = className , Namespace = namespaceName , TargetType = typeSymbol } ;
57+ return new StubClassInfo { ClassName = className , Namespace = namespaceName , TargetType = originalType } ;
7158 }
7259
7360 return null ;
@@ -78,51 +65,49 @@ static bool ImplementsInterface(ITypeSymbol typeSymbol, string interfaceName)
7865 }
7966 }
8067
81- private static bool IsStubAttribute ( ISymbol attributeSymbol ) => attributeSymbol . ContainingType . ToDisplayString ( ) == "Bunit.StubAttribute" ;
82-
83- private static void Execute ( ImmutableArray < StubClassInfo > classes , SourceProductionContext context )
68+ private static void Execute ( StubClassInfo classInfo , SourceProductionContext context )
8469 {
85- context . AddSource ( "StubAttribute.g.cs" , StubAttributeGenerator . StubAttribute ) ;
86-
87- if ( classes . IsDefaultOrEmpty )
70+ var hasSomethingToStub = false ;
71+ var targetTypeSymbol = ( INamedTypeSymbol ) classInfo ! . TargetType ;
72+ var sourceBuilder = new StringBuilder ( ) ;
73+
74+ // TODO: Shall we dictate file-scoped namespaces here?
75+ sourceBuilder . AppendLine ( $ "namespace { classInfo . Namespace } ;") ;
76+
77+ // TODO: If the class is a nested one, that approach does not work
78+ sourceBuilder . AppendLine ( $ "public partial class { classInfo . ClassName } ") ;
79+ sourceBuilder . Append ( "{" ) ;
80+
81+ foreach ( var member in targetTypeSymbol
82+ . GetMembers ( )
83+ . OfType < IPropertySymbol > ( )
84+ . Where ( p => p . GetAttributes ( )
85+ . Any ( attr =>
86+ attr . AttributeClass ? . ToDisplayString ( ) ==
87+ "Microsoft.AspNetCore.Components.ParameterAttribute" ||
88+ attr . AttributeClass ? . ToDisplayString ( ) ==
89+ "Microsoft.AspNetCore.Components.CascadingParameterAttribute" ) ) )
8890 {
89- return ;
91+ sourceBuilder . AppendLine ( ) ;
92+
93+ hasSomethingToStub = true ;
94+ var propertyType = member . Type . ToDisplayString ( ) ;
95+ var propertyName = member . Name ;
96+
97+ var isParameterAttribute = member . GetAttributes ( ) . Any ( attr =>
98+ attr . AttributeClass ? . ToDisplayString ( ) == "Microsoft.AspNetCore.Components.ParameterAttribute" ) ;
99+ var attributeLine = isParameterAttribute
100+ ? "\t [global::Microsoft.AspNetCore.Components.Parameter]"
101+ : "\t [global::Microsoft.AspNetCore.Components.CascadingParameter]" ;
102+
103+ sourceBuilder . AppendLine ( attributeLine ) ;
104+ sourceBuilder . AppendLine ( $ "\t public { propertyType } { propertyName } {{ get; set; }}") ;
90105 }
91106
92- foreach ( var classInfo in classes . Where ( t => t ? . TargetType is INamedTypeSymbol ) )
93- {
94- var targetTypeSymbol = ( INamedTypeSymbol ) classInfo ! . TargetType ;
95- var sourceBuilder = new StringBuilder ( ) ;
96-
97- sourceBuilder . AppendLine ( $ "namespace { classInfo . Namespace } ;") ;
98- sourceBuilder . AppendLine ( $ "public partial class { classInfo . ClassName } ") ;
99- sourceBuilder . AppendLine ( "{" ) ;
100-
101- foreach ( var member in targetTypeSymbol
102- . GetMembers ( )
103- . OfType < IPropertySymbol > ( )
104- . Where ( p => p . GetAttributes ( )
105- . Any ( attr =>
106- attr . AttributeClass ? . ToDisplayString ( ) ==
107- "Microsoft.AspNetCore.Components.ParameterAttribute" ||
108- attr . AttributeClass ? . ToDisplayString ( ) ==
109- "Microsoft.AspNetCore.Components.CascadingParameterAttribute" ) ) )
110- {
111- var propertyType = $ "global::{ member . Type . ToDisplayString ( ) } ";
112- var propertyName = member . Name ;
113-
114- var isParameterAttribute = member . GetAttributes ( ) . Any ( attr =>
115- attr . AttributeClass ? . ToDisplayString ( ) == "Microsoft.AspNetCore.Components.ParameterAttribute" ) ;
116- var attributeLine = isParameterAttribute
117- ? "\t [global::Microsoft.AspNetCore.Components.Parameter]"
118- : "\t [global::Microsoft.AspNetCore.Components.CascadingParameter]" ;
119-
120- sourceBuilder . AppendLine ( attributeLine ) ;
121- sourceBuilder . AppendLine ( $ "\t public { propertyType } { propertyName } {{ get; set; }}") ;
122- sourceBuilder . AppendLine ( ) ;
123- }
107+ sourceBuilder . AppendLine ( "}" ) ;
124108
125- sourceBuilder . AppendLine ( "}" ) ;
109+ if ( hasSomethingToStub )
110+ {
126111 context . AddSource ( $ "{ classInfo . ClassName } Stub.g.cs", sourceBuilder . ToString ( ) ) ;
127112 }
128113 }
0 commit comments