Skip to content

Commit 9147069

Browse files
Added more error checking
1 parent b9bb0b0 commit 9147069

File tree

3 files changed

+88
-32
lines changed

3 files changed

+88
-32
lines changed

src/ThunderDesign.Net-PCL.SourceGenerators/BindablePropertyGenerator.cs

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
51
using Microsoft.CodeAnalysis;
62
using Microsoft.CodeAnalysis.CSharp;
73
using Microsoft.CodeAnalysis.CSharp.Syntax;
84
using Microsoft.CodeAnalysis.Text;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
98

109
namespace ThunderDesign.Net_PCL.SourceGenerators
1110
{
@@ -21,9 +20,12 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
2120
)
2221
.Where(static info => !info.Equals(default(BindableFieldInfo)));
2322

24-
context.RegisterSourceOutput(fieldsWithAttribute, (spc, info) =>
23+
var compilationProvider = context.CompilationProvider;
24+
25+
context.RegisterSourceOutput(fieldsWithAttribute.Combine(compilationProvider), (spc, tuple) =>
2526
{
26-
GenerateBindableProperty(spc, info);
27+
var (info, compilation) = (tuple.Left, tuple.Right);
28+
GenerateBindableProperty(spc, info, compilation);
2729
});
2830
}
2931

@@ -56,7 +58,7 @@ private static BindableFieldInfo GetBindableField(GeneratorSyntaxContext context
5658
return default(BindableFieldInfo);
5759
}
5860

59-
private static void GenerateBindableProperty(SourceProductionContext context, BindableFieldInfo info)
61+
private static void GenerateBindableProperty(SourceProductionContext context, BindableFieldInfo info, Compilation compilation)
6062
{
6163
var classSymbol = info.ContainingClass;
6264
var fieldSymbol = info.FieldSymbol;
@@ -71,10 +73,10 @@ private static void GenerateBindableProperty(SourceProductionContext context, Bi
7173
return;
7274
}
7375

74-
// Rule 2: Field must start with "_" or lowercase
76+
// Rule 2: Field must start with "_" followed by a letter, or a lowercase letter
7577
if (!PropertyGeneratorHelpers.IsValidFieldName(fieldName))
7678
{
77-
PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), $"Field '{fieldName}' must start with '_' or a lowercase letter to use [BindableProperty].");
79+
PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), $"Field '{fieldName}' must start with '_' followed by a letter, or a lowercase letter to use [BindableProperty].");
7880
return;
7981
}
8082

@@ -93,10 +95,12 @@ private static void GenerateBindableProperty(SourceProductionContext context, Bi
9395
// Check for INotifyPropertyChanged, IBindableObject, ThreadObject
9496
var implementsINotify = ImplementsInterface(classSymbol, "System.ComponentModel.INotifyPropertyChanged");
9597
var implementsIBindable = ImplementsInterface(classSymbol, "ThunderDesign.Net.Threading.Interfaces.IBindableObject");
96-
97-
// Check for ThreadObject
9898
var inheritsThreadObject = PropertyGeneratorHelpers.InheritsFrom(classSymbol, "ThunderDesign.Net.Threading.Objects.ThreadObject");
9999

100+
var stringTypeSymbol = compilation.GetSpecialType(SpecialType.System_String);
101+
var voidTypeSymbol = compilation.GetSpecialType(SpecialType.System_Void);
102+
var propertyChangedEventType = compilation.GetTypeByMetadataName("System.ComponentModel.PropertyChangedEventHandler");
103+
100104
var source = new StringBuilder();
101105
var ns = classSymbol.ContainingNamespace.IsGlobalNamespace ? null : classSymbol.ContainingNamespace.ToDisplayString();
102106

@@ -108,7 +112,7 @@ private static void GenerateBindableProperty(SourceProductionContext context, Bi
108112
source.AppendLine("using ThunderDesign.Net.Threading.Extentions;");
109113
source.AppendLine("using ThunderDesign.Net.Threading.Interfaces;");
110114
source.AppendLine("using ThunderDesign.Net.Threading.Objects;");
111-
115+
112116
source.AppendLine($"partial class {classSymbol.Name}");
113117

114118
// Add interface if needed
@@ -121,15 +125,32 @@ private static void GenerateBindableProperty(SourceProductionContext context, Bi
121125
source.AppendLine("{");
122126

123127
// Add event if needed
124-
if (!implementsINotify)
125-
source.AppendLine(" public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;");
128+
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+
}
126143

127144
// Add _Locker if needed
128-
if (!inheritsThreadObject)
145+
if (!inheritsThreadObject && !PropertyGeneratorHelpers.FieldExists(classSymbol, "_Locker"))
129146
source.AppendLine(" protected readonly object _Locker = new object();");
130147

131148
// Add OnPropertyChanged if needed
132-
if (!implementsIBindable)
149+
if (!implementsIBindable && !PropertyGeneratorHelpers.MethodExists(
150+
classSymbol,
151+
"OnPropertyChanged",
152+
new ITypeSymbol[] { stringTypeSymbol },
153+
voidTypeSymbol))
133154
{
134155
source.AppendLine(@"
135156
public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = """")

src/ThunderDesign.Net-PCL.SourceGenerators/PropertyGenerator.cs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
51
using Microsoft.CodeAnalysis;
6-
using Microsoft.CodeAnalysis.CSharp;
72
using Microsoft.CodeAnalysis.CSharp.Syntax;
83
using Microsoft.CodeAnalysis.Text;
9-
using ThunderDesign.Net.Threading.Extentions;
4+
using System.Linq;
5+
using System.Text;
106

117
namespace ThunderDesign.Net_PCL.SourceGenerators
128
{
@@ -22,13 +18,15 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
2218
)
2319
.Where(static info => !info.Equals(default(PropertyFieldInfo)));
2420

25-
context.RegisterSourceOutput(fieldsWithAttribute, (spc, info) =>
21+
var compilationProvider = context.CompilationProvider;
22+
context.RegisterSourceOutput(fieldsWithAttribute.Combine(compilationProvider), (spc, tuple) =>
2623
{
27-
GenerateProperty(spc, info);
24+
var (info, compilation) = (tuple.Left, tuple.Right);
25+
GenerateProperty(spc, info, compilation);
2826
});
2927
}
3028

31-
private static void GenerateProperty(SourceProductionContext context, PropertyFieldInfo info)
29+
private static void GenerateProperty(SourceProductionContext context, PropertyFieldInfo info, Compilation compilation)
3230
{
3331
var classSymbol = info.ContainingClass;
3432
var fieldSymbol = info.FieldSymbol;
@@ -43,10 +41,10 @@ private static void GenerateProperty(SourceProductionContext context, PropertyFi
4341
return;
4442
}
4543

46-
// Rule 2: Field must start with "_" or lowercase
44+
// Rule 2: Field must start with "_" followed by a letter, or a lowercase letter
4745
if (!PropertyGeneratorHelpers.IsValidFieldName(fieldName))
4846
{
49-
PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), $"Field '{fieldName}' must start with '_' or a lowercase letter to use [Property].");
47+
PropertyGeneratorHelpers.ReportDiagnostic(context, info.FieldDeclaration.GetLocation(), $"Field '{fieldName}' must start with '_' followed by a letter, or a lowercase letter to use [Property].");
5048
return;
5149
}
5250

@@ -74,12 +72,12 @@ private static void GenerateProperty(SourceProductionContext context, PropertyFi
7472

7573
source.AppendLine("using ThunderDesign.Net.Threading.Extentions;");
7674
source.AppendLine("using ThunderDesign.Net.Threading.Objects;");
77-
75+
7876
source.AppendLine($"partial class {classSymbol.Name}");
7977
source.AppendLine("{");
8078

8179
// Add _Locker if needed
82-
if (!inheritsThreadObject)
80+
if (!inheritsThreadObject && !PropertyGeneratorHelpers.FieldExists(classSymbol, "_Locker"))
8381
source.AppendLine(" protected readonly object _Locker = new object();");
8482

8583
// Add property

src/ThunderDesign.Net-PCL.SourceGenerators/PropertyGeneratorHelpers.cs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
using System.Linq;
21
using Microsoft.CodeAnalysis;
32
using Microsoft.CodeAnalysis.CSharp;
43
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
using System.Linq;
55

66
namespace ThunderDesign.Net_PCL.SourceGenerators
77
{
@@ -96,9 +96,46 @@ public static bool IsValidFieldName(string fieldName)
9696
}
9797

9898
// Rule 3: Property must not already exist
99-
public static bool PropertyExists(INamedTypeSymbol classSymbol, string propertyName)
99+
public static bool PropertyExists(INamedTypeSymbol classSymbol, string propertyName, ITypeSymbol propertyType = null)
100+
{
101+
return classSymbol.GetMembers().OfType<IPropertySymbol>().Any(p =>
102+
p.Name == propertyName &&
103+
(propertyType == null || SymbolEqualityComparer.Default.Equals(p.Type, propertyType))
104+
);
105+
}
106+
107+
public static bool FieldExists(INamedTypeSymbol classSymbol, string fieldName, ITypeSymbol fieldType = null)
108+
{
109+
return classSymbol.GetMembers().OfType<IFieldSymbol>().Any(f =>
110+
f.Name == fieldName &&
111+
(fieldType == null || SymbolEqualityComparer.Default.Equals(f.Type, fieldType))
112+
);
113+
}
114+
115+
public static bool EventExists(INamedTypeSymbol classSymbol, string eventName, ITypeSymbol eventType = null)
116+
{
117+
return classSymbol.GetMembers().OfType<IEventSymbol>().Any(e =>
118+
e.Name == eventName &&
119+
(eventType == null || SymbolEqualityComparer.Default.Equals(e.Type, eventType))
120+
);
121+
}
122+
123+
public static bool MethodExists(
124+
INamedTypeSymbol classSymbol,
125+
string methodName,
126+
ITypeSymbol[]? parameterTypes = null,
127+
ITypeSymbol? returnType = null)
100128
{
101-
return classSymbol.GetMembers().OfType<IPropertySymbol>().Any(p => p.Name == propertyName);
129+
return classSymbol.GetMembers()
130+
.OfType<IMethodSymbol>()
131+
.Any(m =>
132+
m.Name == methodName &&
133+
(parameterTypes == null ||
134+
(m.Parameters.Length == parameterTypes.Length &&
135+
m.Parameters.Select(p => p.Type.ToDisplayString())
136+
.SequenceEqual(parameterTypes.Select(t => t.ToDisplayString())))) &&
137+
(returnType == null || SymbolEqualityComparer.Default.Equals(m.ReturnType, returnType))
138+
);
102139
}
103140
}
104141
}

0 commit comments

Comments
 (0)