Skip to content

Commit 2c02287

Browse files
authored
Add Reactive, ObservableAsProperty and ReactiveCommand Source Generators (#3)
* Add Reactive, ObservableAsProperty and ReactiveCommand Source Generators * Update test to have private methods as per documentation * Add Attribute Support * Add CancelationToken support
1 parent 3d6e763 commit 2c02287

File tree

64 files changed

+6016
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+6016
-2
lines changed

.editorconfig

Lines changed: 532 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 140 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,142 @@
1-
# ReactiveUI.ObjectGenerators
2-
Use source generators to generate objects.
1+
# ReactiveUI.SourceGenerators
2+
Use source generators to generate ReactiveUI objects.
33

44
Not taking public contributions at this time
5+
6+
## Usage Reactive property `[Reactive]`
7+
```csharp
8+
using ReactiveUI.SourceGenerators;
9+
10+
public partial class MyReactiveClass : ReactiveObject
11+
{
12+
[Reactive] private string _myProperty;
13+
}
14+
```
15+
16+
## Usage ObservableAsPropertyHelper `[ObservableAsProperty]`
17+
```csharp
18+
using ReactiveUI.SourceGenerators;
19+
20+
public partial class MyReactiveClass : ReactiveObject
21+
{
22+
[ObservableAsProperty] private string _myProperty;
23+
}
24+
```
25+
26+
## Usage ReactiveCommand `[ReactiveCommand]`
27+
28+
### Usage ReactiveCommand without parameter
29+
```csharp
30+
using ReactiveUI.SourceGenerators;
31+
32+
public partial class MyReactiveClass
33+
{
34+
public MyReactiveCommand()
35+
{
36+
InitializeCommands();
37+
}
38+
39+
[ReactiveCommand]
40+
private void Execute() { }
41+
}
42+
```
43+
44+
### Usage ReactiveCommand with parameter
45+
```csharp
46+
using ReactiveUI.SourceGenerators;
47+
48+
public partial class MyReactiveClass
49+
{
50+
public MyReactiveCommand()
51+
{
52+
InitializeCommands();
53+
}
54+
55+
[ReactiveCommand]
56+
private void Execute(string parameter) { }
57+
}
58+
```
59+
60+
### Usage ReactiveCommand with parameter and return value
61+
```csharp
62+
using ReactiveUI.SourceGenerators;
63+
64+
public partial class MyReactiveClass
65+
{
66+
public MyReactiveCommand()
67+
{
68+
InitializeCommands();
69+
}
70+
71+
[ReactiveCommand]
72+
private string Execute(string parameter) => parameter;
73+
}
74+
```
75+
76+
### Usage ReactiveCommand with parameter and async return value
77+
```csharp
78+
using ReactiveUI.SourceGenerators;
79+
80+
public partial class MyReactiveClass
81+
{
82+
public MyReactiveCommand()
83+
{
84+
InitializeCommands();
85+
}
86+
87+
[ReactiveCommand]
88+
private async Task<string> Execute(string parameter) => await Task.FromResult(parameter);
89+
}
90+
```
91+
92+
### Usage ReactiveCommand with IObservable return value
93+
```csharp
94+
using ReactiveUI.SourceGenerators;
95+
96+
public partial class MyReactiveClass
97+
{
98+
public MyReactiveCommand()
99+
{
100+
InitializeCommands();
101+
}
102+
103+
[ReactiveCommand]
104+
private IObservable<string> Execute(string parameter) => Observable.Return(parameter);
105+
}
106+
```
107+
108+
### Usage ReactiveCommand with CancellationToken
109+
```csharp
110+
using ReactiveUI.SourceGenerators;
111+
112+
public partial class MyReactiveClass
113+
{
114+
public MyReactiveCommand()
115+
{
116+
InitializeCommands();
117+
}
118+
119+
[ReactiveCommand]
120+
private async Task Execute(CancellationToken token) => await Task.Delay(1000, token);
121+
}
122+
```
123+
124+
### Usage ReactiveCommand with CancellationToken and parameter
125+
```csharp
126+
using ReactiveUI.SourceGenerators;
127+
128+
public partial class MyReactiveClass
129+
{
130+
public MyReactiveCommand()
131+
{
132+
InitializeCommands();
133+
}
134+
135+
[ReactiveCommand]
136+
private async Task<string> Execute(string parameter, CancellationToken token)
137+
{
138+
await Task.Delay(1000, token);
139+
return parameter;
140+
}
141+
}
142+
```

images/logo.png

36.3 KB
Loading

src/Directory.Packages.props

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<Project>
2+
<PropertyGroup>
3+
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
4+
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
8+
<PackageVersion Include="xunit" Version="2.9.0" />
9+
<PackageVersion Include="xunit.runner.console" Version="2.9.0" />
10+
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
11+
<PackageVersion Include="Xunit.StaFact" Version="1.1.11" />
12+
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
13+
<PackageVersion Include="Microsoft.Reactive.Testing" Version="6.0.1" />
14+
<PackageVersion Include="PublicApiGenerator" Version="11.1.0" />
15+
<PackageVersion Include="coverlet.msbuild" Version="6.0.2" />
16+
<PackageVersion Include="Verify.Xunit" Version="25.3.1" />
17+
18+
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
19+
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.6.139" />
20+
<PackageVersion Include="stylecop.analyzers" Version="1.2.0-beta.556" />
21+
<PackageVersion Include="Roslynator.Analyzers" Version="4.12.4" />
22+
23+
<PackageVersion Include="ReactiveUI" Version="20.1.1" />
24+
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
25+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
26+
<PackageVersion Include="PolySharp" Version="1.14.1" />
27+
</ItemGroup>
28+
</Project>

src/Directory.build.props

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<Project>
2+
<PropertyGroup>
3+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
4+
<Platform>AnyCPU</Platform>
5+
<IsTestProject>$(MSBuildProjectName.Contains('Tests'))</IsTestProject>
6+
<DebugType>embedded</DebugType>
7+
<Authors>.NET Foundation and Contributors</Authors>
8+
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
9+
<PackageIcon>logo.png</PackageIcon>
10+
<PackageReadmeFile>README.md</PackageReadmeFile>
11+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
12+
<PackageProjectUrl>https://reactiveui.net</PackageProjectUrl>
13+
<DefaultPackageDescription>A MVVM framework that integrates with the Reactive Extensions for .NET to create elegant, testable User Interfaces that run on any mobile or desktop platform. Supports Xamarin.iOS, Xamarin.Android, Xamarin.Mac, Xamarin Forms, Xamarin.TVOS, Tizen, WPF, Windows Forms, Universal Windows Platform (UWP) and the Uno Platform.</DefaultPackageDescription>
14+
<PackageDescription>$(DefaultPackageDescription)</PackageDescription>
15+
<Owners>anaisbetts;chrispulman</Owners>
16+
<PackageTags>mvvm;reactiveui;rx;reactive extensions;observable;LINQ;events;frp;xamarin;android;ios;mac;forms;monodroid;monotouch;xamarin.android;xamarin.ios;xamarin.forms;xamarin.mac;xamarin.tvos;wpf;net;netstandard;net462;winui;maui;tizen;unoplatform</PackageTags>
17+
<PackageReleaseNotes>https://github.com/reactiveui/ReactiveUI.SourceGenerators/releases</PackageReleaseNotes>
18+
<RepositoryUrl>https://github.com/reactiveui/reactiveui.sourcegenerators</RepositoryUrl>
19+
<RepositoryType>git</RepositoryType>
20+
<NoWarn>$(NoWarn);IDE0060;IDE1006;VSSpell001</NoWarn>
21+
<!-- Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->
22+
<PublishRepositoryUrl>true</PublishRepositoryUrl>
23+
<!-- Embed source files that are not tracked by the source control manager in the PDB -->
24+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
25+
<!-- Include PDB in the built .nupkg -->
26+
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
27+
<Nullable>enable</Nullable>
28+
<LangVersion>preview</LangVersion>
29+
<EnableNETAnalyzers>True</EnableNETAnalyzers>
30+
<AnalysisLevel>latest</AnalysisLevel>
31+
<WarningsAsErrors>nullable</WarningsAsErrors>
32+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
33+
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
34+
<PublishRepositoryUrl>true</PublishRepositoryUrl>
35+
</PropertyGroup>
36+
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
37+
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
38+
</PropertyGroup>
39+
<ItemGroup Condition="$(IsTestProject)">
40+
<PackageReference Include="Microsoft.NET.Test.Sdk" />
41+
<PackageReference Include="xunit" />
42+
<PackageReference Include="xunit.runner.console" />
43+
<PackageReference Include="xunit.runner.visualstudio" />
44+
<PackageReference Include="Xunit.StaFact" />
45+
<PackageReference Include="FluentAssertions" />
46+
<PackageReference Include="Microsoft.Reactive.Testing" />
47+
<PackageReference Include="PublicApiGenerator" />
48+
<PackageReference Include="coverlet.msbuild" PrivateAssets="All" />
49+
<PackageReference Include="Verify.Xunit" />
50+
</ItemGroup>
51+
<ItemGroup Condition="'$(IsTestProject)' != 'true'">
52+
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
53+
</ItemGroup>
54+
<PropertyGroup>
55+
<SolutionDir Condition="'$(SolutionDir)' == ''">$(MSBuildThisFileDirectory)</SolutionDir>
56+
</PropertyGroup>
57+
<ItemGroup>
58+
<None Include="$(MSBuildThisFileDirectory)..\images\logo.png" Pack="true" PackagePath="\" />
59+
<None Include="$(MSBuildThisFileDirectory)..\LICENSE" Pack="true" PackagePath="LICENSE" />
60+
<None Include="$(MSBuildThisFileDirectory)..\README.md" Pack="true" PackagePath="\" />
61+
</ItemGroup>
62+
<ItemGroup>
63+
<PackageReference Include="Nerdbank.GitVersioning" PrivateAssets="all" />
64+
<PackageReference Include="stylecop.analyzers" PrivateAssets="all" />
65+
<PackageReference Include="Roslynator.Analyzers" PrivateAssets="All" />
66+
</ItemGroup>
67+
<ItemGroup>
68+
<AdditionalFiles Include="$(MSBuildThisFileDirectory)stylecop.json" Link="stylecop.json" />
69+
</ItemGroup>
70+
</Project>
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using System.Drawing;
7+
using System.Reactive;
8+
using System.Reactive.Linq;
9+
using System.Runtime.Serialization;
10+
using System.Text.Json.Serialization;
11+
12+
namespace ReactiveUI.SourceGenerators.Test;
13+
14+
#pragma warning disable SA1402 // File may only contain a single type
15+
#pragma warning disable SA1649 // File name should match first type name
16+
#pragma warning disable CA1822 // Mark members as static
17+
18+
/// <summary>
19+
/// EntryPoint.
20+
/// </summary>
21+
public static class EntryPoint
22+
{
23+
/// <summary>
24+
/// Defines the entry point of the application.
25+
/// </summary>
26+
public static void Main() => new TestClass();
27+
}
28+
29+
/// <summary>
30+
/// TestClass.
31+
/// </summary>
32+
[DataContract]
33+
public partial class TestClass : ReactiveObject
34+
{
35+
[JsonInclude]
36+
[DataMember]
37+
[ObservableAsProperty]
38+
private double _test2Property;
39+
40+
[JsonInclude]
41+
[Reactive]
42+
[DataMember]
43+
private int _test1Property;
44+
45+
/// <summary>
46+
/// Initializes a new instance of the <see cref="TestClass"/> class.
47+
/// </summary>
48+
public TestClass()
49+
{
50+
InitializeCommands();
51+
52+
Console.Out.WriteLine(Test1Command);
53+
Console.Out.WriteLine(Test2Command);
54+
Console.Out.WriteLine(Test3AsyncCommand);
55+
Console.Out.WriteLine(Test4AsyncCommand);
56+
Console.Out.WriteLine(Test5StringToIntCommand);
57+
Console.Out.WriteLine(Test6ArgOnlyCommand);
58+
Console.Out.WriteLine(Test7ObservableCommand);
59+
Console.Out.WriteLine(Test8ObservableCommand);
60+
Console.Out.WriteLine(Test9AsyncCommand);
61+
Console.Out.WriteLine(Test10AsyncCommand);
62+
Test1Command?.Execute().Subscribe();
63+
Test2Command?.Execute().Subscribe(r => Console.Out.WriteLine(r));
64+
Test3AsyncCommand?.Execute().Subscribe();
65+
Test4AsyncCommand?.Execute().Subscribe(r => Console.Out.WriteLine(r));
66+
Test5StringToIntCommand?.Execute("100").Subscribe(Console.Out.WriteLine);
67+
Test6ArgOnlyCommand?.Execute("Hello World").Subscribe();
68+
Test7ObservableCommand?.Execute().Subscribe();
69+
70+
_test2PropertyHelper = Test8ObservableCommand!.ToProperty(this, x => x.Test2Property);
71+
72+
Test8ObservableCommand?.Execute(100).Subscribe(Console.Out.WriteLine);
73+
Console.Out.WriteLine($"Test2Property Value: {Test2Property}");
74+
Console.Out.WriteLine($"Test2Property underlying Value: {_test2Property}");
75+
76+
Test9AsyncCommand?.ThrownExceptions.Subscribe(Console.Out.WriteLine);
77+
var cancel = Test9AsyncCommand?.Execute().Subscribe();
78+
Task.Delay(1000).Wait();
79+
cancel?.Dispose();
80+
81+
Test10AsyncCommand?.Execute(200).Subscribe(r => Console.Out.WriteLine(r));
82+
83+
Console.ReadLine();
84+
}
85+
86+
/// <summary>
87+
/// Test1s this instance.
88+
/// </summary>
89+
[ReactiveCommand]
90+
private void Test1() => Console.Out.WriteLine("Test1");
91+
92+
/// <summary>
93+
/// Test2s this instance.
94+
/// </summary>
95+
/// <returns>Rectangle.</returns>
96+
[ReactiveCommand]
97+
private Rectangle Test2() => default;
98+
99+
/// <summary>
100+
/// Test3s the asynchronous.
101+
/// </summary>
102+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
103+
[ReactiveCommand]
104+
private async Task Test3Async() => await Task.Delay(0);
105+
106+
/// <summary>
107+
/// Test4s the asynchronous.
108+
/// </summary>
109+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
110+
[ReactiveCommand]
111+
private async Task<Rectangle> Test4Async() => await Task.FromResult(new Rectangle(0, 0, 100, 100));
112+
113+
/// <summary>
114+
/// Test5s the string to int.
115+
/// </summary>
116+
/// <param name="str">The string.</param>
117+
/// <returns>int.</returns>
118+
[ReactiveCommand]
119+
private int Test5StringToInt(string str) => int.Parse(str);
120+
121+
/// <summary>
122+
/// Test6s the argument only.
123+
/// </summary>
124+
/// <param name="str">The string.</param>
125+
[ReactiveCommand]
126+
private void Test6ArgOnly(string str) => Console.Out.WriteLine($">>> {str}");
127+
128+
/// <summary>
129+
/// Test7s the observable.
130+
/// </summary>
131+
/// <returns>An Observable of Unit.</returns>
132+
[ReactiveCommand]
133+
private IObservable<Unit> Test7Observable() => Observable.Return(Unit.Default);
134+
135+
/// <summary>
136+
/// Test8s the observable.
137+
/// </summary>
138+
/// <param name="i">The i.</param>
139+
/// <returns>An Observable of int.</returns>
140+
[ReactiveCommand]
141+
private IObservable<double> Test8Observable(int i) => Observable.Return(i + 10.0);
142+
143+
[ReactiveCommand]
144+
private async Task Test9Async(CancellationToken ct) => await Task.Delay(2000, ct);
145+
146+
[ReactiveCommand]
147+
private async Task<Rectangle> Test10Async(int size, CancellationToken ct) => await Task.FromResult(new Rectangle(0, 0, size, size));
148+
}
149+
150+
#pragma warning restore CA1822 // Mark members as static
151+
#pragma warning restore SA1649 // File name should match first type name
152+
#pragma warning restore SA1402 // File may only contain a single type

0 commit comments

Comments
 (0)