1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Linq ;
4+ using System . Text ;
5+ using Microsoft . CodeAnalysis ;
6+ using Microsoft . CodeAnalysis . CSharp ;
7+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
8+ using Microsoft . CodeAnalysis . Text ;
9+
10+ namespace ThunderDesign . Net_PCL . SourceGenerators
11+ {
12+ [ Generator ]
13+ public class BindablePropertyGenerator : IIncrementalGenerator
14+ {
15+ public void Initialize ( IncrementalGeneratorInitializationContext context )
16+ {
17+ var fieldsWithAttribute = context . SyntaxProvider
18+ . CreateSyntaxProvider (
19+ predicate : static ( node , _ ) => node is FieldDeclarationSyntax fds && fds . AttributeLists . Count > 0 ,
20+ transform : static ( ctx , _ ) => GetBindableField ( ctx )
21+ )
22+ . Where ( static info => ! info . Equals ( default ( BindableFieldInfo ) ) ) ;
23+
24+ context . RegisterSourceOutput ( fieldsWithAttribute , ( spc , info ) =>
25+ {
26+ GenerateBindableProperty ( spc , info ) ;
27+ } ) ;
28+ }
29+
30+ private static BindableFieldInfo GetBindableField ( GeneratorSyntaxContext context )
31+ {
32+ var fieldDecl = ( FieldDeclarationSyntax ) context . Node ;
33+ var semanticModel = context . SemanticModel ;
34+
35+ foreach ( var variable in fieldDecl . Declaration . Variables )
36+ {
37+ var symbol = semanticModel . GetDeclaredSymbol ( variable ) ;
38+ if ( symbol is IFieldSymbol fieldSymbol )
39+ {
40+ foreach ( var attr in fieldSymbol . GetAttributes ( ) )
41+ {
42+ if ( attr . AttributeClass ? . Name == "BindablePropertyAttribute" )
43+ {
44+ var containingClass = fieldSymbol . ContainingType ;
45+ return new BindableFieldInfo
46+ {
47+ FieldSymbol = fieldSymbol ,
48+ ContainingClass = containingClass ,
49+ AttributeData = attr ,
50+ FieldDeclaration = fieldDecl
51+ } ;
52+ }
53+ }
54+ }
55+ }
56+ return default ( BindableFieldInfo ) ;
57+ }
58+
59+ private static void GenerateBindableProperty ( SourceProductionContext context , BindableFieldInfo info )
60+ {
61+ var classSymbol = info . ContainingClass ;
62+ var fieldSymbol = info . FieldSymbol ;
63+ var fieldName = fieldSymbol . Name ;
64+ var propertyName = PropertyGeneratorHelpers . ToPropertyName ( fieldName ) ;
65+ var typeName = fieldSymbol . Type . ToDisplayString ( ) ;
66+
67+ // Rule 1: Class must be partial
68+ if ( ! PropertyGeneratorHelpers . IsPartial ( classSymbol ) )
69+ {
70+ PropertyGeneratorHelpers . ReportDiagnostic ( context , info . FieldDeclaration . GetLocation ( ) , $ "Class '{ classSymbol . Name } ' must be partial to use [BindableProperty].") ;
71+ return ;
72+ }
73+
74+ // Rule 2: Field must start with "_" or lowercase
75+ if ( ! PropertyGeneratorHelpers . IsValidFieldName ( fieldName ) )
76+ {
77+ PropertyGeneratorHelpers . ReportDiagnostic ( context , info . FieldDeclaration . GetLocation ( ) , $ "Field '{ fieldName } ' must start with '_' or a lowercase letter to use [BindableProperty].") ;
78+ return ;
79+ }
80+
81+ // Rule 3: Property must not already exist
82+ if ( PropertyGeneratorHelpers . PropertyExists ( classSymbol , propertyName ) )
83+ {
84+ PropertyGeneratorHelpers . ReportDiagnostic ( context , info . FieldDeclaration . GetLocation ( ) , $ "Property '{ propertyName } ' already exists in '{ classSymbol . Name } '.") ;
85+ return ;
86+ }
87+
88+ // Attribute arguments
89+ var threadSafe = info . AttributeData . ConstructorArguments . Length > 0 && ( bool ) info . AttributeData . ConstructorArguments [ 0 ] . Value ! ;
90+ var notify = info . AttributeData . ConstructorArguments . Length > 1 && ( bool ) info . AttributeData . ConstructorArguments [ 1 ] . Value ! ;
91+ var readOnly = info . AttributeData . ConstructorArguments . Length > 2 && ( bool ) info . AttributeData . ConstructorArguments [ 2 ] . Value ! ;
92+
93+ // Check for INotifyPropertyChanged, IBindableObject, ThreadObject
94+ var implementsINotify = ImplementsInterface ( classSymbol , "System.ComponentModel.INotifyPropertyChanged" ) ;
95+ var implementsIBindable = ImplementsInterface ( classSymbol , "ThunderDesign.Net.Threading.Interfaces.IBindableObject" ) ;
96+
97+ // Check for ThreadObject
98+ var inheritsThreadObject = PropertyGeneratorHelpers . InheritsFrom ( classSymbol , "ThunderDesign.Net.Threading.Objects.ThreadObject" ) ;
99+
100+ var source = new StringBuilder ( ) ;
101+ var ns = classSymbol . ContainingNamespace . IsGlobalNamespace ? null : classSymbol . ContainingNamespace . ToDisplayString ( ) ;
102+
103+ if ( ! string . IsNullOrEmpty ( ns ) )
104+ {
105+ source . AppendLine ( $ "namespace { ns } {{") ;
106+ }
107+
108+ source . AppendLine ( "using ThunderDesign.Net.Threading.Extentions;" ) ;
109+ source . AppendLine ( "using ThunderDesign.Net.Threading.Interfaces;" ) ;
110+ source . AppendLine ( "using ThunderDesign.Net.Threading.Objects;" ) ;
111+
112+ source . AppendLine ( $ "partial class { classSymbol . Name } ") ;
113+
114+ // Add interface if needed
115+ var interfaces = new List < string > ( ) ;
116+ if ( ! implementsIBindable )
117+ interfaces . Add ( "IBindableObject" ) ;
118+ if ( interfaces . Count > 0 )
119+ source . Append ( " : " + string . Join ( ", " , interfaces ) ) ;
120+ source . AppendLine ( ) ;
121+ source . AppendLine ( "{" ) ;
122+
123+ // Add event if needed
124+ if ( ! implementsINotify )
125+ source . AppendLine ( " public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;" ) ;
126+
127+ // Add _Locker if needed
128+ if ( ! inheritsThreadObject )
129+ source . AppendLine ( " protected readonly object _Locker = new object();" ) ;
130+
131+ // Add OnPropertyChanged if needed
132+ if ( ! implementsIBindable )
133+ {
134+ source . AppendLine ( @"
135+ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = """")
136+ {
137+ this.NotifyPropertyChanged(PropertyChanged, propertyName);
138+ }" ) ;
139+ }
140+
141+ // Add property
142+ var lockerArg = threadSafe ? "_Locker" : "null" ;
143+ var notifyArg = notify ? "true" : "false" ;
144+ if ( readOnly )
145+ {
146+ source . AppendLine ( $@ "
147+ public { typeName } { propertyName }
148+ {{
149+ get {{ return this.GetProperty(ref { fieldName } , { lockerArg } ); }}
150+ }}" ) ;
151+ }
152+ else
153+ {
154+ source . AppendLine ( $@ "
155+ public { typeName } { propertyName }
156+ {{
157+ get {{ return this.GetProperty(ref { fieldName } , { lockerArg } ); }}
158+ set {{ this.SetProperty(ref { fieldName } , value, { lockerArg } , { notifyArg } ); }}
159+ }}" ) ;
160+ }
161+
162+ source . AppendLine ( "}" ) ;
163+
164+ if ( ! string . IsNullOrEmpty ( ns ) )
165+ source . AppendLine ( "}" ) ;
166+
167+ context . AddSource ( $ "{ classSymbol . Name } _{ propertyName } _BindableProperty.g.cs", SourceText . From ( source . ToString ( ) , Encoding . UTF8 ) ) ;
168+ }
169+
170+ private static bool ImplementsInterface ( INamedTypeSymbol type , string interfaceName )
171+ {
172+ return type . AllInterfaces . Any ( i => i . ToDisplayString ( ) == interfaceName ) ;
173+ }
174+
175+ private struct BindableFieldInfo
176+ {
177+ public IFieldSymbol FieldSymbol { get ; set ; }
178+ public INamedTypeSymbol ContainingClass { get ; set ; }
179+ public AttributeData AttributeData { get ; set ; }
180+ public FieldDeclarationSyntax FieldDeclaration { get ; set ; }
181+ }
182+ }
183+ }
0 commit comments