1+ using System . Collections . Immutable ;
2+ using System . Text ;
3+ using Fluid ;
4+ using Microsoft . CodeAnalysis ;
5+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
6+ using Microsoft . CodeAnalysis . Text ;
7+
8+ namespace Jint . SourceGenerator
9+ {
10+ [ Generator ]
11+ public class ObjectGenerator : IIncrementalGenerator
12+ {
13+ private IFluidTemplate _objectTemplate = null ! ;
14+
15+ private const string JsObjectAttribute = "Jint.JsObjectAttribute" ;
16+ private const string JsFunctionAttribute = "Jint.JsFunctionAttribute" ;
17+
18+ public void Initialize ( IncrementalGeneratorInitializationContext context )
19+ {
20+ using var stream = new StreamReader ( typeof ( ObjectGenerator ) . Assembly . GetManifestResourceStream ( typeof ( ObjectGenerator ) , "Templates.JsObject.liquid" ) ! ) ;
21+ var template = stream . ReadToEnd ( ) ;
22+
23+ var parser = new FluidParser ( ) ;
24+ _objectTemplate = parser . Parse ( template ) ;
25+
26+ context . RegisterPostInitializationOutput ( ctx => ctx . AddSource (
27+ "Attributes.g.cs" , SourceText . From ( SourceGenerationHelper . Attributes , Encoding . UTF8 ) ) ) ;
28+
29+ IncrementalValuesProvider < ClassDeclarationSyntax > classDeclarations = context . SyntaxProvider
30+ . CreateSyntaxProvider (
31+ predicate : static ( s , _ ) => IsSyntaxTargetForGeneration ( s ) ,
32+ transform : static ( ctx , _ ) => GetSemanticTargetForGeneration ( ctx ) )
33+ . Where ( static m => m is not null ) ! ;
34+
35+ IncrementalValueProvider < ( Compilation , ImmutableArray < ClassDeclarationSyntax > ) > compilationAndClasses
36+ = context . CompilationProvider . Combine ( classDeclarations . Collect ( ) ) ;
37+
38+ context . RegisterSourceOutput ( compilationAndClasses ,
39+ ( spc , source ) => Execute ( source . Item1 , source . Item2 , spc ) ) ;
40+ }
41+
42+ private static bool IsSyntaxTargetForGeneration ( SyntaxNode node )
43+ => node is ClassDeclarationSyntax { AttributeLists . Count : > 0 } ;
44+
45+ private static ClassDeclarationSyntax ? GetSemanticTargetForGeneration ( GeneratorSyntaxContext context )
46+ {
47+ var classDeclarationSyntax = ( ClassDeclarationSyntax ) context . Node ;
48+
49+ // loop through all the attributes on the method
50+ foreach ( AttributeListSyntax attributeListSyntax in classDeclarationSyntax . AttributeLists )
51+ {
52+ foreach ( AttributeSyntax attributeSyntax in attributeListSyntax . Attributes )
53+ {
54+ var symbolInfo = context . SemanticModel . GetSymbolInfo ( attributeSyntax ) ;
55+ IMethodSymbol symbol ;
56+ if ( symbolInfo . Symbol is IMethodSymbol methodSymbol )
57+ {
58+ symbol = methodSymbol ;
59+ }
60+ else if ( symbolInfo . CandidateSymbols . Length > 0 && symbolInfo . CandidateSymbols [ 0 ] is IMethodSymbol fromCandidate )
61+ {
62+ symbol = fromCandidate ;
63+ }
64+ else
65+ {
66+ // weird, we couldn't get the symbol, ignore it
67+ continue ;
68+ }
69+
70+ INamedTypeSymbol attributeContainingTypeSymbol = symbol . ContainingType ;
71+ string fullName = attributeContainingTypeSymbol . ToDisplayString ( ) ;
72+
73+ // Is the attribute the [EnumExtensions] attribute?
74+ if ( fullName == JsObjectAttribute )
75+ {
76+ // return the enum
77+ return classDeclarationSyntax ;
78+ }
79+ }
80+ }
81+
82+ // we didn't find the attribute we were looking for
83+ return null ;
84+ }
85+
86+ private void Execute ( Compilation compilation , ImmutableArray < ClassDeclarationSyntax > classes , SourceProductionContext context )
87+ {
88+ if ( classes . IsDefaultOrEmpty )
89+ {
90+ // nothing to do yet
91+ return ;
92+ }
93+
94+ var distinctEnums = classes . Distinct ( ) ;
95+ var toGenerate = GetTypesToGenerate ( compilation , distinctEnums , context . CancellationToken ) ;
96+
97+ var templateOptions = new TemplateOptions { MemberAccessStrategy = UnsafeMemberAccessStrategy . Instance } ;
98+ foreach ( var target in toGenerate )
99+ {
100+ // generate the source code and add it to the output
101+ var templateContext = new TemplateContext ( target , templateOptions ) ;
102+ var result = _objectTemplate . Render ( templateContext ) ;
103+ context . AddSource ( target . Name + ".g.cs" , SourceText . From ( result , Encoding . UTF8 ) ) ;
104+ }
105+ }
106+
107+ private static List < ObjectDefinition > GetTypesToGenerate ( Compilation compilation , IEnumerable < ClassDeclarationSyntax > classes , CancellationToken ct )
108+ {
109+ var classesToGenerate = new List < ObjectDefinition > ( ) ;
110+ INamedTypeSymbol ? enumAttribute = compilation . GetTypeByMetadataName ( JsObjectAttribute ) ;
111+ if ( enumAttribute == null )
112+ {
113+ // nothing to do if this type isn't available
114+ return classesToGenerate ;
115+ }
116+
117+ foreach ( var classDeclarationSyntax in classes )
118+ {
119+ // stop if we're asked to
120+ ct . ThrowIfCancellationRequested ( ) ;
121+
122+ SemanticModel semanticModel = compilation . GetSemanticModel ( classDeclarationSyntax . SyntaxTree ) ;
123+ if ( semanticModel . GetDeclaredSymbol ( classDeclarationSyntax ) is not INamedTypeSymbol classSymbol )
124+ {
125+ // something went wrong
126+ continue ;
127+ }
128+
129+ var allMEmbers = classSymbol . GetMembers ( ) ;
130+ var functions = new List < FunctionDefinition > ( ) ;
131+ var properties = new List < PropertyDefinition > ( ) ;
132+
133+ foreach ( ISymbol member in allMEmbers )
134+ {
135+ if ( member is IMethodSymbol method )
136+ {
137+ foreach ( var attribute in method . GetAttributes ( ) )
138+ {
139+ if ( attribute . AttributeClass ? . Name == "JsFunctionAttribute" )
140+ {
141+ functions . Add ( new FunctionDefinition ( method , attribute ) ) ;
142+ break ;
143+ }
144+ }
145+ }
146+
147+ if ( member is IPropertySymbol property )
148+ {
149+ foreach ( var attribute in property . GetAttributes ( ) )
150+ {
151+ if ( attribute . AttributeClass ? . Name == "JsPropertyAttribute" )
152+ {
153+ properties . Add ( new PropertyDefinition ( property , attribute ) ) ;
154+ break ;
155+ }
156+ }
157+ }
158+ }
159+
160+ functions . Sort ( ) ;
161+ classesToGenerate . Add ( new ObjectDefinition ( classDeclarationSyntax , functions , properties ) ) ;
162+ }
163+
164+ return classesToGenerate ;
165+ }
166+ }
167+ }
0 commit comments