// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests; [TestClass] public partial class Test_SourceGeneratorsCodegen { [TestMethod] public void ObservablePropertyWithNonNullableReferenceType_EmitsMemberNotNullAttribute() { string source = """ using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] private string name; } """; #if NET6_0_OR_GREATER string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public string Name { get => name; [global::System.Diagnostics.CodeAnalysis.MemberNotNull("name")] set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value)) { OnNameChanging(value); OnNameChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name); name = value; OnNameChanged(value); OnNameChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string? oldValue, string newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string? oldValue, string newValue); } } """; #else string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public string Name { get => name; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value)) { OnNameChanging(value); OnNameChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name); name = value; OnNameChanged(value); OnNameChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string? oldValue, string newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string? oldValue, string newValue); } } """; #endif VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } [TestMethod] public void ObservablePropertyWithNonNullableValueType_DoesNotEmitMemberNotNullAttribute() { string source = """ using System; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] private Guid id; } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public global::System.Guid Id { get => id; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(id, value)) { OnIdChanging(value); OnIdChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Id); id = value; OnIdChanged(value); OnIdChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Id); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnIdChanging(global::System.Guid value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnIdChanging(global::System.Guid oldValue, global::System.Guid newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnIdChanged(global::System.Guid value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnIdChanged(global::System.Guid oldValue, global::System.Guid newValue); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } [TestMethod] public void ObservablePropertyWithNonNullableUnconstrainedGenericType_EmitsMemberNotNullAttribute() { string source = """ using System; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] private T content; } """; #if NET6_0_OR_GREATER string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public T Content { get => content; [global::System.Diagnostics.CodeAnalysis.MemberNotNull("content")] set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value)) { OnContentChanging(value); OnContentChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content); content = value; OnContentChanged(value); OnContentChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T? oldValue, T newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T? oldValue, T newValue); } } """; #else string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public T Content { get => content; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value)) { OnContentChanging(value); OnContentChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content); content = value; OnContentChanged(value); OnContentChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T? oldValue, T newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T? oldValue, T newValue); } } """; #endif VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result)); // Also test with C# 8 to double check that using a nullable unconstrained type parameter doesn't cause issues, but just a (suppressed) warning VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, LanguageVersion.CSharp8, ("MyApp.MyViewModel`1.g.cs", result)); } [TestMethod] public void ObservablePropertyWithNonNullableConstrainedNotNullGenericType_EmitsMemberNotNullAttribute() { string source = """ using System; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject where T : notnull { [ObservableProperty] private T content; } """; #if NET6_0_OR_GREATER string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public T Content { get => content; [global::System.Diagnostics.CodeAnalysis.MemberNotNull("content")] set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value)) { OnContentChanging(value); OnContentChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content); content = value; OnContentChanged(value); OnContentChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T? oldValue, T newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T? oldValue, T newValue); } } """; #else string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public T Content { get => content; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value)) { OnContentChanging(value); OnContentChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content); content = value; OnContentChanged(value); OnContentChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T? oldValue, T newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T? oldValue, T newValue); } } """; #endif VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result)); } [TestMethod] public void ObservablePropertyWithNonNullableConstrainedReferenceTypeGenericType_EmitsMemberNotNullAttribute() { string source = """ using System; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject where T : class { [ObservableProperty] private T content; } """; #if NET6_0_OR_GREATER string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public T Content { get => content; [global::System.Diagnostics.CodeAnalysis.MemberNotNull("content")] set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value)) { OnContentChanging(value); OnContentChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content); content = value; OnContentChanged(value); OnContentChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T? oldValue, T newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T? oldValue, T newValue); } } """; #else string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public T Content { get => content; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value)) { OnContentChanging(value); OnContentChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content); content = value; OnContentChanged(value); OnContentChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T? oldValue, T newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T? oldValue, T newValue); } } """; #endif VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result)); } [TestMethod] public void ObservablePropertyWithNonNullableConstrainedValueTypeGenericType_DoesNotEmitMemberNotNullAttribute() { string source = """ using System; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject where T : struct { [ObservableProperty] private T content; } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public T Content { get => content; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value)) { OnContentChanging(value); OnContentChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content); content = value; OnContentChanged(value); OnContentChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T oldValue, T newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T oldValue, T newValue); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result)); } [TestMethod] public void ObservablePropertyWithNullableReferenceType_DoesNotEmitMemberNotNullAttribute() { string source = """ using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] private string? name; } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public string? Name { get => name; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value)) { OnNameChanging(value); OnNameChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name); name = value; OnNameChanged(value); OnNameChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string? value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string? oldValue, string? newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string? value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string? oldValue, string? newValue); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } [TestMethod] public void ObservablePropertyWithNullableValueType_DoesNotEmitMemberNotNullAttribute() { string source = """ using System; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] private Guid? id; } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public global::System.Guid? Id { get => id; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(id, value)) { OnIdChanging(value); OnIdChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Id); id = value; OnIdChanged(value); OnIdChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Id); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnIdChanging(global::System.Guid? value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnIdChanging(global::System.Guid? oldValue, global::System.Guid? newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnIdChanged(global::System.Guid? value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnIdChanged(global::System.Guid? oldValue, global::System.Guid? newValue); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } [TestMethod] public void ObservablePropertyWithNullableUnconstrainedGenericType_DoesNotEmitMemberNotNullAttribute() { string source = """ using System; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] private T? content; } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public T? Content { get => content; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value)) { OnContentChanging(value); OnContentChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content); content = value; OnContentChanged(value); OnContentChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T? value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T? oldValue, T? newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T? value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T? oldValue, T? newValue); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result)); } [TestMethod] public void ObservablePropertyWithNullableConstrainedReferenceTypeGenericType_DoesNotEmitMemberNotNullAttribute() { string source = """ using System; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject where T : class { [ObservableProperty] private T? content; } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public T? Content { get => content; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value)) { OnContentChanging(value); OnContentChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content); content = value; OnContentChanged(value); OnContentChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T? value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T? oldValue, T? newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T? value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T? oldValue, T? newValue); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result)); } [TestMethod] public void ObservablePropertyWithNullableConstrainedValueTypeGenericType_DoesNotEmitMemberNotNullAttribute() { string source = """ using System; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject where T : struct { [ObservableProperty] private T? content; } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public T? Content { get => content; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(content, value)) { OnContentChanging(value); OnContentChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Content); content = value; OnContentChanged(value); OnContentChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Content); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T? value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanging(T? oldValue, T? newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T? value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnContentChanged(T? oldValue, T? newValue); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result)); } // See https://github.com/CommunityToolkit/dotnet/issues/601 [TestMethod] public void ObservablePropertyWithForwardedAttributesWithNumberLiterals_PreservesType() { string source = """ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject { const double MyDouble = 3.14; const float MyFloat = 3.14f; [ObservableProperty] [property: DefaultValue(0.0)] [property: DefaultValue(1.24)] [property: DefaultValue(0.0f)] [property: DefaultValue(0.0f)] [property: DefaultValue(MyDouble)] [property: DefaultValue(MyFloat)] private object? a; } public class DefaultValueAttribute : Attribute { public DefaultValueAttribute(object value) { } } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::MyApp.DefaultValueAttribute(0D)] [global::MyApp.DefaultValueAttribute(1.24D)] [global::MyApp.DefaultValueAttribute(0F)] [global::MyApp.DefaultValueAttribute(0F)] [global::MyApp.DefaultValueAttribute(3.14D)] [global::MyApp.DefaultValueAttribute(3.14F)] public object? A { get => a; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(a, value)) { OnAChanging(value); OnAChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.A); a = value; OnAChanged(value); OnAChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.A); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanging(object? value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanging(object? oldValue, object? newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanged(object? value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanged(object? oldValue, object? newValue); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } [TestMethod] public void RelayCommandMethodWithForwardedAttributesWithNumberLiterals_PreservesType() { string source = """ using CommunityToolkit.Mvvm.Input; #nullable enable namespace MyApp; partial class MyViewModel { const double MyDouble = 3.14; const float MyFloat = 3.14f; [RelayCommand] [field: DefaultValue(0.0)] [field: DefaultValue(1.24)] [field: DefaultValue(0.0f)] [field: DefaultValue(0.0f)] [field: DefaultValue(MyDouble)] [field: DefaultValue(MyFloat)] [property: DefaultValue(0.0)] [property: DefaultValue(1.24)] [property: DefaultValue(0.0f)] [property: DefaultValue(0.0f)] [property: DefaultValue(MyDouble)] [property: DefaultValue(MyFloat)] private void Test() { } } public class DefaultValueAttribute : Attribute { public DefaultValueAttribute(object value) { } } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// The backing field for . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", )] [global::MyApp.DefaultValueAttribute(0D)] [global::MyApp.DefaultValueAttribute(1.24D)] [global::MyApp.DefaultValueAttribute(0F)] [global::MyApp.DefaultValueAttribute(0F)] [global::MyApp.DefaultValueAttribute(3.14D)] [global::MyApp.DefaultValueAttribute(3.14F)] private global::CommunityToolkit.Mvvm.Input.RelayCommand? testCommand; /// Gets an instance wrapping . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::MyApp.DefaultValueAttribute(0D)] [global::MyApp.DefaultValueAttribute(1.24D)] [global::MyApp.DefaultValueAttribute(0F)] [global::MyApp.DefaultValueAttribute(0F)] [global::MyApp.DefaultValueAttribute(3.14D)] [global::MyApp.DefaultValueAttribute(3.14F)] public global::CommunityToolkit.Mvvm.Input.IRelayCommand TestCommand => testCommand ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test)); } } """; VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test.g.cs", result)); } // See https://github.com/CommunityToolkit/dotnet/issues/681 [TestMethod] public void ObservablePropertyWithForwardedAttributesWithEnumWithNegativeValue_WorksCorrectly() { string source = """ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] [property: DefaultValue(NegativeEnum1.Problem)] [property: DefaultValue(NegativeEnum2.Problem)] [property: DefaultValue(NegativeEnum3.Problem)] [property: DefaultValue(NegativeEnum4.Problem)] private object? a; } public class DefaultValueAttribute : Attribute { public DefaultValueAttribute(object value) { } } public enum NegativeEnum1 { Problem = -1073741824, OK = 0 } public enum NegativeEnum2 : long { Problem = long.MinValue, OK = 0 } public enum NegativeEnum3 : short { Problem = -1234, OK = 0 } public enum NegativeEnum4 : sbyte { Problem = -1, OK = 0 } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)(-1073741824))] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)(-9223372036854775808))] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)(-1234))] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)(-1))] public object? A { get => a; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(a, value)) { OnAChanging(value); OnAChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.A); a = value; OnAChanged(value); OnAChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.A); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanging(object? value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanging(object? oldValue, object? newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanged(object? value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanged(object? oldValue, object? newValue); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } [TestMethod] public void ObservablePropertyWithForwardedAttributesWithEnumWithCombinedValues_WorksCorrectly() { string source = """ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] [property: DefaultValue(NegativeEnum1.A)] [property: DefaultValue(NegativeEnum1.B)] [property: DefaultValue((NegativeEnum1)42)] [property: DefaultValue((NegativeEnum1)OtherClass.MyNumber)] [property: DefaultValue(OtherClass.MyEnumValue)] [property: DefaultValue(NegativeEnum2.A)] [property: DefaultValue(NegativeEnum2.B)] [property: DefaultValue((NegativeEnum2)42)] [property: DefaultValue((NegativeEnum2)OtherClass.MyNumber)] [property: DefaultValue((NegativeEnum2)(int)OtherClass.MyEnumValue)] [property: DefaultValue(NegativeEnum3.A)] [property: DefaultValue(NegativeEnum3.B)] [property: DefaultValue((NegativeEnum3)42)] [property: DefaultValue((NegativeEnum3)OtherClass.MyNumber)] [property: DefaultValue((NegativeEnum3)(int)OtherClass.MyEnumValue)] [property: DefaultValue(NegativeEnum4.A)] [property: DefaultValue(NegativeEnum4.B)] [property: DefaultValue((NegativeEnum4)42)] [property: DefaultValue((NegativeEnum4)unchecked((sbyte)OtherClass.MyNumber))] [property: DefaultValue((NegativeEnum4)(int)OtherClass.MyEnumValue)] private object? a; } public static class OtherClass { public const int MyNumber = 456; public const NegativeEnum1 MyEnumValue = NegativeEnum1.C; } public class DefaultValueAttribute : Attribute { public DefaultValueAttribute(object value) { } } public enum NegativeEnum1 { A = 0, B = -1073741824, C = 123 } public enum NegativeEnum2 : long { A = 0, B = long.MinValue } public enum NegativeEnum3 : short { A = 1, B = -1234 } public enum NegativeEnum4 : sbyte { A = 1, B = -1 } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)0)] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)(-1073741824))] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)42)] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)456)] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)123)] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)0)] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)(-9223372036854775808))] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)42)] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)456)] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)123)] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)1)] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)(-1234))] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)42)] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)456)] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)123)] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)1)] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)(-1))] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)42)] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)(-56))] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)123)] public object? A { get => a; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(a, value)) { OnAChanging(value); OnAChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.A); a = value; OnAChanged(value); OnAChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.A); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanging(object? value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanging(object? oldValue, object? newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanged(object? value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanged(object? oldValue, object? newValue); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } // See https://github.com/CommunityToolkit/dotnet/issues/632 [TestMethod] public void RelayCommandMethodWithPartialDeclarations_TriggersCorrectly() { string source = """ using CommunityToolkit.Mvvm.Input; #nullable enable namespace MyApp; partial class MyViewModel { [RelayCommand] private partial void Test1() { } private partial void Test1(); private partial void Test2() { } [RelayCommand] private partial void Test2(); } """; string result1 = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// The backing field for . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", )] private global::CommunityToolkit.Mvvm.Input.RelayCommand? test1Command; /// Gets an instance wrapping . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public global::CommunityToolkit.Mvvm.Input.IRelayCommand Test1Command => test1Command ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test1)); } } """; string result2 = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// The backing field for . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", )] private global::CommunityToolkit.Mvvm.Input.RelayCommand? test2Command; /// Gets an instance wrapping . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public global::CommunityToolkit.Mvvm.Input.IRelayCommand Test2Command => test2Command ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test2)); } } """; VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test1.g.cs", result1), ("MyApp.MyViewModel.Test2.g.cs", result2)); } // See https://github.com/CommunityToolkit/dotnet/issues/632 [TestMethod] public void RelayCommandMethodWithForwardedAttributesOverPartialDeclarations_MergesAttributes() { string source = """ using CommunityToolkit.Mvvm.Input; #nullable enable namespace MyApp; partial class MyViewModel { [RelayCommand] [field: Value(0)] [property: Value(1)] private partial void Test1() { } [field: Value(2)] [property: Value(3)] private partial void Test1(); [field: Value(0)] [property: Value(1)] private partial void Test2() { } [RelayCommand] [field: Value(2)] [property: Value(3)] private partial void Test2(); } public class ValueAttribute : Attribute { public ValueAttribute(object value) { } } """; string result1 = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// The backing field for . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", )] [global::MyApp.ValueAttribute(2)] [global::MyApp.ValueAttribute(0)] private global::CommunityToolkit.Mvvm.Input.RelayCommand? test1Command; /// Gets an instance wrapping . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::MyApp.ValueAttribute(3)] [global::MyApp.ValueAttribute(1)] public global::CommunityToolkit.Mvvm.Input.IRelayCommand Test1Command => test1Command ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test1)); } } """; string result2 = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// The backing field for . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", )] [global::MyApp.ValueAttribute(2)] [global::MyApp.ValueAttribute(0)] private global::CommunityToolkit.Mvvm.Input.RelayCommand? test2Command; /// Gets an instance wrapping . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::MyApp.ValueAttribute(3)] [global::MyApp.ValueAttribute(1)] public global::CommunityToolkit.Mvvm.Input.IRelayCommand Test2Command => test2Command ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test2)); } } """; VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test1.g.cs", result1), ("MyApp.MyViewModel.Test2.g.cs", result2)); } // See https://github.com/CommunityToolkit/dotnet/issues/681 [TestMethod] public void RelayCommandMethodWithForwardedAttributesWithEnumValues_WorksCorrectly() { string source = """ using CommunityToolkit.Mvvm.Input; #nullable enable namespace MyApp; partial class MyViewModel { [RelayCommand] [field: DefaultValue(NegativeEnum1.Problem)] [field: DefaultValue(NegativeEnum2.Problem)] [field: DefaultValue(NegativeEnum3.Problem)] [field: DefaultValue(NegativeEnum4.Problem)] [property: DefaultValue(NegativeEnum1.Problem)] [property: DefaultValue(NegativeEnum2.Problem)] [property: DefaultValue(NegativeEnum3.Problem)] [property: DefaultValue(NegativeEnum4.Problem)] private void Test() { } } public class DefaultValueAttribute : Attribute { public DefaultValueAttribute(object value) { } } public enum NegativeEnum1 { Problem = -1073741824, OK = 0 } public enum NegativeEnum2 : long { Problem = long.MinValue, OK = 0 } public enum NegativeEnum3 : short { Problem = -1234, OK = 0 } public enum NegativeEnum4 : sbyte { Problem = -1, OK = 0 } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// The backing field for . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", )] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)(-1073741824))] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)(-9223372036854775808))] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)(-1234))] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)(-1))] private global::CommunityToolkit.Mvvm.Input.RelayCommand? testCommand; /// Gets an instance wrapping . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)(-1073741824))] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)(-9223372036854775808))] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)(-1234))] [global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)(-1))] public global::CommunityToolkit.Mvvm.Input.IRelayCommand TestCommand => testCommand ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test)); } } """; VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test.g.cs", result)); } [TestMethod] public void ObservablePropertyWithinGenericAndNestedTypes() { string source = """ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class Foo { partial class MyViewModel : ObservableObject { [ObservableProperty] private string? a; } } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class Foo { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public string? A { get => a; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(a, value)) { OnAChanging(value); OnAChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.A); a = value; OnAChanged(value); OnAChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.A); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanging(string? value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanging(string? oldValue, string? newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanged(string? value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanged(string? oldValue, string? newValue); } } } """; #if ROSLYN_4_3_1_OR_GREATER VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.Foo+MyViewModel`1.g.cs", result)); #else VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.Foo.MyViewModel_1.g.cs", result)); #endif } [TestMethod] public void ObservableProperty_ValidateGeneratedCachedArguments() { string source = """ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] private string? name; } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public string? Name { get => name; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value)) { OnNameChanging(value); OnNameChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name); name = value; OnNameChanged(value); OnNameChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string? value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string? oldValue, string? newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string? value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string? oldValue, string? newValue); } } """; string changingArgs = """ // #pragma warning disable #nullable enable namespace CommunityToolkit.Mvvm.ComponentModel.__Internals { /// /// A helper type providing cached, reusable instances /// for all properties generated with . /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.DebuggerNonUserCode] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This type is not intended to be used directly by user code")] internal static class __KnownINotifyPropertyChangingArgs { /// The cached instance for all "Name" generated properties. [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This field is not intended to be referenced directly by user code")] public static readonly global::System.ComponentModel.PropertyChangingEventArgs Name = new global::System.ComponentModel.PropertyChangingEventArgs("Name"); } } """; string changedArgs = """ // #pragma warning disable #nullable enable namespace CommunityToolkit.Mvvm.ComponentModel.__Internals { /// /// A helper type providing cached, reusable instances /// for all properties generated with . /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.DebuggerNonUserCode] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This type is not intended to be used directly by user code")] internal static class __KnownINotifyPropertyChangedArgs { /// The cached instance for all "Name" generated properties. [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This field is not intended to be referenced directly by user code")] public static readonly global::System.ComponentModel.PropertyChangedEventArgs Name = new global::System.ComponentModel.PropertyChangedEventArgs("Name"); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result), ("__KnownINotifyPropertyChangingArgs.g.cs", changingArgs), ("__KnownINotifyPropertyChangedArgs.g.cs", changedArgs)); } [TestMethod] public void RelayCommandMethodInTopLevelTypeWithNoNamespace_PreservesInheritdoc() { string source = """ using CommunityToolkit.Mvvm.Input; partial class MyViewModel { [RelayCommand] private void Test() { } } """; string result = """ // #pragma warning disable #nullable enable /// partial class MyViewModel { /// The backing field for . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", )] private global::CommunityToolkit.Mvvm.Input.RelayCommand? testCommand; /// Gets an instance wrapping . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public global::CommunityToolkit.Mvvm.Input.IRelayCommand TestCommand => testCommand ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test)); } """; VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyViewModel.Test.g.cs", result)); } [TestMethod] public void ObservableProperty_AnnotatedFieldHasValueIdentifier() { string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] double value; } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public double Value { get => value; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(this.value, value)) { OnValueChanging(value); OnValueChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Value); this.value = value; OnValueChanged(value); OnValueChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Value); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanging(double value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanging(double oldValue, double newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanged(double value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanged(double oldValue, double newValue); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } [TestMethod] public void ObservableProperty_AnnotatedFieldHasValueIdentifier_WithChangedMethods() { string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] double value; partial void OnValueChanged(double oldValue, double NewValue) { } } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public double Value { get => value; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(this.value, value)) { double __oldValue = this.value; OnValueChanging(value); OnValueChanging(__oldValue, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Value); this.value = value; OnValueChanged(value); OnValueChanged(__oldValue, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Value); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanging(double value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanging(double oldValue, double newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanged(double value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanged(double oldValue, double newValue); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } // See https://github.com/CommunityToolkit/dotnet/issues/710 [TestMethod] public void ObservableProperty_AnnotatedFieldWithEscapedIdentifier() { string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] double @event; } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public double Event { get => @event; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(@event, value)) { OnEventChanging(value); OnEventChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Event); @event = value; OnEventChanged(value); OnEventChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Event); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnEventChanging(double value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnEventChanging(double oldValue, double newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnEventChanged(double value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnEventChanged(double oldValue, double newValue); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } [TestMethod] public void ObservableProperty_AnnotatedFieldWithEscapedIdentifier_WithChangedMethods() { string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] double @object; partial void OnObjectChanged(double oldValue, double NewValue) { } } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public double Object { get => @object; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(@object, value)) { double __oldValue = @object; OnObjectChanging(value); OnObjectChanging(__oldValue, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Object); @object = value; OnObjectChanged(value); OnObjectChanged(__oldValue, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Object); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnObjectChanging(double value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnObjectChanging(double oldValue, double newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnObjectChanged(double value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnObjectChanged(double oldValue, double newValue); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } // See https://github.com/CommunityToolkit/dotnet/issues/711 [TestMethod] public void ObservableProperty_NotifyPropertyChangedFor_WithNotifyPropertyChanging() { // Using integers for properties to avoid needing conditional code in the expected results for nullability attributes. // This test and the one below are only validating the generation related to INotifyPropertyChanging, and nothing else. string source = """ using System; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] [NotifyPropertyChangedFor(nameof(FullName))] private int firstName; [ObservableProperty] [NotifyPropertyChangedFor(nameof(FullName))] private int lastName; public string FullName => ""; } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public int FirstName { get => firstName; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(firstName, value)) { OnFirstNameChanging(value); OnFirstNameChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.FirstName); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.FullName); firstName = value; OnFirstNameChanged(value); OnFirstNameChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FirstName); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FullName); } } } /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public int LastName { get => lastName; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(lastName, value)) { OnLastNameChanging(value); OnLastNameChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.LastName); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.FullName); lastName = value; OnLastNameChanged(value); OnLastNameChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.LastName); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FullName); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnFirstNameChanging(int value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnFirstNameChanging(int oldValue, int newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnFirstNameChanged(int value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnFirstNameChanged(int oldValue, int newValue); /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnLastNameChanging(int value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnLastNameChanging(int oldValue, int newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnLastNameChanged(int value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnLastNameChanged(int oldValue, int newValue); } } """; string changingArgs = """ // #pragma warning disable #nullable enable namespace CommunityToolkit.Mvvm.ComponentModel.__Internals { /// /// A helper type providing cached, reusable instances /// for all properties generated with . /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.DebuggerNonUserCode] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This type is not intended to be used directly by user code")] internal static class __KnownINotifyPropertyChangingArgs { /// The cached instance for all "FirstName" generated properties. [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This field is not intended to be referenced directly by user code")] public static readonly global::System.ComponentModel.PropertyChangingEventArgs FirstName = new global::System.ComponentModel.PropertyChangingEventArgs("FirstName"); /// The cached instance for all "FullName" generated properties. [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This field is not intended to be referenced directly by user code")] public static readonly global::System.ComponentModel.PropertyChangingEventArgs FullName = new global::System.ComponentModel.PropertyChangingEventArgs("FullName"); /// The cached instance for all "LastName" generated properties. [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This field is not intended to be referenced directly by user code")] public static readonly global::System.ComponentModel.PropertyChangingEventArgs LastName = new global::System.ComponentModel.PropertyChangingEventArgs("LastName"); } } """; string changedArgs = """ // #pragma warning disable #nullable enable namespace CommunityToolkit.Mvvm.ComponentModel.__Internals { /// /// A helper type providing cached, reusable instances /// for all properties generated with . /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.DebuggerNonUserCode] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This type is not intended to be used directly by user code")] internal static class __KnownINotifyPropertyChangedArgs { /// The cached instance for all "FirstName" generated properties. [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This field is not intended to be referenced directly by user code")] public static readonly global::System.ComponentModel.PropertyChangedEventArgs FirstName = new global::System.ComponentModel.PropertyChangedEventArgs("FirstName"); /// The cached instance for all "FullName" generated properties. [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This field is not intended to be referenced directly by user code")] public static readonly global::System.ComponentModel.PropertyChangedEventArgs FullName = new global::System.ComponentModel.PropertyChangedEventArgs("FullName"); /// The cached instance for all "LastName" generated properties. [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This field is not intended to be referenced directly by user code")] public static readonly global::System.ComponentModel.PropertyChangedEventArgs LastName = new global::System.ComponentModel.PropertyChangedEventArgs("LastName"); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result), ("__KnownINotifyPropertyChangingArgs.g.cs", changingArgs), ("__KnownINotifyPropertyChangedArgs.g.cs", changedArgs)); } // See https://github.com/CommunityToolkit/dotnet/issues/711 [TestMethod] public void ObservableProperty_NotifyPropertyChangedFor_WithoutNotifyPropertyChanging() { string source = """ using System; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; abstract class BaseViewModel { } [INotifyPropertyChanged(IncludeAdditionalHelperMethods = false)] partial class MyViewModel : BaseViewModel { [ObservableProperty] [NotifyPropertyChangedFor(nameof(FullName))] private int firstName; [ObservableProperty] [NotifyPropertyChangedFor(nameof(FullName))] private int lastName; public string FullName => ""; } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public int FirstName { get => firstName; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(firstName, value)) { OnFirstNameChanging(value); OnFirstNameChanging(default, value); firstName = value; OnFirstNameChanged(value); OnFirstNameChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FirstName); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FullName); } } } /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public int LastName { get => lastName; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(lastName, value)) { OnLastNameChanging(value); OnLastNameChanging(default, value); lastName = value; OnLastNameChanged(value); OnLastNameChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.LastName); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FullName); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnFirstNameChanging(int value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnFirstNameChanging(int oldValue, int newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnFirstNameChanged(int value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnFirstNameChanged(int oldValue, int newValue); /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnLastNameChanging(int value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnLastNameChanging(int oldValue, int newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnLastNameChanged(int value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnLastNameChanged(int oldValue, int newValue); } } """; string changedArgs = """ // #pragma warning disable #nullable enable namespace CommunityToolkit.Mvvm.ComponentModel.__Internals { /// /// A helper type providing cached, reusable instances /// for all properties generated with . /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.DebuggerNonUserCode] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This type is not intended to be used directly by user code")] internal static class __KnownINotifyPropertyChangedArgs { /// The cached instance for all "FirstName" generated properties. [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This field is not intended to be referenced directly by user code")] public static readonly global::System.ComponentModel.PropertyChangedEventArgs FirstName = new global::System.ComponentModel.PropertyChangedEventArgs("FirstName"); /// The cached instance for all "FullName" generated properties. [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This field is not intended to be referenced directly by user code")] public static readonly global::System.ComponentModel.PropertyChangedEventArgs FullName = new global::System.ComponentModel.PropertyChangedEventArgs("FullName"); /// The cached instance for all "LastName" generated properties. [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] [global::System.Obsolete("This field is not intended to be referenced directly by user code")] public static readonly global::System.ComponentModel.PropertyChangedEventArgs LastName = new global::System.ComponentModel.PropertyChangedEventArgs("LastName"); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result), ("__KnownINotifyPropertyChangingArgs.g.cs", null), ("__KnownINotifyPropertyChangedArgs.g.cs", changedArgs)); } [TestMethod] public void ObservableProperty_NotifyDataErrorInfo_EmitsTrimAnnotationsWhenNeeded() { string source = """ using System.ComponentModel.DataAnnotations; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableValidator { [ObservableProperty] [NotifyDataErrorInfo] [Required] private string? name; } """; #if NET6_0_OR_GREATER string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.ComponentModel.DataAnnotations.RequiredAttribute()] public string? Name { get => name; [global::System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")] set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value)) { OnNameChanging(value); OnNameChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name); name = value; ValidateProperty(value, "Name"); OnNameChanged(value); OnNameChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string? value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string? oldValue, string? newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string? value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string? oldValue, string? newValue); } } """; #else string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.ComponentModel.DataAnnotations.RequiredAttribute()] public string? Name { get => name; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value)) { OnNameChanging(value); OnNameChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name); name = value; ValidateProperty(value, "Name"); OnNameChanged(value); OnNameChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string? value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string? oldValue, string? newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string? value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string? oldValue, string? newValue); } } """; #endif VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } [TestMethod] public void ObservableProperty_NotifyDataErrorInfo_EmitsTrimAnnotationsWhenNeeded_AppendToNullableAttribute() { string source = """ using System.ComponentModel.DataAnnotations; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableValidator { [ObservableProperty] [NotifyDataErrorInfo] [Required] private string name; } """; #if NET6_0_OR_GREATER string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.ComponentModel.DataAnnotations.RequiredAttribute()] public string Name { get => name; [global::System.Diagnostics.CodeAnalysis.MemberNotNull("name")] [global::System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("The type of the current instance cannot be statically discovered.")] set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value)) { OnNameChanging(value); OnNameChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name); name = value; ValidateProperty(value, "Name"); OnNameChanged(value); OnNameChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string? oldValue, string newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string? oldValue, string newValue); } } """; #else string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.ComponentModel.DataAnnotations.RequiredAttribute()] public string Name { get => name; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value)) { OnNameChanging(value); OnNameChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name); name = value; ValidateProperty(value, "Name"); OnNameChanged(value); OnNameChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string? oldValue, string newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string? oldValue, string newValue); } } """; #endif VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } [TestMethod] public void ObservableProperty_NotNullableProperty_OfReferenceType_WithChangedMethods() { string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] string name; partial void OnNameChanged(string? oldValue, string NewValue) { } } """; #if NET6_0_OR_GREATER string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public string Name { get => name; [global::System.Diagnostics.CodeAnalysis.MemberNotNull("name")] set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value)) { string? __oldValue = name; OnNameChanging(value); OnNameChanging(__oldValue, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name); name = value; OnNameChanged(value); OnNameChanged(__oldValue, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string? oldValue, string newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string? oldValue, string newValue); } } """; #else string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public string Name { get => name; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value)) { string? __oldValue = name; OnNameChanging(value); OnNameChanging(__oldValue, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name); name = value; OnNameChanged(value); OnNameChanged(__oldValue, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanging(string? oldValue, string newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnNameChanged(string? oldValue, string newValue); } } """; #endif VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } [TestMethod] public void ObservableProperty_NotNullableProperty_OfUnconstrainedGenericType_WithChangedMethods() { string source = """ using CommunityToolkit.Mvvm.ComponentModel; namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] T value; partial void OnValueChanged(T? oldValue, T NewValue) { } } """; #if NET6_0_OR_GREATER string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public T Value { get => value; [global::System.Diagnostics.CodeAnalysis.MemberNotNull("value")] set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(this.value, value)) { T? __oldValue = this.value; OnValueChanging(value); OnValueChanging(__oldValue, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Value); this.value = value; OnValueChanged(value); OnValueChanged(__oldValue, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Value); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanging(T value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanging(T? oldValue, T newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanged(T value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanged(T? oldValue, T newValue); } } """; #else string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public T Value { get => value; set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(this.value, value)) { T? __oldValue = this.value; OnValueChanging(value); OnValueChanging(__oldValue, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Value); this.value = value; OnValueChanged(value); OnValueChanged(__oldValue, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Value); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanging(T value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanging(T? oldValue, T newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanged(T value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnValueChanged(T? oldValue, T newValue); } } """; #endif VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result)); } [TestMethod] public void ObservablePropertyWithForwardedAttributes_OnPropertyAccessors() { string source = """ using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; #nullable enable namespace MyApp; partial class MyViewModel : ObservableObject { [ObservableProperty] [property: Test("Property1")] [property: Test("Property2")] [property: Test("Property3")] [get: Test("Get1")] [get: Test("Get2")] [set: Test("Set1")] [set: Test("Set2")] private object? a; } public class TestAttribute : Attribute { public TestAttribute(string value) { } } """; string result = """ // #pragma warning disable #nullable enable namespace MyApp { /// partial class MyViewModel { /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::MyApp.TestAttribute("Property1")] [global::MyApp.TestAttribute("Property2")] [global::MyApp.TestAttribute("Property3")] public object? A { [global::MyApp.TestAttribute("Get1")] [global::MyApp.TestAttribute("Get2")] get => a; [global::MyApp.TestAttribute("Set1")] [global::MyApp.TestAttribute("Set2")] set { if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(a, value)) { OnAChanging(value); OnAChanging(default, value); OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.A); a = value; OnAChanged(value); OnAChanged(default, value); OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.A); } } } /// Executes the logic for when is changing. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanging(object? value); /// Executes the logic for when is changing. /// The previous property value that is being replaced. /// The new property value being set. /// This method is invoked right before the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanging(object? oldValue, object? newValue); /// Executes the logic for when just changed. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanged(object? value); /// Executes the logic for when just changed. /// The previous property value that was replaced. /// The new property value that was set. /// This method is invoked right after the value of is changed. [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )] partial void OnAChanged(object? oldValue, object? newValue); } } """; VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result)); } /// /// Generates the requested sources /// /// The input source to process. /// The generators to apply to the input syntax tree. /// The source files to compare. private static void VerifyGenerateSources(string source, IIncrementalGenerator[] generators, params (string Filename, string? Text)[] results) { VerifyGenerateSources(source, generators, LanguageVersion.CSharp10, results); } /// /// Generates the requested sources /// /// The input source to process. /// The generators to apply to the input syntax tree. /// The language version to use. /// The source files to compare. private static void VerifyGenerateSources(string source, IIncrementalGenerator[] generators, LanguageVersion languageVersion, params (string Filename, string? Text)[] results) { // Ensure CommunityToolkit.Mvvm and System.ComponentModel.DataAnnotations are loaded Type observableObjectType = typeof(ObservableObject); Type validationAttributeType = typeof(ValidationAttribute); // Get all assembly references for the loaded assemblies (easy way to pull in all necessary dependencies) IEnumerable references = from assembly in AppDomain.CurrentDomain.GetAssemblies() where !assembly.IsDynamic let reference = MetadataReference.CreateFromFile(assembly.Location) select reference; SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(languageVersion)); // Create a syntax tree with the input source CSharpCompilation compilation = CSharpCompilation.Create( "original", new SyntaxTree[] { syntaxTree }, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); GeneratorDriver driver = CSharpGeneratorDriver.Create(generators).WithUpdatedParseOptions((CSharpParseOptions)syntaxTree.Options); // Run all source generators on the input source code _ = driver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation outputCompilation, out ImmutableArray diagnostics); // Ensure that no diagnostics were generated CollectionAssert.AreEquivalent(Array.Empty(), diagnostics); foreach ((string filename, string? text) in results) { if (text is not null) { string filePath = filename; // Update the assembly version using the version from the assembly of the input generators. // This allows the tests to not need updates whenever the version of the MVVM Toolkit changes. string expectedText = text.Replace("", $"\"{generators[0].GetType().Assembly.GetName().Version}\""); #if !ROSLYN_4_3_1_OR_GREATER // Adjust the filenames for the legacy Roslyn 4.0 filePath = filePath.Replace('`', '_'); #endif SyntaxTree generatedTree = outputCompilation.SyntaxTrees.Single(tree => Path.GetFileName(tree.FilePath) == filePath); Assert.AreEqual(expectedText, generatedTree.ToString()); } else { // If the text is null, verify that the file was not generated at all Assert.IsFalse(outputCompilation.SyntaxTrees.Any(tree => Path.GetFileName(tree.FilePath) == filename)); } } GC.KeepAlive(observableObjectType); GC.KeepAlive(validationAttributeType); } }