@@ -20,12 +20,28 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
2020 )
2121 . Where ( static info => ! info . Equals ( default ( BindableFieldInfo ) ) ) ;
2222
23+ // Group fields by containing class
24+ var grouped = fieldsWithAttribute . Collect ( )
25+ . Select ( ( list , _ ) => list
26+ . GroupBy ( info => info . ContainingClass , SymbolEqualityComparer . Default )
27+ . Select ( g => ( ClassSymbol : g . Key , Fields : g . ToList ( ) ) )
28+ . ToList ( )
29+ ) ;
30+
2331 var compilationProvider = context . CompilationProvider ;
2432
25- context . RegisterSourceOutput ( fieldsWithAttribute . Combine ( compilationProvider ) , ( spc , tuple ) =>
33+ context . RegisterSourceOutput ( grouped . Combine ( compilationProvider ) , ( spc , tuple ) =>
2634 {
27- var ( info , compilation ) = ( tuple . Left , tuple . Right ) ;
28- GenerateBindableProperty ( spc , info , compilation ) ;
35+ var ( classGroups , compilation ) = ( tuple . Left , tuple . Right ) ;
36+ foreach ( var group in classGroups )
37+ {
38+ var classSymbol = group . ClassSymbol as INamedTypeSymbol ;
39+ var fields = group . Fields ;
40+ if ( classSymbol != null )
41+ {
42+ GenerateBindablePropertyClass ( spc , classSymbol , fields , compilation ) ;
43+ }
44+ }
2945 } ) ;
3046 }
3147
@@ -58,41 +74,13 @@ private static BindableFieldInfo GetBindableField(GeneratorSyntaxContext context
5874 return default ( BindableFieldInfo ) ;
5975 }
6076
61- private static void GenerateBindableProperty ( SourceProductionContext context , BindableFieldInfo info , Compilation compilation )
77+ // New method to generate all properties and shared members for a class
78+ private static void GenerateBindablePropertyClass (
79+ SourceProductionContext context ,
80+ INamedTypeSymbol classSymbol ,
81+ List < BindableFieldInfo > fields ,
82+ Compilation compilation )
6283 {
63- var classSymbol = info . ContainingClass ;
64- var fieldSymbol = info . FieldSymbol ;
65- var fieldName = fieldSymbol . Name ;
66- var propertyName = PropertyGeneratorHelpers . ToPropertyName ( fieldName ) ;
67- var typeName = fieldSymbol . Type . ToDisplayString ( ) ;
68-
69- // Rule 1: Class must be partial
70- if ( ! PropertyGeneratorHelpers . IsPartial ( classSymbol ) )
71- {
72- PropertyGeneratorHelpers . ReportDiagnostic ( context , info . FieldDeclaration . GetLocation ( ) , $ "Class '{ classSymbol . Name } ' must be partial to use [BindableProperty].") ;
73- return ;
74- }
75-
76- // Rule 2: Field must start with "_" followed by a letter, or a lowercase letter
77- if ( ! PropertyGeneratorHelpers . IsValidFieldName ( fieldName ) )
78- {
79- PropertyGeneratorHelpers . ReportDiagnostic ( context , info . FieldDeclaration . GetLocation ( ) , $ "Field '{ fieldName } ' must start with '_' followed by a letter, or a lowercase letter to use [BindableProperty].") ;
80- return ;
81- }
82-
83- // Rule 3: Property must not already exist
84- if ( PropertyGeneratorHelpers . PropertyExists ( classSymbol , propertyName ) )
85- {
86- PropertyGeneratorHelpers . ReportDiagnostic ( context , info . FieldDeclaration . GetLocation ( ) , $ "Property '{ propertyName } ' already exists in '{ classSymbol . Name } '.") ;
87- return ;
88- }
89-
90- // Attribute arguments
91- var threadSafe = info . AttributeData . ConstructorArguments . Length > 0 && ( bool ) info . AttributeData . ConstructorArguments [ 0 ] . Value ! ;
92- var notify = info . AttributeData . ConstructorArguments . Length > 1 && ( bool ) info . AttributeData . ConstructorArguments [ 1 ] . Value ! ;
93- var readOnly = info . AttributeData . ConstructorArguments . Length > 2 && ( bool ) info . AttributeData . ConstructorArguments [ 2 ] . Value ! ;
94-
95- // Check for INotifyPropertyChanged, IBindableObject, ThreadObject
9684 var implementsINotify = ImplementsInterface ( classSymbol , "System.ComponentModel.INotifyPropertyChanged" ) ;
9785 var implementsIBindable = ImplementsInterface ( classSymbol , "ThunderDesign.Net.Threading.Interfaces.IBindableObject" ) ;
9886 var inheritsThreadObject = PropertyGeneratorHelpers . InheritsFrom ( classSymbol , "ThunderDesign.Net.Threading.Objects.ThreadObject" ) ;
@@ -105,17 +93,13 @@ private static void GenerateBindableProperty(SourceProductionContext context, Bi
10593 var ns = classSymbol . ContainingNamespace . IsGlobalNamespace ? null : classSymbol . ContainingNamespace . ToDisplayString ( ) ;
10694
10795 if ( ! string . IsNullOrEmpty ( ns ) )
108- {
10996 source . AppendLine ( $ "namespace { ns } {{") ;
110- }
11197
11298 source . AppendLine ( "using ThunderDesign.Net.Threading.Extentions;" ) ;
11399 source . AppendLine ( "using ThunderDesign.Net.Threading.Interfaces;" ) ;
114100 source . AppendLine ( "using ThunderDesign.Net.Threading.Objects;" ) ;
115101
116- source . AppendLine ( $ "partial class { classSymbol . Name } ") ;
117-
118- // Add interface if needed
102+ source . Append ( $ "partial class { classSymbol . Name } ") ;
119103 var interfaces = new List < string > ( ) ;
120104 if ( ! implementsIBindable )
121105 interfaces . Add ( "IBindableObject" ) ;
@@ -126,20 +110,7 @@ private static void GenerateBindableProperty(SourceProductionContext context, Bi
126110
127111 // Add event if needed
128112 if ( ! implementsINotify && ! PropertyGeneratorHelpers . EventExists ( classSymbol , "PropertyChanged" , propertyChangedEventType ) )
129- {
130- if ( PropertyGeneratorHelpers . EventExists ( classSymbol , "PropertyChanged" ) )
131- {
132- PropertyGeneratorHelpers . ReportDiagnostic (
133- context ,
134- info . FieldDeclaration . GetLocation ( ) ,
135- $ "Event PropertyChanged already exists in '{ classSymbol . Name } ' with a different type. Expected: System.ComponentModel.PropertyChangedEventHandler."
136- ) ;
137- }
138- else
139- {
140- source . AppendLine ( " public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;" ) ;
141- }
142- }
113+ source . AppendLine ( " public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;" ) ;
143114
144115 // Add _Locker if needed
145116 if ( ! inheritsThreadObject && ! PropertyGeneratorHelpers . FieldExists ( classSymbol , "_Locker" ) )
@@ -159,33 +130,82 @@ public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMem
159130 }" ) ;
160131 }
161132
162- // Add property
163- var lockerArg = threadSafe ? "_Locker" : "null" ;
164- var notifyArg = notify ? "true" : "false" ;
165- if ( readOnly )
133+ // Generate all properties
134+ foreach ( var info in fields )
166135 {
167- source . AppendLine ( $@ "
136+ var fieldSymbol = info . FieldSymbol ;
137+ var fieldName = fieldSymbol . Name ;
138+ var propertyName = PropertyGeneratorHelpers . ToPropertyName ( fieldName ) ;
139+ var typeName = fieldSymbol . Type . ToDisplayString ( ) ;
140+
141+ var readOnly = info . AttributeData . ConstructorArguments . Length > 0 && ( bool ) info . AttributeData . ConstructorArguments [ 0 ] . Value ! ;
142+ var threadSafe = info . AttributeData . ConstructorArguments . Length > 1 && ( bool ) info . AttributeData . ConstructorArguments [ 1 ] . Value ! ;
143+ var notify = info . AttributeData . ConstructorArguments . Length > 2 && ( bool ) info . AttributeData . ConstructorArguments [ 2 ] . Value ! ;
144+ string [ ] alsoNotify = null ;
145+ if ( info . AttributeData . ConstructorArguments . Length > 3 )
146+ {
147+ var arg = info . AttributeData . ConstructorArguments [ 3 ] ;
148+ if ( arg . Kind == TypedConstantKind . Array && arg . Values != null )
149+ {
150+ alsoNotify = arg . Values
151+ . Select ( tc => tc . Value as string )
152+ . Where ( s => ! string . IsNullOrEmpty ( s ) )
153+ . ToArray ( ) ;
154+ }
155+ }
156+ if ( alsoNotify == null )
157+ alsoNotify = new string [ 0 ] ;
158+
159+ var lockerArg = threadSafe ? "_Locker" : "null" ;
160+ var notifyArg = notify ? "true" : "false" ;
161+ if ( readOnly )
162+ {
163+ source . AppendLine ( $@ "
168164 public { typeName } { propertyName }
169165 {{
170166 get {{ return this.GetProperty(ref { fieldName } , { lockerArg } ); }}
171167 }}" ) ;
172- }
173- else
174- {
175- source . AppendLine ( $@ "
168+ }
169+ else
170+ {
171+ string setAccessor ;
172+ if ( alsoNotify . Length > 0 )
173+ {
174+ var notifyCalls = new StringBuilder ( ) ;
175+ foreach ( var prop in alsoNotify )
176+ {
177+ if ( ! string . IsNullOrEmpty ( prop ) )
178+ notifyCalls . AppendLine ( $ " this.OnPropertyChanged(\" { prop } \" );") ;
179+ }
180+ setAccessor = $@ "
181+ set
182+ {{
183+ if (this.SetProperty(ref { fieldName } , value, { lockerArg } , { notifyArg } ))
184+ {{
185+ { notifyCalls . ToString ( ) . TrimEnd ( ) }
186+ }}
187+ }}" ;
188+ }
189+ else
190+ {
191+ setAccessor = $ "set {{ this.SetProperty(ref { fieldName } , value, { lockerArg } , { notifyArg } ); }}";
192+ }
193+
194+ source . AppendLine ( $@ "
176195 public { typeName } { propertyName }
177196 {{
178197 get {{ return this.GetProperty(ref { fieldName } , { lockerArg } ); }}
179- set {{ this.SetProperty(ref { fieldName } , value, { lockerArg } , { notifyArg } ); } }
198+ { setAccessor }
180199 }}" ) ;
200+ }
181201 }
182202
183203 source . AppendLine ( "}" ) ;
184204
185205 if ( ! string . IsNullOrEmpty ( ns ) )
186206 source . AppendLine ( "}" ) ;
187207
188- context . AddSource ( $ "{ classSymbol . Name } _ { propertyName } _BindableProperty .g.cs", SourceText . From ( source . ToString ( ) , Encoding . UTF8 ) ) ;
208+ context . AddSource ( $ "{ classSymbol . Name } _BindableProperties .g.cs", SourceText . From ( source . ToString ( ) , Encoding . UTF8 ) ) ;
189209 }
190210
191211 private static bool ImplementsInterface ( INamedTypeSymbol type , string interfaceName )
0 commit comments