Skip to content

Commit b8db4b0

Browse files
authored
Merge pull request #248 from CommunityToolkit/dev/block-conflicting-generated-properties
Block [ObservableProperty] on properties causing conflicts
2 parents 0fb704d + 914f31d commit b8db4b0

File tree

5 files changed

+156
-0
lines changed

5 files changed

+156
-0
lines changed

CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ MVVMTK0020 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator
3030
MVVMTK0021 | CommunityToolkit.Mvvm.SourceGenerators.ObservableRecipientGenerator | Error | See https://aka.ms/mvvmtoolkit/error
3131
MVVMTK0022 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/error
3232
MVVMTK0023 | CommunityToolkit.Mvvm.SourceGenerators.ICommandGenerator | Error | See https://aka.ms/mvvmtoolkit/error
33+
MVVMTK0024 | CommunityToolkit.Mvvm.SourceGenerators.ICommandGenerator | Error | See https://aka.ms/mvvmtoolkit/error

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,20 @@ internal static class Execute
7171
return null;
7272
}
7373

74+
// Check for special cases that are explicitly not allowed
75+
if (IsGeneratedPropertyInvalid(propertyName, fieldSymbol.Type))
76+
{
77+
builder.Add(
78+
InvalidObservablePropertyError,
79+
fieldSymbol,
80+
fieldSymbol.ContainingType,
81+
fieldSymbol.Name);
82+
83+
diagnostics = builder.ToImmutable();
84+
85+
return null;
86+
}
87+
7488
ImmutableArray<string>.Builder propertyChangedNames = ImmutableArray.CreateBuilder<string>();
7589
ImmutableArray<string>.Builder propertyChangingNames = ImmutableArray.CreateBuilder<string>();
7690
ImmutableArray<string>.Builder notifiedCommandNames = ImmutableArray.CreateBuilder<string>();
@@ -178,6 +192,29 @@ private static bool IsTargetTypeValid(
178192
return isObservableObject || hasObservableObjectAttribute || hasINotifyPropertyChangedAttribute;
179193
}
180194

195+
/// <summary>
196+
/// Checks whether the generated property would be a special case that is marked as invalid.
197+
/// </summary>
198+
/// <param name="propertyName">The property name.</param>
199+
/// <param name="propertyType">The property type.</param>
200+
/// <returns>Whether the generated property is invalid.</returns>
201+
private static bool IsGeneratedPropertyInvalid(string propertyName, ITypeSymbol propertyType)
202+
{
203+
// If the generated property name is called "Property" and the type is either object or it is PropertyChangedEventArgs or
204+
// PropertyChangingEventArgs (or a type derived from either of those two types), consider it invalid. This is needed because
205+
// if such a property was generated, the partial On<PROPERTY_NAME>Changing and OnPropertyChanging(PropertyChangingEventArgs)
206+
// methods, as well as the partial On<PROPERTY_NAME>Changed and OnPropertyChanged(PropertyChangedEventArgs) methods.
207+
if (propertyName == "Property")
208+
{
209+
return
210+
propertyType.SpecialType == SpecialType.System_Object ||
211+
propertyType.HasOrInheritsFromFullyQualifiedName("global::System.ComponentModel.PropertyChangedEventArgs") ||
212+
propertyType.HasOrInheritsFromFullyQualifiedName("global::System.ComponentModel.PropertyChangingEventArgs");
213+
}
214+
215+
return false;
216+
}
217+
181218
/// <summary>
182219
/// Tries to gather dependent properties from the given attribute.
183220
/// </summary>

CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,4 +379,20 @@ internal static class DiagnosticDescriptors
379379
isEnabledByDefault: true,
380380
description: "Methods with multiple overloads cannot be annotated with [ICommand], as command methods must be unique within their containing type.",
381381
helpLinkUri: "https://aka.ms/mvvmtoolkit");
382+
383+
/// <summary>
384+
/// Gets a <see cref="DiagnosticDescriptor"/> indicating when a generated property created with <c>[ObservableProperty]</c> would cause conflicts with other generated members.
385+
/// <para>
386+
/// Format: <c>"The field {0}.{1} cannot be used to generate an observable property, as its name or type would cause conflicts with other generated members"</c>.
387+
/// </para>
388+
/// </summary>
389+
public static readonly DiagnosticDescriptor InvalidObservablePropertyError = new DiagnosticDescriptor(
390+
id: "MVVMTK0024",
391+
title: "Invalid generated property declaration",
392+
messageFormat: "The field {0}.{1} cannot be used to generate an observable property, as its name or type would cause conflicts with other generated members",
393+
category: typeof(ObservablePropertyGenerator).FullName,
394+
defaultSeverity: DiagnosticSeverity.Error,
395+
isEnabledByDefault: true,
396+
description: "The fields annotated with [ObservableProperty] cannot result in a property name or have a type that would cause conflicts with other generated members.",
397+
helpLinkUri: "https://aka.ms/mvvmtoolkit");
382398
}

CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,25 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions;
1313
/// </summary>
1414
internal static class ITypeSymbolExtensions
1515
{
16+
/// <summary>
17+
/// Checks whether or not a given <see cref="ITypeSymbol"/> has or inherits from a specified type.
18+
/// </summary>
19+
/// <param name="typeSymbol">The target <see cref="ITypeSymbol"/> instance to check.</param>
20+
/// <param name="name">The full name of the type to check for inheritance.</param>
21+
/// <returns>Whether or not <paramref name="typeSymbol"/> is or inherits from <paramref name="name"/>.</returns>
22+
public static bool HasOrInheritsFromFullyQualifiedName(this ITypeSymbol typeSymbol, string name)
23+
{
24+
for (ITypeSymbol? currentType = typeSymbol; currentType is not null; currentType = currentType.BaseType)
25+
{
26+
if (currentType.HasFullyQualifiedName(name))
27+
{
28+
return true;
29+
}
30+
}
31+
32+
return false;
33+
}
34+
1635
/// <summary>
1736
/// Checks whether or not a given <see cref="ITypeSymbol"/> inherits from a specified type.
1837
/// </summary>

tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,89 @@ private void GreetUser(object value)
11051105
VerifyGeneratedDiagnostics<ICommandGenerator>(source, "MVVMTK0023");
11061106
}
11071107

1108+
[TestMethod]
1109+
public void InvalidObservablePropertyError_Object()
1110+
{
1111+
string source = @"
1112+
using CommunityToolkit.Mvvm.ComponentModel;
1113+
1114+
namespace MyApp
1115+
{
1116+
public partial class MyViewModel : ObservableObject
1117+
{
1118+
[ObservableProperty]
1119+
public object property;
1120+
}
1121+
}";
1122+
1123+
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0024");
1124+
}
1125+
1126+
[TestMethod]
1127+
public void InvalidObservablePropertyError_PropertyChangingEventArgs()
1128+
{
1129+
string source = @"
1130+
using System.ComponentModel;
1131+
using CommunityToolkit.Mvvm.ComponentModel;
1132+
1133+
namespace MyApp
1134+
{
1135+
public partial class MyViewModel : ObservableObject
1136+
{
1137+
[ObservableProperty]
1138+
public PropertyChangingEventArgs property;
1139+
}
1140+
}";
1141+
1142+
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0024");
1143+
}
1144+
1145+
[TestMethod]
1146+
public void InvalidObservablePropertyError_PropertyChangedEventArgs()
1147+
{
1148+
string source = @"
1149+
using System.ComponentModel;
1150+
using CommunityToolkit.Mvvm.ComponentModel;
1151+
1152+
namespace MyApp
1153+
{
1154+
public partial class MyViewModel : ObservableObject
1155+
{
1156+
[ObservableProperty]
1157+
public PropertyChangedEventArgs property;
1158+
}
1159+
}";
1160+
1161+
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0024");
1162+
}
1163+
1164+
[TestMethod]
1165+
public void InvalidObservablePropertyError_CustomTypeDerivedFromPropertyChangedEventArgs()
1166+
{
1167+
string source = @"
1168+
using System.ComponentModel;
1169+
using CommunityToolkit.Mvvm.ComponentModel;
1170+
1171+
namespace MyApp
1172+
{
1173+
public class MyPropertyChangedEventArgs : PropertyChangedEventArgs
1174+
{
1175+
public MyPropertyChangedEventArgs(string propertyName)
1176+
: base(propertyName)
1177+
{
1178+
}
1179+
}
1180+
1181+
public partial class MyViewModel : ObservableObject
1182+
{
1183+
[ObservableProperty]
1184+
public MyPropertyChangedEventArgs property;
1185+
}
1186+
}";
1187+
1188+
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0024");
1189+
}
1190+
11081191
/// <summary>
11091192
/// Verifies the output of a source generator.
11101193
/// </summary>

0 commit comments

Comments
 (0)