diff --git a/ReactiveGenerator.Tests/ReactiveGeneratorTests.cs b/ReactiveGenerator.Tests/ReactiveGeneratorTests.cs index 0b06efa..2e5f710 100644 --- a/ReactiveGenerator.Tests/ReactiveGeneratorTests.cs +++ b/ReactiveGenerator.Tests/ReactiveGeneratorTests.cs @@ -5,8 +5,8 @@ public class ReactiveGeneratorTests private Task TestAndVerify(string source, Dictionary? analyzerConfigOptions = null) { return SourceGeneratorTestHelper.TestAndVerify( - source, - analyzerConfigOptions, + source, + analyzerConfigOptions, generators: new ReactiveGenerator()); } @@ -186,10 +186,7 @@ public partial class TestClass public partial string Name { get; set; } }"; - var analyzerConfigOptions = new Dictionary - { - ["build_property.UseBackingFields"] = "true" - }; + var analyzerConfigOptions = new Dictionary { ["build_property.UseBackingFields"] = "true" }; return TestAndVerify(source, analyzerConfigOptions); } @@ -206,7 +203,7 @@ public partial class GlobalClass return TestAndVerify(source); } - + [Fact] public Task ClassWithMultipleReactiveAttributes() { @@ -483,7 +480,7 @@ public partial class TestClass return TestAndVerify(source); } - + [Fact] public Task PropertyLevelReactiveWithMixedProperties() { @@ -1069,4 +1066,207 @@ internal partial class GenericViewModel return TestAndVerify(source); } + + [Fact] + public Task InheritedReactiveAttribute() + { + var source = @" + [Reactive] + public abstract partial class BaseViewModel + { + public partial string BaseProp { get; set; } + } + + public partial class ViewModel : BaseViewModel + { + public partial string ViewModelProp { get; set; } + } + + [IgnoreReactive] + public partial class NonReactiveViewModel : BaseViewModel + { + public partial string IgnoredProp { get; set; } + } + + public partial class DerivedViewModel : ViewModel + { + public partial string DerivedProp { get; set; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task MultiLevelInheritanceWithMixedReactiveAttributes() + { + var source = @" + [Reactive] + public abstract partial class BaseViewModel + { + public partial string BaseProp { get; set; } + } + + public partial class MiddleViewModel : BaseViewModel + { + public partial string MiddleProp { get; set; } + + [IgnoreReactive] + public partial string IgnoredMiddleProp { get; set; } + } + + [IgnoreReactive] + public partial class NonReactiveViewModel : MiddleViewModel + { + public partial string NonReactiveProp { get; set; } + } + + [Reactive] + public partial class ReactiveDerivedViewModel : NonReactiveViewModel + { + public partial string ReactiveProp { get; set; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task ReactiveObjectWithSingleReactiveProperty() + { + var source = @" + using ReactiveUI; + + public partial class Car : ReactiveObject + { + [Reactive] + public partial string? Make { get; set; } + + public partial string Model { get; set; } // Should not be processed + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task ReactiveObjectWithClassLevelReactive() + { + var source = @" + using ReactiveUI; + + [Reactive] + internal partial class ReactiveViewModel : ReactiveObject + { + public partial string FirstName { get; set; } + public partial string LastName { get; set; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task ReactiveObjectWithoutAnyReactiveAttribute() + { + var source = @" + using ReactiveUI; + + public partial class OaphViewModel : ReactiveObject + { + public partial string ComputedValue { get; } // Should not be processed + public partial string NormalProperty { get; set; } // Should not be processed + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task ReactiveObjectWithNullableGenerics() + { + var source = @" + using System; + using ReactiveUI; + + public partial class NullableGenericsTest + { + [Reactive] + public partial DateTime? StartDate { get; set; } + + [Reactive] + public partial DateTime? EndDate { get; set; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task ReactiveObjectWithMixedReactiveProperties() + { + var source = @" + using ReactiveUI; + + public partial class MixedViewModel : ReactiveObject + { + [Reactive] + public partial string ReactiveProperty { get; set; } + + public partial string NonReactiveProperty { get; set; } + + [Reactive] + public partial int? NullableReactiveProperty { get; set; } + + [ObservableAsProperty] + public partial string ComputedProperty { get; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task ReactiveObjectWithClassLevelReactiveAndIgnoreOverride() + { + var source = @" + using ReactiveUI; + + [Reactive] + public partial class AllReactiveViewModel : ReactiveObject + { + public partial string Property1 { get; set; } + + [IgnoreReactive] + public partial string IgnoredProperty { get; set; } + + public partial string Property2 { get; set; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task ReactiveObjectWithPropertyInitializer() + { + var source = @" + using ReactiveUI; + + [Reactive] + public partial class InitializedViewModel : ReactiveObject + { + public partial string Name { get; set; } = string.Empty; + public partial int Count { get; set; } = 42; + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task NonReactiveObjectWithMixedProperties() + { + var source = @" + public partial class RegularClass + { + [Reactive] + public partial string ReactiveProperty { get; set; } + + public partial string NonReactiveProperty { get; set; } + }"; + + return TestAndVerify(source); + } } diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithReactiveObjectInheritance.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithReactiveObjectInheritance.verified.txt index 3ecc45f..d80a3b6 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithReactiveObjectInheritance.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithReactiveObjectInheritance.verified.txt @@ -21,31 +21,6 @@ sealed class ReactiveAttribute : Attribute { public ReactiveAttribute() { } } - }, - { - FileName: TestClass.INPC.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class TestClass : INotifyPropertyChanged -{ - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) - { - PropertyChanged?.Invoke(this, args); - } -} - }, { FileName: TestClass.ReactiveProperties.g.cs, @@ -53,27 +28,17 @@ public partial class TestClass : INotifyPropertyChanged // #nullable enable -using System.ComponentModel; -using System.Runtime.CompilerServices; +using ReactiveUI; /// /// A partial class implementation for TestClass. /// public partial class TestClass { - private static readonly PropertyChangedEventArgs _nameChangedEventArgs = new PropertyChangedEventArgs(nameof(Name)); - public partial string Name { get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_nameChangedEventArgs); - } - } + set => this.RaiseAndSetIfChanged(ref field, value); } } diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.GenericPropertyWithReactiveObjectBase.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.GenericPropertyWithReactiveObjectBase.verified.txt index 05ec69a..5bc9f4a 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.GenericPropertyWithReactiveObjectBase.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.GenericPropertyWithReactiveObjectBase.verified.txt @@ -1,54 +1,19 @@ { Sources: [ - { - FileName: GenericViewModel.INPC.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -internal partial class GenericViewModel : INotifyPropertyChanged where T : class -{ - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) - { - PropertyChanged?.Invoke(this, args); - } -} - - }, { FileName: GenericViewModel.ReactiveProperties.g.cs, Source: // #nullable enable -using System.ComponentModel; -using System.Runtime.CompilerServices; +using ReactiveUI; internal partial class GenericViewModel where T : class { - private static readonly PropertyChangedEventArgs _valueChangedEventArgs = new PropertyChangedEventArgs(nameof(Value)); - public partial T? Value { get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_valueChangedEventArgs); - } - } + set => this.RaiseAndSetIfChanged(ref field, value); } } diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.InheritedReactiveAttribute.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.InheritedReactiveAttribute.verified.txt new file mode 100644 index 0000000..439e94b --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.InheritedReactiveAttribute.verified.txt @@ -0,0 +1,145 @@ +{ + Sources: [ + { + FileName: BaseViewModel.INPC.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +public partial class BaseViewModel : INotifyPropertyChanged +{ + public event PropertyChangedEventHandler? PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) + { + PropertyChanged?.Invoke(this, args); + } +} + + }, + { + FileName: BaseViewModel.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +/// +/// A partial class implementation for BaseViewModel. +/// +public partial class BaseViewModel +{ + private static readonly PropertyChangedEventArgs _basePropChangedEventArgs = new PropertyChangedEventArgs(nameof(BaseProp)); + + public partial string BaseProp + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_basePropChangedEventArgs); + } + } + } +} + + }, + { + FileName: DerivedViewModel.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +/// +/// A partial class implementation for DerivedViewModel. +/// +public partial class DerivedViewModel +{ + private static readonly PropertyChangedEventArgs _derivedPropChangedEventArgs = new PropertyChangedEventArgs(nameof(DerivedProp)); + + public partial string DerivedProp + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_derivedPropChangedEventArgs); + } + } + } +} + + }, + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + FileName: ReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class ReactiveAttribute : Attribute +{ + public ReactiveAttribute() { } +} + }, + { + FileName: ViewModel.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +/// +/// A partial class implementation for ViewModel. +/// +public partial class ViewModel +{ + private static readonly PropertyChangedEventArgs _viewModelPropChangedEventArgs = new PropertyChangedEventArgs(nameof(ViewModelProp)); + + public partial string ViewModelProp + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_viewModelPropChangedEventArgs); + } + } + } +} + + } + ], + Diagnostics: null +} \ No newline at end of file diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.MultiLevelInheritanceWithMixedReactiveAttributes.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.MultiLevelInheritanceWithMixedReactiveAttributes.verified.txt new file mode 100644 index 0000000..ddabbbd --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.MultiLevelInheritanceWithMixedReactiveAttributes.verified.txt @@ -0,0 +1,145 @@ +{ + Sources: [ + { + FileName: BaseViewModel.INPC.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +public partial class BaseViewModel : INotifyPropertyChanged +{ + public event PropertyChangedEventHandler? PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) + { + PropertyChanged?.Invoke(this, args); + } +} + + }, + { + FileName: BaseViewModel.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +/// +/// A partial class implementation for BaseViewModel. +/// +public partial class BaseViewModel +{ + private static readonly PropertyChangedEventArgs _basePropChangedEventArgs = new PropertyChangedEventArgs(nameof(BaseProp)); + + public partial string BaseProp + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_basePropChangedEventArgs); + } + } + } +} + + }, + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + FileName: MiddleViewModel.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +/// +/// A partial class implementation for MiddleViewModel. +/// +public partial class MiddleViewModel +{ + private static readonly PropertyChangedEventArgs _middlePropChangedEventArgs = new PropertyChangedEventArgs(nameof(MiddleProp)); + + public partial string MiddleProp + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_middlePropChangedEventArgs); + } + } + } +} + + }, + { + FileName: ReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class ReactiveAttribute : Attribute +{ + public ReactiveAttribute() { } +} + }, + { + FileName: ReactiveDerivedViewModel.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +/// +/// A partial class implementation for ReactiveDerivedViewModel. +/// +public partial class ReactiveDerivedViewModel +{ + private static readonly PropertyChangedEventArgs _reactivePropChangedEventArgs = new PropertyChangedEventArgs(nameof(ReactiveProp)); + + public partial string ReactiveProp + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_reactivePropChangedEventArgs); + } + } + } +} + + } + ], + Diagnostics: null +} \ No newline at end of file diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.MultiLevelNestedClassesWithReactiveProperties.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.MultiLevelNestedClassesWithReactiveProperties.verified.txt index ddfe0f7..4566fcf 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.MultiLevelNestedClassesWithReactiveProperties.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.MultiLevelNestedClassesWithReactiveProperties.verified.txt @@ -38,142 +38,6 @@ public partial class Level1 } } - }, - { - FileName: Level1.Level2.Level3.INPC.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class Level1 -{ - public partial class Level2 - { - private partial class Level3 : INotifyPropertyChanged - { - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) - { - PropertyChanged?.Invoke(this, args); - } - } - } -} - - }, - { - FileName: Level1.Level2.Level3.Level4.INPC.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class Level1 -{ - public partial class Level2 - { - private partial class Level3 - { - internal partial class Level4 : INotifyPropertyChanged - { - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) - { - PropertyChanged?.Invoke(this, args); - } - } - } - } -} - - }, - { - FileName: Level1.Level2.Level3.Level4.ReactiveProperties.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class Level1 -{ - public partial class Level2 - { - private partial class Level3 - { - internal partial class Level4 - { - private static readonly PropertyChangedEventArgs _level4PropChangedEventArgs = new PropertyChangedEventArgs(nameof(Level4Prop)); - - public partial string Level4Prop - { - get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_level4PropChangedEventArgs); - } - } - } - } - } - } -} - - }, - { - FileName: Level1.Level2.Level3.ReactiveProperties.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class Level1 -{ - public partial class Level2 - { - private partial class Level3 - { - private static readonly PropertyChangedEventArgs _level3PropChangedEventArgs = new PropertyChangedEventArgs(nameof(Level3Prop)); - - public partial string Level3Prop - { - get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_level3PropChangedEventArgs); - } - } - } - } - } -} - }, { FileName: Level1.Level2.ReactiveProperties.g.cs, diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithInitOnlyProperties.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithInitOnlyProperties.verified.txt index 14ea7ee..34efdd2 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithInitOnlyProperties.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithInitOnlyProperties.verified.txt @@ -1,84 +1,5 @@ { Sources: [ - { - FileName: Container.Nested.InnerNested.INPC.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class Container -{ - public partial class Nested - { - private partial class InnerNested : INotifyPropertyChanged - { - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) - { - PropertyChanged?.Invoke(this, args); - } - } - } -} - - }, - { - FileName: Container.Nested.InnerNested.ReactiveProperties.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class Container -{ - public partial class Nested - { - private partial class InnerNested - { - private static readonly PropertyChangedEventArgs _innerRegularPropChangedEventArgs = new PropertyChangedEventArgs(nameof(InnerRegularProp)); - private static readonly PropertyChangedEventArgs _innerInitOnlyPropChangedEventArgs = new PropertyChangedEventArgs(nameof(InnerInitOnlyProp)); - - public partial string InnerRegularProp - { - get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_innerRegularPropChangedEventArgs); - } - } - } - - public partial string InnerInitOnlyProp - { - get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_innerInitOnlyPropChangedEventArgs); - } - } - } - } - } -} - - }, { FileName: Container.Nested.INPC.g.cs, Source: diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithReactiveObjectInheritance.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithReactiveObjectInheritance.verified.txt index d9be7ab..4776135 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithReactiveObjectInheritance.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithReactiveObjectInheritance.verified.txt @@ -1,106 +1,12 @@ { Sources: [ - { - FileName: Container.NestedReactiveViewModel.InnerViewModel.INPC.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class Container -{ - public partial class NestedReactiveViewModel - { - private partial class InnerViewModel : INotifyPropertyChanged - { - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) - { - PropertyChanged?.Invoke(this, args); - } - } - } -} - - }, - { - FileName: Container.NestedReactiveViewModel.InnerViewModel.ReactiveProperties.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class Container -{ - public partial class NestedReactiveViewModel - { - private partial class InnerViewModel - { - private static readonly PropertyChangedEventArgs _innerPropChangedEventArgs = new PropertyChangedEventArgs(nameof(InnerProp)); - - public partial string InnerProp - { - get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_innerPropChangedEventArgs); - } - } - } - } - } -} - - }, - { - FileName: Container.NestedReactiveViewModel.INPC.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class Container -{ - public partial class NestedReactiveViewModel : INotifyPropertyChanged - { - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) - { - PropertyChanged?.Invoke(this, args); - } - } -} - - }, { FileName: Container.NestedReactiveViewModel.ReactiveProperties.g.cs, Source: // #nullable enable -using System.ComponentModel; -using System.Runtime.CompilerServices; +using ReactiveUI; public partial class Container { @@ -109,19 +15,10 @@ public partial class Container /// public partial class NestedReactiveViewModel { - private static readonly PropertyChangedEventArgs _viewModelPropChangedEventArgs = new PropertyChangedEventArgs(nameof(ViewModelProp)); - public partial string ViewModelProp { get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_viewModelPropChangedEventArgs); - } - } + set => this.RaiseAndSetIfChanged(ref field, value); } } } diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithStaticAndInstanceMembers.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithStaticAndInstanceMembers.verified.txt index cf3b044..3e18b90 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithStaticAndInstanceMembers.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithStaticAndInstanceMembers.verified.txt @@ -75,71 +75,6 @@ public partial class Container } } - }, - { - FileName: Container.Nested.StaticNested.INPC.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class Container -{ - public partial class Nested - { - private partial class StaticNested : INotifyPropertyChanged - { - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) - { - PropertyChanged?.Invoke(this, args); - } - } - } -} - - }, - { - FileName: Container.Nested.StaticNested.ReactiveProperties.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class Container -{ - public partial class Nested - { - private partial class StaticNested - { - private static readonly PropertyChangedEventArgs _staticNestedPropChangedEventArgs = new PropertyChangedEventArgs(nameof(StaticNestedProp)); - - public partial string StaticNestedProp - { - get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_staticNestedPropChangedEventArgs); - } - } - } - } - } -} - }, { FileName: IgnoreReactiveAttribute.g.cs, diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NonReactiveObjectWithMixedProperties.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NonReactiveObjectWithMixedProperties.verified.txt new file mode 100644 index 0000000..69d6247 --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NonReactiveObjectWithMixedProperties.verified.txt @@ -0,0 +1,83 @@ +{ + Sources: [ + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + FileName: ReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class ReactiveAttribute : Attribute +{ + public ReactiveAttribute() { } +} + }, + { + FileName: RegularClass.INPC.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +public partial class RegularClass : INotifyPropertyChanged +{ + public event PropertyChangedEventHandler? PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) + { + PropertyChanged?.Invoke(this, args); + } +} + + }, + { + FileName: RegularClass.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +/// +/// A partial class implementation for RegularClass. +/// +public partial class RegularClass +{ + private static readonly PropertyChangedEventArgs _reactivePropertyChangedEventArgs = new PropertyChangedEventArgs(nameof(ReactiveProperty)); + + public partial string ReactiveProperty + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_reactivePropertyChangedEventArgs); + } + } + } +} + + } + ], + Diagnostics: null +} \ No newline at end of file diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithComplexProperties.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithComplexProperties.verified.txt index 73eb125..8f2e1aa 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithComplexProperties.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithComplexProperties.verified.txt @@ -21,31 +21,6 @@ sealed class ReactiveAttribute : Attribute { public ReactiveAttribute() { } } - }, - { - FileName: TestViewModel.INPC.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class TestViewModel : INotifyPropertyChanged -{ - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) - { - PropertyChanged?.Invoke(this, args); - } -} - }, { FileName: TestViewModel.ReactiveProperties.g.cs, @@ -53,55 +28,29 @@ public partial class TestViewModel : INotifyPropertyChanged // #nullable enable -using System.ComponentModel; -using System.Runtime.CompilerServices; +using ReactiveUI; /// /// A partial class implementation for TestViewModel. /// public partial class TestViewModel { - private static readonly PropertyChangedEventArgs _itemsChangedEventArgs = new PropertyChangedEventArgs(nameof(Items)); - private static readonly PropertyChangedEventArgs _complexDataChangedEventArgs = new PropertyChangedEventArgs(nameof(ComplexData)); - private static readonly PropertyChangedEventArgs _tupleDataChangedEventArgs = new PropertyChangedEventArgs(nameof(TupleData)); - public partial global::System.Collections.ObjectModel.ObservableCollection Items { get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_itemsChangedEventArgs); - } - } + set => this.RaiseAndSetIfChanged(ref field, value); } public partial Dictionary> ComplexData { get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_complexDataChangedEventArgs); - } - } + set => this.RaiseAndSetIfChanged(ref field, value); } public partial (string Name, int Count) TupleData { get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_tupleDataChangedEventArgs); - } - } + set => this.RaiseAndSetIfChanged(ref field, value); } } diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithCustomImplementations.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithCustomImplementations.verified.txt index a7f9a48..edbd5b4 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithCustomImplementations.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithCustomImplementations.verified.txt @@ -21,31 +21,6 @@ sealed class ReactiveAttribute : Attribute { public ReactiveAttribute() { } } - }, - { - FileName: TestViewModel.INPC.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class TestViewModel : INotifyPropertyChanged -{ - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) - { - PropertyChanged?.Invoke(this, args); - } -} - }, { FileName: TestViewModel.ReactiveProperties.g.cs, @@ -53,27 +28,17 @@ public partial class TestViewModel : INotifyPropertyChanged // #nullable enable -using System.ComponentModel; -using System.Runtime.CompilerServices; +using ReactiveUI; /// /// A partial class implementation for TestViewModel. /// public partial class TestViewModel { - private static readonly PropertyChangedEventArgs _regularPropChangedEventArgs = new PropertyChangedEventArgs(nameof(RegularProp)); - public partial string RegularProp { get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_regularPropChangedEventArgs); - } - } + set => this.RaiseAndSetIfChanged(ref field, value); } } diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithInheritance.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithInheritance.verified.txt index b8685fd..1db86fb 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithInheritance.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithInheritance.verified.txt @@ -1,57 +1,22 @@ { Sources: [ - { - FileName: BaseViewModel.INPC.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class BaseViewModel : INotifyPropertyChanged -{ - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) - { - PropertyChanged?.Invoke(this, args); - } -} - - }, { FileName: BaseViewModel.ReactiveProperties.g.cs, Source: // #nullable enable -using System.ComponentModel; -using System.Runtime.CompilerServices; +using ReactiveUI; /// /// A partial class implementation for BaseViewModel. /// public partial class BaseViewModel { - private static readonly PropertyChangedEventArgs _basePropChangedEventArgs = new PropertyChangedEventArgs(nameof(BaseProp)); - public partial string BaseProp { get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_basePropChangedEventArgs); - } - } + set => this.RaiseAndSetIfChanged(ref field, value); } } @@ -62,27 +27,17 @@ public partial class BaseViewModel // #nullable enable -using System.ComponentModel; -using System.Runtime.CompilerServices; +using ReactiveUI; /// /// A partial class implementation for DerivedViewModel. /// public partial class DerivedViewModel { - private static readonly PropertyChangedEventArgs _derivedPropChangedEventArgs = new PropertyChangedEventArgs(nameof(DerivedProp)); - public partial string DerivedProp { get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_derivedPropChangedEventArgs); - } - } + set => this.RaiseAndSetIfChanged(ref field, value); } } @@ -93,27 +48,17 @@ public partial class DerivedViewModel // #nullable enable -using System.ComponentModel; -using System.Runtime.CompilerServices; +using ReactiveUI; /// /// A partial class implementation for GrandChildViewModel. /// public partial class GrandChildViewModel { - private static readonly PropertyChangedEventArgs _grandChildPropChangedEventArgs = new PropertyChangedEventArgs(nameof(GrandChildProp)); - public partial string GrandChildProp { get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_grandChildPropChangedEventArgs); - } - } + set => this.RaiseAndSetIfChanged(ref field, value); } } diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithInitProperties.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithInitProperties.verified.txt index 683f3c8..b9417a5 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithInitProperties.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithInitProperties.verified.txt @@ -21,31 +21,6 @@ sealed class ReactiveAttribute : Attribute { public ReactiveAttribute() { } } - }, - { - FileName: TestViewModel.INPC.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class TestViewModel : INotifyPropertyChanged -{ - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) - { - PropertyChanged?.Invoke(this, args); - } -} - }, { FileName: TestViewModel.ReactiveProperties.g.cs, @@ -53,42 +28,23 @@ public partial class TestViewModel : INotifyPropertyChanged // #nullable enable -using System.ComponentModel; -using System.Runtime.CompilerServices; +using ReactiveUI; /// /// A partial class implementation for TestViewModel. /// public partial class TestViewModel { - private static readonly PropertyChangedEventArgs _readWritePropChangedEventArgs = new PropertyChangedEventArgs(nameof(ReadWriteProp)); - private static readonly PropertyChangedEventArgs _initOnlyPropChangedEventArgs = new PropertyChangedEventArgs(nameof(InitOnlyProp)); - private static readonly PropertyChangedEventArgs _getOnlyPropChangedEventArgs = new PropertyChangedEventArgs(nameof(GetOnlyProp)); - public partial string ReadWriteProp { get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_readWritePropChangedEventArgs); - } - } + set => this.RaiseAndSetIfChanged(ref field, value); } public partial string InitOnlyProp { get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_initOnlyPropChangedEventArgs); - } - } + set => this.RaiseAndSetIfChanged(ref field, value); } public partial string GetOnlyProp diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithObservableProperties.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithObservableProperties.verified.txt index aeee41c..e58f067 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithObservableProperties.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithObservableProperties.verified.txt @@ -21,31 +21,6 @@ sealed class ReactiveAttribute : Attribute { public ReactiveAttribute() { } } - }, - { - FileName: TestViewModel.INPC.g.cs, - Source: -// -#nullable enable - -using System.ComponentModel; -using System.Runtime.CompilerServices; - -public partial class TestViewModel : INotifyPropertyChanged -{ - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) - { - PropertyChanged?.Invoke(this, args); - } -} - }, { FileName: TestViewModel.ReactiveProperties.g.cs, @@ -53,41 +28,23 @@ public partial class TestViewModel : INotifyPropertyChanged // #nullable enable -using System.ComponentModel; -using System.Runtime.CompilerServices; +using ReactiveUI; /// /// A partial class implementation for TestViewModel. /// public partial class TestViewModel { - private static readonly PropertyChangedEventArgs _nameChangedEventArgs = new PropertyChangedEventArgs(nameof(Name)); - private static readonly PropertyChangedEventArgs _ageChangedEventArgs = new PropertyChangedEventArgs(nameof(Age)); - public partial string Name { get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_nameChangedEventArgs); - } - } + set => this.RaiseAndSetIfChanged(ref field, value); } public partial int Age { get => field; - set - { - if (!Equals(field, value)) - { - field = value; - OnPropertyChanged(_ageChangedEventArgs); - } - } + set => this.RaiseAndSetIfChanged(ref field, value); } } diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithClassLevelReactive.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithClassLevelReactive.verified.txt new file mode 100644 index 0000000..901e896 --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithClassLevelReactive.verified.txt @@ -0,0 +1,51 @@ +{ + Sources: [ + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + FileName: ReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class ReactiveAttribute : Attribute +{ + public ReactiveAttribute() { } +} + }, + { + FileName: ReactiveViewModel.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using ReactiveUI; + +internal partial class ReactiveViewModel +{ + public partial string FirstName + { + get => field; + set => this.RaiseAndSetIfChanged(ref field, value); + } + + public partial string LastName + { + get => field; + set => this.RaiseAndSetIfChanged(ref field, value); + } +} + + } + ], + Diagnostics: null +} \ No newline at end of file diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithClassLevelReactiveAndIgnoreOverride.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithClassLevelReactiveAndIgnoreOverride.verified.txt new file mode 100644 index 0000000..d5117c7 --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithClassLevelReactiveAndIgnoreOverride.verified.txt @@ -0,0 +1,54 @@ +{ + Sources: [ + { + FileName: AllReactiveViewModel.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using ReactiveUI; + +/// +/// A partial class implementation for AllReactiveViewModel. +/// +public partial class AllReactiveViewModel +{ + public partial string Property1 + { + get => field; + set => this.RaiseAndSetIfChanged(ref field, value); + } + + public partial string Property2 + { + get => field; + set => this.RaiseAndSetIfChanged(ref field, value); + } +} + + }, + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + FileName: ReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class ReactiveAttribute : Attribute +{ + public ReactiveAttribute() { } +} + } + ], + Diagnostics: null +} \ No newline at end of file diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithMixedReactiveProperties.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithMixedReactiveProperties.verified.txt new file mode 100644 index 0000000..e009866 --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithMixedReactiveProperties.verified.txt @@ -0,0 +1,54 @@ +{ + Sources: [ + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + FileName: MixedViewModel.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using ReactiveUI; + +/// +/// A partial class implementation for MixedViewModel. +/// +public partial class MixedViewModel +{ + public partial string ReactiveProperty + { + get => field; + set => this.RaiseAndSetIfChanged(ref field, value); + } + + public partial int? NullableReactiveProperty + { + get => field; + set => this.RaiseAndSetIfChanged(ref field, value); + } +} + + }, + { + FileName: ReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class ReactiveAttribute : Attribute +{ + public ReactiveAttribute() { } +} + } + ], + Diagnostics: null +} \ No newline at end of file diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithNullableGenerics.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithNullableGenerics.verified.txt new file mode 100644 index 0000000..6988d4e --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithNullableGenerics.verified.txt @@ -0,0 +1,97 @@ +{ + Sources: [ + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + FileName: NullableGenericsTest.INPC.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +public partial class NullableGenericsTest : INotifyPropertyChanged +{ + public event PropertyChangedEventHandler? PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) + { + PropertyChanged?.Invoke(this, args); + } +} + + }, + { + FileName: NullableGenericsTest.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +/// +/// A partial class implementation for NullableGenericsTest{T}. +/// +public partial class NullableGenericsTest +{ + private static readonly PropertyChangedEventArgs _startDateChangedEventArgs = new PropertyChangedEventArgs(nameof(StartDate)); + private static readonly PropertyChangedEventArgs _endDateChangedEventArgs = new PropertyChangedEventArgs(nameof(EndDate)); + + public partial global::System.DateTime? StartDate + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_startDateChangedEventArgs); + } + } + } + + public partial global::System.DateTime? EndDate + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_endDateChangedEventArgs); + } + } + } +} + + }, + { + FileName: ReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class ReactiveAttribute : Attribute +{ + public ReactiveAttribute() { } +} + } + ], + Diagnostics: null +} \ No newline at end of file diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithPropertyInitializer.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithPropertyInitializer.verified.txt new file mode 100644 index 0000000..134cb90 --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithPropertyInitializer.verified.txt @@ -0,0 +1,54 @@ +{ + Sources: [ + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + FileName: InitializedViewModel.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using ReactiveUI; + +/// +/// A partial class implementation for InitializedViewModel. +/// +public partial class InitializedViewModel +{ + public partial string Name + { + get => field; + set => this.RaiseAndSetIfChanged(ref field, value); + } + + public partial int Count + { + get => field; + set => this.RaiseAndSetIfChanged(ref field, value); + } +} + + }, + { + FileName: ReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class ReactiveAttribute : Attribute +{ + public ReactiveAttribute() { } +} + } + ], + Diagnostics: null +} \ No newline at end of file diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithSingleReactiveProperty.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithSingleReactiveProperty.verified.txt new file mode 100644 index 0000000..945037b --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithSingleReactiveProperty.verified.txt @@ -0,0 +1,48 @@ +{ + Sources: [ + { + FileName: Car.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using ReactiveUI; + +/// +/// A partial class implementation for Car. +/// +public partial class Car +{ + public partial string? Make + { + get => field; + set => this.RaiseAndSetIfChanged(ref field, value); + } +} + + }, + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + FileName: ReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class ReactiveAttribute : Attribute +{ + public ReactiveAttribute() { } +} + } + ], + Diagnostics: null +} \ No newline at end of file diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithoutAnyReactiveAttribute.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithoutAnyReactiveAttribute.verified.txt new file mode 100644 index 0000000..4ce9ee1 --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectWithoutAnyReactiveAttribute.verified.txt @@ -0,0 +1,27 @@ +{ + Sources: [ + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + FileName: ReactiveAttribute.g.cs, + Source: +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class ReactiveAttribute : Attribute +{ + public ReactiveAttribute() { } +} + } + ], + Diagnostics: null +} \ No newline at end of file diff --git a/ReactiveGenerator/ReactiveGenerator.cs b/ReactiveGenerator/ReactiveGenerator.cs index e2cf574..0e414eb 100644 --- a/ReactiveGenerator/ReactiveGenerator.cs +++ b/ReactiveGenerator/ReactiveGenerator.cs @@ -67,25 +67,58 @@ public void Initialize(IncrementalGeneratorInitializationContext context) spc)); } + private static bool IsTypeReactive(INamedTypeSymbol type) + { + // If type inherits from ReactiveObject, it's already reactive + if (InheritsFromReactiveObject(type)) + return true; + + // First check if the type has [IgnoreReactive] + foreach (var attribute in type.GetAttributes()) + { + if (attribute.AttributeClass?.Name is "IgnoreReactiveAttribute" or "IgnoreReactive") + return false; + } + + // Then check if the type has [Reactive] + foreach (var attribute in type.GetAttributes()) + { + if (attribute.AttributeClass?.Name is "ReactiveAttribute" or "Reactive") + return true; + } + + // Finally check base types (excluding ReactiveObject) + var current = type.BaseType; + while (current != null) + { + if (InheritsFromReactiveObject(current)) + return true; + + foreach (var attribute in current.GetAttributes()) + { + if (attribute.AttributeClass?.Name is "ReactiveAttribute" or "Reactive") + return true; + } + + current = current.BaseType; + } + + return false; + } + private static (INamedTypeSymbol Type, Location Location)? GetClassInfo(GeneratorSyntaxContext context) { if (context.Node is not ClassDeclarationSyntax classDeclaration) return null; - foreach (var attributeList in classDeclaration.AttributeLists) + var symbol = (INamedTypeSymbol?)context.SemanticModel.GetDeclaredSymbol(classDeclaration); + if (symbol == null) + return null; + + // Check if this type should be reactive + if (IsTypeReactive(symbol)) { - foreach (var attribute in attributeList.Attributes) - { - var name = attribute.Name.ToString(); - if (name is "Reactive" or "ReactiveAttribute") - { - var symbol = (INamedTypeSymbol?)context.SemanticModel.GetDeclaredSymbol(classDeclaration); - if (symbol != null) - { - return (symbol, classDeclaration.GetLocation()); - } - } - } + return (symbol, classDeclaration.GetLocation()); } return null; @@ -103,6 +136,7 @@ private static (INamedTypeSymbol Type, Location Location)? GetClassInfo(Generato bool hasReactiveAttribute = false; bool hasIgnoreAttribute = false; + // Check property attributes foreach (var attributeList in propertyDeclaration.AttributeLists) { foreach (var attribute in attributeList.Attributes) @@ -115,17 +149,9 @@ private static (INamedTypeSymbol Type, Location Location)? GetClassInfo(Generato } } - // Check if containing type has [Reactive] attribute var containingType = symbol.ContainingType; - bool classHasReactiveAttribute = false; - foreach (var attribute in containingType.GetAttributes()) - { - if (attribute.AttributeClass?.Name is "ReactiveAttribute" or "Reactive") - { - classHasReactiveAttribute = true; - break; - } - } + // Check if containing type should be reactive + bool classHasReactiveAttribute = IsTypeReactive(containingType); // Check if property has an implementation bool hasImplementation = propertyDeclaration.AccessorList?.Accessors.Any( @@ -133,7 +159,7 @@ private static (INamedTypeSymbol Type, Location Location)? GetClassInfo(Generato // Return property info if it either: // 1. Has [Reactive] attribute directly - // 2. Is in a class with [Reactive] attribute and doesn't have [IgnoreReactive] + // 2. Is in a class (or base class) with [Reactive] attribute and doesn't have [IgnoreReactive] // 3. Has no implementation yet if ((hasReactiveAttribute || (classHasReactiveAttribute && !hasIgnoreAttribute)) && !hasImplementation) { @@ -148,9 +174,14 @@ private static bool InheritsFromReactiveObject(INamedTypeSymbol typeSymbol) var current = typeSymbol; while (current is not null) { - if (current.Name == "ReactiveObject" && - current.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat).StartsWith("global::ReactiveUI.")) + if (current.Name == "ReactiveObject" || + current.ToString() == "ReactiveUI.ReactiveObject" || + current.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == + "global::ReactiveUI.ReactiveObject") + { return true; + } + current = current.BaseType; } @@ -170,6 +201,71 @@ private static int GetTypeHierarchyDepth(INamedTypeSymbol type) return depth; } + private static IEnumerable GetAllTypesInCompilation(Compilation compilation) + { + var result = new HashSet(SymbolEqualityComparer.Default); + + void ProcessNamespaceTypes(INamespaceSymbol ns) + { + foreach (var member in ns.GetMembers()) + { + switch (member) + { + case INamespaceSymbol nestedNs: + ProcessNamespaceTypes(nestedNs); + break; + case INamedTypeSymbol type: + result.Add(type); + foreach (var nestedType in type.GetTypeMembers()) + { + result.Add(nestedType); + } + + break; + } + } + } + + ProcessNamespaceTypes(compilation.GlobalNamespace); + return result; + } + + private static bool IsTypeMarkedReactive(INamedTypeSymbol type) + { + // Check if type has [Reactive] + foreach (var attribute in type.GetAttributes()) + { + if (attribute.AttributeClass?.Name is "ReactiveAttribute" or "Reactive") + return true; + } + + // Check base types for [Reactive] + var current = type.BaseType; + while (current != null) + { + foreach (var attribute in current.GetAttributes()) + { + if (attribute.AttributeClass?.Name is "ReactiveAttribute" or "Reactive") + return true; + } + + current = current.BaseType; + } + + return false; + } + + private static bool HasAnyReactiveProperties(INamedTypeSymbol type, + Dictionary> propertyGroups) + { + if (propertyGroups.TryGetValue(type, out var properties)) + { + return properties.Any(p => p.HasReactiveAttribute); + } + + return false; + } + private static void Execute( Compilation compilation, List<(INamedTypeSymbol Type, Location Location)> reactiveClasses, @@ -181,64 +277,36 @@ private static void Execute( return; var processedTypes = new HashSet(SymbolEqualityComparer.Default); - var reactiveTypesSet = new HashSet( - reactiveClasses.Select(rc => rc.Type), - SymbolEqualityComparer.Default); + var allTypes = new HashSet(SymbolEqualityComparer.Default); // Group properties by containing type var propertyGroups = properties .GroupBy(p => p.Property.ContainingType, SymbolEqualityComparer.Default) .ToDictionary(g => g.Key, g => g.ToList(), SymbolEqualityComparer.Default); - // Get all types that need processing - var allTypes = new HashSet(SymbolEqualityComparer.Default); - - // Add types from reactive classes - foreach (var reactiveClass in reactiveClasses) - allTypes.Add(reactiveClass.Type); - - // Add types from properties that should be reactive and don't have implementation - foreach (var property in properties) + // Add types that need processing + foreach (var type in GetAllTypesInCompilation(compilation)) { - if ((property.HasReactiveAttribute || - (reactiveTypesSet.Contains(property.Property.ContainingType) && !property.HasIgnoreAttribute)) && - !property.HasImplementation && - property.Property.ContainingType is INamedTypeSymbol type) + if (IsTypeMarkedReactive(type) || HasAnyReactiveProperties(type, propertyGroups)) { allTypes.Add(type); } } - // First pass: Process base types that need INPC - var typesToProcess = allTypes.ToList(); // Create a copy to avoid modification during enumeration - foreach (var type in typesToProcess) - { - var current = type.BaseType; - while (current is not null) - { - allTypes.Add(current); // Add base type to processing queue - current = current.BaseType; - } - } - - // Process types in correct order (base types first) + // Process types in correct order foreach (var type in allTypes.OrderBy(t => GetTypeHierarchyDepth(t))) { - // Skip if type already processed or inherits from ReactiveObject - if (processedTypes.Contains(type) || InheritsFromReactiveObject(type)) + if (processedTypes.Contains(type)) continue; - // Check if type needs INPC implementation - var needsInpc = !HasINPCImplementation(compilation, type, processedTypes) && - (reactiveTypesSet.Contains(type) || // Has [Reactive] class attribute - propertyGroups.ContainsKey(type)); // Has properties that should be reactive + var isReactiveObjectDerived = InheritsFromReactiveObject(type); - if (needsInpc) + // Generate INPC implementation if needed + if (!isReactiveObjectDerived && !HasINPCImplementation(compilation, type, processedTypes)) { var inpcSource = GenerateINPCImplementation(type); if (!string.IsNullOrEmpty(inpcSource)) { - // Create a unique filename using the full type name (including namespace) var fullTypeName = type.ToDisplayString(new SymbolDisplayFormat( typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions: SymbolDisplayGenericsOptions.None, @@ -246,43 +314,44 @@ private static void Execute( var fileName = $"{fullTypeName}.INPC.g.cs"; context.AddSource(fileName, SourceText.From(inpcSource, Encoding.UTF8)); - processedTypes.Add(type); } } - } - - // Process properties - foreach (var group in propertyGroups) - { - var typeSymbol = group.Key as INamedTypeSymbol; - if (typeSymbol == null) continue; - - // Filter properties that should be reactive - var reactiveProperties = group.Value - .Where(p => p.HasReactiveAttribute || - (reactiveTypesSet.Contains(typeSymbol) && !p.HasIgnoreAttribute)) - .Select(p => p.Property) - .ToList(); - - if (!reactiveProperties.Any()) - continue; - var source = GenerateClassSource( - typeSymbol, - reactiveProperties, - implementInpc: false, // INPC already implemented in first pass if needed - useLegacyMode); - - if (!string.IsNullOrEmpty(source)) + // Generate property implementations + if (propertyGroups.TryGetValue(type, out var typeProperties)) { - var fullTypeName = typeSymbol.ToDisplayString(new SymbolDisplayFormat( - typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, - genericsOptions: SymbolDisplayGenericsOptions.None, - miscellaneousOptions: SymbolDisplayMiscellaneousOptions.None)); + var isTypeReactive = IsTypeMarkedReactive(type); + var reactiveProperties = typeProperties + .Where(p => !p.HasImplementation && + !p.HasIgnoreAttribute && + (p.HasReactiveAttribute || // Include properties marked with [Reactive] + isTypeReactive)) // Include all properties if class is marked with [Reactive] + .Select(p => p.Property) + .ToList(); + + if (reactiveProperties.Any()) + { + var source = GenerateClassSource( + type, + reactiveProperties, + implementInpc: false, + useLegacyMode); - var fileName = $"{fullTypeName}.ReactiveProperties.g.cs"; - context.AddSource(fileName, SourceText.From(source, Encoding.UTF8)); + if (!string.IsNullOrEmpty(source)) + { + var fullTypeName = type.ToDisplayString(new SymbolDisplayFormat( + typeQualificationStyle: SymbolDisplayTypeQualificationStyle + .NameAndContainingTypesAndNamespaces, + genericsOptions: SymbolDisplayGenericsOptions.None, + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.None)); + + var fileName = $"{fullTypeName}.ReactiveProperties.g.cs"; + context.AddSource(fileName, SourceText.From(source, Encoding.UTF8)); + } + } } + + processedTypes.Add(type); } } diff --git a/ReactiveGenerator/ReactiveGenerator.csproj b/ReactiveGenerator/ReactiveGenerator.csproj index 99b4832..2f050ac 100644 --- a/ReactiveGenerator/ReactiveGenerator.csproj +++ b/ReactiveGenerator/ReactiveGenerator.csproj @@ -15,7 +15,7 @@ - 0.9.5 + 0.9.6 Wiesław Šoltés Wiesław Šoltés