diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/FrameworkTests/FrameworkTests.csproj b/DevExpress.Mvvm.CodeGenerators.Tests/FrameworkTests/FrameworkTests.csproj index 332542f..5439fb4 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/FrameworkTests/FrameworkTests.csproj +++ b/DevExpress.Mvvm.CodeGenerators.Tests/FrameworkTests/FrameworkTests.csproj @@ -20,15 +20,17 @@ full false bin\Debug\ - DEBUG;TRACE + DEBUG;TRACE;FRAMEWORK prompt 4 + true + pdbonly true bin\Release\ - TRACE + TRACE;FRAMEWORK prompt 4 @@ -48,6 +50,9 @@ + + 7.1.2 + diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/NetCoreTests/NetCoreTests.csproj b/DevExpress.Mvvm.CodeGenerators.Tests/NetCoreTests/NetCoreTests.csproj index 0473d86..853c5a6 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/NetCoreTests/NetCoreTests.csproj +++ b/DevExpress.Mvvm.CodeGenerators.Tests/NetCoreTests/NetCoreTests.csproj @@ -6,7 +6,7 @@ DevExpress.Mvvm.CodeGenerators.NetCoreTests 9 true - 8618,8604 + True true @@ -15,6 +15,7 @@ + diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/AttributeTransferTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/AttributeTransferTests.cs index 4639622..4baf315 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/AttributeTransferTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/AttributeTransferTests.cs @@ -31,13 +31,13 @@ partial class AttributeTransfer { [TestFixture] public class AttributeTransferTests { [Test] - public void AttributeTransfer() { - var noAttributeProperty = typeof(AttributeTransfer).GetProperty("NoAttribute"); + public void AttributeTransferTest() { + var noAttributeProperty = typeof(AttributeTransfer).GetProperty(nameof(AttributeTransfer.NoAttribute)); var attributes = Attribute.GetCustomAttributes(noAttributeProperty); var expectedAttributes = new Attribute[] { }; Assert.AreEqual(expectedAttributes, attributes); - var withMultipleAttributesProperty = typeof(AttributeTransfer).GetProperty("WithMultipleAttributes"); + var withMultipleAttributesProperty = typeof(AttributeTransfer).GetProperty(nameof(AttributeTransfer.WithMultipleAttributes)); attributes = Attribute.GetCustomAttributes(withMultipleAttributesProperty); expectedAttributes = new Attribute[] { new System.ComponentModel.DataAnnotations.RangeAttribute(0, 1), diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/NullableAnnotationTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/NullableAnnotationTests.cs index 1b5d311..f8eee9d 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/NullableAnnotationTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/NullableAnnotationTests.cs @@ -36,6 +36,7 @@ void NonNullableParameter1(string str) { } int? nullableInt2; [GenerateProperty] string string2; +#pragma warning disable 8632 [GenerateProperty] string? nullableString2; @@ -48,6 +49,7 @@ void NonNullableParameter2(string str) { } [GenerateCommand] Task NonNullableParameterAsync2(string str) => Task.CompletedTask; - void OnString1Changed(string str) { } + void OnString1Changed(string str) { } +#pragma warning restore 8632 } } diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/PropertyGenerationTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/PropertyGenerationTests.cs index 65c3a36..016495e 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/PropertyGenerationTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/PropertyGenerationTests.cs @@ -26,7 +26,7 @@ partial class WithTwoMvvmAttribute { [Prism.GenerateProperty] int prismProperty; [Prism.GenerateCommand] - void PrismMethod() { } + void PrismMethod() { prismProperty++; } } [TestFixture] diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/UsingRaiseMethodTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/UsingRaiseMethodTests.cs index b5200d7..ed52c5a 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/UsingRaiseMethodTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/UsingRaiseMethodTests.cs @@ -20,6 +20,11 @@ partial class ChildWithParentsGeneratedRaiseMethod : ParentWithGeneraetedRaiseMe class ParentWithoutRaiseMethod : INotifyPropertyChanged, INotifyPropertyChanging { public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangingEventHandler PropertyChanging; + public ParentWithoutRaiseMethod() { + //avoid warnings + PropertyChanged?.Invoke(null, null); + PropertyChanging?.Invoke(null, null); + } } [GenerateViewModel(ImplementINotifyPropertyChanging = true)] partial class ChildWithoutRaiseMethod : ParentWithoutRaiseMethod { diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/Helper.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/Helper.cs similarity index 100% rename from DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/DxTest/Helper.cs rename to DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/Helper.cs diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/AttributeTransferTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/AttributeTransferTests.cs index 6489a75..65df1f8 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/AttributeTransferTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/AttributeTransferTests.cs @@ -32,13 +32,13 @@ partial class AttributeTransfer { [TestFixture] public class AttributeTransferTests { [Test] - public void AttributeTransfer() { - var noAttributeProperty = typeof(AttributeTransfer).GetProperty("NoAttribute"); + public void AttributeTransferTest() { + var noAttributeProperty = typeof(AttributeTransfer).GetProperty(nameof(AttributeTransfer.NoAttribute)); var attributes = Attribute.GetCustomAttributes(noAttributeProperty); var expectedAttributes = new Attribute[] { }; Assert.AreEqual(expectedAttributes, attributes); - var withMultipleAttributesProperty = typeof(AttributeTransfer).GetProperty("WithMultipleAttributes"); + var withMultipleAttributesProperty = typeof(AttributeTransfer).GetProperty(nameof(AttributeTransfer.WithMultipleAttributes)); attributes = Attribute.GetCustomAttributes(withMultipleAttributesProperty); expectedAttributes = new Attribute[] { new System.ComponentModel.DataAnnotations.RangeAttribute(0, 1), diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/Helper.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/Helper.cs deleted file mode 100644 index 3d7cc78..0000000 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/Helper.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using NUnit.Framework; -using System; -using System.ComponentModel; -using DevExpress.Mvvm.CodeGenerators.MvvmLight; - -namespace MvvmLight.Mvvm.Tests { - static class DoWith { - static void EventCore( - TSender sender, - Action action, - Action eventAction, - Func, THandler> subscribe, - Action unsubscribe, - int expectedFireCount = 1) - where TArgs : EventArgs { - - var fireCount = 0; - EventHandler handler = (o, e) => { - Assert.AreSame(o, sender); - eventAction(e); - fireCount++; - }; - - var h = subscribe(sender, handler); - try { - action(); - } finally { - unsubscribe(sender, h); - } - Assert.AreEqual(expectedFireCount, fireCount); - } - - public static void PropertyChangedEvent(INotifyPropertyChanged inpc, Action action, Action eventAction, int fireCount = 1) { - EventCore( - inpc, - action, - eventAction, - (o, handler) => { - PropertyChangedEventHandler x = (sender, args) => handler(sender, args); - inpc.PropertyChanged += x; - return x; - }, - (o, handler) => inpc.PropertyChanged -= handler, - fireCount - ); - } - public static void PropertyChangingEvent(INotifyPropertyChanging inpc, Action action, Action eventAction, int fireCount = 1) { - EventCore( - inpc, - action, - eventAction, - (o, handler) => { - PropertyChangingEventHandler x = (sender, args) => handler(sender, args); - inpc.PropertyChanging += x; - return x; - }, - (o, handler) => inpc.PropertyChanging -= handler, - fireCount - ); - } - } -} diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/InterfacesTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/InterfacesTests.cs index bfcf8cb..4f83cf3 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/InterfacesTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/InterfacesTests.cs @@ -18,7 +18,7 @@ partial class NotImplementICU { } [GenerateViewModel(ImplementICleanup = true)] partial class ImplementICUParent { } [GenerateViewModel(ImplementICleanup = true)] - partial class ImplementICUChild { } + partial class ImplementICUChild : ImplementICUParent { } [GenerateViewModel(ImplementINotifyPropertyChanging = true, ImplementICleanup = true)] diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/NullableAnnotationTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/NullableAnnotationTests.cs index 30bebc4..e49910b 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/NullableAnnotationTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/NullableAnnotationTests.cs @@ -34,6 +34,7 @@ void NonNullableParameter1(string str) { } int? nullableInt2; [GenerateProperty] string string2; +#pragma warning disable 8632 [GenerateProperty] string? nullableString2; @@ -47,5 +48,6 @@ void NonNullableParameter2(string str) { } Task NonNullableParameterAsync2(string str) => Task.CompletedTask; void OnString1Changed(string str) { } +#pragma warning restore 8632 } } diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/PropertyGenerationTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/PropertyGenerationTests.cs index 174cf37..fca85df 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/PropertyGenerationTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/PropertyGenerationTests.cs @@ -13,7 +13,7 @@ partial class WithTwoMvvmAttribute { [DevExpress.Mvvm.CodeGenerators.GenerateProperty] int dxProperty; [DevExpress.Mvvm.CodeGenerators.GenerateCommand] - void DxMethod() { } + void DxMethod() { dxProperty++; } } [TestFixture] diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/UsingRaiseMethodTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/UsingRaiseMethodTests.cs index 8a1ed2f..f17821b 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/UsingRaiseMethodTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/UsingRaiseMethodTests.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using DevExpress.Mvvm.CodeGenerators.MvvmLight; using GalaSoft.MvvmLight; +using DevExpress.Mvvm.CodeGenerators.Tests; namespace MvvmLight.Mvvm.Tests { [GenerateViewModel(ImplementINotifyPropertyChanging = true)] @@ -22,6 +23,11 @@ partial class ChildWithParentsGeneratedRaiseMethod : ParentWithGeneraetedRaiseMe class ParentWithoutRaiseMethod : INotifyPropertyChanged, INotifyPropertyChanging { public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangingEventHandler PropertyChanging; + public ParentWithoutRaiseMethod() { + //avoid warnings + PropertyChanged?.Invoke(null, null); + PropertyChanging?.Invoke(null, null); + } } [GenerateViewModel(ImplementINotifyPropertyChanging = true)] partial class ChildWithoutRaiseMethod : ParentWithoutRaiseMethod { diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/AsyncCommandGenerationTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/AsyncCommandGenerationTests.cs new file mode 100644 index 0000000..d160198 --- /dev/null +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/AsyncCommandGenerationTests.cs @@ -0,0 +1,82 @@ +using NUnit.Framework; +using System; +using System.Reflection; +using System.Threading.Tasks; +using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; +using Microsoft.Toolkit.Mvvm.Input; + +namespace MvvmToolkit.Mvvm.Tests { + [GenerateViewModel] + partial class GenerateAsyncCommands { + readonly Task task = new(() => 1); + [GenerateCommand] + public Task WithNoArg() => Task.CompletedTask; + [GenerateCommand] + public Task WithArg(int arg) => Task.CompletedTask; + [GenerateCommand] + public Task WithNullableArg(int? arg) => Task.CompletedTask; + public Task SomeMethod() => Task.CompletedTask; + + [GenerateCommand(Name = "MyAsyncCommand", CanExecuteMethod = "CanDoIt")] + public Task Method(int arg) => Task.CompletedTask; + public bool CanDoIt(int arg) => arg > 0; + + [GenerateCommand] + public Task GenericTask() => task; + } + + [TestFixture] + public class AsyncCommandGenerationTests { + [Test] + public void CallRequiredMethodForAsyncCommand() { + var generated = new GenerateAsyncCommands(); + + var method = GetFieldValue, RelayCommand>(generated.MyAsyncCommand, "execute"); + StringAssert.Contains("MyAsyncCommand", method.Method.Name); + + var canMethod = GetFieldValue, RelayCommand>(generated.MyAsyncCommand, "canExecute"); + var expectedCanMethod = generated.GetType().GetMethod("CanDoIt").Name; + Assert.AreEqual(expectedCanMethod, canMethod.Method.Name); + + var canExecuteMethod = GetFieldValue, RelayCommand>(generated.GenericTaskCommand, "canExecute"); + Assert.IsNull(canExecuteMethod); + } + + static TResult GetFieldValue(T source, string fieldName) { + var fieldInfo = source.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); + Assert.IsNotNull(fieldInfo); + + return (TResult)fieldInfo.GetValue(source); + } + [Test] + public void AsyncCommandImplementation() { + var generated = new GenerateAsyncCommands(); + + Assert.IsNotNull(generated.GetType().GetProperty("WithNoArgCommand")); + Assert.IsNotNull(generated.GetType().GetProperty("WithArgCommand")); + Assert.IsNotNull(generated.GetType().GetProperty("WithNullableArgCommand")); + + Assert.IsNull(generated.GetType().GetProperty("With2ArgsCommand")); + Assert.IsNull(generated.GetType().GetProperty("ReturnNoTaskCommand")); + Assert.IsNull(generated.GetType().GetProperty("SomeMethodCommand")); + } + + [Test] + public void ArgumentTypeForAsyncCommand() { + var generated = new GenerateAsyncCommands(); + + var noArgumentType = generated.WithNoArgCommand.GetType(); + Assert.IsEmpty(noArgumentType.GetGenericArguments()); + var expectedType = typeof(RelayCommand); + Assert.AreEqual(expectedType, noArgumentType); + + var intArgumentType = generated.WithArgCommand.GetType().GetGenericArguments()[0]; + var intExpectedType = typeof(int); + Assert.AreEqual(intExpectedType, intArgumentType); + + var nullableIntArgumentType = generated.WithNullableArgCommand.GetType().GetGenericArguments()[0]; + var nullableIntExpectedType = typeof(int?); + Assert.AreEqual(nullableIntExpectedType, nullableIntArgumentType); + } + } +} diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/AttributeTransferTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/AttributeTransferTests.cs new file mode 100644 index 0000000..54d86f2 --- /dev/null +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/AttributeTransferTests.cs @@ -0,0 +1,51 @@ +using DevExpress.Mvvm.CodeGenerators.Tests.Included; +using NUnit.Framework; +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; + +namespace MvvmToolkit.Mvvm.Tests { + class MyCustomAttribute : Attribute { + public MyCustomAttribute(int value, string str, bool condition, TestEnum testEnum) { } + } + namespace Included { + enum TestEnum { + Num = 1, + String = 2 + } + } + [GenerateViewModel] + partial class AttributeTransfer { + const int number = 1; + + [GenerateProperty] + int noAttribute; + + [GenerateProperty, System.ComponentModel.DataAnnotations.Range(0, 1)] + [Required, MyCustom(number, + "Some string", + true, TestEnum.Num)] + int withMultipleAttributes; + } + + [TestFixture] + public class AttributeTransferTests { + [Test] + public void AttributeTransferTest() { + var noAttributeProperty = typeof(AttributeTransfer).GetProperty(nameof(AttributeTransfer.NoAttribute)); + var attributes = Attribute.GetCustomAttributes(noAttributeProperty); + var expectedAttributes = new Attribute[] { }; + Assert.AreEqual(expectedAttributes, attributes); + + var withMultipleAttributesProperty = typeof(AttributeTransfer).GetProperty(nameof(AttributeTransfer.WithMultipleAttributes)); + attributes = Attribute.GetCustomAttributes(withMultipleAttributesProperty); + expectedAttributes = new Attribute[] { + new System.ComponentModel.DataAnnotations.RangeAttribute(0, 1), + new RequiredAttribute(), + new MyCustomAttribute(1, "Some string", true, TestEnum.Num) + }; + Assert.AreEqual(expectedAttributes, attributes); + } + } +} diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/CommandGenerationTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/CommandGenerationTests.cs similarity index 78% rename from DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/CommandGenerationTests.cs rename to DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/CommandGenerationTests.cs index 533366f..b00d415 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmLightTest/CommandGenerationTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/CommandGenerationTests.cs @@ -1,17 +1,12 @@ using NUnit.Framework; using System; using System.Reflection; -using DevExpress.Mvvm.CodeGenerators.MvvmLight; +using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; using GalaSoft.MvvmLight.Helpers; +using Microsoft.Toolkit.Mvvm.Input; using System.Linq; -#if NETCOREAPP -using GalaSoft.MvvmLight.Command; -#else -using GalaSoft.MvvmLight.CommandWpf; -#endif - -namespace MvvmLight.Mvvm.Tests { +namespace MvvmToolkit.Mvvm.Tests { [GenerateViewModel] partial class GenerateCommands { [GenerateCommand] @@ -31,7 +26,7 @@ public void Method(int arg) { } [GenerateCommand] void WithNullableString1(string? str) { } [GenerateCommand] - void WithNullableString2(string str) { } + void WithNullableString2(string? str) { } [GenerateCommand] void WithNullableString3(string? str) { } bool CanWithNullableString3(string str) => str.Length > 0; @@ -72,24 +67,23 @@ public void CommandImplementation() { public void CallRequiredMethodForCommand() { var generated = new GenerateCommands(); + var executeMethodWithNoArg = GetFieldValue, RelayCommand>(generated.WithNullableArgCommand, "execute"); var expectedExecuteMethodWithNoArg = generated.GetType().GetMethod("WithNullableArg"); - var executeMethodWithNoArg = GetFieldValueMethod, WeakAction>(generated.WithNullableArgCommand, "_execute"); - Assert.AreEqual(expectedExecuteMethodWithNoArg, executeMethodWithNoArg); + Assert.AreEqual(expectedExecuteMethodWithNoArg, executeMethodWithNoArg.Method); - var method = GetFieldValueMethod, WeakAction>(generated.Command, "_execute"); + var method = GetFieldValue, RelayCommand>(generated.Command, "execute"); var expectedMethod = generated.GetType().GetMethod("Method"); - Assert.AreEqual(expectedMethod, method); + Assert.AreEqual(expectedMethod, method.Method); - var canMethod = GetFieldValueMethod, WeakFunc>(generated.Command, "_canExecute"); + var canMethod = GetFieldValue, RelayCommand>(generated.Command, "canExecute"); var expectedCanMethod = generated.GetType().GetMethod("CanDoIt"); - Assert.AreEqual(expectedCanMethod, canMethod); + Assert.AreEqual(expectedCanMethod, canMethod.Method); } - static TResult GetFieldValueMethod(TCommand source, string fieldName) { + static TResult GetFieldValue(T source, string fieldName) { var fieldInfo = source.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); Assert.IsNotNull(fieldInfo); - var fieldInsance = fieldInfo.GetValue(source); - var weakMethod = typeof(T).GetProperty("Method", BindingFlags.NonPublic | BindingFlags.Instance); - return (TResult)weakMethod.GetValue(fieldInsance); + + return (TResult)fieldInfo.GetValue(source); } [Test] public void ArgumentTypeForCommand() { @@ -129,9 +123,10 @@ public void AttributeGenerationTest() { var generated = new GenerateCommands(); var attributes = generated.GetType().GetProperty("AttributeTestCommand").GetCustomAttributes().ToList(); - Assert.AreEqual(2, attributes.Count); - Assert.IsTrue(attributes[0] is FirstAttribute); - Assert.IsTrue(attributes[1] is ThirdAttribute); + Assert.AreEqual(3, attributes.Count); + Assert.IsTrue(attributes[0].GetType().Name == "NullableAttribute"); + Assert.IsTrue(attributes[1] is FirstAttribute); + Assert.IsTrue(attributes[2] is ThirdAttribute); } } } diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/InterfacesTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/InterfacesTests.cs new file mode 100644 index 0000000..6b01fdc --- /dev/null +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/InterfacesTests.cs @@ -0,0 +1,69 @@ +using NUnit.Framework; +using System.ComponentModel; +using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; +using GalaSoft.MvvmLight; + +namespace MvvmToolkit.Mvvm.Tests { + partial class SimpleClass { } + [GenerateViewModel] + partial class ClassWithGenerator { } + + [GenerateViewModel(ImplementINotifyPropertyChanging = false)] + partial class NotImplementINPCing { } + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + partial class ImplementINPCing { } + + [GenerateViewModel] + partial class ChildWithInheritedUserINPC : ViewModelBase { } + + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + partial class GeneratedParent { } + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + partial class GeneratedChild : GeneratedParent { + [GenerateProperty] + int value; + } + + [TestFixture] + public class IntefacesTests { + [Test] + public void INPCedImplimentation() { + var withGenerator = new ClassWithGenerator(); + Assert.IsTrue(withGenerator is INotifyPropertyChanged); + + var withoutGenerator = new SimpleClass(); + Assert.IsTrue(withoutGenerator is not INotifyPropertyChanged); + } + + [Test] + public void INPCingImplementation() { + var inpcingDefault = new ClassWithGenerator(); + Assert.IsTrue(inpcingDefault is not INotifyPropertyChanging); + + var inpcingImpl = new ImplementINPCing(); + Assert.IsTrue(inpcingImpl is INotifyPropertyChanging); + + var inpcingNotImpl = new NotImplementINPCing(); + Assert.IsTrue(inpcingNotImpl is not INotifyPropertyChanging); + } + + [Test] + public void InheritanceUserINPC() { + var child = new ChildWithInheritedUserINPC(); + + Assert.IsTrue(child is INotifyPropertyChanged); + Assert.IsTrue(child is not INotifyPropertyChanging); + } + + [Test] + public void InheritanceGeneratedINPC() { + var parent = new GeneratedParent(); + var child = new GeneratedChild(); + + Assert.IsTrue(parent is INotifyPropertyChanged); + Assert.IsTrue(parent is INotifyPropertyChanging); + Assert.IsTrue(child is INotifyPropertyChanged); + Assert.IsTrue(child is INotifyPropertyChanging); + } + } +} diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/NullableAnnotationTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/NullableAnnotationTests.cs new file mode 100644 index 0000000..dcc87ed --- /dev/null +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/NullableAnnotationTests.cs @@ -0,0 +1,48 @@ +using System.Threading.Tasks; +using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; + +namespace MvvmToolkit.Mvvm.Tests { + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + public partial class NullableAnnotation { +#nullable enable + public NullableAnnotation() { + string1 = "123"; + } + [GenerateProperty] + int int1; + [GenerateProperty] + int? nullableInt1; + [GenerateProperty] + string string1; + [GenerateProperty] + string? nullableString1; + + [GenerateCommand] + void NullableParameter1(string? str) { } + [GenerateCommand] + Task NullableParameterAsync1(string? str) => Task.CompletedTask; + +#nullable disable + [GenerateProperty] + int int2; + [GenerateProperty] + int? nullableInt2; + [GenerateProperty] + string string2; +#pragma warning disable 8632 + [GenerateProperty] + string? nullableString2; + + [GenerateCommand] + void NullableParameter2(string? str) { } + [GenerateCommand] + void NonNullableParameter2(string str) { } + [GenerateCommand] + Task NullableParameterAsync2(string? str) => Task.CompletedTask; + [GenerateCommand] + Task NonNullableParameterAsync2(string str) => Task.CompletedTask; + + void OnString1Changed(string str) { } +#pragma warning restore 8632 + } +} diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/PropertyGenerationTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/PropertyGenerationTests.cs new file mode 100644 index 0000000..7b6b8c1 --- /dev/null +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/PropertyGenerationTests.cs @@ -0,0 +1,90 @@ +using NUnit.Framework; +using System.ComponentModel; +using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Microsoft.Toolkit.Mvvm.Messaging; +using Microsoft.Toolkit.Mvvm.Messaging.Messages; + +namespace MvvmToolkit.Mvvm.Tests { + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + partial class GenerateProperties { + [GenerateProperty(IsVirtual = true)] + int property; + } + [GenerateViewModel] + partial class WithTwoMvvmAttribute { + [DevExpress.Mvvm.CodeGenerators.GenerateProperty] + int dxProperty; + [DevExpress.Mvvm.CodeGenerators.GenerateCommand] + void DxMethod() { dxProperty++; } + } + + [GenerateViewModel] + partial class Broadcast : ObservableRecipient { + public Broadcast(IMessenger messenger) : base(messenger) { + } + [GenerateProperty(Broadcast = true)] + int value; + } + + [GenerateViewModel] + partial class Validator : ObservableValidator { + [System.ComponentModel.DataAnnotations.Range(0, 9)] + [GenerateProperty(Validate = true)] + int value; + } + + + [TestFixture] + public class PropertyGenerationTests { + [Test] + public void PropertyImplementation() { + var generated = new GenerateProperties(); + + Assert.IsNotNull(generated.GetType().GetProperty("Property")); + Assert.IsNull(generated.GetType().GetProperty("NotProperty")); + } + [Test] + public void DoNotGenerateDxMembers() { + var generated = new WithTwoMvvmAttribute(); + + Assert.IsNull(generated.GetType().GetProperty("DxProperty")); + Assert.IsNull(generated.GetType().GetProperty("DxMethodCommand")); + } +#if !FRAMEWORK + [Test] + public void BroadcastProperty() { + var messenger = new StrongReferenceMessenger(); + var broadcast = new Broadcast(messenger) { IsActive = true }; + + int messageCount = 0; + messenger.Register>(this, (r, m) => { + Assert.AreEqual(r, this); + Assert.AreEqual(0, m.OldValue); + Assert.AreEqual(1, m.NewValue); + Assert.AreEqual("Value", m.PropertyName); + Assert.AreEqual(broadcast, m.Sender); + Assert.AreEqual(1, broadcast.Value); + messageCount++; + }); + broadcast.Value = 1; + Assert.AreEqual(1, messageCount); + broadcast.Value = 1; + Assert.AreEqual(1, messageCount); + } + [Test] + public void ValidateProperty() { + var validator = new Validator(); + int messageCount = 0; + + validator.ErrorsChanged += (o, e) => { + Assert.AreEqual("Value", e.PropertyName); + Assert.AreEqual(validator, o); + messageCount++; + }; + validator.Value = 13; + Assert.AreEqual(1, messageCount); + } +#endif + } +} diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/UsingRaiseMethodTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/UsingRaiseMethodTests.cs new file mode 100644 index 0000000..64fa000 --- /dev/null +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/MvvmToolkit/UsingRaiseMethodTests.cs @@ -0,0 +1,140 @@ +using NUnit.Framework; +using System; +using System.ComponentModel; +using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; +using Microsoft.Toolkit.Mvvm; +using DevExpress.Mvvm.CodeGenerators.Tests; +using Microsoft.Toolkit.Mvvm.ComponentModel; + +namespace MvvmToolkit.Mvvm.Tests { + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + partial class DefaultRaiseMethod { + [GenerateProperty] + int value; + } + + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + partial class ParentWithGeneraetedRaiseMethod { } + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + partial class ChildWithParentsGeneratedRaiseMethod : ParentWithGeneraetedRaiseMethod { + [GenerateProperty] + int value; + } + + class ParentWithoutRaiseMethod : INotifyPropertyChanged, INotifyPropertyChanging { + public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangingEventHandler PropertyChanging; + public ParentWithoutRaiseMethod() { + //avoid warnings + PropertyChanged?.Invoke(null, null); + PropertyChanging?.Invoke(null, null); + } + } + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + partial class ChildWithoutRaiseMethod : ParentWithoutRaiseMethod { + // Has error DXCG0008! Look at DiagnosticTest.cs/RaiseMethodNotFoundDiagnostic() + } + + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + partial class ParentWithImplementationAndGeneratedRaiseMethod : INotifyPropertyChanged, INotifyPropertyChanging { + public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangingEventHandler PropertyChanging; + } + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + partial class ChildWithParentsGeneratedRaiseMethod2 : ParentWithImplementationAndGeneratedRaiseMethod { + [GenerateProperty] + int value; + } + + class ParentWithEventArgsParameterRaiseMethod : INotifyPropertyChanged, INotifyPropertyChanging { + public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangingEventHandler PropertyChanging; + + protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs) => PropertyChanged?.Invoke(this, eventArgs); + protected void OnPropertyChanging(PropertyChangingEventArgs eventArgs) => PropertyChanging?.Invoke(this, eventArgs); + } + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + partial class ChildWithParentsImplementedRaiseMethod : ParentWithEventArgsParameterRaiseMethod { + [GenerateProperty] + int value; + } + + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + partial class ChildWithParentsStringParameterRaiseMethod4 : ObservableObject { + [GenerateProperty] + int value; + } + + [TestFixture] + class UsingRaiseMethodTests { + [Test] + public void DefaultRaiseMethod() { + var generated = new DefaultRaiseMethod(); + Assert.Throws(() => + DoWith.PropertyChangedEvent( + generated, + () => DoWith.PropertyChangingEvent( + generated, + () => generated.Value = 1, + e => throw new Exception()), + e => throw new Exception()) + ); + } + + [Test] + public void ParentsGeneratedRaiseMethod() { + var generated = new ChildWithParentsGeneratedRaiseMethod(); + Assert.Throws(() => + DoWith.PropertyChangedEvent( + generated, + () => DoWith.PropertyChangingEvent( + generated, + () => generated.Value = 1, + e => throw new Exception()), + e => throw new Exception()) + ); + } + + [Test] + public void ParentWithImplementationAndGeneratedRaiseMethod() { + var generated = new ChildWithParentsGeneratedRaiseMethod2(); + Assert.Throws(() => + DoWith.PropertyChangedEvent( + generated, + () => DoWith.PropertyChangingEvent( + generated, + () => generated.Value = 1, + e => throw new Exception()), + e => throw new Exception()) + ); + } + + [Test] + public void ParentWithEventArgsParameterRaiseMethod() { + var generated = new ChildWithParentsImplementedRaiseMethod(); + Assert.Throws(() => + DoWith.PropertyChangedEvent( + generated, + () => DoWith.PropertyChangingEvent( + generated, + () => generated.Value = 1, + e => throw new Exception()), + e => throw new Exception()) + ); + } + + [Test] + public void ParentWithStringParameterRaiseMethod() { + var generated = new ChildWithParentsStringParameterRaiseMethod4(); + Assert.Throws(() => + DoWith.PropertyChangedEvent( + generated, + () => DoWith.PropertyChangingEvent( + generated, + () => generated.Value = 1, + e => throw new Exception()), + e => throw new Exception()) + ); + } + } +} diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/AttributeTransferTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/AttributeTransferTests.cs index 5d14a1b..263ee6a 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/AttributeTransferTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/AttributeTransferTests.cs @@ -31,13 +31,13 @@ partial class AttributeTransfer { [TestFixture] public class AttributeTransferTests { [Test] - public void AttributeTransfer() { - var noAttributeProperty = typeof(AttributeTransfer).GetProperty("NoAttribute"); + public void AttributeTransferTest() { + var noAttributeProperty = typeof(AttributeTransfer).GetProperty(nameof(AttributeTransfer.NoAttribute)); var attributes = Attribute.GetCustomAttributes(noAttributeProperty); var expectedAttributes = new Attribute[] { }; Assert.AreEqual(expectedAttributes, attributes); - var withMultipleAttributesProperty = typeof(AttributeTransfer).GetProperty("WithMultipleAttributes"); + var withMultipleAttributesProperty = typeof(AttributeTransfer).GetProperty(nameof(AttributeTransfer.WithMultipleAttributes)); attributes = Attribute.GetCustomAttributes(withMultipleAttributesProperty); expectedAttributes = new Attribute[] { new System.ComponentModel.DataAnnotations.RangeAttribute(0, 1), diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/Helper.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/Helper.cs deleted file mode 100644 index 7e271ec..0000000 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/Helper.cs +++ /dev/null @@ -1,61 +0,0 @@ -using NUnit.Framework; -using System; -using System.ComponentModel; - -namespace Prism.Mvvm.Tests { - static class DoWith { - static void EventCore( - TSender sender, - Action action, - Action eventAction, - Func, THandler> subscribe, - Action unsubscribe, - int expectedFireCount = 1) - where TArgs : EventArgs { - - var fireCount = 0; - EventHandler handler = (o, e) => { - Assert.AreSame(o, sender); - eventAction(e); - fireCount++; - }; - - var h = subscribe(sender, handler); - try { - action(); - } finally { - unsubscribe(sender, h); - } - Assert.AreEqual(expectedFireCount, fireCount); - } - - public static void PropertyChangedEvent(INotifyPropertyChanged inpc, Action action, Action eventAction, int fireCount = 1) { - EventCore( - inpc, - action, - eventAction, - (o, handler) => { - PropertyChangedEventHandler x = (sender, args) => handler(sender, args); - inpc.PropertyChanged += x; - return x; - }, - (o, handler) => inpc.PropertyChanged -= handler, - fireCount - ); - } - public static void PropertyChangingEvent(INotifyPropertyChanging inpc, Action action, Action eventAction, int fireCount = 1) { - EventCore( - inpc, - action, - eventAction, - (o, handler) => { - PropertyChangingEventHandler x = (sender, args) => handler(sender, args); - inpc.PropertyChanging += x; - return x; - }, - (o, handler) => inpc.PropertyChanging -= handler, - fireCount - ); - } - } -} diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/InterfacesTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/InterfacesTests.cs index 43aefb9..15cf680 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/InterfacesTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/InterfacesTests.cs @@ -36,6 +36,10 @@ partial class FullImplemented : INotifyPropertyChanged, INotifyPropertyChanging, public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangingEventHandler PropertyChanging; public event EventHandler IsActiveChanged; + public FullImplemented() { + //avoid warninngs + IsActiveChanged?.Invoke(null, null); + } } diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/NullableAnnotationTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/NullableAnnotationTests.cs index 9e7d7c4..2666a0a 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/NullableAnnotationTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/NullableAnnotationTests.cs @@ -33,6 +33,7 @@ void NonNullableParameter1(string str) { } int? nullableInt2; [GenerateProperty] string string2; +#pragma warning disable 8632 [GenerateProperty] string? nullableString2; @@ -46,5 +47,6 @@ void NonNullableParameter2(string str) { } Task NonNullableParameterAsync2(string str) => Task.CompletedTask; void OnString1Changed(string str) { } +#pragma warning restore 8632 } } diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/PropertyGenerationTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/PropertyGenerationTests.cs index 1a954fa..d62ba61 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/PropertyGenerationTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/PropertyGenerationTests.cs @@ -13,7 +13,7 @@ partial class WithTwoMvvmAttribute { [DevExpress.Mvvm.CodeGenerators.GenerateProperty] int dxProperty; [DevExpress.Mvvm.CodeGenerators.GenerateCommand] - void DxMethod() { } + void DxMethod() { dxProperty++; } } [TestFixture] diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/UsingRaiseMethodTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/UsingRaiseMethodTests.cs index 043a5d8..4813e32 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/UsingRaiseMethodTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/PrismTest/UsingRaiseMethodTests.cs @@ -2,6 +2,7 @@ using System; using System.ComponentModel; using DevExpress.Mvvm.CodeGenerators.Prism; +using DevExpress.Mvvm.CodeGenerators.Tests; namespace Prism.Mvvm.Tests { [GenerateViewModel(ImplementINotifyPropertyChanging = true)] @@ -21,6 +22,11 @@ partial class ChildWithParentsGeneratedRaiseMethod : ParentWithGeneraetedRaiseMe class ParentWithoutRaiseMethod : INotifyPropertyChanged, INotifyPropertyChanging { public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangingEventHandler PropertyChanging; + public ParentWithoutRaiseMethod() { + //avoid warnings + PropertyChanged?.Invoke(null, null); + PropertyChanging?.Invoke(null, null); + } } [GenerateViewModel(ImplementINotifyPropertyChanging = true)] partial class ChildWithoutRaiseMethod : ParentWithoutRaiseMethod { diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/SharedTests.projitems b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/SharedTests.projitems index 94128da..325a032 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/SharedTests.projitems +++ b/DevExpress.Mvvm.CodeGenerators.Tests/SharedTests/SharedTests.projitems @@ -12,7 +12,7 @@ - + @@ -20,16 +20,20 @@ - - + + + + + + + - diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests.WinUI/UnitTests.WinUI.csproj b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests.WinUI/UnitTests.WinUI.csproj index b46a2ea..4160d8c 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests.WinUI/UnitTests.WinUI.csproj +++ b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests.WinUI/UnitTests.WinUI.csproj @@ -6,7 +6,7 @@ 9 true - 8618,8604 + true @@ -23,7 +23,7 @@ - ..\..\..\2022.1\Bin\WinUI\Sdk\Mvvm\DevExpress.WinUI.Mvvm.v22.1.dll + ..\..\..\..\2022.1\Bin\WinUI\Sdk\Mvvm\DevExpress.WinUI.Mvvm.v22.1.dll diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/DiagnosticTests/DiagnosticTestMvvmLight.cs b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/DiagnosticTests/DiagnosticTestMvvmLight.cs index 0e9237a..8184d65 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/DiagnosticTests/DiagnosticTestMvvmLight.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/DiagnosticTests/DiagnosticTestMvvmLight.cs @@ -21,12 +21,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - Assert.AreEqual(2, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(2, trees.Count()); Assert.AreEqual(GeneratorDiagnostics.NoPartialModifier.Id, diagnostics[0].Id); } @@ -46,13 +43,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); - - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(GeneratorDiagnostics.InvalidPropertyName.Id, diagnostics[0].Id); } @@ -77,13 +70,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(4, diagnostics.Count()); foreach(var diagnostic in diagnostics) Assert.AreEqual(GeneratorDiagnostics.OnChangedMethodNotFound.Id, diagnostic.Id); @@ -108,14 +97,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); - - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(2, diagnostics.Count()); Assert.AreEqual(GeneratorDiagnostics.IncorrectCommandSignature.Id, diagnostics[0].Id); Assert.AreEqual(GeneratorDiagnostics.IncorrectCommandSignature.Id, diagnostics[1].Id); @@ -155,13 +139,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); - - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(6, diagnostics.Count()); foreach(var diagnostic in diagnostics) Assert.AreEqual(GeneratorDiagnostics.CanExecuteMethodNotFound.Id, diagnostic.Id); @@ -188,13 +168,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(2, diagnostics.Count()); foreach(var diagnostic in diagnostics) Assert.AreEqual(GeneratorDiagnostics.RaiseMethodNotFound.Id, diagnostic.Id); @@ -223,13 +199,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(2, diagnostics.Count()); foreach(var diagnostic in diagnostics) Assert.AreEqual(GeneratorDiagnostics.TwoSuitableMethods.Id, diagnostic.Id); @@ -243,33 +215,17 @@ public void TwoGenerateViewModelAttributeDiagnostic(string generateViewModel) { partial class TwoGenerateViewModelAttributeClass { } } "; - Compilation inputCompilation = CSharpCompilation.Create("MyCompilation", - new[] { CSharpSyntaxTree.ParseText(sourceCode) }, - new[] { - MetadataReference.CreateFromFile(typeof(System.Windows.Input.ICommand).Assembly.Location), - MetadataReference.CreateFromFile(typeof(RelayCommand).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Prism.Commands.DelegateCommand).Assembly.Location), - MetadataReference.CreateFromFile(typeof(DevExpress.Mvvm.DelegateCommand).Assembly.Location), - MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - MetadataReference.CreateFromFile(typeof(INotifyPropertyChanged).Assembly.Location), - }, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - ViewModelGenerator generator = new ViewModelGenerator(); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - var asdf = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(GeneratorHelper.CreateCompilation(sourceCode, new[] { + typeof(INotifyPropertyChanged), + typeof(RelayCommand), + typeof(Prism.Commands.DelegateCommand), + typeof(DevExpress.Mvvm.DelegateCommand), + })); Assert.AreEqual(GeneratorDiagnostics.MoreThanOneGenerateViewModelAttributes.Id, diagnostics[0].Id); - Assert.AreEqual(4, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(4, trees.Count()); } public static Compilation CreateCompilation(string source) => - CSharpCompilation.Create("MyCompilation", - new[] { CSharpSyntaxTree.ParseText(source) }, - new[] { - MetadataReference.CreateFromFile(typeof(System.Windows.Input.ICommand).Assembly.Location), - MetadataReference.CreateFromFile(typeof(RelayCommand).Assembly.Location), - MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - MetadataReference.CreateFromFile(typeof(INotifyPropertyChanged).Assembly.Location), - }, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + GeneratorHelper.CreateCompilation(source, new[] { typeof(INotifyPropertyChanged), typeof(RelayCommand) }); } } diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/DiagnosticTests/DiagnosticTestMvvmToolkit.cs b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/DiagnosticTests/DiagnosticTestMvvmToolkit.cs new file mode 100644 index 0000000..f74047c --- /dev/null +++ b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/DiagnosticTests/DiagnosticTestMvvmToolkit.cs @@ -0,0 +1,272 @@ +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using NUnit.Framework; +using System.ComponentModel; +using System.Linq; +using System.Collections.Immutable; +using System.Collections.Generic; + +namespace DevExpress.Mvvm.CodeGenerators.Tests { + [TestFixture] + public class DiagnosticTestsMvvmToolkit { + [Test] + public void NoPartialDiagnostic() { + var sourceCode = @"using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; + +namespace Test { + [GenerateViewModel] + class NoPartialClass { } + + public class Program { + public static void Main(string[] args) { } + } +} +"; + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); + + Assert.AreEqual(2, trees.Count()); + Assert.AreEqual(GeneratorDiagnostics.NoPartialModifier.Id, diagnostics[0].Id); + } + + [Test] + public void InvalidPropertyNameDiagnostic() { + var sourceCode = @"using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; + +namespace Test { + [GenerateViewModel] + partial class WithProperty { + [GenerateProperty] + int Property; + } + + public class Program { + public static void Main(string[] args) { } + } +} +"; + + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); + + Assert.AreEqual(3, trees.Count()); + Assert.AreEqual(GeneratorDiagnostics.InvalidPropertyName.Id, diagnostics[0].Id); + } + + [Test] + public void OnChangedMethodNotFoundDiagnostic() { + var sourceCode = @"using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; + +namespace Test { + [GenerateViewModel] + partial class OnChangedMethodNotFound { + [GenerateProperty(OnChangedMethod = ""NotCreatedChangedMethod"", OnChangingMethod = ""NotCreatedChangingMethod"")] + int value1; + [GenerateProperty(OnChangedMethod = ""IncorrectSignatureChangedMethod"", OnChangingMethod = ""IncorrectSignatureChangingMethod"")] + int value2; + + public int IncorrectSignatureChangedMethod() { return 1; } + public void IncorrectSignatureChangingMethod(int arg1, int arg2) { } + } + + public class Program { + public static void Main(string[] args) { } + } +} +"; + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); + + Assert.AreEqual(3, trees.Count()); + Assert.AreEqual(4, diagnostics.Count()); + foreach(var diagnostic in diagnostics) + Assert.AreEqual(GeneratorDiagnostics.OnChangedMethodNotFound.Id, diagnostic.Id); + } + + [Test] + public void IncorrectCommandSignatureDiagnostic() { + var sourceCode = @"using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; + +namespace Test { + [GenerateViewModel] + partial class WithCommand { + [GenerateCommand] + public int Command1() {} + + [GenerateCommand] + public void Command2(int a, int b) {} + } + + public class Program { + public static void Main(string[] args) { } + } +} +"; + + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); + + Assert.AreEqual(3, trees.Count()); + + Assert.AreEqual(2, diagnostics.Count()); + Assert.AreEqual(GeneratorDiagnostics.IncorrectCommandSignature.Id, diagnostics[0].Id); + Assert.AreEqual(GeneratorDiagnostics.IncorrectCommandSignature.Id, diagnostics[1].Id); + } + + [Test] + public void CanExecuteMethodNotFoundDiagnostic() { + var sourceCode = @"using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; +using System.Threading.Tasks; + +namespace Test { + [GenerateViewModel] + partial class CanExecuteMethodNotFound { + [GenerateCommand(CanExecuteMethod = ""WrongParameter"")] + public void Command1(int arg) { } + [GenerateCommand(CanExecuteMethod = ""NoCreated"")] + public void Command2(int arg) { } + [GenerateCommand(CanExecuteMethod = ""ReturnNoBool"")] + public void Command3(int arg) { } + + [GenerateCommand(CanExecuteMethod = ""WrongParameter"")] + public Task AsyncCommand1(int arg) => Task.CompletedTask; + [GenerateCommand(CanExecuteMethod = ""NoCreated"")] + public Task AsyncCommand2(int arg) => Task.CompletedTask; + [GenerateCommand(CanExecuteMethod = ""ReturnNoBool"")] + public Task AsyncCommand3(int arg) => Task.CompletedTask; + + public bool WrongParameter() => true; + public bool WrongParameter(string arg) => arg.Length > 0; + public bool WrongParameter(int arg1, int arg2) => arg1 > arg2; + + public int ReturnNoBool(int arg) => arg; + } + + public class Program { + public static void Main(string[] args) { } + } +} +"; + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); + + Assert.AreEqual(3, trees.Count()); + Assert.AreEqual(6, diagnostics.Count()); + foreach(var diagnostic in diagnostics) + Assert.AreEqual(GeneratorDiagnostics.CanExecuteMethodNotFound.Id, diagnostic.Id); + } + + [Test] + public void RaiseMethodNotFoundDiagnostic() { + var sourceCode = @"using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; +using System.ComponentModel; + +namespace Test { + class ParentWithoutRaiseMethod : INotifyPropertyChanged, INotifyPropertyChanging { + public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangingEventHandler PropertyChanging; + } + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + partial class ChildWithoutRaiseMethod : ParentWithoutRaiseMethod { + [GenerateProperty] + int value; + } + + public class Program { + public static void Main(string[] args) { } + } +} +"; + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); + + Assert.AreEqual(3, trees.Count()); + Assert.AreEqual(2, diagnostics.Count()); + foreach(var diagnostic in diagnostics) + Assert.AreEqual(GeneratorDiagnostics.OnMethodNotFound.Id, diagnostic.Id); + } + + [Test] + public void TwoSuitableMethodsDiagnostic() { + var sourceCode = @"using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; +using System.Threading.Tasks; + +namespace Test { + [GenerateViewModel] + partial class TwoSuitableMethods { + [GenerateProperty(OnChangedMethod = ""TwoChangedMethods"", OnChangingMethod = ""TwoChangingMethods"")] + int value; + + public void TwoChangedMethods() { } + public void TwoChangedMethods(int arg) { } + + public void TwoChangingMethods() { } + public void TwoChangingMethods(int arg) { } + } + + public class Program { + public static void Main(string[] args) { } + } +} +"; + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); + + Assert.AreEqual(3, trees.Count()); + Assert.AreEqual(2, diagnostics.Count()); + foreach(var diagnostic in diagnostics) + Assert.AreEqual(GeneratorDiagnostics.TwoSuitableMethods.Id, diagnostic.Id); + } + [Test] + public void NoBaseObservableRecipientClass() { + var sourceCode = @"using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; +using Microsoft.Toolkit.Mvvm.ComponentModel; + +namespace Test { + [GenerateViewModel] + partial class Broadcast { + [GenerateProperty(Broadcast = true)] + int value; + public Broadcast() => value.ToString(); //avoid not used warning + } +} +"; + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); + Assert.AreEqual(3, trees.Count()); + Assert.AreEqual(GeneratorDiagnostics.NoBaseObservableRecipientClass.Id, diagnostics.Single().Id); + } + [Test] + public void NoBaseObservableValidatorClass() { + var sourceCode = @"using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; +using Microsoft.Toolkit.Mvvm.ComponentModel; + +namespace Test { + [GenerateViewModel] + partial class Validator { + [GenerateProperty(Validate = true)] + int value; + public Validator() => value.ToString(); //avoid not used warning + } +} +"; + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); + Assert.AreEqual(3, trees.Count()); + Assert.AreEqual(GeneratorDiagnostics.NoBaseObservableValidatorClass.Id, diagnostics.Single().Id); + } + [TestCase("[DevExpress.Mvvm.CodeGenerators.Prism.GenerateViewModel]\r\n")] + [TestCase("[DevExpress.Mvvm.CodeGenerators.GenerateViewModel]\r\n")] + public void TwoGenerateViewModelAttributeDiagnostic(string generateViewModel) { + var sourceCode = "namespace Test {\r\n" + + generateViewModel + + @"[DevExpress.Mvvm.CodeGenerators.MvvmToolkit.GenerateViewModel] + partial class TwoGenerateViewModelAttributeClass { } + } +"; + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(GeneratorHelper.CreateCompilation(sourceCode, new[] { + typeof(INotifyPropertyChanged), + typeof(RelayCommand), + typeof(Prism.Commands.DelegateCommand), + typeof(DevExpress.Mvvm.DelegateCommand), + })); + + Assert.AreEqual(GeneratorDiagnostics.MoreThanOneGenerateViewModelAttributes.Id, diagnostics[0].Id); + Assert.AreEqual(4, trees.Count()); + } + public static Compilation CreateCompilation(string source) => + GeneratorHelper.CreateCompilation(source, new[] { typeof(INotifyPropertyChanged), typeof(RelayCommand) }); + } +} diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/DiagnosticTests/DiagnosticTestsDx.cs b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/DiagnosticTests/DiagnosticTestsDx.cs index f75fa25..956c273 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/DiagnosticTests/DiagnosticTestsDx.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/DiagnosticTests/DiagnosticTestsDx.cs @@ -20,12 +20,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - Assert.AreEqual(2, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(2, trees.Count()); Assert.AreEqual(GeneratorDiagnostics.NoPartialModifier.Id, diagnostics[0].Id); } @@ -45,13 +42,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); - - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(GeneratorDiagnostics.InvalidPropertyName.Id, diagnostics[0].Id); } @@ -76,13 +69,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(4, diagnostics.Count()); foreach(var diagnostic in diagnostics) Assert.AreEqual(GeneratorDiagnostics.OnChangedMethodNotFound.Id, diagnostic.Id); @@ -107,13 +96,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(2, diagnostics.Count()); Assert.AreEqual(GeneratorDiagnostics.IncorrectCommandSignature.Id, diagnostics[0].Id); @@ -154,13 +139,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); - - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(6, diagnostics.Count()); foreach(var diagnostic in diagnostics) Assert.AreEqual(GeneratorDiagnostics.CanExecuteMethodNotFound.Id, diagnostic.Id); @@ -187,13 +168,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(2, diagnostics.Count()); foreach(var diagnostic in diagnostics) Assert.AreEqual(GeneratorDiagnostics.RaiseMethodNotFound.Id, diagnostic.Id); @@ -222,26 +199,17 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(2, diagnostics.Count()); foreach(var diagnostic in diagnostics) Assert.AreEqual(GeneratorDiagnostics.TwoSuitableMethods.Id, diagnostic.Id); } public static Compilation CreateCompilation(string source) => - CSharpCompilation.Create("MyCompilation", - new[] { CSharpSyntaxTree.ParseText(source) }, - new[] { - MetadataReference.CreateFromFile(typeof(System.Windows.Input.ICommand).Assembly.Location), - MetadataReference.CreateFromFile(typeof(DelegateCommand).Assembly.Location), - MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - MetadataReference.CreateFromFile(typeof(INotifyPropertyChanged).Assembly.Location), - }, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + GeneratorHelper.CreateCompilation(source, new[] { + typeof(INotifyPropertyChanged), + typeof(DelegateCommand), + }); } } diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/DiagnosticTests/DiagnosticTestsPrism.cs b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/DiagnosticTests/DiagnosticTestsPrism.cs index 1b7bb37..ba8c0df 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/DiagnosticTests/DiagnosticTestsPrism.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/DiagnosticTests/DiagnosticTestsPrism.cs @@ -20,12 +20,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - Assert.AreEqual(2, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(2, trees.Count()); Assert.AreEqual(GeneratorDiagnostics.NoPartialModifier.Id, diagnostics[0].Id); } @@ -45,13 +42,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); - - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(GeneratorDiagnostics.InvalidPropertyName.Id, diagnostics[0].Id); } @@ -76,13 +69,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(4, diagnostics.Count()); foreach(var diagnostic in diagnostics) Assert.AreEqual(GeneratorDiagnostics.OnChangedMethodNotFound.Id, diagnostic.Id); @@ -107,13 +96,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); - - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(2, diagnostics.Count()); Assert.AreEqual(GeneratorDiagnostics.IncorrectCommandSignature.Id, diagnostics[0].Id); @@ -154,13 +139,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(6, diagnostics.Count()); foreach(var diagnostic in diagnostics) Assert.AreEqual(GeneratorDiagnostics.CanExecuteMethodNotFound.Id, diagnostic.Id); @@ -187,13 +168,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); - - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(2, diagnostics.Count()); foreach(var diagnostic in diagnostics) Assert.AreEqual(GeneratorDiagnostics.RaiseMethodNotFound.Id, diagnostic.Id); @@ -219,13 +196,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(2, diagnostics.Count()); foreach(var diagnostic in diagnostics) Assert.AreEqual(GeneratorDiagnostics.NonNullableDelegateCommandArgument.Id, diagnostic.Id); @@ -254,13 +227,9 @@ public static void Main(string[] args) { } } } "; - Compilation inputCompilation = CreateCompilation(sourceCode); - ViewModelGenerator generator = new ViewModelGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(CreateCompilation(sourceCode)); - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); Assert.AreEqual(2, diagnostics.Count()); foreach(var diagnostic in diagnostics) Assert.AreEqual(GeneratorDiagnostics.TwoSuitableMethods.Id, diagnostic.Id); @@ -284,22 +253,12 @@ partial class TwoGenerateViewModelAttributeClass { } MetadataReference.CreateFromFile(typeof(INotifyPropertyChanged).Assembly.Location), }, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - ViewModelGenerator generator = new ViewModelGenerator(); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + var (trees, diagnostics) = GeneratorHelper.GetDiagnostics(inputCompilation); Assert.AreEqual(GeneratorDiagnostics.MoreThanOneGenerateViewModelAttributes.Id, diagnostics[0].Id); - Assert.AreEqual(3, outputCompilation.SyntaxTrees.Count()); + Assert.AreEqual(3, trees.Count()); } public static Compilation CreateCompilation(string source) => - CSharpCompilation.Create("MyCompilation", - new[] { CSharpSyntaxTree.ParseText(source) }, - new[] { - MetadataReference.CreateFromFile(typeof(System.Windows.Input.ICommand).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Prism.Commands.DelegateCommand).Assembly.Location), - MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - MetadataReference.CreateFromFile(typeof(INotifyPropertyChanged).Assembly.Location), - }, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + GeneratorHelper.CreateCompilation(source, new[] { typeof(INotifyPropertyChanged), typeof(Prism.Commands.DelegateCommand) }); } } diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GenearationTests/CommonGenerationTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GenearationTests/CommonGenerationTests.cs index a9e2b4b..4526f8d 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GenearationTests/CommonGenerationTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GenearationTests/CommonGenerationTests.cs @@ -2,6 +2,8 @@ using Microsoft.CodeAnalysis.CSharp; using NUnit.Framework; using System; +using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; namespace DevExpress.Mvvm.CodeGenerators.Tests { @@ -163,25 +165,7 @@ public void Command1(int arg) { } } static string GenerateCode(string source) { - var references = new[] { - MetadataReference.CreateFromFile(typeof(System.ComponentModel.DataAnnotations.RangeAttribute).Assembly.Location), - MetadataReference.CreateFromFile(typeof(System.Windows.Input.ICommand).Assembly.Location), - MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - }; - Compilation inputCompilation = CSharpCompilation.Create("MyCompilation", - new[] { CSharpSyntaxTree.ParseText(source) }, - references, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - ViewModelGenerator generator = new ViewModelGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); - - GeneratorDriverRunResult runResult = driver.GetRunResult(); - GeneratorRunResult generatorResult = runResult.Results[0]; - - var generatedCode = generatorResult.GeneratedSources[1].SourceText.ToString(); - return generatedCode; + return GeneratorHelper.GenerateCode(source, null); } } } diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GenearationTests/GenerationTestsMvvmLight.cs b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GenearationTests/GenerationTestsMvvmLight.cs index e0d482a..ae70038 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GenearationTests/GenerationTestsMvvmLight.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GenearationTests/GenerationTestsMvvmLight.cs @@ -5,6 +5,7 @@ using Prism.Commands; using DevExpress.Mvvm.CodeGenerators; using GalaSoft.MvvmLight.Command; +using DevExpress.Mvvm.CodeGenerators.Tests; namespace MvvmLight.Mvvm.Tests { [TestFixture] @@ -52,7 +53,7 @@ public static void Main(string[] args) { } } [Test] - public void PrismUsing_Command() { + public void Using_Command() { var source = @" using DevExpress.Mvvm.CodeGenerators.MvvmLight; namespace Test { @@ -139,26 +140,7 @@ partial class Example { } static string GenerateCode(string source) { - var references = new[] { - MetadataReference.CreateFromFile(typeof(System.ComponentModel.DataAnnotations.RangeAttribute).Assembly.Location), - MetadataReference.CreateFromFile(typeof(System.Windows.Input.ICommand).Assembly.Location), - MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - MetadataReference.CreateFromFile(typeof(RelayCommand).Assembly.Location), - }; - Compilation inputCompilation = CSharpCompilation.Create("MyCompilation", - new[] { CSharpSyntaxTree.ParseText(source) }, - references, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - ViewModelGenerator generator = new ViewModelGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); - - GeneratorDriverRunResult runResult = driver.GetRunResult(); - GeneratorRunResult generatorResult = runResult.Results[0]; - - var generatedCode = generatorResult.GeneratedSources[1].SourceText.ToString(); - return generatedCode; + return GeneratorHelper.GenerateCode(source, typeof(RelayCommand)); } [Test] public void FormattingTest() { diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GenearationTests/GenerationTestsMvvmToolkit.cs b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GenearationTests/GenerationTestsMvvmToolkit.cs new file mode 100644 index 0000000..53b5030 --- /dev/null +++ b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GenearationTests/GenerationTestsMvvmToolkit.cs @@ -0,0 +1,343 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using NUnit.Framework; +using System; +using DevExpress.Mvvm.CodeGenerators; +using Microsoft.Toolkit.Mvvm; +using Microsoft.Toolkit.Mvvm.Input; +using Microsoft.Toolkit.Mvvm.Messaging; +using DevExpress.Mvvm.CodeGenerators.Tests; + +namespace MvvmToolkit.Mvvm.Tests { + [TestFixture] + public class GenerationTestsMvvmToolkit { + [Test] + public void GenerationFormat() { + var source = @"using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; + + namespace Test { + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + partial class ExamplePrism { + [GenerateProperty] + [System.ComponentModel.DataAnnotations.Range(0, + 1)] + int property; + + [GenerateCommand] + public void Method(int? arg) { } + } + + public class Program { + public static void Main(string[] args) { } + } + } + "; + string generatedCode = GenerateCode(source); + var tabs = 0; + foreach(var str in generatedCode.Split(new[] { Environment.NewLine }, StringSplitOptions.None)) { + if(string.IsNullOrEmpty(str)) + continue; + + if(str.Contains("}") && !str.Contains("{")) + if(str.EndsWith("}")) + tabs--; + else + Assert.Fail(); + + var expectedLeadingWhitespaceCount = tabs * 4; + var leadingWhitespaceCount = str.Length - str.TrimStart().Length; + Assert.AreEqual(expectedLeadingWhitespaceCount, leadingWhitespaceCount); + + if(str.EndsWith("{")) + tabs++; + } + } + + [Test] + public void Using_Command() { + var source = @" + using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; + namespace Test { + [GenerateViewModel] + partial class Example { + [GenerateCommand] + public void Method(int arg) { } + } + }"; + string generatedCode = GenerateCode(source); + StringAssert.Contains("using Microsoft.Toolkit.Mvvm.Input;", generatedCode); + StringAssert.Contains("MethodCommand", generatedCode); + } + [Test] + public void GenerateComments() { + var source = @"using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; + + namespace Test { + [GenerateViewModel] + partial class Example { + + // Ordinary comment + /// + /// Ignorable comment + /// + + /// + /// Test property comment + /// + // Ordinary comment + [GenerateProperty] + int property; + + /** + + MultiLine Comment + + */ + [GenerateProperty] + int property2; + + /// + /// Test command comment + /// + [GenerateCommand] + public void Method(int? arg) { } + } + } + "; + var propertyComment = +@" /// + /// Test command comment + /// "; + var property2Comment = +@" /** + + MultiLine Comment + + */"; + var commandComment = +@" /// + /// Test property comment + /// "; + var generatedCode = GenerateCode(source); + StringAssert.Contains(propertyComment, generatedCode); + StringAssert.Contains(property2Comment, generatedCode); + StringAssert.Contains(commandComment, generatedCode); + StringAssert.DoesNotContain("Ignorable comment", generatedCode); + StringAssert.DoesNotContain("Ordinary comment", generatedCode); + } + [Test] + public void GeneratePropertyName_() { + var source = @"using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; + + namespace Test { + [GenerateViewModel] + partial class Example { + [GenerateProperty] + int _; + } + } + "; + Assert.DoesNotThrow(() => GenerateCode(source)); + } + + static string GenerateCode(string source) { + return GeneratorHelper.GenerateCode(source, typeof(RelayCommand)); + } + [Test] + public void FormattingTest() { + const string source = +@"using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; +using System.Threading.Tasks; +using System; + +namespace Test { + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + partial class Example0 { + [GenerateProperty] + int _Int; + + [GenerateProperty(OnChangedMethod = ""OnStrChanged_"", OnChangingMethod = ""OnStrChanging_"")] + string _Str; + void OnStrChanged_() { } + void OnStrChanging_(string newValue) { } + + /// + /// Test property comment + /// + [GenerateProperty] + [System.ComponentModel.DataAnnotations.Required] + [System.ComponentModel.DataAnnotations.Key] + long _Long; + void OnLongChanged(long oldValue) { } + + [GenerateProperty] + DateTime _DateTime; + void OnDateTimeChanging() { } + + [GenerateProperty(IsVirtual = true, SetterAccessModifier = AccessModifier.Protected)] + double _Double; + + [GenerateProperty] + int _; + + /// + /// Test command comment + /// + [GenerateCommand] + public void Command1(int? arg) { } + bool CanCommand1(int? arg) => true; + + [GenerateCommand(Name = ""SomeCommand"")] + public Task Command2() => null; + bool CanCommand2(int arg) => true; + + [GenerateCommand(CanExecuteMethod = ""CanCommand3_"")] + public void Command3(int? arg) { } + bool CanCommand3_(int? arg) => true; + + [GenerateProperty] + bool a, b; + + void OnCleanup() { } + } + }"; + const string expected = +@"using System.Collections.Generic; +using System.ComponentModel; +using Microsoft.Toolkit.Mvvm; +using Microsoft.Toolkit.Mvvm.Input; + +#nullable enable + +namespace Test { + partial class Example0 : INotifyPropertyChanged, INotifyPropertyChanging { + public event PropertyChangedEventHandler? PropertyChanged; + public event PropertyChangingEventHandler? PropertyChanging; + + protected void OnPropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e); + protected void OnPropertyChanging(PropertyChangingEventArgs e) => PropertyChanging?.Invoke(this, e); + + public int Int { + get => _Int; + set { + if(EqualityComparer.Default.Equals(_Int, value)) return; + OnPropertyChanging(IntChangingEventArgs); + _Int = value; + OnPropertyChanged(IntChangedEventArgs); + } + } + public string? Str { + get => _Str; + set { + if(EqualityComparer.Default.Equals(_Str, value)) return; + OnPropertyChanging(StrChangingEventArgs); + OnStrChanging_(value); + _Str = value; + OnPropertyChanged(StrChangedEventArgs); + OnStrChanged_(); + } + } + /// + /// Test property comment + /// + [System.ComponentModel.DataAnnotations.RequiredAttribute] + [System.ComponentModel.DataAnnotations.KeyAttribute] + public long Long { + get => _Long; + set { + if(EqualityComparer.Default.Equals(_Long, value)) return; + OnPropertyChanging(LongChangingEventArgs); + var oldValue = _Long; + _Long = value; + OnPropertyChanged(LongChangedEventArgs); + OnLongChanged(oldValue); + } + } + public System.DateTime DateTime { + get => _DateTime; + set { + if(EqualityComparer.Default.Equals(_DateTime, value)) return; + OnPropertyChanging(DateTimeChangingEventArgs); + OnDateTimeChanging(); + _DateTime = value; + OnPropertyChanged(DateTimeChangedEventArgs); + } + } + public virtual double Double { + get => _Double; + protected set { + if(EqualityComparer.Default.Equals(_Double, value)) return; + OnPropertyChanging(DoubleChangingEventArgs); + _Double = value; + OnPropertyChanged(DoubleChangedEventArgs); + } + } + public bool A { + get => a; + set { + if(EqualityComparer.Default.Equals(a, value)) return; + OnPropertyChanging(AChangingEventArgs); + a = value; + OnPropertyChanged(AChangedEventArgs); + } + } + public bool B { + get => b; + set { + if(EqualityComparer.Default.Equals(b, value)) return; + OnPropertyChanging(BChangingEventArgs); + b = value; + OnPropertyChanged(BChangedEventArgs); + } + } + RelayCommand? command1Command; + /// + /// Test command comment + /// + public RelayCommand Command1Command => command1Command ??= new RelayCommand(Command1, CanCommand1); + RelayCommand? someCommand; + public RelayCommand SomeCommand => someCommand ??= new RelayCommand(async () => await Command2()); + RelayCommand? command3Command; + public RelayCommand Command3Command => command3Command ??= new RelayCommand(Command3, CanCommand3_); + static PropertyChangedEventArgs IntChangedEventArgs = new PropertyChangedEventArgs(nameof(Int)); + static PropertyChangedEventArgs StrChangedEventArgs = new PropertyChangedEventArgs(nameof(Str)); + static PropertyChangedEventArgs LongChangedEventArgs = new PropertyChangedEventArgs(nameof(Long)); + static PropertyChangedEventArgs DateTimeChangedEventArgs = new PropertyChangedEventArgs(nameof(DateTime)); + static PropertyChangedEventArgs DoubleChangedEventArgs = new PropertyChangedEventArgs(nameof(Double)); + static PropertyChangedEventArgs AChangedEventArgs = new PropertyChangedEventArgs(nameof(A)); + static PropertyChangedEventArgs BChangedEventArgs = new PropertyChangedEventArgs(nameof(B)); + static PropertyChangingEventArgs IntChangingEventArgs = new PropertyChangingEventArgs(nameof(Int)); + static PropertyChangingEventArgs StrChangingEventArgs = new PropertyChangingEventArgs(nameof(Str)); + static PropertyChangingEventArgs LongChangingEventArgs = new PropertyChangingEventArgs(nameof(Long)); + static PropertyChangingEventArgs DateTimeChangingEventArgs = new PropertyChangingEventArgs(nameof(DateTime)); + static PropertyChangingEventArgs DoubleChangingEventArgs = new PropertyChangingEventArgs(nameof(Double)); + static PropertyChangingEventArgs AChangingEventArgs = new PropertyChangingEventArgs(nameof(A)); + static PropertyChangingEventArgs BChangingEventArgs = new PropertyChangingEventArgs(nameof(B)); + } +} +"; + string generatedCode = GenerateCode(source); + Assert.AreEqual(expected, generatedCode); + + } + [Test] + public void PrivateInSealedClass() { + const string source = +@"using DevExpress.Mvvm.CodeGenerators.MvvmToolkit; +using Microsoft.Toolkit.Mvvm; + +namespace Test { + [GenerateViewModel(ImplementINotifyPropertyChanging = true)] + sealed partial class SealdClass { + [GenerateProperty] + string str; + [GenerateCommand] + public void Command1(int arg) { } + } +}"; + var generated = GenerateCode(source); + StringAssert.DoesNotContain("protected", generated); + } + } +} + diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GenearationTests/GenerationTestsPrism.cs b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GenearationTests/GenerationTestsPrism.cs index a1cd30c..b2c70f3 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GenearationTests/GenerationTestsPrism.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GenearationTests/GenerationTestsPrism.cs @@ -4,6 +4,7 @@ using System; using Prism.Commands; using DevExpress.Mvvm.CodeGenerators; +using DevExpress.Mvvm.CodeGenerators.Tests; namespace Prism.Mvvm.Tests { [TestFixture] @@ -51,7 +52,7 @@ public static void Main(string[] args) { } } [Test] - public void PrismUsing_Command() { + public void Using_Command() { var source = @" using DevExpress.Mvvm.CodeGenerators.Prism; namespace Test { @@ -139,26 +140,7 @@ partial class Example { } static string GenerateCode(string source) { - var references = new[] { - MetadataReference.CreateFromFile(typeof(System.ComponentModel.DataAnnotations.RangeAttribute).Assembly.Location), - MetadataReference.CreateFromFile(typeof(System.Windows.Input.ICommand).Assembly.Location), - MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - MetadataReference.CreateFromFile(typeof(DelegateCommand).Assembly.Location), - }; - Compilation inputCompilation = CSharpCompilation.Create("MyCompilation", - new[] { CSharpSyntaxTree.ParseText(source) }, - references, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - ViewModelGenerator generator = new ViewModelGenerator(); - - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); - - GeneratorDriverRunResult runResult = driver.GetRunResult(); - GeneratorRunResult generatorResult = runResult.Results[0]; - - var generatedCode = generatorResult.GeneratedSources[1].SourceText.ToString(); - return generatedCode; + return GeneratorHelper.GenerateCode(source, typeof(DelegateCommand)); } [Test] public void FormattingTest() { diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GeneratorHelper.cs b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GeneratorHelper.cs new file mode 100644 index 0000000..5598616 --- /dev/null +++ b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/GeneratorHelper.cs @@ -0,0 +1,47 @@ +using DevExpress.Mvvm.Native; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +namespace DevExpress.Mvvm.CodeGenerators.Tests { + public static class GeneratorHelper { + public static string GenerateCode(string source, Type frameworkType) { + Compilation inputCompilation = CreateCompilation(source, frameworkType.YieldIfNotNull().Concat(typeof(System.ComponentModel.DataAnnotations.RangeAttribute).Yield())); + ViewModelGenerator generator = new ViewModelGenerator(); + + GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); + driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + + GeneratorDriverRunResult runResult = driver.GetRunResult(); + GeneratorRunResult generatorResult = runResult.Results[0]; + + var generatedCode = generatorResult.GeneratedSources[1].SourceText.ToString(); + return generatedCode; + } + + public static Compilation CreateCompilation(string source, IEnumerable types) { + IEnumerable baseTypes = new[] { + typeof(System.Windows.Input.ICommand), + typeof(object), + }.Concat(types); + Compilation inputCompilation = CSharpCompilation.Create( + "MyCompilation", + new[] { CSharpSyntaxTree.ParseText(source) }, + baseTypes.Select(x => MetadataReference.CreateFromFile(x.Assembly.Location)), + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + ); + return inputCompilation; + } + + public static (IEnumerable, ImmutableArray) GetDiagnostics(Compilation inputCompilation) { + ViewModelGenerator generator = new ViewModelGenerator(); + GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); + _ = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); + return (outputCompilation.SyntaxTrees, diagnostics); + } + } +} + diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/UnitTests.csproj b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/UnitTests.csproj index d646db0..c03205a 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/UnitTests.csproj +++ b/DevExpress.Mvvm.CodeGenerators.Tests/UnitTests/UnitTests.csproj @@ -6,7 +6,7 @@ 9 true - 8618,8604 + true true @@ -15,6 +15,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/WinUITest/WinUITests.csproj b/DevExpress.Mvvm.CodeGenerators.Tests/WinUITest/WinUITests.csproj index d220bc7..45e0b65 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/WinUITest/WinUITests.csproj +++ b/DevExpress.Mvvm.CodeGenerators.Tests/WinUITest/WinUITests.csproj @@ -6,7 +6,8 @@ DevExpress.Mvvm.CodeGenerators.WinUITests 9 true - 8618,8604 + True + 0105 WINUI @@ -15,11 +16,12 @@ - ..\..\..\2022.1\Bin\WinUI\Sdk\Mvvm\DevExpress.WinUI.Mvvm.v22.1.dll + ..\..\..\..\2022.1\Bin\WinUI\Sdk\Mvvm\DevExpress.WinUI.Mvvm.v22.1.dll + diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/WithoutMvvm/PropertyGenerationTests.cs b/DevExpress.Mvvm.CodeGenerators.Tests/WithoutMvvm/PropertyGenerationTests.cs index 713f381..631695c 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/WithoutMvvm/PropertyGenerationTests.cs +++ b/DevExpress.Mvvm.CodeGenerators.Tests/WithoutMvvm/PropertyGenerationTests.cs @@ -11,6 +11,10 @@ partial class GlobalClass { namespace DevExpress.Mvvm.CodeGenerators.Tests { class ImplementedINPCingClass : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; + public ImplementedINPCingClass() { + //avoid warnings + PropertyChanged?.Invoke(null, null); + } public int B { get; set; } protected void RaisePropertyChanging(PropertyChangingEventArgs e) => B = 1; protected void RaisePropertyChanging(string e) => B = 1; @@ -141,8 +145,10 @@ void OnNonNullableIntChanging(int? newValue) => NonNullableIntNewValue = newValue; #nullable disable +#pragma warning disable DXCG1001 [GenerateProperty(OnChangedMethod = "OnChanged", OnChangingMethod = "OnChanging")] int noParameter; +#pragma warning restore DXCG1001 public bool ChangedMethodVisited; public bool ChangingMethodVisited; diff --git a/DevExpress.Mvvm.CodeGenerators.Tests/WithoutMvvm/WithoutMvvm.csproj b/DevExpress.Mvvm.CodeGenerators.Tests/WithoutMvvm/WithoutMvvm.csproj index 283b458..8b10802 100644 --- a/DevExpress.Mvvm.CodeGenerators.Tests/WithoutMvvm/WithoutMvvm.csproj +++ b/DevExpress.Mvvm.CodeGenerators.Tests/WithoutMvvm/WithoutMvvm.csproj @@ -5,6 +5,7 @@ false 9 true + True diff --git a/DevExpress.Mvvm.CodeGenerators/AnalyzerReleasesTracking/AnalyzerReleases.Shipped.md b/DevExpress.Mvvm.CodeGenerators/AnalyzerReleasesTracking/AnalyzerReleases.Shipped.md index 15765c4..7da9dc6 100644 --- a/DevExpress.Mvvm.CodeGenerators/AnalyzerReleasesTracking/AnalyzerReleases.Shipped.md +++ b/DevExpress.Mvvm.CodeGenerators/AnalyzerReleasesTracking/AnalyzerReleases.Shipped.md @@ -37,3 +37,12 @@ DXCG0011 | DevExpress.Mvvm.CodeGenerators | error | Method has Non-Nullable Argu Rule ID | Category | Severity | Notes --------|----------|----------|-------------------- DXCG0003 | DevExpress.Mvvm.CodeGenerators | error | Cannot find the DevExpress.Mvvm assembly + +## Release 21.2.2 + +### New Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|-------------------- +DXCG0012 | DevExpress.Mvvm.CodeGenerators | error | Cannot find OnPropertyChanged methods +DXCG0013 | DevExpress.Mvvm.CodeGenerators | error | Class should be inherited from the ObservableRecipient class +DXCG0014 | DevExpress.Mvvm.CodeGenerators | error | Class should be inherited from the ObservableValidator class \ No newline at end of file diff --git a/DevExpress.Mvvm.CodeGenerators/Diagnostics/ConstExpressions.cs b/DevExpress.Mvvm.CodeGenerators/Diagnostics/ConstExpressions.cs index 63c2419..f88c7f0 100644 --- a/DevExpress.Mvvm.CodeGenerators/Diagnostics/ConstExpressions.cs +++ b/DevExpress.Mvvm.CodeGenerators/Diagnostics/ConstExpressions.cs @@ -36,10 +36,6 @@ static partial class GeneratorDiagnostics { const string raiseMethodNotFoundTitle = "Cannot find Raise methods"; const string raiseMethodNotFoundMessageFormat = "Cannot find the 'void RaisePropertyChang{0}(PropertyChang{0}EventArgs)' or 'void RaisePropertyChang{0}(string)' methods"; - const string genericViewModelId = idPrefix + "0009"; - const string genericViewModelTitle = "Cannot generate the generic View Model"; - const string genericViewModelMessageFormat = "The '{0}' class must be non-generic"; - const string twoSuitableMethodsId = idPrefix + "1001"; const string twoSuitableMethodsTitle = "The class contains two suitable methods"; const string twoSuitableMethodsMessageFormat = "The '{0}' contains two suitable methods: 'void {1}()' and 'void {1}({2})'. 'void {1}({2})' is used."; @@ -51,5 +47,17 @@ static partial class GeneratorDiagnostics { const string nonNullableDelegateCommandArgumentId = idPrefix + "0011"; const string nonNullableDelegateCommandArgumentTitle = "Non Nullable DelegateCommand Argument"; const string nonNullableDelegateCommandArgumentMessageFormat = "The {0} method parameter cannot be of value types (int, double, bool, etc). Use the Nullable parameter instead."; + + const string onMethodNotFoundId = idPrefix + "0012"; + const string onMethodNotFoundTitle = "Cannot find OnPropertyChanged methods"; + const string onMethodNotFoundMessageFormat = "Cannot find the 'void OnPropertyChang{0}(PropertyChang{0}EventArgs)' or 'void OnPropertyChang{0}(string)' methods"; + + const string noBaseObservableRecipientClassId = idPrefix + "0013"; + const string noBaseObservableRecipientClassTitle = "Class should be inherited from the ObservableRecipient class"; + const string noBaseObservableRecipientClassMessageFormat = "Inherit from the 'ObservableRecipient' class to use the 'Broadcast=true' option in the '{0}' property"; + + const string noBaseObservableValidatorClassId = idPrefix + "0014"; + const string noBaseObservableValidatorClassTitle = "Class should be inherited from the ObservableValidator class"; + const string noBaseObservableValidatorClassMessageFormat = "Inherit from the 'ObservableValidator' class to use the 'Validate=true' option in the '{0}' property"; } } diff --git a/DevExpress.Mvvm.CodeGenerators/Diagnostics/GeneratorDiagnostics.cs b/DevExpress.Mvvm.CodeGenerators/Diagnostics/GeneratorDiagnostics.cs index 293b2e6..5093237 100644 --- a/DevExpress.Mvvm.CodeGenerators/Diagnostics/GeneratorDiagnostics.cs +++ b/DevExpress.Mvvm.CodeGenerators/Diagnostics/GeneratorDiagnostics.cs @@ -11,9 +11,12 @@ public static partial class GeneratorDiagnostics { public static readonly DiagnosticDescriptor IncorrectCommandSignature = CreateDiagnosticDescriptor(incorrectCommandSignatureId, incorrectCommandSignatureTitle, incorrectCommandSignatureMessageFormat); public static readonly DiagnosticDescriptor CanExecuteMethodNotFound = CreateDiagnosticDescriptor(canExecuteMethodNotFoundId, canExecuteMethodNotFoundTitle, canExecuteMethodNotFoundMessageFormat); public static readonly DiagnosticDescriptor RaiseMethodNotFound = CreateDiagnosticDescriptor(raiseMethodNotFoundId, raiseMethodNotFoundTitle, raiseMethodNotFoundMessageFormat); + public static readonly DiagnosticDescriptor OnMethodNotFound = CreateDiagnosticDescriptor(onMethodNotFoundId, onMethodNotFoundTitle, onMethodNotFoundMessageFormat); public static readonly DiagnosticDescriptor TwoSuitableMethods = CreateDiagnosticDescriptor(twoSuitableMethodsId, twoSuitableMethodsTitle, twoSuitableMethodsMessageFormat, DiagnosticSeverity.Warning); public static readonly DiagnosticDescriptor MoreThanOneGenerateViewModelAttributes = CreateDiagnosticDescriptor(moreThanOneGenerateViewModelAttributesId, moreThanOneGenerateViewModelAttributesTitle, moreThanOneGenerateViewModelAttributesMessageFormat); public static readonly DiagnosticDescriptor NonNullableDelegateCommandArgument = CreateDiagnosticDescriptor(nonNullableDelegateCommandArgumentId, nonNullableDelegateCommandArgumentTitle, nonNullableDelegateCommandArgumentMessageFormat); + public static readonly DiagnosticDescriptor NoBaseObservableRecipientClass = CreateDiagnosticDescriptor(noBaseObservableRecipientClassId, noBaseObservableRecipientClassTitle, noBaseObservableRecipientClassMessageFormat); + public static readonly DiagnosticDescriptor NoBaseObservableValidatorClass = CreateDiagnosticDescriptor(noBaseObservableValidatorClassId, noBaseObservableValidatorClassTitle, noBaseObservableValidatorClassMessageFormat); public static void ReportNoPartialModifier(this GeneratorExecutionContext context, INamedTypeSymbol classSymbol) => context.ReportDiagnostic(NoPartialModifier, SymbolNameLocation(classSymbol), classSymbol.Name); @@ -25,8 +28,13 @@ public static void ReportIncorrectCommandSignature(this GeneratorExecutionContex context.ReportDiagnostic(IncorrectCommandSignature, SymbolNameLocation(methodSymbol), methodSymbol.ReturnType.ToDisplayStringNullable(), methodSymbol.Name, ParameterTypesToDisplayString(methodSymbol)); public static void ReportCanExecuteMethodNotFound(this GeneratorExecutionContext context, IMethodSymbol methodSymbol, string canExecuteMethodName, string parameterType, IEnumerable candidates) => context.ReportDiagnostic(CanExecuteMethodNotFound, SymbolNameLocation(methodSymbol, AttributesGenerator.CanExecuteMethod), canExecuteMethodName, parameterType, CandidatesMessage(candidates)); - public static void ReportRaiseMethodNotFound(this GeneratorExecutionContext context, INamedTypeSymbol classSymbol, string end) => - context.ReportDiagnostic(RaiseMethodNotFound, SymbolNameLocation(classSymbol), end); + internal static void ReportRaiseMethodNotFound(this GeneratorExecutionContext context, INamedTypeSymbol classSymbol, string end, RaiseMethodPrefix prefix) { + if(prefix == RaiseMethodPrefix.Raise) + context.ReportDiagnostic(RaiseMethodNotFound, SymbolNameLocation(classSymbol), end); + else + context.ReportDiagnostic(OnMethodNotFound, SymbolNameLocation(classSymbol), end); + } + public static void ReportTwoSuitableMethods(this GeneratorExecutionContext context, INamedTypeSymbol classSymbol, IFieldSymbol fieldSymbol, string methodName, string parameterType) => context.ReportDiagnostic(TwoSuitableMethods, SymbolNameLocation(fieldSymbol), classSymbol.Name, methodName, parameterType); public static void ReportMoreThanOneGenerateViewModelAttributes(this GeneratorExecutionContext context, INamedTypeSymbol classSymbol) => @@ -34,6 +42,11 @@ public static void ReportMoreThanOneGenerateViewModelAttributes(this GeneratorEx public static void ReportNonNullableDelegateCommandArgument(this GeneratorExecutionContext context, IMethodSymbol methodSymbol) => context.ReportDiagnostic(NonNullableDelegateCommandArgument, SymbolNameLocation(methodSymbol), methodSymbol.Name); + public static void ReportNoBaseObservableRecipientClass(this GeneratorExecutionContext context, IFieldSymbol fieldSymbol, string propertyName) => + context.ReportDiagnostic(NoBaseObservableRecipientClass, SymbolNameLocation(fieldSymbol), propertyName); + public static void ReportNoBaseObservableValidatorClass(this GeneratorExecutionContext context, IFieldSymbol fieldSymbol, string propertyName) => + context.ReportDiagnostic(NoBaseObservableValidatorClass, SymbolNameLocation(fieldSymbol), propertyName); + static void ReportDiagnostic(this GeneratorExecutionContext context, DiagnosticDescriptor descriptor, Location location, params object[] messageArgs) => context.ReportDiagnostic(Diagnostic.Create(descriptor, location, messageArgs)); static DiagnosticDescriptor CreateDiagnosticDescriptor(string id, string title, string messageFormat, DiagnosticSeverity diagnosticSeverity = DiagnosticSeverity.Error) => diff --git a/DevExpress.Mvvm.CodeGenerators/ExtensionMethods.cs b/DevExpress.Mvvm.CodeGenerators/ExtensionMethods.cs index 5717d74..9a2e10f 100644 --- a/DevExpress.Mvvm.CodeGenerators/ExtensionMethods.cs +++ b/DevExpress.Mvvm.CodeGenerators/ExtensionMethods.cs @@ -16,6 +16,12 @@ static NullableFlowState ToNullableFlowState(NullableAnnotation nullableAnnotati NullableAnnotation.Annotated => NullableFlowState.MaybeNull, _ => NullableFlowState.None }; + public static IEnumerable GetParents(this INamedTypeSymbol typeSymbol) { + for(INamedTypeSymbol parent = typeSymbol.BaseType!; parent != null; parent = parent.BaseType!) { + yield return parent; + } + } + #endregion #region String @@ -25,5 +31,21 @@ static NullableFlowState ToNullableFlowState(NullableAnnotation nullableAnnotati public static string TypeToString(this TypeKind type) => type == TypeKind.Structure ? "struct" : type.ToString().ToLower(); public static string BoolToStringValue(this bool val) => val ? "true" : "false"; + + internal static string ToStringValue(this RaiseMethodPrefix val) { + return val switch { + RaiseMethodPrefix.On => "On", + RaiseMethodPrefix.Raise => "Raise", + _ => throw new InvalidOperationException(), + }; + } + + internal static RaiseMethodPrefix GetRasiePrefix(this SupportedMvvm mvvm) { + return mvvm switch { + SupportedMvvm.None or SupportedMvvm.Dx or SupportedMvvm.Prism or SupportedMvvm.MvvmLight => RaiseMethodPrefix.Raise, + SupportedMvvm.MvvmToolkit => RaiseMethodPrefix.On, + _ => throw new InvalidOperationException() + }; + } } } diff --git a/DevExpress.Mvvm.CodeGenerators/Generators/ClassGenerator.cs b/DevExpress.Mvvm.CodeGenerators/Generators/ClassGenerator.cs index d81b909..a3a4da9 100644 --- a/DevExpress.Mvvm.CodeGenerators/Generators/ClassGenerator.cs +++ b/DevExpress.Mvvm.CodeGenerators/Generators/ClassGenerator.cs @@ -6,6 +6,16 @@ namespace DevExpress.Mvvm.CodeGenerators { enum ChangeEventRaiseMode { EventArgs, PropertyName } + enum RaiseMethodPrefix { On, Raise } + struct RaiseInfo { + public readonly ChangeEventRaiseMode Mode; + public readonly RaiseMethodPrefix Prefix; + public RaiseInfo(ChangeEventRaiseMode mode, RaiseMethodPrefix prefix) { + Mode = mode; + Prefix = prefix; + } + } + static class ClassGenerator { const string defaultUsings = @"using System.Collections.Generic; @@ -43,8 +53,8 @@ public static void GenerateSourceCode(SourceBuilder source, ContextInfo contextI genericTypes, outerClasses, mvvm, contextInfo.Compilation); - bool needStaticChangedEventArgs = inpcedInfo.HasRaiseMethodWithEventArgsParameter || impelementRaiseChangedMethod; - bool needStaticChangingEventArgs = inpcingInfo.HasAttribute && (inpcingInfo.HasRaiseMethodWithEventArgsParameter || impelementRaiseChangingMethod); + bool needStaticChangedEventArgs = inpcedInfo.HasMethodWithEventArgsPrefix || impelementRaiseChangedMethod; + bool needStaticChangingEventArgs = inpcingInfo.HasAttribute && (inpcingInfo.HasMethodWithEventArgsPrefix || impelementRaiseChangingMethod); IReadOnlyList propertyNames = GenerateProperties(source, contextInfo, classSymbol, inpcedInfo, inpcingInfo, needStaticChangedEventArgs, needStaticChangingEventArgs, mvvm); GenerateCommands(source, contextInfo, classSymbol, mvvm); @@ -70,6 +80,9 @@ static SourceBuilder GenerateHeader(ContextInfo contextInfo, SourceBuilder sourc source.AppendLine("using GalaSoft.MvvmLight.Command;"); source.AppendLine("using GalaSoft.MvvmLight.Messaging;"); break; + case SupportedMvvm.MvvmToolkit: + source.AppendLine("using Microsoft.Toolkit.Mvvm;").AppendLine("using Microsoft.Toolkit.Mvvm.Input;"); + break; case SupportedMvvm.None: break; default: @@ -123,31 +136,24 @@ static void AppendGenericArguments(SourceBuilder source, List gener } } static IReadOnlyList GenerateProperties(SourceBuilder source, ContextInfo contextInfo, INamedTypeSymbol classSymbol, INPCInfo inpcedInfo, INPCInfo inpcingInfo, bool needStaticChangedEventArgs, bool needStaticChangingEventArgs, SupportedMvvm mvvm) { - ChangeEventRaiseMode? changedRaiseMode = needStaticChangedEventArgs - ? ChangeEventRaiseMode.EventArgs - : inpcedInfo.HasRaiseMethodWithStringParameter - ? ChangeEventRaiseMode.PropertyName - : default(ChangeEventRaiseMode?); - ChangeEventRaiseMode? changingRaiseMode = needStaticChangingEventArgs - ? ChangeEventRaiseMode.EventArgs - : inpcingInfo.HasAttribute && inpcingInfo.HasRaiseMethodWithStringParameter - ? ChangeEventRaiseMode.PropertyName - : default(ChangeEventRaiseMode?); + RaiseMethodPrefix prefix = mvvm.GetRasiePrefix(); + RaiseInfo? changedInfo = GetRaiseInfo(inpcedInfo, needStaticChangedEventArgs, true, prefix); + RaiseInfo? changingInfo = GetRaiseInfo(inpcingInfo, needStaticChangingEventArgs, inpcingInfo.HasAttribute, prefix); bool generateProperties = true; List propertyNames = new(); IEnumerable fieldCandidates = ClassHelper.GetFieldCandidates(classSymbol, contextInfo.GetFrameworkAttributes(mvvm).PropertyAttributeSymbol); if(fieldCandidates.Any()) { - if(changedRaiseMode == null) { - contextInfo.Context.ReportRaiseMethodNotFound(classSymbol, "ed"); + if(changedInfo == null) { + contextInfo.Context.ReportRaiseMethodNotFound(classSymbol, "ed", prefix); generateProperties = false; } - if(inpcingInfo.HasAttribute && changingRaiseMode == null) { - contextInfo.Context.ReportRaiseMethodNotFound(classSymbol, "ing"); + if(inpcingInfo.HasAttribute && changingInfo == null) { + contextInfo.Context.ReportRaiseMethodNotFound(classSymbol, "ing", prefix); generateProperties = false; } if(generateProperties) foreach(IFieldSymbol fieldSymbol in fieldCandidates) { - string? propertyName = PropertyGenerator.Generate(source, contextInfo, classSymbol, fieldSymbol, changedRaiseMode, changingRaiseMode, mvvm); + string? propertyName = PropertyGenerator.Generate(source, contextInfo, classSymbol, fieldSymbol, changedInfo, changingInfo, mvvm); if(propertyName != null) { propertyNames.Add(propertyName); } @@ -156,6 +162,14 @@ static IReadOnlyList GenerateProperties(SourceBuilder source, ContextInf return propertyNames; } + private static RaiseInfo? GetRaiseInfo(INPCInfo inpcingInfo, bool needStaticChangingEventArgs, bool hasAttribute, RaiseMethodPrefix prefix) { + return needStaticChangingEventArgs + ? new RaiseInfo(ChangeEventRaiseMode.EventArgs, prefix) + : hasAttribute && inpcingInfo.HasMethodWithStringPrefix + ? new RaiseInfo(ChangeEventRaiseMode.PropertyName, prefix) + : default(RaiseInfo?); + } + static void GenerateCommands(SourceBuilder source, ContextInfo contextInfo, INamedTypeSymbol classSymbol, SupportedMvvm mvvm) { IEnumerable commandCandidates = ClassHelper.GetCommandCandidates(classSymbol, contextInfo.GetFrameworkAttributes(mvvm).CommandAttributeSymbol); foreach(IMethodSymbol methodSymbol in commandCandidates) { @@ -168,19 +182,21 @@ static void AddAvailableInterfaces(List interfaces, Context case SupportedMvvm.Dx: if(ClassHelper.GetImplementIDEIValue(contextInfo, classSymbol) && !ClassHelper.IsInterfaceImplementedInCurrentClass(classSymbol, contextInfo.Dx!.IDEISymbol)) interfaces.Add(new IDataErrorInfoGenerator()); - if(ClassHelper.GetImplementISPVMValue(contextInfo, classSymbol, mvvm) && !ClassHelper.IsInterfaceImplemented(classSymbol, contextInfo.Dx!.ISPVMSymbol, contextInfo, mvvm)) + if(ClassHelper.ShouldImplementInterface(classSymbol, contextInfo.Dx!.ISPVMSymbol, contextInfo, mvvm, (context, type) => ClassHelper.GetImplementISPVMValue(context, type, mvvm))) interfaces.Add(new ISupportParentViewModelGenerator(ClassHelper.ContainsOnChangedMethod(classSymbol, "OnParentViewModelChanged", 1, "object"))); if(ClassHelper.GetImplementISSValue(contextInfo, classSymbol) && !ClassHelper.IsInterfaceImplementedInCurrentClass(classSymbol, contextInfo.Dx!.ISSSymbol)) interfaces.Add(new ISupportServicesGenerator(classSymbol.IsSealed, contextInfo.IsWinUI)); break; case SupportedMvvm.Prism: - if(ClassHelper.GetImplementIAAValue(contextInfo, classSymbol) && !ClassHelper.IsInterfaceImplemented(classSymbol, contextInfo.Prism!.IAASymbol, contextInfo, mvvm)) + if(ClassHelper.ShouldImplementInterface(classSymbol, contextInfo.Prism!.IAASymbol, contextInfo, mvvm, ClassHelper.GetImplementIAAValue)) interfaces.Add(new IActiveAwareGenerator(ClassHelper.ContainsOnChangedMethod(classSymbol, "OnIsActiveChanged", 0, null))); break; case SupportedMvvm.MvvmLight: - if(ClassHelper.GetImplementICUValue(contextInfo, classSymbol) && !ClassHelper.IsInterfaceImplemented(classSymbol, contextInfo.MvvmLight!.ICUSymbol, contextInfo, mvvm)) + if(ClassHelper.ShouldImplementInterface(classSymbol, contextInfo.MvvmLight!.ICUSymbol, contextInfo, mvvm, ClassHelper.GetImplementICUValue)) interfaces.Add(new ICleanupGenerator(ClassHelper.ContainsOnChangedMethod(classSymbol, "OnCleanup", 0, null), classSymbol.IsSealed)); break; + case SupportedMvvm.MvvmToolkit: + break; case SupportedMvvm.None: break; default: diff --git a/DevExpress.Mvvm.CodeGenerators/Generators/CommandGenerator.cs b/DevExpress.Mvvm.CodeGenerators/Generators/CommandGenerator.cs index aed8c0a..bf0066d 100644 --- a/DevExpress.Mvvm.CodeGenerators/Generators/CommandGenerator.cs +++ b/DevExpress.Mvvm.CodeGenerators/Generators/CommandGenerator.cs @@ -69,15 +69,15 @@ public static void Generate(SourceBuilder source, ContextInfo info, INamedTypeSy string[] observesProperties = CommandHelper.GetObservesProperties(methodSymbol, commandAttribute); AppendGetterPrism(source, info, methodSymbol, mvvm, isCommand, canExecuteMethodName, genericArgumentType, name, observesCanExecuteProperty, observesProperties); break; - case SupportedMvvm.MvvmLight: - AppendGetterMvvmLight(source, info, methodSymbol, mvvm, isCommand, canExecuteMethodName, genericArgumentType, name); + case SupportedMvvm.MvvmLight or SupportedMvvm.MvvmToolkit: + AppendGetterRelayCommand(source, info, methodSymbol, mvvm, isCommand, canExecuteMethodName, genericArgumentType, name); break; default: throw new InvalidEnumArgumentException(); } } - static string GetCanExecuteMethodName(ContextInfo info, INamedTypeSymbol classSymbol, IMethodSymbol methodSymbol, ITypeSymbol? parameterType, INamedTypeSymbol commandAttributeSymbol) { + static string? GetCanExecuteMethodName(ContextInfo info, INamedTypeSymbol classSymbol, IMethodSymbol methodSymbol, ITypeSymbol? parameterType, INamedTypeSymbol commandAttributeSymbol) { string? canExecuteMethodName = CommandHelper.GetCanExecuteMethodName(methodSymbol, commandAttributeSymbol); if(canExecuteMethodName == null) { IEnumerable candidate = CommandHelper.GetCanExecuteMethodCandidates(classSymbol, "Can" + methodSymbol.Name, parameterType, info); @@ -88,12 +88,12 @@ static string GetCanExecuteMethodName(ContextInfo info, INamedTypeSymbol classSy info.Context.ReportCanExecuteMethodNotFound(methodSymbol, canExecuteMethodName, parameterType?.ToDisplayStringNullable() ?? string.Empty, CommandHelper.GetMethods(classSymbol, canExecuteMethodName)); } } - return canExecuteMethodName ?? "null"; + return canExecuteMethodName; } - static void AppendGetterPrism(SourceBuilder source, ContextInfo info, IMethodSymbol methodSymbol, SupportedMvvm mvvm, bool isCommand, string canExecuteMethodName, string genericArgumentType, string name, string observesCanExecuteProperty, string[] observesProperties) { + static void AppendGetterPrism(SourceBuilder source, ContextInfo info, IMethodSymbol methodSymbol, SupportedMvvm mvvm, bool isCommand, string? canExecuteMethodName, string genericArgumentType, string name, string observesCanExecuteProperty, string[] observesProperties) { source.AppendCommandNameWithGenericType(mvvm, isCommand, genericArgumentType, name).AppendMethodName(isCommand, methodSymbol.Name, genericArgumentType); - if(canExecuteMethodName != "null") + if(canExecuteMethodName != null) source.Append(", ").Append(canExecuteMethodName); if(!string.IsNullOrEmpty(observesCanExecuteProperty)) source.Append(").ObservesCanExecute(() => ").Append(observesCanExecuteProperty); @@ -103,18 +103,25 @@ static void AppendGetterPrism(SourceBuilder source, ContextInfo info, IMethodSym source.Append(").ObservesProperty(() => ").Append(property); source.AppendLine(");"); } - static void AppendGetterDx(SourceBuilder source, ContextInfo info, IMethodSymbol methodSymbol, SupportedMvvm mvvm, bool isCommand, string canExecuteMethodName, string genericArgumentType, string name) { + static void AppendGetterDx(SourceBuilder source, ContextInfo info, IMethodSymbol methodSymbol, SupportedMvvm mvvm, bool isCommand, string? canExecuteMethodName, string genericArgumentType, string name) { INamedTypeSymbol commandAttribute = info.Dx!.CommandAttributeSymbol; source.AppendCommandNameWithGenericType(mvvm, isCommand, genericArgumentType, name).Append('('); source.AppendDxParametersList(methodSymbol, commandAttribute, canExecuteMethodName, isCommand, methodSymbol.Name, info.IsWinUI); source.AppendLine(");"); } - static void AppendGetterMvvmLight(SourceBuilder source, ContextInfo info, IMethodSymbol methodSymbol, SupportedMvvm mvvm, bool isCommand, string canExecuteMethodName, string genericArgumentType, string name) { + static void AppendGetterRelayCommand(SourceBuilder source, ContextInfo info, IMethodSymbol methodSymbol, SupportedMvvm mvvm, bool isCommand, string? canExecuteMethodName, string genericArgumentType, string name) { source.AppendCommandNameWithGenericType(mvvm, isCommand, genericArgumentType, name).AppendMethodName(isCommand, methodSymbol.Name, genericArgumentType); - source.Append(", ").Append(canExecuteMethodName).AppendLine(");"); + if(mvvm == SupportedMvvm.MvvmLight) { + source.Append(", "); + source.Append(canExecuteMethodName ?? "null"); + } else if(canExecuteMethodName != null) { + source.Append(", "); + source.Append(canExecuteMethodName); + } + source.AppendLine(");"); } - static void AppendDxParametersList(this SourceBuilder source, IMethodSymbol methodSymbol, INamedTypeSymbol commandAttributeSymbol, string canExecuteMethodName, bool isCommand, string executeMethod, bool isWinUI) { - source.Append(executeMethod).Append(", ").Append(canExecuteMethodName); + static void AppendDxParametersList(this SourceBuilder source, IMethodSymbol methodSymbol, INamedTypeSymbol commandAttributeSymbol, string? canExecuteMethodName, bool isCommand, string executeMethod, bool isWinUI) { + source.Append(executeMethod).Append(", ").Append(canExecuteMethodName ?? "null"); if(!isCommand) { string allowMultipleExecution = CommandHelper.GetAllowMultipleExecutionValue(methodSymbol, commandAttributeSymbol).BoolToStringValue(); source.Append(", ").Append(allowMultipleExecution); diff --git a/DevExpress.Mvvm.CodeGenerators/Generators/GeneratorCore.cs b/DevExpress.Mvvm.CodeGenerators/Generators/GeneratorCore.cs index aa63bbb..c3ed6c2 100644 --- a/DevExpress.Mvvm.CodeGenerators/Generators/GeneratorCore.cs +++ b/DevExpress.Mvvm.CodeGenerators/Generators/GeneratorCore.cs @@ -40,11 +40,13 @@ public static void Execute(GeneratorExecutionContext context) { bool hasDxAttribute = AttributeHelper.HasAttribute(classSymbol, contextInfo.Dx?.ViewModelAttributeSymbol); bool hasPrismAttribute = AttributeHelper.HasAttribute(classSymbol, contextInfo.Prism?.ViewModelAttributeSymbol); bool hasMvvmLightAttribute = AttributeHelper.HasAttribute(classSymbol, contextInfo.MvvmLight?.ViewModelAttributeSymbol); + bool hasMvvmToolkitAttribute = AttributeHelper.HasAttribute(classSymbol, contextInfo.MvvmToolkit?.ViewModelAttributeSymbol); int mvvmCount = 0; if(hasDxAttribute) mvvmCount++; if(hasPrismAttribute) mvvmCount++; if(hasMvvmLightAttribute) mvvmCount++; + if(hasMvvmToolkitAttribute) mvvmCount++; if(mvvmCount > 1) { context.ReportMoreThanOneGenerateViewModelAttributes(classSymbol); @@ -61,6 +63,8 @@ public static void Execute(GeneratorExecutionContext context) { mvvm = SupportedMvvm.Prism; else if(hasMvvmLightAttribute) mvvm = SupportedMvvm.MvvmLight; + else if(hasMvvmToolkitAttribute) + mvvm = SupportedMvvm.MvvmToolkit; else continue; if(processedSymbols.Contains(classSymbol)) diff --git a/DevExpress.Mvvm.CodeGenerators/Generators/PropertyGenerator.cs b/DevExpress.Mvvm.CodeGenerators/Generators/PropertyGenerator.cs index 60c5033..c797a5d 100644 --- a/DevExpress.Mvvm.CodeGenerators/Generators/PropertyGenerator.cs +++ b/DevExpress.Mvvm.CodeGenerators/Generators/PropertyGenerator.cs @@ -1,13 +1,24 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using System.Diagnostics; namespace DevExpress.Mvvm.CodeGenerators { static class PropertyGenerator { - public static string? Generate(SourceBuilder source, ContextInfo info, INamedTypeSymbol classSymbol, IFieldSymbol fieldSymbol, ChangeEventRaiseMode? changedEventRaiseMode, ChangeEventRaiseMode? changingEventRaiseMode, SupportedMvvm mvvm) { + public static string? Generate(SourceBuilder source, ContextInfo info, INamedTypeSymbol classSymbol, IFieldSymbol fieldSymbol, RaiseInfo? changedInfo, RaiseInfo? changingInfo, SupportedMvvm mvvm) { var propertyAttributeSymbol = info.GetFrameworkAttributes(mvvm).PropertyAttributeSymbol; string propertyName = PropertyHelper.CreatePropertyName(fieldSymbol.Name); if(propertyName == fieldSymbol.Name) info.Context.ReportInvalidPropertyName(fieldSymbol, propertyName); + bool toolkitBroadcast = PropertyHelper.GetBroadcastAttributeValue(fieldSymbol, propertyAttributeSymbol); + if(toolkitBroadcast && !info.Compilation.HasImplicitConversion(classSymbol, info.MvvmToolkit!.ObservableRecipientSymbol)) { + info.Context.ReportNoBaseObservableRecipientClass(fieldSymbol, propertyName); + return null; + } + bool toolkitValidate = PropertyHelper.GetValidateAttributeValue(fieldSymbol, propertyAttributeSymbol); + if(toolkitValidate && !info.Compilation.HasImplicitConversion(classSymbol, info.MvvmToolkit!.ObservableValidatorSymbol)) { + info.Context.ReportNoBaseObservableValidatorClass(fieldSymbol, propertyName); + return null; + } string? changedMethod = PropertyHelper.GetChangedMethod(info, classSymbol, fieldSymbol, propertyName, fieldSymbol.Type, mvvm); string? changingMethod = PropertyHelper.GetChangingMethod(info, classSymbol, fieldSymbol, propertyName, fieldSymbol.Type, mvvm); @@ -33,15 +44,23 @@ static class PropertyGenerator { source.Tab.Append(setterAccessModifier).AppendLine("set {"); source.Tab.Tab.Append("if(EqualityComparer<").Append(typeName).Append(">.Default.Equals(").Append(fieldName).AppendLine(", value)) return;"); - AppendRaiseChangingMethod(source.Tab.Tab, changingEventRaiseMode, propertyName); + AppendRaiseChangeMethod(source.Tab.Tab, changingInfo, propertyName, "ing"); if(!string.IsNullOrEmpty(changingMethod)) source.Tab.Tab.AppendLine(changingMethod); - if(!string.IsNullOrEmpty(changedMethod) && !changedMethod.EndsWith("();")) + if(toolkitBroadcast || toolkitValidate) + Debug.Assert(mvvm == SupportedMvvm.MvvmToolkit); + + if(toolkitBroadcast || (!string.IsNullOrEmpty(changedMethod) && !changedMethod.EndsWith("();"))) source.Tab.Tab.Append("var oldValue = ").Append(fieldName).AppendLine(";"); source.Tab.Tab.Append(fieldName).AppendLine(" = value;"); - AppendRaiseChangedMethod(source.Tab.Tab, changedEventRaiseMode, propertyName); + AppendRaiseChangeMethod(source.Tab.Tab, changedInfo, propertyName, "ed"); + + if(toolkitBroadcast) + source.Tab.Tab.AppendLine($"Broadcast<{typeName}>(oldValue, value, nameof({propertyName}));"); + if(toolkitValidate) + source.Tab.Tab.AppendLine($"ValidateProperty(value, nameof({propertyName}));"); if(!string.IsNullOrEmpty(changedMethod)) source.Tab.Tab.AppendLine(changedMethod); @@ -52,18 +71,27 @@ static class PropertyGenerator { return propertyName; } - static void AppendRaiseChangingMethod(SourceBuilder source, ChangeEventRaiseMode? eventRaiseMode, string propertyName) { - if(eventRaiseMode == ChangeEventRaiseMode.EventArgs) - source.Append("RaisePropertyChanging(").Append(propertyName).AppendLine("ChangingEventArgs);"); - if(eventRaiseMode == ChangeEventRaiseMode.PropertyName) - source.Append("RaisePropertyChanging(nameof(").Append(propertyName).AppendLine("));"); - } - - static void AppendRaiseChangedMethod(SourceBuilder source, ChangeEventRaiseMode? eventRaiseMode, string propertyName) { - if(eventRaiseMode == ChangeEventRaiseMode.EventArgs) - source.Append("RaisePropertyChanged(").Append(propertyName).AppendLine("ChangedEventArgs);"); - if(eventRaiseMode == ChangeEventRaiseMode.PropertyName) - source.Append("RaisePropertyChanged(nameof(").Append(propertyName).AppendLine("));"); + static void AppendRaiseChangeMethod(SourceBuilder source, RaiseInfo? raiseInfo, string propertyName, string suffix) { + if(raiseInfo?.Mode == ChangeEventRaiseMode.EventArgs) { + source + .Append(raiseInfo.Value.Prefix.ToStringValue()) + .Append("PropertyChang") + .Append(suffix) + .Append('(') + .Append(propertyName) + .Append("Chang") + .Append(suffix) + .AppendLine("EventArgs);"); + } + if(raiseInfo?.Mode == ChangeEventRaiseMode.PropertyName) { + source + .Append(raiseInfo.Value.Prefix.ToStringValue()) + .Append("PropertyChang") + .Append(suffix) + .Append("(nameof(") + .Append(propertyName) + .AppendLine("));"); + } } static void AppendSetterAttribute(SourceBuilder source, ContextInfo info, IFieldSymbol fieldSymbol, string fieldName) { diff --git a/DevExpress.Mvvm.CodeGenerators/Helpers/ClassHelper.cs b/DevExpress.Mvvm.CodeGenerators/Helpers/ClassHelper.cs index a6d7d1e..0d87fa3 100644 --- a/DevExpress.Mvvm.CodeGenerators/Helpers/ClassHelper.cs +++ b/DevExpress.Mvvm.CodeGenerators/Helpers/ClassHelper.cs @@ -1,4 +1,5 @@ using Microsoft.CodeAnalysis; +using System; using System.Collections.Generic; using System.Linq; @@ -27,18 +28,21 @@ public static IEnumerable GetCommandCandidates(INamedTypeSymbol c GetProcessingMembers(classSymbol, commandSymbol); public static bool IsInterfaceImplementedInCurrentClass(INamedTypeSymbol classSymbol, INamedTypeSymbol interfaceSymbol) => classSymbol.Interfaces.Contains(interfaceSymbol); - public static bool IsInterfaceImplemented(INamedTypeSymbol classSymbol, INamedTypeSymbol? interfaceSymbol, ContextInfo contextInfo, SupportedMvvm mvvm) { - if(interfaceSymbol == null) + + public static bool ShouldImplementInterface(INamedTypeSymbol classSymbol, INamedTypeSymbol? interfaceSymbol, ContextInfo contextInfo, SupportedMvvm mvvm, Func getShouldImplementValue) { + if(!getShouldImplementValue(contextInfo, classSymbol)) return false; - if(IsInterfaceImplementedInCurrentClass(classSymbol, interfaceSymbol)) + if(interfaceSymbol == null) return true; - for(INamedTypeSymbol parent = classSymbol.BaseType!; parent != null; parent = parent.BaseType!) { - bool hasAttribute = AttributeHelper.HasAttribute(parent, contextInfo.GetFrameworkAttributes(mvvm).ViewModelAttributeSymbol) && GetImplementISPVMValue(contextInfo, parent, mvvm); + if(IsInterfaceImplementedInCurrentClass(classSymbol, interfaceSymbol)) + return false; + foreach(INamedTypeSymbol parent in classSymbol.GetParents()) { + bool hasAttribute = AttributeHelper.HasAttribute(parent, contextInfo.GetFrameworkAttributes(mvvm).ViewModelAttributeSymbol) && getShouldImplementValue(contextInfo, parent); bool hasImplementation = IsInterfaceImplementedInCurrentClass(parent, interfaceSymbol); if(hasAttribute || hasImplementation) - return true; + return false; } - return false; + return true; } static IEnumerable GetProcessingMembers(INamedTypeSymbol classSymbol, INamedTypeSymbol attributeSymbol) where T : ISymbol => diff --git a/DevExpress.Mvvm.CodeGenerators/Helpers/CommandHelper.cs b/DevExpress.Mvvm.CodeGenerators/Helpers/CommandHelper.cs index d1b41a9..39e79e6 100644 --- a/DevExpress.Mvvm.CodeGenerators/Helpers/CommandHelper.cs +++ b/DevExpress.Mvvm.CodeGenerators/Helpers/CommandHelper.cs @@ -29,7 +29,7 @@ public static SourceBuilder AppendCommandNameWithGenericType(this SourceBuilder public static SourceBuilder AppendCommandGenericType(this SourceBuilder source, SupportedMvvm mvvm, bool isCommand, string genericArgumentType) => mvvm switch { SupportedMvvm.Dx => source.AppendCommandGenericTypeCore(isCommand, genericArgumentType, "DelegateCommand"), SupportedMvvm.Prism => source.AppendCommandGenericTypeCore(true, genericArgumentType, "DelegateCommand"), - SupportedMvvm.MvvmLight => source.AppendCommandGenericTypeCore(true, genericArgumentType, "RelayCommand"), + SupportedMvvm.MvvmLight or SupportedMvvm.MvvmToolkit => source.AppendCommandGenericTypeCore(true, genericArgumentType, "RelayCommand"), SupportedMvvm.None => source, _ => throw new InvalidOperationException() }; diff --git a/DevExpress.Mvvm.CodeGenerators/Helpers/PropertyHelper.cs b/DevExpress.Mvvm.CodeGenerators/Helpers/PropertyHelper.cs index 8248e67..d26c7fe 100644 --- a/DevExpress.Mvvm.CodeGenerators/Helpers/PropertyHelper.cs +++ b/DevExpress.Mvvm.CodeGenerators/Helpers/PropertyHelper.cs @@ -4,14 +4,9 @@ namespace DevExpress.Mvvm.CodeGenerators { static class PropertyHelper { - static readonly string nameofIsVirtual = AttributesGenerator.IsVirtual; - static readonly string nameofChangedMethod = AttributesGenerator.OnChangedMethod; - static readonly string nameofChangingMethod = AttributesGenerator.OnChangingMethod; - static readonly string nameofSetterAccessModifier = AttributesGenerator.SetterAccessModifier; - public static string CreatePropertyName(string fieldName) => fieldName.TrimStart('_').FirstToUpperCase(); public static bool GetIsVirtualValue(IFieldSymbol fieldSymbol, INamedTypeSymbol propertySymbol) => - AttributeHelper.GetPropertyActualValue(fieldSymbol, propertySymbol, nameofIsVirtual, false); + AttributeHelper.GetPropertyActualValue(fieldSymbol, propertySymbol, AttributesGenerator.IsVirtual, false); public static string? GetChangedMethod(ContextInfo info, INamedTypeSymbol classSymbol, IFieldSymbol fieldSymbol, string propertyName, ITypeSymbol fieldType, SupportedMvvm mvvm) { string? methodName = GetChangedMethodName(fieldSymbol, info.GetFrameworkAttributes(mvvm).PropertyAttributeSymbol); return GetMethod(info, classSymbol, fieldSymbol, methodName, "On" + propertyName + "Changed", "oldValue", fieldType); @@ -21,9 +16,13 @@ public static bool GetIsVirtualValue(IFieldSymbol fieldSymbol, INamedTypeSymbol return GetMethod(info, classSymbol, fieldSymbol, methodName, "On" + propertyName + "Changing", "value", fieldType); } public static string GetSetterAccessModifierValue(IFieldSymbol fieldSymbol, INamedTypeSymbol propertySymbol) { - int enumIndex = AttributeHelper.GetPropertyActualValue(fieldSymbol, propertySymbol, nameofSetterAccessModifier, 0); + int enumIndex = AttributeHelper.GetPropertyActualValue(fieldSymbol, propertySymbol, AttributesGenerator.SetterAccessModifier, 0); return AccessModifierGenerator.GetCodeRepresentation((AccessModifier)enumIndex); } + public static bool GetBroadcastAttributeValue(IFieldSymbol fieldSymbol, INamedTypeSymbol propertySymbol) => + AttributeHelper.GetPropertyActualValue(fieldSymbol, propertySymbol, AttributesGenerator.Broadcast, false); + public static bool GetValidateAttributeValue(IFieldSymbol fieldSymbol, INamedTypeSymbol propertySymbol) => + AttributeHelper.GetPropertyActualValue(fieldSymbol, propertySymbol, AttributesGenerator.Validate, false); public static NullableAnnotation GetNullableAnnotation(ITypeSymbol type) => type.IsReferenceType && type.NullableAnnotation == NullableAnnotation.None ? NullableAnnotation.Annotated @@ -49,9 +48,9 @@ static string ToNotAnnotatedDisplayString(ITypeSymbol type) { } static string? GetChangedMethodName(IFieldSymbol fieldSymbol, INamedTypeSymbol propertySymbol) => - AttributeHelper.GetPropertyActualValue(fieldSymbol, propertySymbol, nameofChangedMethod, (string?)null); + AttributeHelper.GetPropertyActualValue(fieldSymbol, propertySymbol, AttributesGenerator.OnChangedMethod, (string?)null); static string? GetChangingMethodName(IFieldSymbol fieldSymbol, INamedTypeSymbol propertySymbol) => - AttributeHelper.GetPropertyActualValue(fieldSymbol, propertySymbol, nameofChangingMethod, (string?)null); + AttributeHelper.GetPropertyActualValue(fieldSymbol, propertySymbol, AttributesGenerator.OnChangingMethod, (string?)null); static string? GetMethod(ContextInfo info, INamedTypeSymbol classSymbol, IFieldSymbol fieldSymbol, string? methodName, string defaultMethodName, string parameterName, ITypeSymbol fieldType) { bool useDefaultName = false; if(methodName == null) { @@ -82,7 +81,8 @@ static IEnumerable GetOnChangedMethods(INamedTypeSymbol classSymb public static bool CanAppendAttribute(string attributeName) { return !(attributeName.StartsWith(AttributesGenerator.DxPropertyAttributeFullName!) || attributeName.StartsWith(AttributesGenerator.PrismPropertyAttributeFullName!) || - attributeName.StartsWith(AttributesGenerator.MvvmLightPropertyAttributeFullName!)); + attributeName.StartsWith(AttributesGenerator.MvvmLightPropertyAttributeFullName!) || + attributeName.StartsWith(AttributesGenerator.MvvmToolkitPropertyAttributeFullName!)); } } } diff --git a/DevExpress.Mvvm.CodeGenerators/Info/ContextInfo.cs b/DevExpress.Mvvm.CodeGenerators/Info/ContextInfo.cs index 6f794b6..7cba4d7 100644 --- a/DevExpress.Mvvm.CodeGenerators/Info/ContextInfo.cs +++ b/DevExpress.Mvvm.CodeGenerators/Info/ContextInfo.cs @@ -15,6 +15,7 @@ public FrameworkAttributes(Compilation compilation, SupportedMvvm mvvm) { SupportedMvvm.None or SupportedMvvm.Dx => InitializationGenerator.DxNamespace, SupportedMvvm.Prism => InitializationGenerator.PrismNamespace, SupportedMvvm.MvvmLight => InitializationGenerator.MvvmLightNamespace, + SupportedMvvm.MvvmToolkit => InitializationGenerator.MvvmToolkitNamespace, _ => throw new InvalidOperationException() }; ViewModelAttributeSymbol = compilation.GetTypeByMetadataName($"{attributeNamespace}.GenerateViewModelAttribute")!; @@ -47,20 +48,30 @@ public PrismFrameworkAttributes(Compilation compilation) IAASymbol = compilation.GetTypeByMetadataName("Prism.IActiveAware")!; } } - class MvmLightFrameWorkAttributes : FrameworkAttributes { + class MvvmLightFrameworkAttributes : FrameworkAttributes { public INamedTypeSymbol ICUSymbol { get; } - public MvmLightFrameWorkAttributes(Compilation compilation) + public MvvmLightFrameworkAttributes(Compilation compilation) : base(compilation, SupportedMvvm.MvvmLight) { ICUSymbol = compilation.GetTypeByMetadataName("GalaSoft.MvvmLight.ICleanup")!; } } + class MvvmToolkitFrameworkAttributes : FrameworkAttributes { + public INamedTypeSymbol ObservableRecipientSymbol { get; } + public INamedTypeSymbol ObservableValidatorSymbol { get; } + public MvvmToolkitFrameworkAttributes(Compilation compilation) + : base(compilation, SupportedMvvm.MvvmToolkit) { + ObservableRecipientSymbol = compilation.GetTypeByMetadataName("Microsoft.Toolkit.Mvvm.ComponentModel.ObservableRecipient")!; + ObservableValidatorSymbol = compilation.GetTypeByMetadataName("Microsoft.Toolkit.Mvvm.ComponentModel.ObservableValidator")!; + } + } class ContextInfo { public GeneratorExecutionContext Context { get; } public Compilation Compilation { get; } public DXFrameworkAttributes? Dx { get; } public PrismFrameworkAttributes? Prism { get; } - public MvmLightFrameWorkAttributes? MvvmLight { get; } + public MvvmLightFrameworkAttributes? MvvmLight { get; } + public MvvmToolkitFrameworkAttributes? MvvmToolkit { get; } public INamedTypeSymbol INPCedSymbol { get; } public INamedTypeSymbol INPCingSymbol { get; } @@ -83,7 +94,9 @@ public ContextInfo(GeneratorExecutionContext context, Compilation compilation) { if(AvailableMvvm.Contains(SupportedMvvm.Prism)) Prism = new PrismFrameworkAttributes(Compilation); if(AvailableMvvm.Contains(SupportedMvvm.MvvmLight)) - MvvmLight = new MvmLightFrameWorkAttributes(Compilation); + MvvmLight = new MvvmLightFrameworkAttributes(Compilation); + if(AvailableMvvm.Contains(SupportedMvvm.MvvmToolkit)) + MvvmToolkit = new MvvmToolkitFrameworkAttributes(Compilation); INPCedSymbol = compilation.GetTypeByMetadataName(typeof(INotifyPropertyChanged).FullName)!; INPCingSymbol = compilation.GetTypeByMetadataName(typeof(INotifyPropertyChanging).FullName)!; @@ -97,31 +110,35 @@ public ContextInfo(GeneratorExecutionContext context, Compilation compilation) { SupportedMvvm.None or SupportedMvvm.Dx => Dx!, SupportedMvvm.Prism => Prism!, SupportedMvvm.MvvmLight => MvvmLight!, + SupportedMvvm.MvvmToolkit => MvvmToolkit!, _ => throw new InvalidOperationException() }; public static bool GetIsWinUI(Compilation compilation) => GetIsDxMvvmAvailable(compilation) && compilation.GetTypeByMetadataName("DevExpress.Mvvm.POCO.ViewModelSource") == null; - static bool GetIsDxMvvmAvailable(Compilation compilation) => compilation.GetTypeByMetadataName("DevExpress.Mvvm.DelegateCommand") != null; - static bool GetIsPrismAvailable(Compilation compilation) => compilation.GetTypeByMetadataName("Prism.Commands.DelegateCommand") != null; - static bool GetIsMvvmLightAvailable(Compilation compilation) => compilation.GetTypeByMetadataName("GalaSoft.MvvmLight.Command.RelayCommand") != null; - public static bool GetIsMvvmLightCommandWpfAvalible(Compilation compilation) => compilation.GetTypeByMetadataName("GalaSoft.MvvmLight.CommandWpf.RelayCommand") != null; + static bool GetIsDxMvvmAvailable(Compilation compilation) => IsTypeAvailable(compilation, "DevExpress.Mvvm.DelegateCommand"); + public static bool GetIsMvvmLightCommandAvalible(Compilation compilation) => IsTypeAvailable(compilation, "GalaSoft.MvvmLight.Command.RelayCommand"); + public static bool GetIsMvvmLightCommandWpfAvalible(Compilation compilation) => IsTypeAvailable(compilation, "GalaSoft.MvvmLight.CommandWpf.RelayCommand"); + static bool IsTypeAvailable(Compilation compilation, string type) => compilation.GetTypeByMetadataName(type) != null; public static List GetAvailableMvvm(Compilation compilation) { List available = new(); if(GetIsDxMvvmAvailable(compilation)) available.Add(SupportedMvvm.Dx); - if(GetIsPrismAvailable(compilation)) + if(IsTypeAvailable(compilation, "Prism.Commands.DelegateCommand")) available.Add(SupportedMvvm.Prism); - if(GetIsMvvmLightAvailable(compilation)) + if(GetIsMvvmLightCommandAvalible(compilation)) available.Add(SupportedMvvm.MvvmLight); + if(IsTypeAvailable(compilation, "Microsoft.Toolkit.Mvvm.Input.RelayCommand")) + available.Add(SupportedMvvm.MvvmToolkit); if(!available.Any()) available.Add(SupportedMvvm.None); return available; } } internal enum SupportedMvvm { - None = 0, - Dx = 1, - Prism = 2, - MvvmLight = 3, + None, + Dx, + Prism, + MvvmLight, + MvvmToolkit, } } diff --git a/DevExpress.Mvvm.CodeGenerators/Info/INPCInfo.cs b/DevExpress.Mvvm.CodeGenerators/Info/INPCInfo.cs index 474ce9d..4c4c7a0 100644 --- a/DevExpress.Mvvm.CodeGenerators/Info/INPCInfo.cs +++ b/DevExpress.Mvvm.CodeGenerators/Info/INPCInfo.cs @@ -8,30 +8,30 @@ class INPCInfo { readonly bool hasImplementationInCurrentClass; public bool HasAttribute { get; } - public bool HasRaiseMethodWithEventArgsParameter { get; } - public bool HasRaiseMethodWithStringParameter { get; } + public bool HasMethodWithEventArgsPrefix { get; } + public bool HasMethodWithStringPrefix { get; } public string RaiseMethodImplementation { get; } public static INPCInfo GetINPCedInfo(ContextInfo info, INamedTypeSymbol classSymbol, SupportedMvvm mvvm) => new INPCInfo(classSymbol, info.INPCedSymbol, symbol => AttributeHelper.HasAttribute(symbol, info.GetFrameworkAttributes(mvvm).ViewModelAttributeSymbol), - "RaisePropertyChanged", + mvvm.GetRasiePrefix() + "PropertyChanged", "System.ComponentModel.PropertyChangedEventArgs", - "void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);"); + $"void {mvvm.GetRasiePrefix().ToStringValue()}PropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);"); public static INPCInfo GetINPCingInfo(ContextInfo info, INamedTypeSymbol classSymbol, SupportedMvvm mvvm) => new INPCInfo(classSymbol, info.INPCingSymbol, symbol => AttributeHelper.HasAttribute(symbol, info.GetFrameworkAttributes(mvvm).ViewModelAttributeSymbol) && AttributeHelper.GetPropertyActualValue(symbol, info.GetFrameworkAttributes(mvvm).ViewModelAttributeSymbol, AttributesGenerator.ImplementINPCing, false), - "RaisePropertyChanging", + mvvm.GetRasiePrefix() + "PropertyChanging", "System.ComponentModel.PropertyChangingEventArgs", - "void RaisePropertyChanging(PropertyChangingEventArgs e) => PropertyChanging?.Invoke(this, e);"); + $"void {mvvm.GetRasiePrefix().ToStringValue()}PropertyChanging(PropertyChangingEventArgs e) => PropertyChanging?.Invoke(this, e);"); public bool HasNoImplementation() => HasAttribute && !hasImplementation; public bool ShouldImplementRaiseMethod() => - HasAttribute && !HasRaiseMethodWithEventArgsParameter && (!hasImplementation || hasImplementationInCurrentClass); + HasAttribute && !HasMethodWithEventArgsPrefix && (!hasImplementation || hasImplementationInCurrentClass); INPCInfo(INamedTypeSymbol classSymbol, INamedTypeSymbol interfaceSymbol, Func checkAttribute, string methodName, string eventArgsParameter, string raiseMethodImplementation) { HasAttribute = checkAttribute(classSymbol); @@ -39,11 +39,11 @@ public bool ShouldImplementRaiseMethod() => if(HasAttribute && hasImplementation) hasImplementationInCurrentClass = true; - HasRaiseMethodWithEventArgsParameter = HasMethod(classSymbol, methodName, eventArgsParameter, true); - HasRaiseMethodWithStringParameter = HasMethod(classSymbol, methodName, "string", true); + HasMethodWithEventArgsPrefix = HasRaiseMethod(classSymbol, methodName, eventArgsParameter, true); + HasMethodWithStringPrefix = HasRaiseMethod(classSymbol, methodName, "string", true); bool isRaiseMethodGenerated = false; - for(INamedTypeSymbol parent = classSymbol.BaseType!; parent != null; parent = parent.BaseType!) { + foreach(INamedTypeSymbol parent in classSymbol.GetParents()) { bool hasAttribute = checkAttribute(parent); bool hasImplementation = ClassHelper.IsInterfaceImplementedInCurrentClass(parent, interfaceSymbol); if(hasAttribute || hasImplementation) @@ -53,21 +53,26 @@ public bool ShouldImplementRaiseMethod() => if(hasAttribute) isRaiseMethodGenerated = true; - if(!HasRaiseMethodWithEventArgsParameter) - HasRaiseMethodWithEventArgsParameter = HasMethod(parent, methodName, eventArgsParameter); - if(!HasRaiseMethodWithStringParameter) - HasRaiseMethodWithStringParameter = HasMethod(parent, methodName, "string"); + if(!HasMethodWithEventArgsPrefix) + HasMethodWithEventArgsPrefix = HasRaiseMethod(parent, methodName, eventArgsParameter, false); + if(!HasMethodWithStringPrefix) + HasMethodWithStringPrefix = HasRaiseMethod(parent, methodName, "string", false); } if(isRaiseMethodGenerated) - HasRaiseMethodWithEventArgsParameter = true; + HasMethodWithEventArgsPrefix = true; RaiseMethodImplementation = raiseMethodImplementation; } - bool HasMethod(INamedTypeSymbol classSymbol, string methodName, string parameterType, bool ignorePrivateAccessibility = false) => - CommandHelper.GetMethods(classSymbol, - symbol => (symbol.DeclaredAccessibility != Accessibility.Private || ignorePrivateAccessibility) && - symbol.ReturnsVoid && - symbol.Name == methodName && - symbol.Parameters.Length == 1 && symbol.Parameters.First().Type.ToDisplayString(NullableFlowState.None) == parameterType) - .Any(); + + bool HasRaiseMethod(INamedTypeSymbol classSymbol, string methodName, string parameterType, bool ignorePrivateAccessibility) { + return classSymbol + .GetMembers() + .OfType() + .Any(symbol => { + return (symbol.DeclaredAccessibility != Accessibility.Private || ignorePrivateAccessibility) && + symbol.ReturnsVoid && + symbol.Name == methodName && + symbol.Parameters.Length == 1 && symbol.Parameters.First().Type.ToDisplayString(NullableFlowState.None) == parameterType; + }); + } } } diff --git a/DevExpress.Mvvm.CodeGenerators/InitializationGenerator/AttributesGenerator.cs b/DevExpress.Mvvm.CodeGenerators/InitializationGenerator/AttributesGenerator.cs index b81c67e..9e30f93 100644 --- a/DevExpress.Mvvm.CodeGenerators/InitializationGenerator/AttributesGenerator.cs +++ b/DevExpress.Mvvm.CodeGenerators/InitializationGenerator/AttributesGenerator.cs @@ -5,6 +5,7 @@ public static class AttributesGenerator { public static readonly string DxPropertyAttributeFullName = $"{InitializationGenerator.DxNamespace}.GeneratePropertyAttribute"; public static readonly string PrismPropertyAttributeFullName = $"{InitializationGenerator.PrismNamespace}.GeneratePropertyAttribute"; public static readonly string MvvmLightPropertyAttributeFullName = $"{InitializationGenerator.MvvmLightNamespace}.GeneratePropertyAttribute"; + public static readonly string MvvmToolkitPropertyAttributeFullName = $"{InitializationGenerator.MvvmToolkitNamespace}.GeneratePropertyAttribute"; public const string ImplementIDEI = "ImplementIDataErrorInfo"; public const string ImplementINPCing = "ImplementINotifyPropertyChanging"; @@ -18,6 +19,8 @@ public static class AttributesGenerator { public const string OnChangedMethod = "OnChangedMethod"; public const string OnChangingMethod = "OnChangingMethod"; public const string SetterAccessModifier = "SetterAccessModifier"; + public const string Broadcast = "Broadcast"; + public const string Validate = "Validate"; public const string AllowMultipleExecution = "AllowMultipleExecution"; public const string UseCommandManager = "UseCommandManager"; @@ -32,6 +35,7 @@ internal static string GetSourceCode(SupportedMvvm mvvm, bool isWinUI) => SupportedMvvm.Dx => isWinUI ? dxwinUISourceCode : dxMvvmSourceCode, SupportedMvvm.Prism => prismMvvmSourceCode, SupportedMvvm.MvvmLight => mvvmLightSourceCode, + SupportedMvvm.MvvmToolkit => mvvmToolkitSourceCode, SupportedMvvm.None => commonSourceCode, _ => throw new InvalidOperationException() }; @@ -323,6 +327,68 @@ class GeneratePropertyAttribute : Attribute { public AccessModifier SetterAccessModifier { get; set; } } + /// + /// Indicates that the View Model Code Generator should process this method and produce a Command. + /// + [AttributeUsage(AttributeTargets.Method)] + class GenerateCommandAttribute : Attribute { + /// + /// Specifies a custom CanExecute method name. If the property is not specified, the method’s name should follow the Can[ActionName] pattern. + /// + public string? CanExecuteMethod { get; set; } + /// + /// Specifies a custom Command name. The default value is [ActionName]Command. + /// + public string? Name { get; set; } + }"; + const string mvvmToolkitSourceCode = @" /// + /// Indicates that the View Model Code Generator should process this class and produce a View Model. + /// + [AttributeUsage(AttributeTargets.Class)] + class GenerateViewModelAttribute : Attribute { + /// + /// Implements + /// INotifyPropertyChanging. + /// + public bool ImplementINotifyPropertyChanging { get; set; } + } + + /// + /// Indicates that the View Model Code Generator should process this field and produce a property. + /// + [AttributeUsage(AttributeTargets.Field)] + class GeneratePropertyAttribute : Attribute { + /// + /// Assigns a virtual modifier to the property. + /// + public bool IsVirtual { get; set; } + /// + /// Specifies the name of the method invoked after the property value is changed.
+ /// If the property is not specified, the method’s name should follow the On[PropertyName]Changed pattern. + ///
+ public string? OnChangedMethod { get; set; } + /// + /// Specifies the name of the method invoked when the property value is changing.
+ /// If the property is not specified, the method’s name should follow the On[PropertyName]Changing pattern. + ///
+ public string? OnChangingMethod { get; set; } + /// + /// Specifies an access modifier for a set accessor. The default value is the same as a property’s modifier.
+ /// Available values: Public, Private, Protected, Internal, ProtectedInternal, PrivateProtected. + ///
+ public AccessModifier SetterAccessModifier { get; set; } + /// + /// Broadcasts a PropertyChangedMessage after the property value is changed. + /// Inherit your class from the ObservableRecipient class to enable this functionality. + /// + public bool Broadcast{ get; set; } + /// + /// Validates property value and raises the ErrorsChanged event if needed. + /// Inherit your class from the ObservableValidator class to enable this functionality. + /// + public bool Validate{ get; set; } + } + /// /// Indicates that the View Model Code Generator should process this method and produce a Command. /// diff --git a/DevExpress.Mvvm.CodeGenerators/InitializationGenerator/InitializationGenerator.cs b/DevExpress.Mvvm.CodeGenerators/InitializationGenerator/InitializationGenerator.cs index 5f6e9af..498b517 100644 --- a/DevExpress.Mvvm.CodeGenerators/InitializationGenerator/InitializationGenerator.cs +++ b/DevExpress.Mvvm.CodeGenerators/InitializationGenerator/InitializationGenerator.cs @@ -5,6 +5,7 @@ static class InitializationGenerator { public const string DxNamespace = "DevExpress.Mvvm.CodeGenerators"; public const string PrismNamespace = "DevExpress.Mvvm.CodeGenerators.Prism"; public const string MvvmLightNamespace = "DevExpress.Mvvm.CodeGenerators.MvvmLight"; + public const string MvvmToolkitNamespace = "DevExpress.Mvvm.CodeGenerators.MvvmToolkit"; public static string GetSourceCode(SupportedMvvm mvvm, bool isWinUI) => $@"using System; @@ -16,6 +17,7 @@ public static string GetSourceCode(SupportedMvvm mvvm, bool isWinUI) => SupportedMvvm.None or SupportedMvvm.Dx => DxNamespace, SupportedMvvm.Prism => PrismNamespace, SupportedMvvm.MvvmLight => MvvmLightNamespace, + SupportedMvvm.MvvmToolkit => MvvmToolkitNamespace, _ => throw new InvalidOperationException() }} {{ {AccessModifierGenerator.GetSourceCode()} diff --git a/Readme.md b/Readme.md index a164470..bda1deb 100644 --- a/Readme.md +++ b/Readme.md @@ -82,6 +82,15 @@ Your project should meet the following requirements: - .NET Framework v4.6.1+ or .NET Core v3.0+ - Visual Studio v16.9.0+ +## Supported MVVM Frameworks + +Our View Model Code Generator [supports](https://docs.devexpress.com/WPF/402989/mvvm-framework/viewmodels/compile-time-generated-viewmodels#third-party-libraries-support) following MVVM frameworks: +- [DevExpress WPF MVVM Framework](https://docs.devexpress.com/WPF/15112/mvvm-framework) +- [DevExpress WinUI MVVM Framework](https://docs.devexpress.com/WinUI/102569/mvvm-framework) +- [Microsoft MVVM Toolkit](https://docs.microsoft.com/en-us/windows/communitytoolkit/mvvm/introduction) +- [MVVM Light Toolkit](https://github.com/lbugnion/mvvmlight) +- [Prism Library](https://prismlibrary.com) + ## Prepare Your Project Prepare your project as outlined below to enable support for View Models generated at compile time: @@ -114,16 +123,19 @@ Prepare your project as outlined below to enable support for View Models generat ## Release Notes +### 22.1.1 +- Our View Model Code Generator now [supports](https://docs.devexpress.com/WPF/402989/mvvm-framework/viewmodels/compile-time-generated-viewmodels#microsoft-mvvm-toolkit) the [Microsoft MVVM Toolkit](https://docs.microsoft.com/en-us/windows/communitytoolkit/mvvm/introduction). + ### 22.1.0 - The View Model Code Generator now supports the updated WinUI MVVM Framework that is available in the **DevExpress.WinUI.Mvvm.v22.1** assembly. If your **WinUI** project uses **DevExpress.Mvvm.v21.2**, replace it with **DevExpress.WinUI.Mvvm.v22.1**. ### 21.2.2 -- Our View Model Code Generator now supports the [MVVM Light Toolkit](https://github.com/lbugnion/mvvmlight). +- Our View Model Code Generator now [supports](https://docs.devexpress.com/WPF/402989/mvvm-framework/viewmodels/compile-time-generated-viewmodels#mvvm-light-toolkit) the [MVVM Light Toolkit](https://github.com/lbugnion/mvvmlight). - We implemented the following suggestion: [Proposal: additional attributes for generated commands](https://github.com/DevExpress/DevExpress.Mvvm.CodeGenerators/issues/14). ### 21.2.1 -Our View Model Code Generator now supports the [Prism Library](https://prismlibrary.com). +Our View Model Code Generator now [supports](https://docs.devexpress.com/WPF/402989/mvvm-framework/viewmodels/compile-time-generated-viewmodels#prism-library) the [Prism Library](https://prismlibrary.com). ### 21.1.3